@@ -11,18 +11,27 @@ import type {
11
11
SourceLocation
12
12
} from '../types'
13
13
14
- type AnyValue = VAST . ESLintLiteral [ 'value' ]
14
+ type AnyValue =
15
+ | VAST . ESLintLiteral [ 'value' ]
16
+ | VAST . ESLintTemplateElement [ 'value' ]
15
17
const config : {
16
18
ignorePattern : RegExp
17
19
ignoreNodes : string [ ]
18
20
ignoreText : string [ ]
19
21
} = { ignorePattern : / ^ [ ^ \S \s ] $ / , ignoreNodes : [ ] , ignoreText : [ ] }
20
22
const hasOnlyWhitespace = ( value : string ) => / ^ [ \r \n \s \t \f \v ] + $ / . test ( value )
23
+ const hasTemplateElementValue = (
24
+ value : any // eslint-disable-line @typescript-eslint/no-explicit-any
25
+ ) : value is { raw : string ; cooked : string } =>
26
+ 'raw' in value &&
27
+ typeof value . raw === 'string' &&
28
+ 'cooked' in value &&
29
+ typeof value . cooked === 'string'
21
30
const INNER_START_OFFSET = '<template>' . length
22
31
23
32
function calculateLoc (
24
- node : VAST . ESLintLiteral ,
25
- base : VAST . ESLintLiteral | null = null
33
+ node : VAST . ESLintLiteral | VAST . ESLintTemplateElement ,
34
+ base : VAST . ESLintLiteral | VAST . ESLintTemplateElement | null = null
26
35
) {
27
36
return ! base
28
37
? node . loc
@@ -40,15 +49,24 @@ function calculateLoc(
40
49
}
41
50
}
42
51
43
- function testValue ( value : AnyValue ) {
52
+ function testTextable ( value : string ) : boolean {
44
53
return (
45
- typeof value !== 'string' ||
46
54
hasOnlyWhitespace ( value ) ||
47
55
config . ignorePattern . test ( value . trim ( ) ) ||
48
56
config . ignoreText . includes ( value . trim ( ) )
49
57
)
50
58
}
51
59
60
+ function testValue ( value : AnyValue ) : boolean {
61
+ if ( typeof value === 'string' ) {
62
+ return testTextable ( value )
63
+ } else if ( hasTemplateElementValue ( value ) ) {
64
+ return testTextable ( value . raw )
65
+ } else {
66
+ return false
67
+ }
68
+ }
69
+
52
70
// parent is directive (e.g <p v-xxx="..."></p>)
53
71
function checkVAttributeDirective (
54
72
context : RuleContext ,
@@ -65,63 +83,25 @@ function checkVAttributeDirective(
65
83
( attrNode . key . name === 'text' ||
66
84
// for vue-eslint-parser v6+
67
85
attrNode . key . name . name === 'text' ) &&
68
- node . expression &&
69
- node . expression . type === 'Literal'
86
+ node . expression
70
87
) {
71
- const literalNode = node . expression
72
- const value = literalNode . value
73
-
74
- if ( testValue ( value ) ) {
75
- return
76
- }
77
-
78
- const loc = calculateLoc ( literalNode , baseNode )
79
- context . report ( {
80
- loc,
81
- message : `raw text '${ literalNode . value } ' is used`
82
- } )
88
+ checkExpressionContainerText ( context , node . expression , baseNode )
83
89
}
84
90
}
85
91
}
86
92
87
- function checkVExpressionContainerText (
93
+ function checkVExpressionContainer (
88
94
context : RuleContext ,
89
95
node : VAST . VExpressionContainer ,
90
- baseNode : VAST . ESLintLiteral | null = null
96
+ baseNode : VAST . ESLintLiteral | VAST . ESLintTemplateElement | null = null
91
97
) {
92
98
if ( ! node . expression ) {
93
99
return
94
100
}
95
101
96
102
if ( node . parent && node . parent . type === 'VElement' ) {
97
103
// parent is element (e.g. <p>{{ ... }}</p>)
98
- if ( node . expression . type === 'Literal' ) {
99
- const literalNode = node . expression
100
- if ( testValue ( literalNode . value ) ) {
101
- return
102
- }
103
-
104
- const loc = calculateLoc ( literalNode , baseNode )
105
- context . report ( {
106
- loc,
107
- message : `raw text '${ literalNode . value } ' is used`
108
- } )
109
- } else if ( node . expression . type === 'ConditionalExpression' ) {
110
- const targets = [ node . expression . consequent , node . expression . alternate ]
111
- targets . forEach ( target => {
112
- if ( target . type === 'Literal' ) {
113
- if ( testValue ( target . value ) ) {
114
- return
115
- }
116
-
117
- const loc = calculateLoc ( target , baseNode )
118
- context . report ( {
119
- loc,
120
- message : `raw text '${ target . value } ' is used`
121
- } )
122
- }
123
- } )
124
- }
104
+ checkExpressionContainerText ( context , node . expression , baseNode )
125
105
} else if (
126
106
node . parent &&
127
107
node . parent . type === 'VAttribute' &&
@@ -135,6 +115,67 @@ function checkVExpressionContainerText(
135
115
)
136
116
}
137
117
}
118
+ function checkExpressionContainerText (
119
+ context : RuleContext ,
120
+ expression : Exclude < VAST . VExpressionContainer [ 'expression' ] , null > ,
121
+ baseNode : VAST . ESLintLiteral | VAST . ESLintTemplateElement | null = null
122
+ ) {
123
+ if ( expression . type === 'Literal' ) {
124
+ const literalNode = expression
125
+ if ( testValue ( literalNode . value ) ) {
126
+ return
127
+ }
128
+
129
+ const loc = calculateLoc ( literalNode , baseNode )
130
+ context . report ( {
131
+ loc,
132
+ message : `raw text '${ literalNode . value } ' is used`
133
+ } )
134
+ } else if (
135
+ expression . type === 'TemplateLiteral' &&
136
+ expression . expressions . length === 0
137
+ ) {
138
+ const templateNode = expression . quasis [ 0 ]
139
+ if ( testValue ( templateNode . value ) ) {
140
+ return
141
+ }
142
+
143
+ const loc = calculateLoc ( templateNode , baseNode )
144
+ context . report ( {
145
+ loc,
146
+ message : `raw text '${ templateNode . value . raw } ' is used`
147
+ } )
148
+ } else if ( expression . type === 'ConditionalExpression' ) {
149
+ const targets = [ expression . consequent , expression . alternate ]
150
+ targets . forEach ( target => {
151
+ if ( target . type === 'Literal' ) {
152
+ if ( testValue ( target . value ) ) {
153
+ return
154
+ }
155
+
156
+ const loc = calculateLoc ( target , baseNode )
157
+ context . report ( {
158
+ loc,
159
+ message : `raw text '${ target . value } ' is used`
160
+ } )
161
+ } else if (
162
+ target . type === 'TemplateLiteral' &&
163
+ target . expressions . length === 0
164
+ ) {
165
+ const node = target . quasis [ 0 ]
166
+ if ( testValue ( node . value ) ) {
167
+ return
168
+ }
169
+
170
+ const loc = calculateLoc ( node , baseNode )
171
+ context . report ( {
172
+ loc,
173
+ message : `raw text '${ node . value . raw } ' is used`
174
+ } )
175
+ }
176
+ } )
177
+ }
178
+ }
138
179
139
180
function checkRawText (
140
181
context : RuleContext ,
@@ -158,7 +199,7 @@ function findVariable(variables: Variable[], name: string) {
158
199
function getComponentTemplateValueNode (
159
200
context : RuleContext ,
160
201
node : VAST . ESLintObjectExpression
161
- ) : VAST . ESLintLiteral | null {
202
+ ) : VAST . ESLintLiteral | VAST . ESLintTemplateElement | null {
162
203
const templateNode = node . properties . find (
163
204
( p ) : p is VAST . ESLintProperty =>
164
205
p . type === 'Property' &&
@@ -169,6 +210,11 @@ function getComponentTemplateValueNode(
169
210
if ( templateNode ) {
170
211
if ( templateNode . value . type === 'Literal' ) {
171
212
return templateNode . value
213
+ } else if (
214
+ templateNode . value . type === 'TemplateLiteral' &&
215
+ templateNode . value . expressions . length === 0
216
+ ) {
217
+ return templateNode . value . quasis [ 0 ]
172
218
} else if ( templateNode . value . type === 'Identifier' ) {
173
219
const templateVariable = findVariable (
174
220
context . getScope ( ) . variables ,
@@ -177,8 +223,15 @@ function getComponentTemplateValueNode(
177
223
if ( templateVariable ) {
178
224
const varDeclNode = templateVariable . defs [ 0 ]
179
225
. node as VAST . ESLintVariableDeclarator
180
- if ( varDeclNode . init && varDeclNode . init . type === 'Literal' ) {
181
- return varDeclNode . init
226
+ if ( varDeclNode . init ) {
227
+ if ( varDeclNode . init . type === 'Literal' ) {
228
+ return varDeclNode . init
229
+ } else if (
230
+ varDeclNode . init . type === 'TemplateLiteral' &&
231
+ varDeclNode . init . expressions . length === 0
232
+ ) {
233
+ return varDeclNode . init . quasis [ 0 ]
234
+ }
182
235
}
183
236
}
184
237
}
@@ -188,7 +241,17 @@ function getComponentTemplateValueNode(
188
241
}
189
242
190
243
function getComponentTemplateNode ( value : AnyValue ) {
191
- return parse ( `<template>${ value } </template>` , { } ) . templateBody !
244
+ return parse (
245
+ `<template>${
246
+ // prettier-ignore
247
+ typeof value === 'string'
248
+ ? value
249
+ : hasTemplateElementValue ( value )
250
+ ? value . raw
251
+ : value
252
+ } </template>`,
253
+ { }
254
+ ) . templateBody !
192
255
}
193
256
194
257
function create ( context : RuleContext ) : RuleListener {
@@ -213,7 +276,7 @@ function create(context: RuleContext): RuleListener {
213
276
{
214
277
// template block
215
278
VExpressionContainer ( node : VAST . VExpressionContainer ) {
216
- checkVExpressionContainerText ( context , node )
279
+ checkVExpressionContainer ( context , node )
217
280
} ,
218
281
219
282
VText ( node : VAST . VText ) {
@@ -238,7 +301,7 @@ function create(context: RuleContext): RuleListener {
238
301
if ( node . type === 'VText' ) {
239
302
checkRawText ( context , node . value , valueNode . loc )
240
303
} else if ( node . type === 'VExpressionContainer' ) {
241
- checkVExpressionContainerText ( context , node , valueNode )
304
+ checkVExpressionContainer ( context , node , valueNode )
242
305
}
243
306
} ,
244
307
leaveNode ( ) {
0 commit comments