6
6
const { extname } = require ( 'path' )
7
7
const jsonDiffPatch = require ( 'jsondiffpatch' ) . create ( { } )
8
8
const flatten = require ( 'flat' )
9
- const collectKeys = require ( '../utils/collect-keys' )
9
+ const { collectKeysFromFiles , collectKeysFromAST } = require ( '../utils/collect-keys' )
10
10
const collectLinkedKeys = require ( '../utils/collect-linked-keys' )
11
11
const {
12
12
UNEXPECTED_ERROR_LOCATION ,
@@ -21,7 +21,7 @@ const debug = require('debug')('eslint-plugin-vue-i18n:no-unused-keys')
21
21
*/
22
22
23
23
/** @type {string[] | null } */
24
- let usedLocaleMessageKeys = null // used locale message keys
24
+ let cacheUsedLocaleMessageKeys = null // used locale message keys
25
25
26
26
/**
27
27
* @param {RuleContext } context
@@ -99,54 +99,78 @@ function traverseNode (fullpath, paths, ast, fn) {
99
99
100
100
function create ( context ) {
101
101
const filename = context . getFilename ( )
102
- if ( extname ( filename ) !== '.json' ) {
103
- debug ( `ignore ${ filename } in no-unused-keys` )
104
- return { }
105
- }
106
102
107
- const { settings } = context
108
- if ( ! settings [ 'vue-i18n' ] || ! settings [ 'vue-i18n' ] . localeDir ) {
109
- context . report ( {
110
- loc : UNEXPECTED_ERROR_LOCATION ,
111
- message : `You need to 'localeDir' at 'settings. See the 'eslint-plugin-vue-i18n documentation`
112
- } )
113
- return { }
114
- }
115
-
116
- const localeMessages = getLocaleMessages ( settings [ 'vue-i18n' ] . localeDir )
117
- const targetLocaleMessage = localeMessages . findExistLocaleMessage ( filename )
118
- if ( ! targetLocaleMessage ) {
119
- debug ( `ignore ${ filename } in no-unused-keys` )
120
- return { }
121
- }
103
+ function verifyJson ( jsonString , jsonFilename , targetLocaleMessage , usedLocaleMessageKeys , offsetLoc = { line : 1 , column : 1 } ) {
104
+ const ast = generateJsonAst ( context , jsonString , jsonFilename )
105
+ if ( ! ast ) { return }
122
106
123
- const options = ( context . options && context . options [ 0 ] ) || { }
124
- const src = options . src || process . cwd ( )
125
- const extensions = options . extensions || [ '.js' , '.vue' ]
107
+ const unusedKeys = getUnusedKeys ( context , targetLocaleMessage , jsonString , usedLocaleMessageKeys )
108
+ if ( ! unusedKeys ) { return }
126
109
127
- if ( ! usedLocaleMessageKeys ) {
128
- usedLocaleMessageKeys = collectKeys ( [ src ] , extensions )
110
+ traverseJsonAstWithUnusedKeys ( unusedKeys , ast , ( fullpath , node ) => {
111
+ let { line, column } = node . loc . start
112
+ if ( line === 1 ) {
113
+ line += offsetLoc . line - 1
114
+ column += offsetLoc . column - 1
115
+ } else {
116
+ line += offsetLoc . line - 1
117
+ }
118
+ context . report ( {
119
+ message : `unused '${ fullpath } ' key'` ,
120
+ loc : { line, column }
121
+ } )
122
+ } )
129
123
}
130
124
131
- return {
132
- Program ( node ) {
133
- const [ jsonString , jsonFilename ] = extractJsonInfo ( context , node )
134
- if ( ! jsonString || ! jsonFilename ) { return }
125
+ if ( extname ( filename ) === '.vue' ) {
126
+ return {
127
+ Program ( node ) {
128
+ const documentFragment = context . parserServices . getDocumentFragment && context . parserServices . getDocumentFragment ( )
129
+ /** @type {VElement[] } */
130
+ const i18nBlocks = documentFragment && documentFragment . children . filter ( node => node . type === 'VElement' && node . name === 'i18n' ) || [ ]
131
+ if ( ! i18nBlocks . length ) {
132
+ return
133
+ }
134
+ const localeMessages = getLocaleMessages ( context )
135
+ const usedLocaleMessageKeys = collectKeysFromAST ( node , context . getSourceCode ( ) . visitorKeys )
135
136
136
- const ast = generateJsonAst ( context , jsonString , jsonFilename )
137
- if ( ! ast ) { return }
137
+ for ( const block of i18nBlocks ) {
138
+ if ( block . startTag . attributes . some ( attr => ! attr . directive && attr . key . name === 'src' ) || ! block . endTag ) {
139
+ continue
140
+ }
141
+ const targetLocaleMessage = localeMessages . findBlockLocaleMessage ( block )
142
+ const tokenStore = context . parserServices . getTemplateBodyTokenStore ( )
143
+ const tokens = tokenStore . getTokensBetween ( block . startTag , block . endTag )
144
+ const jsonString = tokens . map ( t => t . value ) . join ( '' )
145
+ if ( jsonString . trim ( ) ) {
146
+ verifyJson ( jsonString , filename , targetLocaleMessage , usedLocaleMessageKeys , block . startTag . loc . start )
147
+ }
148
+ }
149
+ }
150
+ }
151
+ } else if ( extname ( filename ) === '.json' ) {
152
+ const localeMessages = getLocaleMessages ( context )
153
+ const targetLocaleMessage = localeMessages . findExistLocaleMessage ( filename )
154
+ if ( ! targetLocaleMessage ) {
155
+ debug ( `ignore ${ filename } in no-unused-keys` )
156
+ return { }
157
+ }
158
+ const options = ( context . options && context . options [ 0 ] ) || { }
159
+ const src = options . src || process . cwd ( )
160
+ const extensions = options . extensions || [ '.js' , '.vue' ]
138
161
139
- const unusedKeys = getUnusedKeys ( context , targetLocaleMessage , jsonString , usedLocaleMessageKeys )
140
- if ( ! unusedKeys ) { return }
162
+ const usedLocaleMessageKeys = cacheUsedLocaleMessageKeys || ( cacheUsedLocaleMessageKeys = collectKeysFromFiles ( [ src ] , extensions ) )
141
163
142
- traverseJsonAstWithUnusedKeys ( unusedKeys , ast , ( fullpath , node ) => {
143
- const { line, column } = node . loc . start
144
- context . report ( {
145
- message : `unused '${ fullpath } ' key'` ,
146
- loc : { line, column }
147
- } )
148
- } )
164
+ return {
165
+ Program ( node ) {
166
+ const [ jsonString , jsonFilename ] = extractJsonInfo ( context , node )
167
+ if ( ! jsonString || ! jsonFilename ) { return }
168
+ verifyJson ( jsonString , jsonFilename , targetLocaleMessage , usedLocaleMessageKeys )
169
+ }
149
170
}
171
+ } else {
172
+ debug ( `ignore ${ filename } in no-unused-keys` )
173
+ return { }
150
174
}
151
175
}
152
176
0 commit comments