@intlify/eslint-plugin-vue-i18n
Advanced tools
Comparing version 0.9.0 to 0.10.0
@@ -0,2 +1,27 @@ | ||
Version 9 of Highlight.js has reached EOL and is no longer supported. | ||
Please upgrade or ask whatever dependency you are using to upgrade. | ||
https://github.com/highlightjs/highlight.js/issues/2877 | ||
## v0.10.0 (2021-01-04) | ||
#### :star: Features | ||
* [#149](https://github.com/intlify/eslint-plugin-vue-i18n/pull/149) Add `prefer-linked-key-with-paren` rule ([@ota-meshi](https://github.com/ota-meshi)) | ||
* [#148](https://github.com/intlify/eslint-plugin-vue-i18n/pull/148) Add `no-missing-keys-in-other-locales` rule ([@ota-meshi](https://github.com/ota-meshi)) | ||
* [#148](https://github.com/intlify/eslint-plugin-vue-i18n/pull/148) Change `no-missing-keys` rule to not report if there is one matching key ([@ota-meshi](https://github.com/ota-meshi)) | ||
* [#147](https://github.com/intlify/eslint-plugin-vue-i18n/pull/147) Add `valid-message-syntax` rule ([@ota-meshi](https://github.com/ota-meshi)) | ||
* [#145](https://github.com/intlify/eslint-plugin-vue-i18n/pull/145) Supports vue-i18n v9 message format ([@ota-meshi](https://github.com/ota-meshi)) | ||
#### :bug: Bug Fixes | ||
* [#150](https://github.com/intlify/eslint-plugin-vue-i18n/pull/150) Fix false negatives for member expression in `no-dynamic-keys` rule ([@ota-meshi](https://github.com/ota-meshi)) | ||
#### :pencil: Documentation | ||
* [#153](https://github.com/intlify/eslint-plugin-vue-i18n/pull/153) Replace documentation example with `vue-eslint-editor` ([@ota-meshi](https://github.com/ota-meshi)) | ||
* [#144](https://github.com/intlify/eslint-plugin-vue-i18n/pull/144) Chores: Add to documentation that eslint-plugin-jsonc and eslint-plugin-yml can be used ([@ota-meshi](https://github.com/ota-meshi)) | ||
* [#122](https://github.com/intlify/eslint-plugin-vue-i18n/pull/122) Docs: Fix typo and dead link ([@mfmfuyu](https://github.com/mfmfuyu)) | ||
#### Committers: 2 | ||
- Yosuke Ota ([@ota-meshi](https://github.com/ota-meshi)) | ||
- fuyu ([@mfmfuyu](https://github.com/mfmfuyu)) | ||
## v0.9.0 (2020-08-17) | ||
@@ -3,0 +28,0 @@ |
@@ -9,2 +9,3 @@ "use strict"; | ||
const no_html_messages_1 = __importDefault(require("./rules/no-html-messages")); | ||
const no_missing_keys_in_other_locales_1 = __importDefault(require("./rules/no-missing-keys-in-other-locales")); | ||
const no_missing_keys_1 = __importDefault(require("./rules/no-missing-keys")); | ||
@@ -14,2 +15,4 @@ const no_raw_text_1 = __importDefault(require("./rules/no-raw-text")); | ||
const no_v_html_1 = __importDefault(require("./rules/no-v-html")); | ||
const prefer_linked_key_with_paren_1 = __importDefault(require("./rules/prefer-linked-key-with-paren")); | ||
const valid_message_syntax_1 = __importDefault(require("./rules/valid-message-syntax")); | ||
module.exports = { | ||
@@ -20,6 +23,9 @@ 'key-format-style': key_format_style_1.default, | ||
'no-html-messages': no_html_messages_1.default, | ||
'no-missing-keys-in-other-locales': no_missing_keys_in_other_locales_1.default, | ||
'no-missing-keys': no_missing_keys_1.default, | ||
'no-raw-text': no_raw_text_1.default, | ||
'no-unused-keys': no_unused_keys_1.default, | ||
'no-v-html': no_v_html_1.default | ||
'no-v-html': no_v_html_1.default, | ||
'prefer-linked-key-with-paren': prefer_linked_key_with_paren_1.default, | ||
'valid-message-syntax': valid_message_syntax_1.default | ||
}; |
@@ -11,3 +11,2 @@ "use strict"; | ||
const allowedCaseOptions = ['camelCase', 'kebab-case', 'snake_case']; | ||
const unknownKey = Symbol('unknown key'); | ||
function create(context) { | ||
@@ -19,3 +18,27 @@ var _a, _b; | ||
const allowArray = (_b = context.options[1]) === null || _b === void 0 ? void 0 : _b.allowArray; | ||
function createVisitor(targetLocaleMessage, { skipNode, resolveKey, resolveReportNode }) { | ||
function reportUnknown(reportNode) { | ||
context.report({ | ||
message: `Unexpected object key. Use ${expectCasing} string key instead`, | ||
loc: reportNode.loc | ||
}); | ||
} | ||
function verifyKey(key, reportNode) { | ||
if (typeof key === 'number') { | ||
if (!allowArray) { | ||
context.report({ | ||
message: `Unexpected array element`, | ||
loc: reportNode.loc | ||
}); | ||
} | ||
} | ||
else { | ||
if (!checker(key)) { | ||
context.report({ | ||
message: `"${key}" is not ${expectCasing}`, | ||
loc: reportNode.loc | ||
}); | ||
} | ||
} | ||
} | ||
function createVisitorForJson(targetLocaleMessage) { | ||
let keyStack = { | ||
@@ -25,10 +48,3 @@ inLocale: targetLocaleMessage.localeKey === 'file' | ||
return { | ||
enterNode(node) { | ||
if (skipNode(node)) { | ||
return; | ||
} | ||
const key = resolveKey(node); | ||
if (key == null) { | ||
return; | ||
} | ||
JSONProperty(node) { | ||
const { inLocale } = keyStack; | ||
@@ -43,151 +59,86 @@ keyStack = { | ||
} | ||
if (key === unknownKey) { | ||
context.report({ | ||
message: `Unexpected object key. Use ${expectCasing} string key instead`, | ||
loc: resolveReportNode(node).loc | ||
}); | ||
} | ||
else if (typeof key === 'number') { | ||
if (!allowArray) { | ||
context.report({ | ||
message: `Unexpected array element`, | ||
loc: resolveReportNode(node).loc | ||
}); | ||
} | ||
} | ||
else { | ||
if (!checker(key)) { | ||
context.report({ | ||
message: `"${key}" is not ${expectCasing}`, | ||
loc: resolveReportNode(node).loc | ||
}); | ||
} | ||
} | ||
const key = node.key.type === 'JSONLiteral' ? `${node.key.value}` : node.key.name; | ||
verifyKey(key, node.key); | ||
}, | ||
leaveNode(node) { | ||
if (keyStack.node === node) { | ||
keyStack = keyStack.upper; | ||
} | ||
'JSONProperty:exit'() { | ||
keyStack = keyStack.upper; | ||
}, | ||
'JSONArrayExpression > *'(node) { | ||
const key = node.parent.elements.indexOf(node); | ||
verifyKey(key, node); | ||
} | ||
}; | ||
} | ||
function createVisitorForJson(targetLocaleMessage) { | ||
return createVisitor(targetLocaleMessage, { | ||
skipNode(node) { | ||
if (node.type === 'Program' || | ||
node.type === 'JSONExpressionStatement' || | ||
node.type === 'JSONProperty') { | ||
return true; | ||
} | ||
const parent = node.parent; | ||
if (parent.type === 'JSONProperty' && parent.key === node) { | ||
return true; | ||
} | ||
return false; | ||
}, | ||
resolveKey(node) { | ||
const parent = node.parent; | ||
if (parent.type === 'JSONProperty') { | ||
return parent.key.type === 'JSONLiteral' | ||
? `${parent.key.value}` | ||
: parent.key.name; | ||
} | ||
else if (parent.type === 'JSONArrayExpression') { | ||
return parent.elements.indexOf(node); | ||
} | ||
return null; | ||
}, | ||
resolveReportNode(node) { | ||
const parent = node.parent; | ||
return parent.type === 'JSONProperty' ? parent.key : node; | ||
} | ||
}); | ||
} | ||
function createVisitorForYaml(targetLocaleMessage) { | ||
const yamlKeyNodes = new Set(); | ||
return createVisitor(targetLocaleMessage, { | ||
skipNode(node) { | ||
if (node.type === 'Program' || | ||
node.type === 'YAMLDocument' || | ||
node.type === 'YAMLDirective' || | ||
node.type === 'YAMLAnchor' || | ||
node.type === 'YAMLTag') { | ||
let keyStack = { | ||
inLocale: targetLocaleMessage.localeKey === 'file' | ||
}; | ||
function withinKey(node) { | ||
for (const keyNode of yamlKeyNodes) { | ||
if (keyNode.range[0] <= node.range[0] && | ||
node.range[0] < keyNode.range[1]) { | ||
return true; | ||
} | ||
if (yamlKeyNodes.has(node)) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
return { | ||
YAMLPair(node) { | ||
const { inLocale } = keyStack; | ||
keyStack = { | ||
node, | ||
inLocale: true, | ||
upper: keyStack | ||
}; | ||
if (!inLocale) { | ||
return; | ||
} | ||
const parent = node.parent; | ||
if (yamlKeyNodes.has(parent)) { | ||
yamlKeyNodes.add(node); | ||
return true; | ||
} | ||
if (node.type === 'YAMLPair') { | ||
if (node.key != null) { | ||
if (withinKey(node)) { | ||
return; | ||
} | ||
yamlKeyNodes.add(node.key); | ||
return true; | ||
} | ||
return false; | ||
}, | ||
resolveKey(node) { | ||
const parent = node.parent; | ||
if (parent.type === 'YAMLPair') { | ||
if (parent.key == null) { | ||
return unknownKey; | ||
} | ||
if (parent.key.type === 'YAMLScalar') { | ||
const key = parent.key.value; | ||
return typeof key === 'string' ? key : String(key); | ||
} | ||
return unknownKey; | ||
if (node.key == null) { | ||
reportUnknown(node); | ||
} | ||
else if (parent.type === 'YAMLSequence') { | ||
return parent.entries.indexOf(node); | ||
else if (node.key.type === 'YAMLScalar') { | ||
const keyValue = node.key.value; | ||
const key = typeof keyValue === 'string' ? keyValue : String(keyValue); | ||
verifyKey(key, node.key); | ||
} | ||
return null; | ||
else { | ||
reportUnknown(node); | ||
} | ||
}, | ||
resolveReportNode(node) { | ||
const parent = node.parent; | ||
return parent.type === 'YAMLPair' ? parent.key || parent : node; | ||
} | ||
}); | ||
} | ||
if (path_1.extname(filename) === '.vue') { | ||
return { | ||
Program() { | ||
const documentFragment = context.parserServices.getDocumentFragment && | ||
context.parserServices.getDocumentFragment(); | ||
const i18nBlocks = (documentFragment && | ||
documentFragment.children.filter((node) => node.type === 'VElement' && node.name === 'i18n')) || | ||
[]; | ||
if (!i18nBlocks.length) { | ||
'YAMLPair:exit'() { | ||
keyStack = keyStack.upper; | ||
}, | ||
'YAMLSequence > *'(node) { | ||
if (withinKey(node)) { | ||
return; | ||
} | ||
const localeMessages = index_1.getLocaleMessages(context); | ||
for (const block of i18nBlocks) { | ||
if (block.startTag.attributes.some(attr => !attr.directive && attr.key.name === 'src')) { | ||
continue; | ||
} | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(block); | ||
if (!targetLocaleMessage) { | ||
continue; | ||
} | ||
const parserLang = targetLocaleMessage.getParserLang(); | ||
let visitor; | ||
if (parserLang === 'json') { | ||
visitor = createVisitorForJson(targetLocaleMessage); | ||
} | ||
else if (parserLang === 'yaml') { | ||
visitor = createVisitorForYaml(targetLocaleMessage); | ||
} | ||
if (visitor == null) { | ||
return; | ||
} | ||
targetLocaleMessage.traverseNodes({ | ||
enterNode: visitor.enterNode, | ||
leaveNode: visitor.leaveNode | ||
}); | ||
} | ||
const key = node.parent.entries.indexOf(node); | ||
verifyKey(key, node); | ||
} | ||
}; | ||
} | ||
if (path_1.extname(filename) === '.vue') { | ||
return index_1.defineCustomBlocksVisitor(context, ctx => { | ||
const localeMessages = index_1.getLocaleMessages(context); | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(ctx.parserServices.customBlock); | ||
if (!targetLocaleMessage) { | ||
return {}; | ||
} | ||
return createVisitorForJson(targetLocaleMessage); | ||
}, ctx => { | ||
const localeMessages = index_1.getLocaleMessages(context); | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(ctx.parserServices.customBlock); | ||
if (!targetLocaleMessage) { | ||
return {}; | ||
} | ||
return createVisitorForYaml(targetLocaleMessage); | ||
}); | ||
} | ||
else if (context.parserServices.isJSON || context.parserServices.isYAML) { | ||
@@ -201,14 +152,6 @@ const localeMessages = index_1.getLocaleMessages(context); | ||
if (context.parserServices.isJSON) { | ||
const { enterNode, leaveNode } = createVisitorForJson(targetLocaleMessage); | ||
return { | ||
'[type=/^JSON/]': enterNode, | ||
'[type=/^JSON/]:exit': leaveNode | ||
}; | ||
return createVisitorForJson(targetLocaleMessage); | ||
} | ||
else if (context.parserServices.isYAML) { | ||
const { enterNode, leaveNode } = createVisitorForYaml(targetLocaleMessage); | ||
return { | ||
'[type=/^YAML/]': enterNode, | ||
'[type=/^YAML/]:exit': leaveNode | ||
}; | ||
return createVisitorForYaml(targetLocaleMessage); | ||
} | ||
@@ -215,0 +158,0 @@ return {}; |
@@ -12,3 +12,3 @@ "use strict"; | ||
if (fullPath.startsWith(process.cwd())) { | ||
return fullPath.replace(process.cwd(), '.'); | ||
return fullPath.replace(process.cwd() + '/', './'); | ||
} | ||
@@ -21,20 +21,10 @@ return fullPath; | ||
const ignoreI18nBlock = Boolean(options.ignoreI18nBlock); | ||
function createVisitor(targetLocaleMessage, otherLocaleMessages, { skipNode, resolveKey, resolveReportNode }) { | ||
let pathStack; | ||
function createInitPathStack(targetLocaleMessage, otherLocaleMessages) { | ||
if (targetLocaleMessage.localeKey === 'file') { | ||
const locale = targetLocaleMessage.locales[0]; | ||
pathStack = { | ||
keyPath: '', | ||
locale, | ||
otherDictionaries: otherLocaleMessages.map(lm => { | ||
return { | ||
dict: lm.getMessagesFromLocale(locale), | ||
source: lm | ||
}; | ||
}) | ||
}; | ||
return createInitLocalePathStack(locale, otherLocaleMessages); | ||
} | ||
else { | ||
pathStack = { | ||
keyPath: '', | ||
return { | ||
keyPath: [], | ||
locale: null, | ||
@@ -44,28 +34,29 @@ otherDictionaries: [] | ||
} | ||
} | ||
function createInitLocalePathStack(locale, otherLocaleMessages) { | ||
return { | ||
keyPath: [], | ||
locale, | ||
otherDictionaries: otherLocaleMessages.map(lm => { | ||
return { | ||
dict: lm.getMessagesFromLocale(locale), | ||
source: lm | ||
}; | ||
}) | ||
}; | ||
} | ||
function createVerifyContext(targetLocaleMessage, otherLocaleMessages) { | ||
let pathStack = createInitPathStack(targetLocaleMessage, otherLocaleMessages); | ||
const existsKeyNodes = {}; | ||
const existsLocaleNodes = {}; | ||
function pushKey(exists, key, reportNode) { | ||
const keyNodes = exists[key] || (exists[key] = []); | ||
keyNodes.push(reportNode); | ||
} | ||
return { | ||
enterNode(node) { | ||
if (skipNode(node)) { | ||
return; | ||
} | ||
const key = resolveKey(node); | ||
if (key == null) { | ||
return; | ||
} | ||
enterKey(key, reportNode) { | ||
if (pathStack.locale == null) { | ||
const locale = key; | ||
verifyDupeKey(existsLocaleNodes, locale, node); | ||
pathStack = { | ||
upper: pathStack, | ||
node, | ||
keyPath: '', | ||
locale, | ||
otherDictionaries: otherLocaleMessages.map(lm => { | ||
return { | ||
dict: lm.getMessagesFromLocale(locale), | ||
source: lm | ||
}; | ||
}) | ||
}; | ||
pushKey(existsLocaleNodes, locale, reportNode); | ||
pathStack = Object.assign({ upper: pathStack, node: reportNode }, createInitLocalePathStack(locale, otherLocaleMessages)); | ||
return; | ||
@@ -81,9 +72,9 @@ } | ||
}); | ||
const keyPath = key_path_1.joinPath(pathStack.keyPath, key); | ||
const keyPath = [...pathStack.keyPath, key]; | ||
const keyPathStr = key_path_1.joinPath(...keyPath); | ||
const nextOtherDictionaries = []; | ||
for (const value of keyOtherValues) { | ||
if (typeof value.value === 'string') { | ||
const reportNode = resolveReportNode(node); | ||
context.report({ | ||
message: `duplicate key '${keyPath}' in '${pathStack.locale}'. "${getMessageFilepath(value.source.fullpath)}" has the same key`, | ||
message: `duplicate key '${keyPathStr}' in '${pathStack.locale}'. "${getMessageFilepath(value.source.fullpath)}" has the same key`, | ||
loc: reportNode.loc | ||
@@ -99,7 +90,7 @@ }); | ||
} | ||
verifyDupeKey(existsKeyNodes[pathStack.locale] || | ||
(existsKeyNodes[pathStack.locale] = {}), keyPath, node); | ||
pushKey(existsKeyNodes[pathStack.locale] || | ||
(existsKeyNodes[pathStack.locale] = {}), keyPathStr, reportNode); | ||
pathStack = { | ||
upper: pathStack, | ||
node, | ||
node: reportNode, | ||
keyPath, | ||
@@ -110,147 +101,117 @@ locale: pathStack.locale, | ||
}, | ||
leaveNode(node) { | ||
leaveKey(node) { | ||
if (pathStack.node === node) { | ||
pathStack = pathStack.upper; | ||
} | ||
}, | ||
reports() { | ||
for (const localeNodes of [ | ||
existsLocaleNodes, | ||
...Object.values(existsKeyNodes) | ||
]) { | ||
for (const key of Object.keys(localeNodes)) { | ||
const keyNodes = localeNodes[key]; | ||
if (keyNodes.length > 1) { | ||
for (const keyNode of keyNodes) { | ||
context.report({ | ||
message: `duplicate key '${key}'`, | ||
loc: keyNode.loc | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
function verifyDupeKey(exists, key, node) { | ||
const keyNodes = exists[key] || (exists[key] = []); | ||
keyNodes.push({ | ||
node, | ||
reported: false | ||
}); | ||
if (keyNodes.length > 1) { | ||
for (const keyNode of keyNodes.filter(e => !e.reported)) { | ||
const reportNode = resolveReportNode(keyNode.node); | ||
context.report({ | ||
message: `duplicate key '${key}'`, | ||
loc: reportNode.loc | ||
}); | ||
keyNode.reported = true; | ||
} | ||
} | ||
} | ||
} | ||
function createVisitorForJson(_sourceCode, targetLocaleMessage, otherLocaleMessages) { | ||
return createVisitor(targetLocaleMessage, otherLocaleMessages, { | ||
skipNode(node) { | ||
if (node.type === 'Program' || | ||
node.type === 'JSONExpressionStatement' || | ||
node.type === 'JSONProperty') { | ||
return true; | ||
} | ||
const parent = node.parent; | ||
if (parent.type === 'JSONProperty' && parent.key === node) { | ||
return true; | ||
} | ||
return false; | ||
const verifyContext = createVerifyContext(targetLocaleMessage, otherLocaleMessages); | ||
return { | ||
JSONProperty(node) { | ||
const key = node.key.type === 'JSONLiteral' ? `${node.key.value}` : node.key.name; | ||
verifyContext.enterKey(key, node.key); | ||
}, | ||
resolveKey(node) { | ||
const parent = node.parent; | ||
if (parent.type === 'JSONProperty') { | ||
return parent.key.type === 'JSONLiteral' | ||
? `${parent.key.value}` | ||
: parent.key.name; | ||
} | ||
else if (parent.type === 'JSONArrayExpression') { | ||
return parent.elements.indexOf(node); | ||
} | ||
return null; | ||
'JSONProperty:exit'(node) { | ||
verifyContext.leaveKey(node.key); | ||
}, | ||
resolveReportNode(node) { | ||
const parent = node.parent; | ||
return parent.type === 'JSONProperty' ? parent.key : node; | ||
'JSONArrayExpression > *'(node) { | ||
const key = node.parent.elements.indexOf(node); | ||
verifyContext.enterKey(key, node); | ||
}, | ||
'JSONArrayExpression > *:exit'(node) { | ||
verifyContext.leaveKey(node); | ||
}, | ||
'Program:exit'() { | ||
verifyContext.reports(); | ||
} | ||
}); | ||
}; | ||
} | ||
function createVisitorForYaml(sourceCode, targetLocaleMessage, otherLocaleMessages) { | ||
const verifyContext = createVerifyContext(targetLocaleMessage, otherLocaleMessages); | ||
const yamlKeyNodes = new Set(); | ||
return createVisitor(targetLocaleMessage, otherLocaleMessages, { | ||
skipNode(node) { | ||
if (node.type === 'Program' || | ||
node.type === 'YAMLDocument' || | ||
node.type === 'YAMLDirective' || | ||
node.type === 'YAMLAnchor' || | ||
node.type === 'YAMLTag') { | ||
function withinKey(node) { | ||
for (const keyNode of yamlKeyNodes) { | ||
if (keyNode.range[0] <= node.range[0] && | ||
node.range[0] < keyNode.range[1]) { | ||
return true; | ||
} | ||
if (yamlKeyNodes.has(node)) { | ||
return true; | ||
} | ||
const parent = node.parent; | ||
if (yamlKeyNodes.has(parent)) { | ||
yamlKeyNodes.add(node); | ||
return true; | ||
} | ||
if (node.type === 'YAMLPair') { | ||
} | ||
return false; | ||
} | ||
return { | ||
YAMLPair(node) { | ||
if (node.key != null) { | ||
if (withinKey(node)) { | ||
return; | ||
} | ||
yamlKeyNodes.add(node.key); | ||
return true; | ||
} | ||
return false; | ||
}, | ||
resolveKey(node) { | ||
const parent = node.parent; | ||
if (parent.type === 'YAMLPair' && parent.key) { | ||
const key = parent.key.type !== 'YAMLScalar' | ||
? sourceCode.getText(parent.key) | ||
: parent.key.value; | ||
return typeof key === 'boolean' || key === null ? String(key) : key; | ||
else { | ||
return; | ||
} | ||
else if (parent.type === 'YAMLSequence') { | ||
return parent.entries.indexOf(node); | ||
} | ||
return null; | ||
const keyValue = node.key.type !== 'YAMLScalar' | ||
? sourceCode.getText(node.key) | ||
: node.key.value; | ||
const key = typeof keyValue === 'boolean' || keyValue === null | ||
? String(keyValue) | ||
: keyValue; | ||
verifyContext.enterKey(key, node.key); | ||
}, | ||
resolveReportNode(node) { | ||
const parent = node.parent; | ||
return parent.type === 'YAMLPair' ? parent.key || parent : node; | ||
'YAMLPair:exit'(node) { | ||
verifyContext.leaveKey(node.key); | ||
}, | ||
'YAMLSequence > *'(node) { | ||
const key = node.parent.entries.indexOf(node); | ||
verifyContext.enterKey(key, node); | ||
}, | ||
'YAMLSequence > *:exit'(node) { | ||
verifyContext.leaveKey(node); | ||
}, | ||
'Program:exit'() { | ||
verifyContext.reports(); | ||
} | ||
}); | ||
}; | ||
} | ||
if (path_1.extname(filename) === '.vue') { | ||
return { | ||
Program() { | ||
const documentFragment = context.parserServices.getDocumentFragment && | ||
context.parserServices.getDocumentFragment(); | ||
const i18nBlocks = (documentFragment && | ||
documentFragment.children.filter((node) => node.type === 'VElement' && node.name === 'i18n')) || | ||
[]; | ||
if (!i18nBlocks.length) { | ||
return; | ||
} | ||
const localeMessages = index_1.getLocaleMessages(context); | ||
for (const block of i18nBlocks) { | ||
if (block.startTag.attributes.some(attr => !attr.directive && attr.key.name === 'src')) { | ||
continue; | ||
} | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(block); | ||
if (!targetLocaleMessage) { | ||
continue; | ||
} | ||
const sourceCode = targetLocaleMessage.getSourceCode(); | ||
if (!sourceCode) { | ||
continue; | ||
} | ||
const otherLocaleMessages = ignoreI18nBlock | ||
? [] | ||
: localeMessages.localeMessages.filter(lm => lm !== targetLocaleMessage); | ||
const parserLang = targetLocaleMessage.getParserLang(); | ||
let visitor; | ||
if (parserLang === 'json') { | ||
visitor = createVisitorForJson(sourceCode, targetLocaleMessage, otherLocaleMessages); | ||
} | ||
else if (parserLang === 'yaml') { | ||
visitor = createVisitorForYaml(sourceCode, targetLocaleMessage, otherLocaleMessages); | ||
} | ||
if (visitor == null) { | ||
return; | ||
} | ||
targetLocaleMessage.traverseNodes({ | ||
enterNode: visitor.enterNode, | ||
leaveNode: visitor.leaveNode | ||
}); | ||
} | ||
return index_1.defineCustomBlocksVisitor(context, ctx => { | ||
const localeMessages = index_1.getLocaleMessages(context); | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(ctx.parserServices.customBlock); | ||
if (!targetLocaleMessage) { | ||
return {}; | ||
} | ||
}; | ||
const otherLocaleMessages = ignoreI18nBlock | ||
? [] | ||
: localeMessages.localeMessages.filter(lm => lm !== targetLocaleMessage); | ||
return createVisitorForJson(ctx.getSourceCode(), targetLocaleMessage, otherLocaleMessages); | ||
}, ctx => { | ||
const localeMessages = index_1.getLocaleMessages(context); | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(ctx.parserServices.customBlock); | ||
if (!targetLocaleMessage) { | ||
return {}; | ||
} | ||
const otherLocaleMessages = ignoreI18nBlock | ||
? [] | ||
: localeMessages.localeMessages.filter(lm => lm !== targetLocaleMessage); | ||
return createVisitorForYaml(ctx.getSourceCode(), targetLocaleMessage, otherLocaleMessages); | ||
}); | ||
} | ||
@@ -267,14 +228,6 @@ else if (context.parserServices.isJSON || context.parserServices.isYAML) { | ||
if (context.parserServices.isJSON) { | ||
const { enterNode, leaveNode } = createVisitorForJson(sourceCode, targetLocaleMessage, otherLocaleMessages); | ||
return { | ||
'[type=/^JSON/]': enterNode, | ||
'[type=/^JSON/]:exit': leaveNode | ||
}; | ||
return createVisitorForJson(sourceCode, targetLocaleMessage, otherLocaleMessages); | ||
} | ||
else if (context.parserServices.isYAML) { | ||
const { enterNode, leaveNode } = createVisitorForYaml(sourceCode, targetLocaleMessage, otherLocaleMessages); | ||
return { | ||
'[type=/^YAML/]': enterNode, | ||
'[type=/^YAML/]:exit': leaveNode | ||
}; | ||
return createVisitorForYaml(sourceCode, targetLocaleMessage, otherLocaleMessages); | ||
} | ||
@@ -281,0 +234,0 @@ return {}; |
"use strict"; | ||
const index_1 = require("../utils/index"); | ||
function isStatic(node) { | ||
if (node.type === 'Literal') { | ||
return true; | ||
} | ||
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
function getNodeName(context, node) { | ||
if (node.type === 'Identifier') { | ||
return node.name; | ||
} | ||
const sourceCode = context.getSourceCode(); | ||
if (sourceCode.ast.range[0] <= node.range[0] && | ||
node.range[1] <= sourceCode.ast.range[1]) { | ||
return sourceCode | ||
.getTokens(node) | ||
.map(t => t.value) | ||
.join(''); | ||
} | ||
const tokenStore = context.parserServices.getTemplateBodyTokenStore(); | ||
return tokenStore | ||
.getTokens(node) | ||
.map(t => t.value) | ||
.join(''); | ||
} | ||
function checkDirective(context, node) { | ||
@@ -7,4 +34,4 @@ if (node.value && | ||
node.value.expression && | ||
node.value.expression.type === 'Identifier') { | ||
const name = node.value.expression.name; | ||
!isStatic(node.value.expression)) { | ||
const name = getNodeName(context, node.value.expression); | ||
context.report({ | ||
@@ -17,3 +44,2 @@ node, | ||
function checkComponent(context, node) { | ||
const parent = node.parent; | ||
if (node.name.type === 'VIdentifier' && | ||
@@ -24,7 +50,7 @@ node.name.name === 'bind' && | ||
node.argument.name === 'path' && | ||
parent.value && | ||
parent.value.type === 'VExpressionContainer' && | ||
parent.value.expression && | ||
parent.value.expression.type === 'Identifier') { | ||
const name = parent.value.expression.name; | ||
node.parent.value && | ||
node.parent.value.type === 'VExpressionContainer' && | ||
node.parent.value.expression && | ||
!isStatic(node.parent.value.expression)) { | ||
const name = getNodeName(context, node.parent.value.expression); | ||
context.report({ | ||
@@ -48,4 +74,4 @@ node, | ||
const [keyNode] = node.arguments; | ||
if (keyNode.type === 'Identifier') { | ||
const name = keyNode.name; | ||
if (!isStatic(keyNode)) { | ||
const name = getNodeName(context, keyNode); | ||
context.report({ | ||
@@ -52,0 +78,0 @@ node, |
@@ -68,36 +68,11 @@ "use strict"; | ||
if (path_1.extname(filename) === '.vue') { | ||
return { | ||
Program() { | ||
const documentFragment = context.parserServices.getDocumentFragment && | ||
context.parserServices.getDocumentFragment(); | ||
const i18nBlocks = (documentFragment && | ||
documentFragment.children.filter((node) => node.type === 'VElement' && node.name === 'i18n')) || | ||
[]; | ||
if (!i18nBlocks.length) { | ||
return; | ||
} | ||
const localeMessages = index_1.getLocaleMessages(context); | ||
for (const block of i18nBlocks) { | ||
if (block.startTag.attributes.some(attr => !attr.directive && attr.key.name === 'src')) { | ||
continue; | ||
} | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(block); | ||
if (!targetLocaleMessage) { | ||
continue; | ||
} | ||
targetLocaleMessage.traverseNodes({ | ||
enterNode(node) { | ||
if (node.type === 'JSONLiteral') { | ||
verifyJSONLiteral(node); | ||
} | ||
else if (node.type === 'YAMLScalar') { | ||
verifyYAMLScalar(node); | ||
} | ||
}, | ||
leaveNode() { | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
return index_1.defineCustomBlocksVisitor(context, () => { | ||
return { | ||
JSONLiteral: verifyJSONLiteral | ||
}; | ||
}, () => { | ||
return { | ||
YAMLScalar: verifyYAMLScalar | ||
}; | ||
}); | ||
} | ||
@@ -104,0 +79,0 @@ else if (context.parserServices.isJSON) { |
@@ -39,5 +39,9 @@ "use strict"; | ||
} | ||
const missings = localeMessages.findMissingPaths(String(key)); | ||
if (missings.length) { | ||
missings.forEach(data => context.report({ node, messageId: 'missing', data })); | ||
const missingPath = localeMessages.findMissingPath(String(key)); | ||
if (missingPath) { | ||
context.report({ | ||
node, | ||
messageId: 'missing', | ||
data: { path: missingPath } | ||
}); | ||
} | ||
@@ -56,5 +60,9 @@ } | ||
} | ||
const missings = localeMessages.findMissingPaths(key); | ||
if (missings.length) { | ||
missings.forEach(data => context.report({ node, messageId: 'missing', data })); | ||
const missingPath = localeMessages.findMissingPath(key); | ||
if (missingPath) { | ||
context.report({ | ||
node, | ||
messageId: 'missing', | ||
data: { path: missingPath } | ||
}); | ||
} | ||
@@ -86,5 +94,5 @@ } | ||
} | ||
const missings = localeMessages.findMissingPaths(String(key)); | ||
if (missings.length) { | ||
missings.forEach(data => context.report({ node, messageId: 'missing', data })); | ||
const missingPath = localeMessages.findMissingPath(String(key)); | ||
if (missingPath) { | ||
context.report({ node, messageId: 'missing', data: { path: missingPath } }); | ||
} | ||
@@ -103,3 +111,3 @@ } | ||
messages: { | ||
missing: "'{{path}}' does not exist in '{{locale}}'" | ||
missing: "'{{path}}' does not exist in localization message resources" | ||
} | ||
@@ -106,0 +114,0 @@ }, |
@@ -15,5 +15,5 @@ "use strict"; | ||
} | ||
function getUsedKeysMap(targetLocaleMessage, values, usedkeys) { | ||
function getUsedKeysMap(targetLocaleMessage, values, usedkeys, context) { | ||
const usedKeysMap = {}; | ||
for (const key of [...usedkeys, ...collect_linked_keys_1.collectLinkedKeys(values)]) { | ||
for (const key of [...usedkeys, ...collect_linked_keys_1.collectLinkedKeys(values, context)]) { | ||
const paths = key.split('.'); | ||
@@ -38,18 +38,11 @@ let map = usedKeysMap; | ||
const enableFix = options.enableFix; | ||
function createVisitor(usedKeys, { skipNode, resolveKey, resolveReportNode, buildFixer, buildAllFixer }) { | ||
let pathStack = { usedKeys, keyPath: '' }; | ||
function createVerifyContext(usedKeys, { buildFixer, buildAllFixer }) { | ||
let pathStack = { usedKeys, keyPath: [] }; | ||
const reports = []; | ||
return { | ||
enterNode(node) { | ||
if (skipNode(node)) { | ||
return; | ||
} | ||
const key = resolveKey(node); | ||
if (key == null) { | ||
return; | ||
} | ||
const keyPath = key_path_1.joinPath(pathStack.keyPath, key); | ||
enterKey(key, reportNode, ignoreReport) { | ||
const keyPath = [...pathStack.keyPath, key]; | ||
pathStack = { | ||
upper: pathStack, | ||
node, | ||
node: reportNode, | ||
usedKeys: (pathStack.usedKeys && pathStack.usedKeys[key]) || | ||
@@ -61,14 +54,11 @@ false, | ||
if (isUnused) { | ||
const reportNode = resolveReportNode(node); | ||
if (reportNode == null) { | ||
return; | ||
} | ||
reports.push({ | ||
node: reportNode, | ||
keyPath | ||
}); | ||
if (!ignoreReport) | ||
reports.push({ | ||
node: reportNode, | ||
keyPath | ||
}); | ||
} | ||
}, | ||
leaveNode(node) { | ||
if (pathStack.node === node) { | ||
leaveKey(reportNode) { | ||
if (pathStack.node === reportNode) { | ||
pathStack = pathStack.upper; | ||
@@ -79,5 +69,6 @@ } | ||
for (const { node, keyPath } of reports) { | ||
const keyPathStr = key_path_1.joinPath(...keyPath); | ||
const fix = buildFixer(node); | ||
context.report({ | ||
message: `unused '${keyPath}' key`, | ||
message: `unused '${keyPathStr}' key`, | ||
loc: node.loc, | ||
@@ -87,3 +78,3 @@ fix: enableFix ? fix : null, | ||
{ | ||
desc: `Remove the '${keyPath}' key.`, | ||
desc: `Remove the '${keyPathStr}' key.`, | ||
fix | ||
@@ -104,35 +95,3 @@ }, | ||
function createVisitorForJson(sourceCode, usedKeys) { | ||
return createVisitor(usedKeys, { | ||
skipNode(node) { | ||
if (node.type === 'Program' || | ||
node.type === 'JSONExpressionStatement' || | ||
node.type === 'JSONProperty') { | ||
return true; | ||
} | ||
const parent = node.parent; | ||
if (parent.type === 'JSONProperty' && parent.key === node) { | ||
return true; | ||
} | ||
return false; | ||
}, | ||
resolveKey(node) { | ||
const parent = node.parent; | ||
if (parent.type === 'JSONProperty') { | ||
return parent.key.type === 'JSONLiteral' | ||
? `${parent.key.value}` | ||
: parent.key.name; | ||
} | ||
else if (parent.type === 'JSONArrayExpression') { | ||
return parent.elements.indexOf(node); | ||
} | ||
return null; | ||
}, | ||
resolveReportNode(node) { | ||
if (node.type === 'JSONObjectExpression' || | ||
node.type === 'JSONArrayExpression') { | ||
return null; | ||
} | ||
const parent = node.parent; | ||
return parent.type === 'JSONProperty' ? parent.key : node; | ||
}, | ||
const verifyContext = createVerifyContext(usedKeys, { | ||
buildFixer(node) { | ||
@@ -147,2 +106,25 @@ return fixer => fixer.removeRange(fixRemoveRange(node)); | ||
}); | ||
function isIgnore(node) { | ||
return (node.type === 'JSONArrayExpression' || | ||
node.type === 'JSONObjectExpression'); | ||
} | ||
return { | ||
JSONProperty(node) { | ||
const key = node.key.type === 'JSONLiteral' ? `${node.key.value}` : node.key.name; | ||
verifyContext.enterKey(key, node.key, isIgnore(node.value)); | ||
}, | ||
'JSONProperty:exit'(node) { | ||
verifyContext.leaveKey(node.key); | ||
}, | ||
'JSONArrayExpression > *'(node) { | ||
const key = node.parent.elements.indexOf(node); | ||
verifyContext.enterKey(key, node, isIgnore(node)); | ||
}, | ||
'JSONArrayExpression > *:exit'(node) { | ||
verifyContext.leaveKey(node); | ||
}, | ||
'Program:exit'() { | ||
verifyContext.reports(); | ||
} | ||
}; | ||
function* fixAllRemoveKeys(fixer, nodes) { | ||
@@ -195,46 +177,3 @@ const ranges = nodes.map(node => fixRemoveRange(node)); | ||
function createVisitorForYaml(sourceCode, usedKeys) { | ||
const yamlKeyNodes = new Set(); | ||
return createVisitor(usedKeys, { | ||
skipNode(node) { | ||
if (node.type === 'Program' || | ||
node.type === 'YAMLDocument' || | ||
node.type === 'YAMLDirective' || | ||
node.type === 'YAMLAnchor' || | ||
node.type === 'YAMLTag') { | ||
return true; | ||
} | ||
if (yamlKeyNodes.has(node)) { | ||
return true; | ||
} | ||
const parent = node.parent; | ||
if (yamlKeyNodes.has(parent)) { | ||
yamlKeyNodes.add(node); | ||
return true; | ||
} | ||
if (node.type === 'YAMLPair') { | ||
yamlKeyNodes.add(node.key); | ||
return true; | ||
} | ||
return false; | ||
}, | ||
resolveKey(node) { | ||
const parent = node.parent; | ||
if (parent.type === 'YAMLPair' && parent.key) { | ||
const key = parent.key.type !== 'YAMLScalar' | ||
? sourceCode.getText(parent.key) | ||
: parent.key.value; | ||
return typeof key === 'boolean' || key === null ? String(key) : key; | ||
} | ||
else if (parent.type === 'YAMLSequence') { | ||
return parent.entries.indexOf(node); | ||
} | ||
return null; | ||
}, | ||
resolveReportNode(node) { | ||
if (node.type === 'YAMLMapping' || node.type === 'YAMLSequence') { | ||
return null; | ||
} | ||
const parent = node.parent; | ||
return parent.type === 'YAMLPair' ? parent.key : node; | ||
}, | ||
const verifyContext = createVerifyContext(usedKeys, { | ||
buildFixer(node) { | ||
@@ -288,3 +227,3 @@ return function* (fixer) { | ||
else if (parent.type === 'YAMLSequence') { | ||
if (parent.entries.every(p => removeNodes.includes(p))) { | ||
if (parent.entries.every(p => p && removeNodes.includes(p))) { | ||
const before = sourceCode.getTokenBefore(parent); | ||
@@ -312,2 +251,48 @@ if (before) { | ||
}); | ||
const yamlKeyNodes = new Set(); | ||
function withinKey(node) { | ||
for (const keyNode of yamlKeyNodes) { | ||
if (keyNode.range[0] <= node.range[0] && | ||
node.range[0] < keyNode.range[1]) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function isIgnore(node) { | ||
return Boolean(node && (node.type === 'YAMLMapping' || node.type === 'YAMLSequence')); | ||
} | ||
return { | ||
YAMLPair(node) { | ||
if (node.key != null) { | ||
if (withinKey(node)) { | ||
return; | ||
} | ||
yamlKeyNodes.add(node.key); | ||
} | ||
else { | ||
return; | ||
} | ||
const keyValue = node.key.type !== 'YAMLScalar' | ||
? sourceCode.getText(node.key) | ||
: node.key.value; | ||
const key = typeof keyValue === 'boolean' || keyValue === null | ||
? String(keyValue) | ||
: keyValue; | ||
verifyContext.enterKey(key, node.key, isIgnore(node.value)); | ||
}, | ||
'YAMLPair:exit'(node) { | ||
verifyContext.leaveKey(node.key); | ||
}, | ||
'YAMLSequence > *'(node) { | ||
const key = node.parent.entries.indexOf(node); | ||
verifyContext.enterKey(key, node, isIgnore(node)); | ||
}, | ||
'YAMLSequence > *:exit'(node) { | ||
verifyContext.leaveKey(node); | ||
}, | ||
'Program:exit'() { | ||
verifyContext.reports(); | ||
} | ||
}; | ||
function* fixForBlock(fixer, removeNode) { | ||
@@ -391,46 +376,15 @@ const parent = removeNode.parent; | ||
if (path_1.extname(filename) === '.vue') { | ||
return { | ||
Program(node) { | ||
const documentFragment = context.parserServices.getDocumentFragment && | ||
context.parserServices.getDocumentFragment(); | ||
const i18nBlocks = (documentFragment && | ||
documentFragment.children.filter((node) => node.type === 'VElement' && node.name === 'i18n')) || | ||
[]; | ||
if (!i18nBlocks.length) { | ||
return; | ||
} | ||
const createCustomBlockRule = (createVisitor) => { | ||
return ctx => { | ||
const localeMessages = index_1.getLocaleMessages(context); | ||
const usedLocaleMessageKeys = collect_keys_1.collectKeysFromAST(node, context.getSourceCode().visitorKeys); | ||
for (const block of i18nBlocks) { | ||
if (block.startTag.attributes.some(attr => !attr.directive && attr.key.name === 'src')) { | ||
continue; | ||
} | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(block); | ||
if (!targetLocaleMessage) { | ||
continue; | ||
} | ||
const sourceCode = targetLocaleMessage.getSourceCode(); | ||
if (!sourceCode) { | ||
continue; | ||
} | ||
const usedKeys = getUsedKeysMap(targetLocaleMessage, targetLocaleMessage.messages, usedLocaleMessageKeys); | ||
const parserLang = targetLocaleMessage.getParserLang(); | ||
let visitor; | ||
if (parserLang === 'json') { | ||
visitor = createVisitorForJson(sourceCode, usedKeys); | ||
} | ||
else if (parserLang === 'yaml') { | ||
visitor = createVisitorForYaml(sourceCode, usedKeys); | ||
} | ||
if (visitor == null) { | ||
return; | ||
} | ||
targetLocaleMessage.traverseNodes({ | ||
enterNode: visitor.enterNode, | ||
leaveNode: visitor.leaveNode | ||
}); | ||
visitor.reports(); | ||
const usedLocaleMessageKeys = collect_keys_1.collectKeysFromAST(context.getSourceCode().ast, context.getSourceCode().visitorKeys); | ||
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(ctx.parserServices.customBlock); | ||
if (!targetLocaleMessage) { | ||
return {}; | ||
} | ||
} | ||
const usedKeys = getUsedKeysMap(targetLocaleMessage, targetLocaleMessage.messages, usedLocaleMessageKeys, context); | ||
return createVisitor(ctx.getSourceCode(), usedKeys); | ||
}; | ||
}; | ||
return index_1.defineCustomBlocksVisitor(context, createCustomBlockRule(createVisitorForJson), createCustomBlockRule(createVisitorForYaml)); | ||
} | ||
@@ -448,22 +402,8 @@ else if (context.parserServices.isJSON || context.parserServices.isYAML) { | ||
const sourceCode = context.getSourceCode(); | ||
const usedKeys = getUsedKeysMap(targetLocaleMessage, targetLocaleMessage.messages, usedLocaleMessageKeys); | ||
const usedKeys = getUsedKeysMap(targetLocaleMessage, targetLocaleMessage.messages, usedLocaleMessageKeys, context); | ||
if (context.parserServices.isJSON) { | ||
const { enterNode, leaveNode, reports } = createVisitorForJson(sourceCode, usedKeys); | ||
return { | ||
'[type=/^JSON/]': enterNode, | ||
'[type=/^JSON/]:exit': leaveNode, | ||
'Program:exit'() { | ||
reports(); | ||
} | ||
}; | ||
return createVisitorForJson(sourceCode, usedKeys); | ||
} | ||
else if (context.parserServices.isYAML) { | ||
const { enterNode, leaveNode, reports } = createVisitorForYaml(sourceCode, usedKeys); | ||
return { | ||
'[type=/^YAML/]': enterNode, | ||
'[type=/^YAML/]:exit': leaveNode, | ||
'Program:exit'() { | ||
reports(); | ||
} | ||
}; | ||
return createVisitorForYaml(sourceCode, usedKeys); | ||
} | ||
@@ -470,0 +410,0 @@ return {}; |
@@ -10,3 +10,3 @@ "use strict"; | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
@@ -13,0 +13,0 @@ Object.defineProperty(exports, "__esModule", { value: true }); |
@@ -17,3 +17,3 @@ "use strict"; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
@@ -20,0 +20,0 @@ return result; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.collectLinkedKeys = void 0; | ||
const linkKeyMatcher = /(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))/g; | ||
const linkKeyPrefixMatcher = /^@(?:\.([a-z]+))?:/; | ||
const bracketsMatcher = /[()]/g; | ||
function* extractUsedKeysFromLinks(object) { | ||
const traverser_1 = require("./message-compiler/traverser"); | ||
const parser_1 = require("./message-compiler/parser"); | ||
const parser_v8_1 = require("./message-compiler/parser-v8"); | ||
const utils_1 = require("./message-compiler/utils"); | ||
function* extractUsedKeysFromLinks(object, messageSyntaxVersions) { | ||
for (const value of Object.values(object)) { | ||
@@ -13,23 +14,36 @@ if (!value) { | ||
if (typeof value === 'object') { | ||
yield* extractUsedKeysFromLinks(value); | ||
yield* extractUsedKeysFromLinks(value, messageSyntaxVersions); | ||
} | ||
else if (typeof value === 'string') { | ||
if (value.indexOf('@:') >= 0 || value.indexOf('@.') >= 0) { | ||
const matches = value.match(linkKeyMatcher); | ||
for (const idx in matches) { | ||
const link = matches[idx]; | ||
const linkKeyPrefixMatches = link.match(linkKeyPrefixMatcher); | ||
const [linkPrefix] = linkKeyPrefixMatches; | ||
const linkPlaceholder = link | ||
.replace(linkPrefix, '') | ||
.replace(bracketsMatcher, ''); | ||
yield linkPlaceholder; | ||
} | ||
if (messageSyntaxVersions.v9) { | ||
yield* extractUsedKeysFromAST(parser_1.parse(value).ast); | ||
} | ||
if (messageSyntaxVersions.v8) { | ||
yield* extractUsedKeysFromAST(parser_v8_1.parse(value).ast); | ||
} | ||
} | ||
} | ||
} | ||
function collectLinkedKeys(object) { | ||
return [...new Set(extractUsedKeysFromLinks(object))]; | ||
function collectLinkedKeys(object, context) { | ||
return [ | ||
...new Set(extractUsedKeysFromLinks(object, utils_1.getMessageSyntaxVersions(context))) | ||
].filter(s => !!s); | ||
} | ||
exports.collectLinkedKeys = collectLinkedKeys; | ||
function extractUsedKeysFromAST(ast) { | ||
const keys = new Set(); | ||
traverser_1.traverseNode(ast, node => { | ||
if (node.type === 6) { | ||
if (node.key.type === 7) { | ||
keys.add(node.key.value); | ||
} | ||
else if (node.key.type === 9) { | ||
keys.add(node.key.value); | ||
} | ||
else if (node.key.type === 5) { | ||
keys.add(String(node.key.index)); | ||
} | ||
} | ||
}); | ||
return keys; | ||
} |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -6,3 +25,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getLocaleMessages = exports.defineTemplateBodyVisitor = void 0; | ||
exports.defineCustomBlocksVisitor = exports.getLocaleMessages = exports.defineTemplateBodyVisitor = void 0; | ||
const glob_1 = __importDefault(require("glob")); | ||
@@ -13,2 +32,4 @@ const path_1 = require("path"); | ||
const cache_function_1 = require("./cache-function"); | ||
const jsoncESLintParser = __importStar(require("jsonc-eslint-parser")); | ||
const yamlESLintParser = __importStar(require("yaml-eslint-parser")); | ||
const UNEXPECTED_ERROR_LOCATION = { line: 1, column: 0 }; | ||
@@ -171,1 +192,43 @@ function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVisitor) { | ||
} | ||
function defineCustomBlocksVisitor(context, jsonRule, yamlRule) { | ||
if (!context.parserServices.defineCustomBlocksVisitor) { | ||
return {}; | ||
} | ||
const jsonVisitor = context.parserServices.defineCustomBlocksVisitor(context, jsoncESLintParser, { | ||
target(lang, block) { | ||
if (block.name !== 'i18n') { | ||
return false; | ||
} | ||
return !lang || lang === 'json' || lang === 'json5'; | ||
}, | ||
create: jsonRule | ||
}); | ||
const yamlVisitor = context.parserServices.defineCustomBlocksVisitor(context, yamlESLintParser, { | ||
target(lang, block) { | ||
if (block.name !== 'i18n') { | ||
return false; | ||
} | ||
return lang === 'yaml' || lang === 'yml'; | ||
}, | ||
create: yamlRule | ||
}); | ||
return compositingVisitors(jsonVisitor, yamlVisitor); | ||
} | ||
exports.defineCustomBlocksVisitor = defineCustomBlocksVisitor; | ||
function compositingVisitors(visitor, ...visitors) { | ||
for (const v of visitors) { | ||
for (const key in v) { | ||
if (visitor[key]) { | ||
const o = visitor[key]; | ||
visitor[key] = (...args) => { | ||
o(...args); | ||
v[key](...args); | ||
}; | ||
} | ||
else { | ||
visitor[key] = v[key]; | ||
} | ||
} | ||
} | ||
return visitor; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.joinPath = void 0; | ||
function joinPath(base, ...paths) { | ||
let result = base; | ||
function joinPath(...paths) { | ||
let result = ''; | ||
for (const p of paths) { | ||
@@ -7,0 +7,0 @@ if (typeof p === 'number') { |
@@ -13,2 +13,3 @@ "use strict"; | ||
const js_yaml_1 = __importDefault(require("js-yaml")); | ||
const key_path_1 = require("./key-path"); | ||
class LocaleMessage { | ||
@@ -37,20 +38,2 @@ constructor({ fullpath, locales, localeKey }) { | ||
} | ||
hasMessage(locale, key) { | ||
return this.getMessage(locale, key) != null; | ||
} | ||
getMessage(locale, key) { | ||
const paths = key.split('.'); | ||
const length = paths.length; | ||
let last = this.getMessagesFromLocale(locale); | ||
let i = 0; | ||
while (i < length) { | ||
const value = last && typeof last !== 'string' ? last[paths[i]] : undefined; | ||
if (value == null) { | ||
return null; | ||
} | ||
last = value; | ||
i++; | ||
} | ||
return last !== null && last !== void 0 ? last : null; | ||
} | ||
getMessagesFromLocale(locale) { | ||
@@ -71,3 +54,3 @@ if (this.localeKey === 'file') { | ||
class BlockLocaleMessage extends LocaleMessage { | ||
constructor({ block, fullpath, locales, localeKey, context, lang = 'json' }) { | ||
constructor({ block, fullpath, locales, localeKey, lang = 'json' }) { | ||
super({ | ||
@@ -78,5 +61,3 @@ fullpath, | ||
}); | ||
this._parsed = null; | ||
this._messages = null; | ||
this.context = context; | ||
this.block = block; | ||
@@ -89,48 +70,13 @@ this.lang = lang || 'json'; | ||
} | ||
const parsed = this._getParsed(); | ||
if (!parsed) { | ||
return {}; | ||
} | ||
return (this._messages = parsed.getStaticValue(parsed.ast)); | ||
} | ||
getAST() { | ||
const parsed = this._getParsed(); | ||
if (!parsed) { | ||
return null; | ||
} | ||
return parsed.ast; | ||
} | ||
getParserLang() { | ||
const parsed = this._getParsed(); | ||
if (!parsed) { | ||
return null; | ||
} | ||
return parsed.lang; | ||
} | ||
traverseNodes(visitor) { | ||
const parsed = this._getParsed(); | ||
if (!parsed) { | ||
return; | ||
} | ||
parsed.traverseNodes(parsed.ast, visitor); | ||
} | ||
getSourceCode() { | ||
const parsed = this._getParsed(); | ||
if (!parsed) { | ||
return null; | ||
} | ||
return parsed.getSourceCode(); | ||
} | ||
_getParsed() { | ||
if (this._parsed) { | ||
return this._parsed; | ||
} | ||
const { lang } = this; | ||
if (lang === 'json' || lang === 'json5') { | ||
return (this._parsed = parsers_1.parseJsonInI18nBlock(this.context, this.block)); | ||
this._messages = parsers_1.parseJsonValuesInI18nBlock(this.block) || {}; | ||
} | ||
else if (lang === 'yaml' || lang === 'yml') { | ||
return (this._parsed = parsers_1.parseYamlInI18nBlock(this.context, this.block)); | ||
this._messages = parsers_1.parseYamlValuesInI18nBlock(this.block) || {}; | ||
} | ||
return null; | ||
else { | ||
this._messages = {}; | ||
} | ||
return this._messages; | ||
} | ||
@@ -148,3 +94,3 @@ } | ||
const ext = path_1.extname(fileName).toLowerCase(); | ||
if (ext === '.json' || ext === '.js') { | ||
if (ext === '.js') { | ||
const key = require.resolve(fileName); | ||
@@ -157,3 +103,3 @@ delete require.cache[key]; | ||
} | ||
else if (ext === '.json5') { | ||
else if (ext === '.json' || ext === '.json5') { | ||
return json5_1.default.parse(fs_1.default.readFileSync(fileName, 'utf8')); | ||
@@ -190,4 +136,4 @@ } | ||
} | ||
findMissingPaths(key) { | ||
const missings = []; | ||
findMissingPath(key) { | ||
let missingPath = []; | ||
for (const locale of this.locales) { | ||
@@ -198,2 +144,3 @@ const paths = key.split('.'); | ||
let i = 0; | ||
let missing = false; | ||
while (i < length) { | ||
@@ -206,6 +153,6 @@ const values = lasts | ||
if (values.length === 0) { | ||
missings.push({ | ||
locale, | ||
path: paths.slice(0, i + 1).join('.') | ||
}); | ||
if (missingPath.length <= i) { | ||
missingPath = paths.slice(0, i + 1); | ||
} | ||
missing = true; | ||
break; | ||
@@ -216,6 +163,9 @@ } | ||
} | ||
if (!missing) { | ||
return null; | ||
} | ||
} | ||
return missings.sort(({ locale: localeA }, { locale: localeB }) => localeA > localeB ? 1 : localeA < localeB ? -1 : 0); | ||
return key_path_1.joinPath(...missingPath); | ||
} | ||
} | ||
exports.LocaleMessages = LocaleMessages; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseYamlInI18nBlock = exports.parseJsonInI18nBlock = void 0; | ||
const jsonc_eslint_parser_1 = require("jsonc-eslint-parser"); | ||
const yaml_eslint_parser_1 = require("yaml-eslint-parser"); | ||
const eslint_1 = require("eslint"); | ||
const vue_eslint_parser_1 = require("vue-eslint-parser"); | ||
const location_fixer_1 = require("./location-fixer"); | ||
exports.parseYamlValuesInI18nBlock = exports.parseJsonValuesInI18nBlock = void 0; | ||
const json5_1 = __importDefault(require("json5")); | ||
const js_yaml_1 = __importDefault(require("js-yaml")); | ||
function hasEndTag(element) { | ||
return !!element.endTag; | ||
} | ||
function getSourceCodeString(context, i18nBlock) { | ||
var _a, _b; | ||
const tokenStore = context.parserServices.getTemplateBodyTokenStore(); | ||
const tokens = tokenStore.getTokensBetween(i18nBlock.startTag, i18nBlock.endTag); | ||
if (tokens.length || | ||
i18nBlock.startTag.range[1] === i18nBlock.endTag.range[0]) { | ||
return tokens.map(t => t.value).join(''); | ||
} | ||
const df = (_b = (_a = context.parserServices).getDocumentFragment) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
if (!df) { | ||
return ''; | ||
} | ||
const start = i18nBlock.startTag.range[1]; | ||
const end = i18nBlock.endTag.range[0]; | ||
let sourceCode = ''; | ||
for (const token of df.tokens) { | ||
if (start <= token.range[0] && token.range[1] <= end) { | ||
sourceCode += token.value; | ||
} | ||
if (end <= token.range[0]) { | ||
break; | ||
} | ||
} | ||
return sourceCode; | ||
} | ||
function parseInI18nBlock(context, i18nBlock, parseForESLint) { | ||
function parseValuesInI18nBlock(i18nBlock, parse) { | ||
if (!hasEndTag(i18nBlock)) { | ||
return null; | ||
} | ||
const sourceString = getSourceCodeString(context, i18nBlock); | ||
const text = i18nBlock.children[0]; | ||
const sourceString = text != null && text.type === 'VText' ? text.value : ''; | ||
if (!sourceString.trim()) { | ||
return null; | ||
} | ||
const offsetIndex = i18nBlock.startTag.range[1]; | ||
const sourceCode = context.getSourceCode(); | ||
const locationFixer = new location_fixer_1.LocationFixer(sourceCode, offsetIndex, sourceCode.text.slice(offsetIndex, i18nBlock.endTag.range[0]), sourceString); | ||
let ast, visitorKeys; | ||
try { | ||
const result = parseForESLint(sourceString, { | ||
ecmaVersion: 2019, | ||
loc: true, | ||
range: true, | ||
raw: true, | ||
tokens: true, | ||
comment: true, | ||
eslintVisitorKeys: true, | ||
eslintScopeManager: true | ||
}); | ||
ast = result.ast; | ||
visitorKeys = result.visitorKeys; | ||
return parse(sourceString); | ||
} | ||
catch (e) { | ||
const { line, column } = locationFixer.getFixLoc(e.lineNumber, e.column, e.index); | ||
context.report({ | ||
message: e.message, | ||
loc: { line, column } | ||
}); | ||
return null; | ||
} | ||
vue_eslint_parser_1.AST.traverseNodes(ast, { | ||
visitorKeys, | ||
enterNode(node, parent) { | ||
node.parent = parent || null; | ||
locationFixer.fixLocations(node); | ||
}, | ||
leaveNode() { | ||
} | ||
}); | ||
for (const token of ast.tokens || []) { | ||
locationFixer.fixLocations(token); | ||
} | ||
for (const comment of ast.comments || []) { | ||
locationFixer.fixLocations(comment); | ||
} | ||
let resourceSourceCode; | ||
return { | ||
ast, | ||
sourceString, | ||
getSourceCode() { | ||
return (resourceSourceCode || | ||
(resourceSourceCode = new eslint_1.SourceCode(sourceCode.text, ast))); | ||
}, | ||
traverseNodes(node, visitor) { | ||
vue_eslint_parser_1.AST.traverseNodes(node, Object.assign({ visitorKeys }, visitor)); | ||
} | ||
}; | ||
} | ||
function parseJsonInI18nBlock(context, i18nBlock) { | ||
const result = parseInI18nBlock(context, i18nBlock, jsonc_eslint_parser_1.parseForESLint); | ||
if (result == null) { | ||
return result; | ||
} | ||
return Object.assign({ lang: 'json', getStaticValue(node) { | ||
return jsonc_eslint_parser_1.getStaticJSONValue(node); | ||
} }, result); | ||
function parseJsonValuesInI18nBlock(i18nBlock) { | ||
return parseValuesInI18nBlock(i18nBlock, code => json5_1.default.parse(code)); | ||
} | ||
exports.parseJsonInI18nBlock = parseJsonInI18nBlock; | ||
function parseYamlInI18nBlock(context, i18nBlock) { | ||
const result = parseInI18nBlock(context, i18nBlock, yaml_eslint_parser_1.parseForESLint); | ||
if (result == null) { | ||
return result; | ||
} | ||
return Object.assign({ lang: 'yaml', getStaticValue(node) { | ||
return yaml_eslint_parser_1.getStaticYAMLValue(node); | ||
} }, result); | ||
exports.parseJsonValuesInI18nBlock = parseJsonValuesInI18nBlock; | ||
function parseYamlValuesInI18nBlock(i18nBlock) { | ||
return parseValuesInI18nBlock(i18nBlock, code => js_yaml_1.default.safeLoad(code)); | ||
} | ||
exports.parseYamlInI18nBlock = parseYamlInI18nBlock; | ||
exports.parseYamlValuesInI18nBlock = parseYamlValuesInI18nBlock; |
{ | ||
"name": "@intlify/eslint-plugin-vue-i18n", | ||
"description": "ESLint plugin for Vue I18n", | ||
"version": "0.9.0", | ||
"version": "0.10.0", | ||
"author": { | ||
@@ -27,3 +27,3 @@ "name": "kazuya kawaguchi", | ||
"dependencies": { | ||
"fast-diff": "^1.2.0", | ||
"@intlify/message-compiler": "^9.0.0-beta.16", | ||
"glob": "^7.1.3", | ||
@@ -33,7 +33,8 @@ "ignore": "^5.0.5", | ||
"json5": "^2.1.3", | ||
"jsonc-eslint-parser": "^0.5.2", | ||
"jsonc-eslint-parser": "^0.6.2", | ||
"lodash": "^4.17.11", | ||
"parse5": "^6.0.0", | ||
"vue-eslint-parser": "^7.0.0", | ||
"yaml-eslint-parser": "^0.0.8" | ||
"semver": "^7.3.4", | ||
"vue-eslint-parser": "^7.3.0", | ||
"yaml-eslint-parser": "^0.2.1" | ||
}, | ||
@@ -44,2 +45,3 @@ "devDependencies": { | ||
"@types/eslint-scope": "^3.7.0", | ||
"@types/eslint-visitor-keys": "^1.0.0", | ||
"@types/js-yaml": "^3.12.5", | ||
@@ -50,11 +52,14 @@ "@types/json5": "^0.0.30", | ||
"@types/parse5": "^5.0.3", | ||
"@typescript-eslint/eslint-plugin": "^3.8.0", | ||
"@typescript-eslint/parser": "^3.8.0", | ||
"@types/semver": "^7.3.4", | ||
"@typescript-eslint/eslint-plugin": "^4.10.0", | ||
"@typescript-eslint/parser": "^4.10.0", | ||
"eslint": "^5.15.0 || ^6.0.0 || ^7.0.0", | ||
"eslint-config-prettier": "^6.11.0", | ||
"eslint-config-prettier": "^7.0.0", | ||
"eslint-plugin-markdown": "^1.0.0", | ||
"eslint-plugin-prettier": "^3.1.4", | ||
"eslint-plugin-vue-libs": "^3.0.0 || ^4.0.0", | ||
"eslint4b": "^7.16.0", | ||
"lerna-changelog": "^1.0.0", | ||
"mocha": "^8.0.0", | ||
"monaco-editor": "^0.21.2", | ||
"nyc": "^15.0.0", | ||
@@ -64,5 +69,6 @@ "opener": "^1.5.1", | ||
"rimraf": "^3.0.0", | ||
"shipjs": "^0.20.0", | ||
"ts-node": "^8.10.2", | ||
"typescript": "^3.9.7", | ||
"shipjs": "^0.23.0", | ||
"ts-node": "^9.0.0", | ||
"typescript": "^4.0.0", | ||
"vue-eslint-editor": "^1.1.0", | ||
"vue-github-button": "^1.2.0", | ||
@@ -102,4 +108,4 @@ "vuepress": "^1.5.2" | ||
"coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html", | ||
"docs": "vuepress dev docs", | ||
"docs:build": "vuepress build docs", | ||
"docs": "npm run build && vuepress dev docs", | ||
"docs:build": "npm run build && vuepress build docs", | ||
"generate": "ts-node scripts/update.ts", | ||
@@ -112,5 +118,5 @@ "lint": "eslint --ext js,ts lib scripts tests/lib docs/.vuepress --ignore-pattern \"!.*\"", | ||
"test:debug": "mocha --require ts-node/register --inspect \"./tests/**/*.ts\"", | ||
"test:coverage": "nyc mocha --require ts-node/register \"./tests/**/*.ts\"", | ||
"test:coverage": "nyc mocha --require ts-node/register \"./tests/**/*.ts\" --timeout 60000", | ||
"test:integrations": "mocha ./tests-integrations/*.js --timeout 60000" | ||
} | ||
} |
@@ -25,3 +25,3 @@ <p align="center"><img width="143px" height="130px" src="./assets/eslint-plugin-vue-i18n.svg" alt="ESLint plugin for Vue I18n logo"></p> | ||
Please make sure to read the [Contributing Guide](https://github.com/intlify/eslint-plugin-vue-i18n/blob/master/CONTRIBUTING.md) before making a pull request. | ||
Please make sure to read the [Contributing Guide](https://github.com/intlify/eslint-plugin-vue-i18n/blob/master/.github/CONTRIBUTING.md) before making a pull request. | ||
@@ -28,0 +28,0 @@ ## :copyright: License |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
170488
48
4160
12
31
+ Addedsemver@^7.3.4
+ Added@intlify/message-compiler@9.14.2(transitive)
+ Added@intlify/shared@9.14.2(transitive)
+ Addedjsonc-eslint-parser@0.6.2(transitive)
+ Addedsource-map-js@1.2.1(transitive)
+ Addedyaml-eslint-parser@0.2.2(transitive)
- Removedfast-diff@^1.2.0
- Removedfast-diff@1.3.0(transitive)
- Removedjsonc-eslint-parser@0.5.2(transitive)
- Removedyaml-eslint-parser@0.0.8(transitive)
Updatedjsonc-eslint-parser@^0.6.2
Updatedvue-eslint-parser@^7.3.0
Updatedyaml-eslint-parser@^0.2.1