@@ -8,207 +8,105 @@ const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
88function isStringLiteral ( node ) {
99 return ! ! node && node . type === experimental_utils_1 . AST_NODE_TYPES . Literal && typeof node . value === 'string' ;
1010}
11- function isObjectLiteral ( node ) {
12- return ! ! node && node . type === experimental_utils_1 . AST_NODE_TYPES . ObjectExpression ;
11+ function isDoubleQuoted ( node ) {
12+ return node . raw [ 0 ] === '"' && node . raw [ node . raw . length - 1 ] === '"' ;
1313}
14- function isPropertyAssignment ( node ) {
15- return ! ! node && node . type === experimental_utils_1 . AST_NODE_TYPES . Property ;
16- }
17- module . exports = new ( _a = class NoUnexternalizedStringsRuleWalker {
14+ module . exports = new ( _a = class NoUnexternalizedStrings {
1815 constructor ( ) {
19- this . signatures = Object . create ( null ) ;
20- this . ignores = Object . create ( null ) ;
21- this . usedKeys = Object . create ( null ) ;
2216 this . meta = {
2317 type : 'problem' ,
2418 schema : { } ,
2519 messages : {
26- badQuotes : 'Do not use double quotes for imports.' ,
27- unexternalized : 'Unexternalized string.' ,
28- duplicateKey : `Duplicate key '{{key}}' with different message value.` ,
29- badKey : `The key {{key}} doesn't conform to a valid localize identifier` ,
30- emptyKey : 'Key is empty.' ,
31- whitespaceKey : 'Key is only whitespace.' ,
32- badMessage : `Message argument to '{{message}}' must be a string literal.`
20+ doubleQuoted : 'Only use double-quoted strings for externalized strings.' ,
21+ badKey : 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.' ,
22+ duplicateKey : 'Duplicate key \'{{key}}\' with different message value.' ,
23+ badMessage : 'Message argument to \'{{message}}\' must be a string literal.'
3324 }
3425 } ;
3526 }
3627 create ( context ) {
37- const first = context . options [ 0 ] ;
38- if ( first ) {
39- if ( Array . isArray ( first . signatures ) ) {
40- first . signatures . forEach ( ( signature ) => this . signatures [ signature ] = true ) ;
41- }
42- if ( Array . isArray ( first . ignores ) ) {
43- first . ignores . forEach ( ( ignore ) => this . ignores [ ignore ] = true ) ;
44- }
45- if ( typeof first . messageIndex !== 'undefined' ) {
46- this . messageIndex = first . messageIndex ;
47- }
48- if ( typeof first . keyIndex !== 'undefined' ) {
49- this . keyIndex = first . keyIndex ;
28+ const externalizedStringLiterals = new Map ( ) ;
29+ const doubleQuotedStringLiterals = new Set ( ) ;
30+ function collectDoubleQuotedStrings ( node ) {
31+ if ( isStringLiteral ( node ) && isDoubleQuoted ( node ) ) {
32+ doubleQuotedStringLiterals . add ( node ) ;
5033 }
5134 }
52- return {
53- [ 'Program:exit' ] : ( ) => {
54- this . _checkProgramEnd ( context ) ;
55- } ,
56- [ 'Literal' ] : ( node ) => {
57- if ( typeof node . value === 'string' ) {
58- this . _checkStringLiteral ( context , node ) ;
59- }
60- } ,
61- } ;
62- }
63- _checkProgramEnd ( context ) {
64- Object . keys ( this . usedKeys ) . forEach ( key => {
65- // Keys are quoted.
66- const identifier = key . substr ( 1 , key . length - 2 ) ;
67- const occurrences = this . usedKeys [ key ] ;
68- // bad key
69- if ( ! NoUnexternalizedStringsRuleWalker . IDENTIFIER . test ( identifier ) ) {
70- context . report ( {
71- loc : occurrences [ 0 ] . key . loc ,
72- messageId : 'badKey' ,
73- data : { key : occurrences [ 0 ] . key . value }
74- } ) ;
35+ function visitLocalizeCall ( node ) {
36+ // localize(key, message)
37+ const [ keyNode , messageNode ] = node . arguments ;
38+ // (1)
39+ // extract key so that it can be checked later
40+ let key ;
41+ if ( isStringLiteral ( keyNode ) ) {
42+ doubleQuotedStringLiterals . delete ( keyNode ) ; //todo@joh reconsider
43+ key = keyNode . value ;
7544 }
76- // duplicates key
77- if ( occurrences . length > 1 ) {
78- occurrences . forEach ( occurrence => {
79- context . report ( {
80- loc : occurrence . key . loc ,
81- messageId : 'duplicateKey' ,
82- data : { key : occurrence . key . value }
83- } ) ;
84- } ) ;
85- }
86- } ) ;
87- }
88- _checkStringLiteral ( context , node ) {
89- var _a ;
90- const text = node . raw ;
91- const doubleQuoted = text . length >= 2 && text [ 0 ] === NoUnexternalizedStringsRuleWalker . DOUBLE_QUOTE && text [ text . length - 1 ] === NoUnexternalizedStringsRuleWalker . DOUBLE_QUOTE ;
92- const info = this . _findDescribingParent ( node ) ;
93- // Ignore strings in import and export nodes.
94- if ( info && info . isImport && doubleQuoted ) {
95- context . report ( {
96- loc : node . loc ,
97- messageId : 'badQuotes'
98- } ) ;
99- return ;
100- }
101- const callInfo = info ? info . callInfo : null ;
102- const functionName = callInfo && isStringLiteral ( callInfo . callExpression . callee )
103- ? callInfo . callExpression . callee . value
104- : null ;
105- if ( functionName && this . ignores [ functionName ] ) {
106- return ;
107- }
108- if ( doubleQuoted && ( ! callInfo || callInfo . argIndex === - 1 || ! this . signatures [ functionName ] ) ) {
109- context . report ( {
110- loc : node . loc ,
111- messageId : 'unexternalized'
112- } ) ;
113- return ;
114- }
115- // We have a single quoted string outside a localize function name.
116- if ( ! doubleQuoted && ! this . signatures [ functionName ] ) {
117- return ;
118- }
119- // We have a string that is a direct argument into the localize call.
120- const keyArg = callInfo && callInfo . argIndex === this . keyIndex
121- ? callInfo . callExpression . arguments [ this . keyIndex ]
122- : null ;
123- if ( keyArg ) {
124- if ( isStringLiteral ( keyArg ) ) {
125- this . recordKey ( context , keyArg , this . messageIndex && callInfo ? callInfo . callExpression . arguments [ this . messageIndex ] : undefined ) ;
126- }
127- else if ( isObjectLiteral ( keyArg ) ) {
128- for ( const property of keyArg . properties ) {
129- if ( isPropertyAssignment ( property ) ) {
130- const name = NoUnexternalizedStringsRuleWalker . _getText ( context . getSourceCode ( ) , property . key ) ;
131- if ( name === 'key' ) {
132- const initializer = property . value ;
133- if ( isStringLiteral ( initializer ) ) {
134- this . recordKey ( context , initializer , this . messageIndex && callInfo ? callInfo . callExpression . arguments [ this . messageIndex ] : undefined ) ;
45+ else if ( keyNode . type === experimental_utils_1 . AST_NODE_TYPES . ObjectExpression ) {
46+ for ( let property of keyNode . properties ) {
47+ if ( property . type === experimental_utils_1 . AST_NODE_TYPES . Property && ! property . computed ) {
48+ if ( property . key . type === experimental_utils_1 . AST_NODE_TYPES . Identifier && property . key . name === 'key' ) {
49+ if ( isStringLiteral ( property . value ) ) {
50+ doubleQuotedStringLiterals . delete ( property . value ) ; //todo@joh reconsider
51+ key = property . value . value ;
52+ break ;
13553 }
136- break ;
13754 }
13855 }
13956 }
14057 }
141- }
142- const messageArg = callInfo . callExpression . arguments [ this . messageIndex ] ;
143- if ( messageArg && ! isStringLiteral ( messageArg ) ) {
144- context . report ( {
145- loc : messageArg . loc ,
146- messageId : 'badMessage' ,
147- data : { message : NoUnexternalizedStringsRuleWalker . _getText ( context . getSourceCode ( ) , ( _a = callInfo ) === null || _a === void 0 ? void 0 : _a . callExpression . callee ) }
148- } ) ;
149- return ;
150- }
151- }
152- recordKey ( context , keyNode , messageNode ) {
153- const text = keyNode . raw ;
154- // We have an empty key
155- if ( text . match ( / ( [ ' " ] ) * \1/ ) ) {
156- if ( messageNode ) {
157- context . report ( {
158- loc : keyNode . loc ,
159- messageId : 'whitespaceKey'
160- } ) ;
58+ if ( typeof key === 'string' ) {
59+ let array = externalizedStringLiterals . get ( key ) ;
60+ if ( ! array ) {
61+ array = [ ] ;
62+ externalizedStringLiterals . set ( key , array ) ;
63+ }
64+ array . push ( { call : node , message : messageNode } ) ;
16165 }
162- else {
66+ // (2)
67+ // remove message-argument from doubleQuoted list and make
68+ // sure it is a string-literal
69+ doubleQuotedStringLiterals . delete ( messageNode ) ;
70+ if ( ! isStringLiteral ( messageNode ) ) {
16371 context . report ( {
164- loc : keyNode . loc ,
165- messageId : 'emptyKey'
72+ loc : messageNode . loc ,
73+ messageId : 'badMessage' ,
74+ data : { message : context . getSourceCode ( ) . getText ( node ) }
16675 } ) ;
16776 }
168- return ;
16977 }
170- let occurrences = this . usedKeys [ text ] ;
171- if ( ! occurrences ) {
172- occurrences = [ ] ;
173- this . usedKeys [ text ] = occurrences ;
174- }
175- if ( messageNode ) {
176- if ( occurrences . some ( pair => pair . message ? NoUnexternalizedStringsRuleWalker . _getText ( context . getSourceCode ( ) , pair . message ) === NoUnexternalizedStringsRuleWalker . _getText ( context . getSourceCode ( ) , messageNode ) : false ) ) {
177- return ;
178- }
179- }
180- occurrences . push ( { key : keyNode , message : messageNode } ) ;
181- }
182- _findDescribingParent ( node ) {
183- let parent ;
184- while ( ( parent = node . parent ) ) {
185- const kind = parent . type ;
186- if ( kind === experimental_utils_1 . AST_NODE_TYPES . CallExpression ) {
187- const callExpression = parent ;
188- return { callInfo : { callExpression : callExpression , argIndex : callExpression . arguments . indexOf ( node ) } } ;
189- }
190- else if ( kind === experimental_utils_1 . AST_NODE_TYPES . TSImportEqualsDeclaration || kind === experimental_utils_1 . AST_NODE_TYPES . ImportDeclaration || kind === experimental_utils_1 . AST_NODE_TYPES . ExportNamedDeclaration ) {
191- return { isImport : true } ;
78+ function reportBadStringsAndBadKeys ( ) {
79+ // (1)
80+ // report all strings that are in double quotes
81+ for ( const node of doubleQuotedStringLiterals ) {
82+ context . report ( { loc : node . loc , messageId : 'doubleQuoted' } ) ;
19283 }
193- else if ( kind === experimental_utils_1 . AST_NODE_TYPES . VariableDeclaration || kind === experimental_utils_1 . AST_NODE_TYPES . FunctionDeclaration || kind === experimental_utils_1 . AST_NODE_TYPES . TSPropertySignature
194- || kind === experimental_utils_1 . AST_NODE_TYPES . TSMethodSignature || kind === experimental_utils_1 . AST_NODE_TYPES . TSInterfaceDeclaration
195- || kind === experimental_utils_1 . AST_NODE_TYPES . ClassDeclaration || kind === experimental_utils_1 . AST_NODE_TYPES . TSEnumDeclaration || kind === experimental_utils_1 . AST_NODE_TYPES . TSModuleDeclaration
196- || kind === experimental_utils_1 . AST_NODE_TYPES . TSTypeAliasDeclaration || kind === experimental_utils_1 . AST_NODE_TYPES . Program ) {
197- return null ;
84+ for ( const [ key , values ] of externalizedStringLiterals ) {
85+ // (2)
86+ // report all invalid NLS keys
87+ if ( ! key . match ( NoUnexternalizedStrings . _rNlsKeys ) ) {
88+ for ( let value of values ) {
89+ context . report ( { loc : value . call . loc , messageId : 'badKey' , data : { key } } ) ;
90+ }
91+ }
92+ // (2)
93+ // report all invalid duplicates (same key, different message)
94+ if ( values . length > 1 ) {
95+ for ( let i = 1 ; i < values . length ; i ++ ) {
96+ if ( context . getSourceCode ( ) . getText ( values [ i - 1 ] . message ) !== context . getSourceCode ( ) . getText ( values [ i ] . message ) ) {
97+ context . report ( { loc : values [ i ] . call . loc , messageId : 'duplicateKey' , data : { key } } ) ;
98+ }
99+ }
100+ }
198101 }
199- node = parent ;
200102 }
201- return null ;
202- }
203- static _getText ( source , node ) {
204- if ( node . type === experimental_utils_1 . AST_NODE_TYPES . Literal ) {
205- return String ( node . value ) ;
206- }
207- const start = source . getIndexFromLoc ( node . loc . start ) ;
208- const end = source . getIndexFromLoc ( node . loc . end ) ;
209- return source . getText ( ) . substring ( start , end ) ;
103+ return {
104+ [ 'Literal' ] : ( node ) => collectDoubleQuotedStrings ( node ) ,
105+ [ 'CallExpression[callee.type="MemberExpression"][callee.property.name="localize"]:exit' ] : ( node ) => visitLocalizeCall ( node ) ,
106+ [ 'CallExpression[callee.name="localize"][arguments.length>=2]:exit' ] : ( node ) => visitLocalizeCall ( node ) ,
107+ [ 'Program:exit' ] : reportBadStringsAndBadKeys ,
108+ } ;
210109 }
211110 } ,
212- _a . DOUBLE_QUOTE = '"' ,
213- _a . IDENTIFIER = / ^ [ _ a - z A - Z 0 - 9 ] [ . \- _ a - z A - Z 0 - 9 ] * $ / ,
111+ _a . _rNlsKeys = / ^ [ _ a - z A - Z 0 - 9 ] [ . \- _ a - z A - Z 0 - 9 ] * $ / ,
214112 _a ) ;
0 commit comments