-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathno-invalid-token-paths.ts
132 lines (111 loc) · 3.85 KB
/
no-invalid-token-paths.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import {
getInvalidTokens,
getTaggedTemplateCaller,
isPandaAttribute,
isPandaIsh,
isPandaProp,
isRecipeVariant,
} from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'
import { isNodeOfTypes } from '@typescript-eslint/utils/ast-utils'
import { isIdentifier, isJSXExpressionContainer, isLiteral, isTemplateLiteral } from '../utils/nodes'
export const RULE_NAME = 'no-invalid-token-paths'
const rule: Rule = createRule({
name: RULE_NAME,
meta: {
docs: {
description: 'Disallow the use of invalid token paths within token function syntax.',
},
messages: {
noInvalidTokenPaths: '`{{token}}` is an invalid token path.',
},
type: 'problem',
schema: [],
},
defaultOptions: [],
create(context) {
// Cache for invalid tokens to avoid redundant computations
const invalidTokensCache = new Map<string, string[]>()
const sendReport = (node: TSESTree.Node, value: string | undefined) => {
if (!value) return
let tokens: string[] | undefined = invalidTokensCache.get(value)
if (!tokens) {
tokens = getInvalidTokens(value, context)
invalidTokensCache.set(value, tokens)
}
if (tokens.length === 0) return
tokens.forEach((token) => {
context.report({
node,
messageId: 'noInvalidTokenPaths',
data: { token },
})
})
}
const handleLiteralOrTemplate = (node: TSESTree.Node | undefined) => {
if (!node) return
if (isLiteral(node)) {
const value = node.value?.toString()
sendReport(node, value)
} else if (isTemplateLiteral(node) && node.expressions.length === 0) {
const value = node.quasis[0].value.raw
sendReport(node.quasis[0], value)
}
}
return {
JSXAttribute(node: TSESTree.JSXAttribute) {
if (!node.value || !isPandaProp(node, context)) return
if (isLiteral(node.value)) {
handleLiteralOrTemplate(node.value)
} else if (isJSXExpressionContainer(node.value)) {
handleLiteralOrTemplate(node.value.expression)
}
},
Property(node: TSESTree.Property) {
if (
!isIdentifier(node.key) ||
!isNodeOfTypes([AST_NODE_TYPES.Literal, AST_NODE_TYPES.TemplateLiteral])(node.value) ||
!isPandaAttribute(node, context) ||
isRecipeVariant(node, context)
) {
return
}
handleLiteralOrTemplate(node.value)
},
TaggedTemplateExpression(node: TSESTree.TaggedTemplateExpression) {
const caller = getTaggedTemplateCaller(node)
if (!caller || !isPandaIsh(caller, context)) return
const quasis = node.quasi.quasis
quasis.forEach((quasi) => {
const styles = quasi.value.raw
if (!styles) return
let tokens: string[] | undefined = invalidTokensCache.get(styles)
if (!tokens) {
tokens = getInvalidTokens(styles, context)
invalidTokensCache.set(styles, tokens)
}
if (tokens.length === 0) return
tokens.forEach((token) => {
let index = styles.indexOf(token)
while (index !== -1) {
const start = quasi.range[0] + index + 1 // +1 for the backtick
const end = start + token.length
context.report({
loc: {
start: context.sourceCode.getLocFromIndex(start),
end: context.sourceCode.getLocFromIndex(end),
},
messageId: 'noInvalidTokenPaths',
data: { token },
})
// Check for other occurences of the invalid token
index = styles.indexOf(token, index + token.length)
}
})
})
},
}
},
})
export default rule