Skip to content

Commit 8c912c1

Browse files
authored
Change to apply update when resource file is updated. (#87)
1 parent 2e713f3 commit 8c912c1

File tree

18 files changed

+479
-75
lines changed

18 files changed

+479
-75
lines changed

lib/rules/no-unused-keys.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
'use strict'
55

66
const { extname } = require('path')
7-
const { collectKeysFromFiles, collectKeysFromAST } = require('../utils/collect-keys')
7+
const { collectKeysFromAST, usedKeysCache } = require('../utils/collect-keys')
88
const collectLinkedKeys = require('../utils/collect-linked-keys')
99
const { getLocaleMessages } = require('../utils/index')
1010
const { traverseNodes, getStaticJSONValue } = require('eslint-plugin-jsonc')
@@ -17,9 +17,6 @@ const debug = require('debug')('eslint-plugin-vue-i18n:no-unused-keys')
1717
* @typedef {import('../utils/locale-messages').LocaleMessage} LocaleMessage
1818
*/
1919

20-
/** @type {string[] | null} */
21-
let cacheUsedLocaleMessageKeys = null // used locale message keys
22-
2320
/**
2421
* @param {LocaleMessage} targetLocaleMessage
2522
* @param {object} jsonValue
@@ -236,7 +233,7 @@ function create (context) {
236233
const src = options.src || process.cwd()
237234
const extensions = options.extensions || ['.js', '.vue']
238235

239-
const usedLocaleMessageKeys = cacheUsedLocaleMessageKeys || (cacheUsedLocaleMessageKeys = collectKeysFromFiles([src], extensions))
236+
const usedLocaleMessageKeys = usedKeysCache.collectKeysFromFiles([src], extensions)
240237
const sourceCode = context.getSourceCode()
241238
const { enterNode, leaveNode, reports } = createVisitor(sourceCode, targetLocaleMessage, usedLocaleMessageKeys)
242239

lib/utils.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
'use strict'
33

44
module.exports = {
5+
'cache-function': require('./utils/cache-function'),
6+
'cache-loader': require('./utils/cache-loader'),
57
'collect-keys': require('./utils/collect-keys'),
68
'collect-linked-keys': require('./utils/collect-linked-keys'),
9+
'default-timeouts': require('./utils/default-timeouts'),
710
'glob-sync': require('./utils/glob-sync'),
811
'glob-utils': require('./utils/glob-utils'),
912
'ignored-paths': require('./utils/ignored-paths'),
1013
'index': require('./utils/index'),
1114
'json-parsers': require('./utils/json-parsers'),
1215
'locale-messages': require('./utils/locale-messages'),
13-
'path-utils': require('./utils/path-utils')
16+
'path-utils': require('./utils/path-utils'),
17+
'resource-loader': require('./utils/resource-loader')
1418
}

lib/utils/cache-function.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @fileoverview Cache function
3+
* @author Yosuke Ota
4+
*/
5+
6+
const CacheLoader = require('./cache-loader')
7+
8+
/**
9+
* This function returns a function that returns the result value that was called for the given function.
10+
* But when called, it returns the cached value if it is the same as the previous argument.
11+
*/
12+
module.exports = function defineCacheFunction (fn) {
13+
const loader = new CacheLoader(fn, Infinity)
14+
return (...args) => {
15+
return loader.get(...args)
16+
}
17+
}
18+

lib/utils/cache-loader.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @fileoverview Cache loader class
3+
* @author Yosuke Ota
4+
*/
5+
6+
/**
7+
* The class that returns load value or cache value.
8+
* This class returns the called result value of of the given loader.
9+
* But when the loader is called, it does not get a new value and returns a cached value until it times out.
10+
*/
11+
module.exports = class CacheLoader {
12+
constructor (loader, timeout = () => require('./default-timeouts').CACHE_LOADER) {
13+
this.loader = loader
14+
this.timeout = timeout
15+
this._cacheTime = Number.MIN_SAFE_INTEGER
16+
}
17+
18+
get (...args) {
19+
const key = JSON.stringify(args)
20+
const now = Date.now()
21+
if (this._cacheKey === key) {
22+
const timeout = typeof this.timeout === 'function' ? this.timeout() : this.timeout
23+
if (this._cacheTime + timeout > now) {
24+
return this._cache
25+
}
26+
}
27+
this._cacheKey = key
28+
this._cacheTime = now
29+
return (this._cache = this.loader(...args))
30+
}
31+
}
32+

lib/utils/collect-keys.js

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ const vueESLintParser = require('vue-eslint-parser')
99
const { readFileSync } = require('fs')
1010
const { resolve, extname } = require('path')
1111
const { listFilesToProcess } = require('./glob-utils')
12+
const ResourceLoader = require('./resource-loader')
13+
const CacheLoader = require('./cache-loader')
14+
const defineCacheFunction = require('./cache-function')
1215
const debug = require('debug')('eslint-plugin-vue-i18n:collect-keys')
1316

1417
/**
@@ -94,30 +97,26 @@ function collectKeysFromText (text, filename, cliEngine) {
9497

9598
/**
9699
* Collect the used keys from files.
97-
* @returns {string[]}
100+
* @returns {ResourceLoader[]}
98101
*/
99-
function collectKeysFromFiles (files, extensions) {
100-
debug('collectKeysFromFiles', files, extensions)
102+
function collectKeyResourcesFromFiles (fileNames) {
103+
debug('collectKeysFromFiles', fileNames)
101104

102105
const cliEngine = new CLIEngine()
103106

104-
const results = new Set()
107+
const results = []
105108

106109
// detect used lodalization keys with linter
107-
for (const { filename, ignored } of listFilesToProcess(files, { extensions })) {
110+
for (const filename of fileNames) {
108111
debug(`Processing file ... ${filename}`)
109112

110-
if (ignored) { continue }
111-
if (!extensions.includes(extname(filename))) { continue }
112-
113-
const text = readFileSync(resolve(filename), 'utf8')
114-
115-
for (const usedKey of collectKeysFromText(text, filename, cliEngine)) {
116-
results.add(usedKey)
117-
}
113+
results.push(new ResourceLoader(resolve(filename), () => {
114+
const text = readFileSync(resolve(filename), 'utf8')
115+
return collectKeysFromText(text, filename, cliEngine)
116+
}))
118117
}
119118

120-
return [...results]
119+
return results
121120
}
122121

123122
/**
@@ -164,7 +163,45 @@ function collectKeysFromAST (node, visitorKeys) {
164163
return [...results]
165164
}
166165

166+
class UsedKeysCache {
167+
constructor () {
168+
this._targetFilesLoader = new CacheLoader((files, extensions) => {
169+
return listFilesToProcess(files, { extensions })
170+
.filter(f => !f.ignored && extensions.includes(extname(f.filename)))
171+
.map(f => f.filename)
172+
})
173+
this._collectKeyResourcesFromFiles = defineCacheFunction((fileNames) => {
174+
return collectKeyResourcesFromFiles(fileNames)
175+
})
176+
}
177+
/**
178+
* Collect the used keys from files.
179+
* @param {string[]} files
180+
* @param {string[]} extensions
181+
* @returns {string[]}
182+
*/
183+
collectKeysFromFiles (files, extensions) {
184+
const result = new Set()
185+
for (const resource of this._getKeyResources(files, extensions)) {
186+
for (const key of resource.getResource()) {
187+
result.add(key)
188+
}
189+
}
190+
return [...result]
191+
}
192+
193+
/**
194+
* @returns {ResourceLoader[]}
195+
*/
196+
_getKeyResources (files, extensions) {
197+
const fileNames = this._targetFilesLoader.get(files, extensions)
198+
return this._collectKeyResourcesFromFiles(fileNames)
199+
}
200+
}
201+
202+
const usedKeysCache = new UsedKeysCache() // used locale message keys
203+
167204
module.exports = {
168-
collectKeysFromFiles,
169-
collectKeysFromAST
205+
collectKeysFromAST,
206+
usedKeysCache
170207
}

lib/utils/default-timeouts.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @fileoverview Default timeouts
3+
* @author Yosuke Ota
4+
*/
5+
6+
module.exports = {
7+
CACHE_LOADER: 1000,
8+
MTIME_MS_CHECK: 300
9+
}

lib/utils/index.js

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
const glob = require('glob')
88
const { resolve, dirname, extname } = require('path')
99
const { FileLocaleMessage, BlockLocaleMessage, LocaleMessages } = require('./locale-messages')
10+
const CacheLoader = require('./cache-loader')
11+
const defineCacheFunction = require('./cache-function')
1012

1113
/**
1214
* @typedef {import('vue-eslint-parser').AST.ESLintProgram} Program
@@ -57,19 +59,15 @@ function defineTemplateBodyVisitor (context, templateBodyVisitor, scriptVisitor)
5759
}
5860

5961
/**
60-
* @param {SettingsVueI18nLocaleDir} localeDir
62+
* @param {string[]} files
63+
* @param {LocaleKeyType} localeKey
6164
* @returns {LocaleMessage[]}
6265
*/
63-
function loadLocaleMessages (localeDir) {
64-
if (typeof localeDir === 'string') {
65-
return loadLocaleMessages({ pattern: localeDir, localeKey: 'file' })
66-
} else {
67-
const files = glob.sync(localeDir.pattern)
68-
return files.map(file => {
69-
const fullpath = resolve(process.cwd(), file)
70-
return new FileLocaleMessage({ fullpath, localeKey: localeDir.localeKey || 'file' })
71-
})
72-
}
66+
function loadLocaleMessages (files, localeKey) {
67+
return files.map(file => {
68+
const fullpath = resolve(process.cwd(), file)
69+
return new FileLocaleMessage({ fullpath, localeKey })
70+
})
7371
}
7472

7573
/** @type {Set<RuleContext>} */
@@ -99,30 +97,42 @@ function getLocaleMessages (context) {
9997

10098
return new LocaleMessages([
10199
...(getLocaleMessagesFromI18nBlocks(context, i18nBlocks) || []),
102-
...(localeDir && getLocaleMessagesFromLocaleDir(localeDir) || [])
100+
...(localeDir && localeDirLocaleMessagesCache.getLocaleMessagesFromLocaleDir(localeDir) || [])
103101
])
104102
}
105103

106-
/** @type {LocaleMessage[] | null} */
107-
let localeDirLocaleMessages = null // locale messages
108-
let localeDir = null // locale dir
109-
/** @type {Map<Program, LocaleMessage[]>} */
110-
const i18nBlockLocaleMessages = new WeakMap()
104+
class LocaleDirLocaleMessagesCache {
105+
constructor () {
106+
this._targetFilesLoader = new CacheLoader((pattern) => glob.sync(pattern))
111107

112-
/**
113-
* @param {SettingsVueI18nLocaleDir} localeDirectory
114-
* @returns {LocaleMessage[]}
115-
*/
116-
function getLocaleMessagesFromLocaleDir (localeDirectory) {
117-
if (localeDir !== localeDirectory) {
118-
localeDir = localeDirectory
119-
localeDirLocaleMessages = loadLocaleMessages(localeDir)
120-
} else {
121-
localeDirLocaleMessages = localeDirLocaleMessages || loadLocaleMessages(localeDir)
108+
this._loadLocaleMessages = defineCacheFunction((files, localeKey) => {
109+
return loadLocaleMessages(files, localeKey)
110+
})
111+
}
112+
/**
113+
* @param {SettingsVueI18nLocaleDir} localeDir
114+
* @returns {LocaleMessage[]}
115+
*/
116+
getLocaleMessagesFromLocaleDir (localeDir) {
117+
const targetFilesLoader = this._targetFilesLoader
118+
let files
119+
let localeKey
120+
if (typeof localeDir === 'string') {
121+
files = targetFilesLoader.get(localeDir)
122+
localeKey = 'file'
123+
} else {
124+
files = targetFilesLoader.get(localeDir.pattern)
125+
localeKey = `${localeDir.localeKey || 'file'}`
126+
}
127+
return this._loadLocaleMessages(files, localeKey)
122128
}
123-
return localeDirLocaleMessages
124129
}
125130

131+
const localeDirLocaleMessagesCache = new LocaleDirLocaleMessagesCache()
132+
133+
/** @type {Map<Program, LocaleMessage[]>} */
134+
const i18nBlockLocaleMessages = new WeakMap()
135+
126136
/**
127137
* @param {RuleContext} context
128138
* @param {VElement[]} i18nBlocks

lib/utils/locale-messages.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
'use strict'
66

77
const { parseJsonInI18nBlock } = require('./json-parsers')
8+
const ResourceLoader = require('./resource-loader')
89
const { getStaticJSONValue } = require('eslint-plugin-jsonc')
910

1011
/**
@@ -37,11 +38,21 @@ class LocaleMessage {
3738
// abstract
3839
}
3940

41+
/**
42+
* @protected
43+
*/
44+
getMessagesInternal () {
45+
if (this._messages) {
46+
return this._messages
47+
}
48+
return (this._messages = this.loadMessages())
49+
}
50+
4051
/**
4152
* @returns {object} The localization messages object.
4253
*/
4354
get messages () {
44-
return this._messages || (this._messages = this.loadMessages())
55+
return this.getMessagesInternal()
4556
}
4657
/**
4758
* @returns {string[]} Array of locales.
@@ -186,10 +197,15 @@ class FileLocaleMessage extends LocaleMessage {
186197
locales,
187198
localeKey
188199
})
200+
this._resource = new ResourceLoader(fullpath, (p) => {
201+
const key = require.resolve(p)
202+
delete require.cache[key]
203+
return require(p)
204+
})
189205
}
190206

191-
loadMessages () {
192-
return require(this.fullpath)
207+
getMessagesInternal () {
208+
return this._resource.getResource()
193209
}
194210
}
195211

lib/utils/resource-loader.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @fileoverview Resource loader class
3+
* @author Yosuke Ota
4+
*/
5+
6+
const fs = require('fs')
7+
const CacheLoader = require('./cache-loader')
8+
9+
/**
10+
* The class of resource loader.
11+
* This class gets and returns the resource for the given file.
12+
* But when the loader is called, the loader will not check for new values until 300ms has elapsed.
13+
* Also, if the result of the check is that the mtime of the file has not changed, the new value is not load and the cached value is returned.
14+
*/
15+
module.exports = class ResourceLoader {
16+
constructor (filename, loader, mtimeCheckTimeout = () => require('./default-timeouts').MTIME_MS_CHECK) {
17+
this.filename = filename
18+
this.loader = loader
19+
this._resource = null
20+
this._mtimeLoader = new CacheLoader(() => {
21+
try {
22+
const stat = fs.statSync(this.filename)
23+
return stat.mtimeMs
24+
} catch (_e) {
25+
// ignore
26+
}
27+
return this._mtimeMs || 0
28+
}, mtimeCheckTimeout)
29+
}
30+
31+
getResource () {
32+
const mtimeMs = this._mtimeLoader.get()
33+
if (this._resource && this._mtimeMs >= mtimeMs) {
34+
return this._resource
35+
}
36+
this._mtimeMs = mtimeMs
37+
return (this._resource = this.loader(this.filename))
38+
}
39+
}

0 commit comments

Comments
 (0)