@@ -11,18 +11,27 @@ import type {
1111 SourceLocation
1212} from '../types'
1313
14- type AnyValue = VAST . ESLintLiteral [ 'value' ]
14+ type AnyValue =
15+ | VAST . ESLintLiteral [ 'value' ]
16+ | VAST . ESLintTemplateElement [ 'value' ]
1517const config : {
1618 ignorePattern : RegExp
1719 ignoreNodes : string [ ]
1820 ignoreText : string [ ]
1921} = { ignorePattern : / ^ [ ^ \S \s ] $ / , ignoreNodes : [ ] , ignoreText : [ ] }
2022const 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'
2130const INNER_START_OFFSET = '<template>' . length
2231
2332function 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
2635) {
2736 return ! base
2837 ? node . loc
@@ -40,15 +49,24 @@ function calculateLoc(
4049 }
4150}
4251
43- function testValue ( value : AnyValue ) {
52+ function testTextable ( value : string ) : boolean {
4453 return (
45- typeof value !== 'string' ||
4654 hasOnlyWhitespace ( value ) ||
4755 config . ignorePattern . test ( value . trim ( ) ) ||
4856 config . ignoreText . includes ( value . trim ( ) )
4957 )
5058}
5159
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+
5270// parent is directive (e.g <p v-xxx="..."></p>)
5371function checkVAttributeDirective (
5472 context : RuleContext ,
@@ -65,63 +83,25 @@ function checkVAttributeDirective(
6583 ( attrNode . key . name === 'text' ||
6684 // for vue-eslint-parser v6+
6785 attrNode . key . name . name === 'text' ) &&
68- node . expression &&
69- node . expression . type === 'Literal'
86+ node . expression
7087 ) {
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 )
8389 }
8490 }
8591}
8692
87- function checkVExpressionContainerText (
93+ function checkVExpressionContainer (
8894 context : RuleContext ,
8995 node : VAST . VExpressionContainer ,
90- baseNode : VAST . ESLintLiteral | null = null
96+ baseNode : VAST . ESLintLiteral | VAST . ESLintTemplateElement | null = null
9197) {
9298 if ( ! node . expression ) {
9399 return
94100 }
95101
96102 if ( node . parent && node . parent . type === 'VElement' ) {
97103 // 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 )
125105 } else if (
126106 node . parent &&
127107 node . parent . type === 'VAttribute' &&
@@ -135,6 +115,67 @@ function checkVExpressionContainerText(
135115 )
136116 }
137117}
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+ }
138179
139180function checkRawText (
140181 context : RuleContext ,
@@ -158,7 +199,7 @@ function findVariable(variables: Variable[], name: string) {
158199function getComponentTemplateValueNode (
159200 context : RuleContext ,
160201 node : VAST . ESLintObjectExpression
161- ) : VAST . ESLintLiteral | null {
202+ ) : VAST . ESLintLiteral | VAST . ESLintTemplateElement | null {
162203 const templateNode = node . properties . find (
163204 ( p ) : p is VAST . ESLintProperty =>
164205 p . type === 'Property' &&
@@ -169,6 +210,11 @@ function getComponentTemplateValueNode(
169210 if ( templateNode ) {
170211 if ( templateNode . value . type === 'Literal' ) {
171212 return templateNode . value
213+ } else if (
214+ templateNode . value . type === 'TemplateLiteral' &&
215+ templateNode . value . expressions . length === 0
216+ ) {
217+ return templateNode . value . quasis [ 0 ]
172218 } else if ( templateNode . value . type === 'Identifier' ) {
173219 const templateVariable = findVariable (
174220 context . getScope ( ) . variables ,
@@ -177,8 +223,15 @@ function getComponentTemplateValueNode(
177223 if ( templateVariable ) {
178224 const varDeclNode = templateVariable . defs [ 0 ]
179225 . 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+ }
182235 }
183236 }
184237 }
@@ -188,7 +241,17 @@ function getComponentTemplateValueNode(
188241}
189242
190243function 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 !
192255}
193256
194257function create ( context : RuleContext ) : RuleListener {
@@ -213,7 +276,7 @@ function create(context: RuleContext): RuleListener {
213276 {
214277 // template block
215278 VExpressionContainer ( node : VAST . VExpressionContainer ) {
216- checkVExpressionContainerText ( context , node )
279+ checkVExpressionContainer ( context , node )
217280 } ,
218281
219282 VText ( node : VAST . VText ) {
@@ -238,7 +301,7 @@ function create(context: RuleContext): RuleListener {
238301 if ( node . type === 'VText' ) {
239302 checkRawText ( context , node . value , valueNode . loc )
240303 } else if ( node . type === 'VExpressionContainer' ) {
241- checkVExpressionContainerText ( context , node , valueNode )
304+ checkVExpressionContainer ( context , node , valueNode )
242305 }
243306 } ,
244307 leaveNode ( ) {
0 commit comments