fileoverview author kazuya kawaguchi (a.k.a. kazupon)
  import type { Linter } from 'eslint' ; import { parseForESLint , AST as VAST } from 'vue-eslint-parser' ; import { readFileSync } from 'fs' ; import { resolve , extname } from 'path' ; import { listFilesToProcess } from './glob-utils' ; import { ResourceLoader } from './resource-loader' ; import { CacheLoader } from './cache-loader' ; import { defineCacheFunction } from './cache-function' ; import debugBuilder from 'debug' ; import type { RuleContext , VisitorKeys } from '../types' ; import { Legacy } from '@eslint/eslintrc' ; import { getCwd } from './get-cwd' ; const debug = debugBuilder ( 'eslint-plugin-vue-i18n:collect-keys' ) ; const { CascadingConfigArrayFactory } = Legacy ; param { CallExpression } node function getKeyFromCallExpression ( node ) { const funcName = ( node . callee . type === 'MemberExpression' && node . callee . property . type === 'Identifier' && node . callee . property . name ) || ( node . callee . type === 'Identifier' && node . callee . name ) || '' ; if ( ! /^(\$t|t|\$tc|tc)$/ . test ( funcName ) || ! node . arguments || ! node . arguments . length ) { return null ; } const [ keyNode ] = node . arguments ; if ( keyNode . type !== 'Literal' ) { return null ; } return keyNode . value ? keyNode . value : null ; } param { VDirective } node function getKeyFromVDirective ( node ) { if ( node . value && node . value . type === 'VExpressionContainer' && node . value . expression && node . value . expression . type === 'Literal' ) { return node . value . expression . value ? node . value . expression . value : null ; } else { return null ; } } param { VAttribute } node function getKeyFromI18nComponent ( node ) { if ( node . value && node . value . type === 'VLiteral' ) { return node . value . value ; } else { return null ; } } function getParser ( parser ) { if ( parser ) { try { return require ( parser ) ; } catch ( _e ) { } } return { parseForESLint , parse ( code , options ) { return parseForESLint ( code , options ) . ast ; } } ; } param { string } text param { string } filename returns { string [ ] } function collectKeysFromText ( text , filename , getConfigForFile ) { const effectiveFilename = filename || '<text>' ; debug ( `collectKeysFromFile ${ effectiveFilename }` ) ; const config = getConfigForFile ( effectiveFilename ) ; const parser = getParser ( config . parser ) ; const parserOptions = Object . assign ( { } , config . parserOptions , { loc : true , range : true , raw : true , tokens : true , comment : true , eslintVisitorKeys : true , eslintScopeManager : true , filePath : effectiveFilename } ) ; try { const parseResult = typeof parser . parseForESLint === 'function' ? parser . parseForESLint ( text , parserOptions ) : { ast : parser . parse ( text , parserOptions ) } ; return collectKeysFromAST ( parseResult . ast , parseResult . visitorKeys ) ; } catch ( _e ) { return [ ] ; } } returns { ResourceLoader [ ] } function collectKeyResourcesFromFiles ( fileNames , cwd ) { debug ( 'collectKeysFromFiles' , fileNames ) ; const configArrayFactory = new CascadingConfigArrayFactory ( { additionalPluginPool : new Map ( [ [ '@intlify/vue-i18n' , require ( '../index' ) ] ] ) , cwd , eslintRecommendedPath : require . resolve ( '../../files/empty.json' ) , eslintAllPath : require . resolve ( '../../files/empty.json' ) } ) ; const results = [ ] ; for ( const filename of fileNames ) { debug ( `Processing file ... ${ filename }` ) ; results . push ( new ResourceLoader ( resolve ( filename ) , ( ) => { const text = readFileSync ( resolve ( filename ) , 'utf8' ) ; return collectKeysFromText ( text , filename , getConfigForFile ) ; } ) ) ; } return results ; function getConfigForFile ( filePath ) { const absolutePath = resolve ( cwd , filePath ) ; return configArrayFactory . getConfigArrayForFile ( absolutePath ) . extractConfig ( absolutePath ) . toCompatibleObjectAsConfigFileContent ( ) ; } } returns { string [ ] } export function collectKeysFromAST ( node , visitorKeys ? ) { debug ( 'collectKeysFromAST' ) ; const results = new Set < string > ( ) ; param { Node } node function enterNode ( node ) { if ( node . type === 'VAttribute' ) { if ( node . directive ) { if ( node . key . name . name === 't' || node . key . name === 't' ) { debug ( "call VAttribute[directive=true][key.name.name='t'] handling ..." ) ; const key = getKeyFromVDirective ( node ) ; if ( key ) { results . add ( String ( key ) ) ; } } } else { if ( ( node . key . name === 'path' && ( node . parent . parent . name === 'i18n' || node . parent . parent . name === 'i18n-t' ) ) || ( node . key . name === 'keypath' && node . parent . parent . name === 'i18n-t' ) ) { debug ( "call VElement:matches([name=i18n], [name=i18n-t]) > VStartTag > VAttribute[key.name='path'] handling ..." ) ; const key = getKeyFromI18nComponent ( node ) ; if ( key ) { results . add ( key ) ; } } } } else if ( node . type === 'CallExpression' ) { debug ( 'CallExpression handling ...' ) ; const key = getKeyFromCallExpression ( node ) ; if ( key ) { results . add ( String ( key ) ) ; } } } if ( node . templateBody ) { VAST . traverseNodes ( node . templateBody , { enterNode , leaveNode ( ) { } } ) ; } VAST . traverseNodes ( node , { visitorKeys , enterNode , leaveNode ( ) { } } ) ; return [ ... results ] ; } class UsedKeysCache { private _targetFilesLoader : CacheLoader < [ string , string [ ] , string [ ] , string ] , string [ ] > ; private _collectKeyResourcesFromFiles : ( fileNames : string [ ] , cwd : string ) => ResourceLoader < string [ ] > [ ] ; constructor ( ) { this . _targetFilesLoader = new CacheLoader ( ( cwd , files , extensions ) => { return listFilesToProcess ( files , { cwd , extensions } ) . filter ( f => ! f . ignored && extensions . includes ( extname ( f . filename ) ) ) . map ( f => f . filename ) ; } ) ; this . _collectKeyResourcesFromFiles = defineCacheFunction ( ( fileNames , cwd ) => { return collectKeyResourcesFromFiles ( fileNames , cwd ) ; } ) ; } param { string [ ] } files param { string [ ] } extensions returns { string [ ] } collectKeysFromFiles ( files , extensions , context ) { const result = new Set < string > ( ) ; for ( const resource of this . _getKeyResources ( context , files , extensions ) ) { for ( const key of resource . getResource ( ) ) { result . add ( key ) ; } } return [ ... result ] ; } returns { ResourceLoader [ ] } _getKeyResources ( context , files , extensions ) { const cwd = getCwd ( context ) ; const fileNames = this . _targetFilesLoader . get ( cwd , files , extensions , cwd ) ; return this . _collectKeyResourcesFromFiles ( fileNames , cwd ) ; } } export const usedKeysCache = new UsedKeysCache ( ) ;