66const { extname } = require ( 'path' )
77const jsonDiffPatch = require ( 'jsondiffpatch' ) . create ( { } )
88const flatten = require ( 'flat' )
9- const collectKeys = require ( '../utils/collect-keys' )
9+ const { collectKeysFromFiles , collectKeysFromAST } = require ( '../utils/collect-keys' )
1010const collectLinkedKeys = require ( '../utils/collect-linked-keys' )
1111const {
1212 UNEXPECTED_ERROR_LOCATION ,
@@ -21,7 +21,7 @@ const debug = require('debug')('eslint-plugin-vue-i18n:no-unused-keys')
2121 */
2222
2323/** @type {string[] | null } */
24- let usedLocaleMessageKeys = null // used locale message keys
24+ let cacheUsedLocaleMessageKeys = null // used locale message keys
2525
2626/**
2727 * @param {RuleContext } context
@@ -99,54 +99,78 @@ function traverseNode (fullpath, paths, ast, fn) {
9999
100100function create ( context ) {
101101 const filename = context . getFilename ( )
102- if ( extname ( filename ) !== '.json' ) {
103- debug ( `ignore ${ filename } in no-unused-keys` )
104- return { }
105- }
106102
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 }
122106
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 }
126109
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+ } )
129123 }
130124
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 )
135136
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' ]
138161
139- const unusedKeys = getUnusedKeys ( context , targetLocaleMessage , jsonString , usedLocaleMessageKeys )
140- if ( ! unusedKeys ) { return }
162+ const usedLocaleMessageKeys = cacheUsedLocaleMessageKeys || ( cacheUsedLocaleMessageKeys = collectKeysFromFiles ( [ src ] , extensions ) )
141163
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+ }
149170 }
171+ } else {
172+ debug ( `ignore ${ filename } in no-unused-keys` )
173+ return { }
150174 }
151175}
152176
0 commit comments