@@ -6,23 +6,63 @@ import type ESTree from 'estree'
6
6
import type { RuleContext , RuleListener } from '../types'
7
7
import { defineRule } from '../utils'
8
8
9
- type AnyValue = ESTree . Literal [ 'value' ]
9
+ type LiteralValue = ESTree . Literal [ 'value' ]
10
+ type StaticTemplateLiteral = ESTree . TemplateLiteral & {
11
+ quasis : [ ESTree . TemplateElement ]
12
+ expressions : [ /* empty */ ]
13
+ }
14
+ type TargetAttrs = { name : RegExp ; attrs : Set < string > }
10
15
type Config = {
16
+ attributes : TargetAttrs [ ]
11
17
ignorePattern : RegExp
12
18
ignoreNodes : string [ ]
13
19
ignoreText : string [ ]
14
20
}
21
+ const RE_REGEXP_STR = / ^ \/ ( .+ ) \/ ( .* ) $ / u
22
+ function toRegExp ( str : string ) : RegExp {
23
+ const parts = RE_REGEXP_STR . exec ( str )
24
+ if ( parts ) {
25
+ return new RegExp ( parts [ 1 ] , parts [ 2 ] )
26
+ }
27
+ return new RegExp ( `^${ escape ( str ) } $` )
28
+ }
15
29
const hasOnlyWhitespace = ( value : string ) => / ^ [ \r \n \s \t \f \v ] + $ / . test ( value )
16
30
17
- function isValidValue ( value : AnyValue , config : Config ) {
18
- return (
19
- typeof value !== 'string' ||
20
- hasOnlyWhitespace ( value ) ||
21
- config . ignorePattern . test ( value . trim ( ) ) ||
22
- config . ignoreText . includes ( value . trim ( ) )
31
+ /**
32
+ * Get the attribute to be verified from the element name.
33
+ */
34
+ function getTargetAttrs ( tagName : string , config : Config ) : Set < string > {
35
+ const result = [ ]
36
+ for ( const { name, attrs } of config . attributes ) {
37
+ name . lastIndex = 0
38
+ if ( name . test ( tagName ) ) {
39
+ result . push ( ...attrs )
40
+ }
41
+ }
42
+
43
+ return new Set ( result )
44
+ }
45
+
46
+ function isStaticTemplateLiteral (
47
+ node : ESTree . Expression | ESTree . Pattern
48
+ ) : node is StaticTemplateLiteral {
49
+ return Boolean (
50
+ node && node . type === 'TemplateLiteral' && node . expressions . length === 0
23
51
)
24
52
}
25
53
54
+ function testValue ( value : LiteralValue , config : Config ) : boolean {
55
+ if ( typeof value === 'string' ) {
56
+ return (
57
+ hasOnlyWhitespace ( value ) ||
58
+ config . ignorePattern . test ( value . trim ( ) ) ||
59
+ config . ignoreText . includes ( value . trim ( ) )
60
+ )
61
+ } else {
62
+ return false
63
+ }
64
+ }
65
+
26
66
function checkSvelteMustacheTagText (
27
67
context : RuleContext ,
28
68
node : SvAST . SvelteMustacheTag ,
@@ -34,56 +74,106 @@ function checkSvelteMustacheTagText(
34
74
35
75
if ( node . parent . type === 'SvelteElement' ) {
36
76
// parent is element (e.g. <p>{ ... }</p>)
37
- if ( node . expression . type === 'Literal' ) {
38
- const literalNode = node . expression
39
- if ( isValidValue ( literalNode . value , config ) ) {
40
- return
77
+ checkExpressionText ( context , node . expression , config )
78
+ }
79
+ }
80
+
81
+ function checkExpressionText (
82
+ context : RuleContext ,
83
+ expression : ESTree . Expression ,
84
+ config : Config
85
+ ) {
86
+ if ( expression . type === 'Literal' ) {
87
+ checkLiteral ( context , expression , config )
88
+ } else if ( isStaticTemplateLiteral ( expression ) ) {
89
+ checkLiteral ( context , expression , config )
90
+ } else if ( expression . type === 'ConditionalExpression' ) {
91
+ const targets = [ expression . consequent , expression . alternate ]
92
+ targets . forEach ( target => {
93
+ if ( target . type === 'Literal' ) {
94
+ checkLiteral ( context , target , config )
95
+ } else if ( isStaticTemplateLiteral ( target ) ) {
96
+ checkLiteral ( context , target , config )
41
97
}
98
+ } )
99
+ }
100
+ }
42
101
43
- context . report ( {
44
- node : literalNode ,
45
- message : `raw text '${ literalNode . value } ' is used`
46
- } )
47
- } else if ( node . expression . type === 'ConditionalExpression' ) {
48
- for ( const target of [
49
- node . expression . consequent ,
50
- node . expression . alternate
51
- ] ) {
52
- if ( target . type !== 'Literal' ) {
53
- continue
54
- }
55
- if ( isValidValue ( target . value , config ) ) {
56
- continue
57
- }
102
+ function checkSvelteLiteralOrText (
103
+ context : RuleContext ,
104
+ literal : SvAST . SvelteLiteral | SvAST . SvelteText ,
105
+ config : Config
106
+ ) {
107
+ if ( testValue ( literal . value , config ) ) {
108
+ return
109
+ }
58
110
59
- context . report ( {
60
- node : target ,
61
- message : `raw text '${ target . value } ' is used`
62
- } )
63
- }
64
- }
111
+ const loc = literal . loc !
112
+ context . report ( {
113
+ loc,
114
+ message : `raw text '${ literal . value } ' is used`
115
+ } )
116
+ }
117
+
118
+ function checkLiteral (
119
+ context : RuleContext ,
120
+ literal : ESTree . Literal | StaticTemplateLiteral ,
121
+ config : Config
122
+ ) {
123
+ const value =
124
+ literal . type !== 'TemplateLiteral'
125
+ ? literal . value
126
+ : literal . quasis [ 0 ] . value . cooked
127
+
128
+ if ( testValue ( value , config ) ) {
129
+ return
130
+ }
131
+
132
+ const loc = literal . loc !
133
+ context . report ( {
134
+ loc,
135
+ message : `raw text '${ value } ' is used`
136
+ } )
137
+ }
138
+ /**
139
+ * Parse attributes option
140
+ */
141
+ function parseTargetAttrs (
142
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
+ options : any
144
+ ) {
145
+ const regexps : TargetAttrs [ ] = [ ]
146
+ for ( const tagName of Object . keys ( options ) ) {
147
+ const attrs : Set < string > = new Set ( options [ tagName ] )
148
+ regexps . push ( {
149
+ name : toRegExp ( tagName ) ,
150
+ attrs
151
+ } )
65
152
}
153
+ return regexps
66
154
}
67
155
68
156
function create ( context : RuleContext ) : RuleListener {
69
157
const sourceCode = context . getSourceCode ( )
70
158
const config : Config = {
71
- ignorePattern : / ^ [ ^ \S \s ] $ / ,
159
+ attributes : [ ] ,
160
+ ignorePattern : / ^ $ / ,
72
161
ignoreNodes : [ ] ,
73
162
ignoreText : [ ]
74
163
}
75
164
76
165
if ( context . options [ 0 ] ?. ignorePattern ) {
77
166
config . ignorePattern = new RegExp ( context . options [ 0 ] . ignorePattern , 'u' )
78
167
}
79
-
80
168
if ( context . options [ 0 ] ?. ignoreNodes ) {
81
169
config . ignoreNodes = context . options [ 0 ] . ignoreNodes
82
170
}
83
-
84
171
if ( context . options [ 0 ] ?. ignoreText ) {
85
172
config . ignoreText = context . options [ 0 ] . ignoreText
86
173
}
174
+ if ( context . options [ 0 ] ?. attributes ) {
175
+ config . attributes = parseTargetAttrs ( context . options [ 0 ] . attributes )
176
+ }
87
177
88
178
function isIgnore ( node : SvAST . SvelteMustacheTag | SvAST . SvelteText ) {
89
179
const element = getElement ( node )
@@ -98,7 +188,8 @@ function create(context: RuleContext): RuleListener {
98
188
| SvAST . SvelteText [ 'parent' ]
99
189
| SvAST . SvelteMustacheTag [ 'parent' ]
100
190
| SvAST . SvelteElement
101
- | SvAST . SvelteAwaitBlock = node . parent
191
+ | SvAST . SvelteAwaitBlock
192
+ | SvAST . SvelteElseBlockElseIf = node . parent
102
193
while (
103
194
target . type === 'SvelteIfBlock' ||
104
195
target . type === 'SvelteElseBlock' ||
@@ -118,6 +209,19 @@ function create(context: RuleContext): RuleListener {
118
209
}
119
210
120
211
return {
212
+ SvelteAttribute ( node : SvAST . SvelteAttribute ) {
213
+ if ( node . value . length !== 1 || node . value [ 0 ] . type !== 'SvelteLiteral' ) {
214
+ return
215
+ }
216
+ const nameNode = node . parent . parent . name
217
+ const tagName = sourceCode . text . slice ( ...nameNode . range ! )
218
+ const attrName = node . key . name
219
+ if ( ! getTargetAttrs ( tagName , config ) . has ( attrName ) ) {
220
+ return
221
+ }
222
+
223
+ checkSvelteLiteralOrText ( context , node . value [ 0 ] , config )
224
+ } ,
121
225
SvelteMustacheTag ( node : SvAST . SvelteMustacheTag ) {
122
226
if ( isIgnore ( node ) ) {
123
227
return
@@ -129,15 +233,7 @@ function create(context: RuleContext): RuleListener {
129
233
if ( isIgnore ( node ) ) {
130
234
return
131
235
}
132
-
133
- if ( isValidValue ( node . value , config ) ) {
134
- return
135
- }
136
-
137
- context . report ( {
138
- node,
139
- message : `raw text '${ node . value } ' is used`
140
- } )
236
+ checkSvelteLiteralOrText ( context , node , config )
141
237
}
142
238
}
143
239
}
@@ -154,6 +250,17 @@ export = defineRule('no-raw-text', {
154
250
{
155
251
type : 'object' ,
156
252
properties : {
253
+ attributes : {
254
+ type : 'object' ,
255
+ patternProperties : {
256
+ '^(?:\\S+|/.*/[a-z]*)$' : {
257
+ type : 'array' ,
258
+ items : { type : 'string' } ,
259
+ uniqueItems : true
260
+ }
261
+ } ,
262
+ additionalProperties : false
263
+ } ,
157
264
ignoreNodes : {
158
265
type : 'array'
159
266
} ,
0 commit comments