Files
vscode/build/lib/eslint/code-no-unexternalized-strings.js
2019-12-30 17:48:21 +01:00

215 lines
10 KiB
JavaScript

"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var _a;
const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
function isStringLiteral(node) {
return !!node && node.type === experimental_utils_1.AST_NODE_TYPES.Literal && typeof node.value === 'string';
}
function isObjectLiteral(node) {
return !!node && node.type === experimental_utils_1.AST_NODE_TYPES.ObjectExpression;
}
function isPropertyAssignment(node) {
return !!node && node.type === experimental_utils_1.AST_NODE_TYPES.Property;
}
module.exports = new (_a = class NoUnexternalizedStringsRuleWalker {
constructor() {
this.signatures = Object.create(null);
this.ignores = Object.create(null);
this.usedKeys = Object.create(null);
this.meta = {
type: 'problem',
schema: {},
messages: {
badQuotes: 'Do not use double quotes for imports.',
unexternalized: 'Unexternalized string.',
duplicateKey: `Duplicate key '{{key}}' with different message value.`,
badKey: `The key {{key}} doesn't conform to a valid localize identifier`,
emptyKey: 'Key is empty.',
whitespaceKey: 'Key is only whitespace.',
badMessage: `Message argument to '{{message}}' must be a string literal.`
}
};
}
create(context) {
const first = context.options[0];
if (first) {
if (Array.isArray(first.signatures)) {
first.signatures.forEach((signature) => this.signatures[signature] = true);
}
if (Array.isArray(first.ignores)) {
first.ignores.forEach((ignore) => this.ignores[ignore] = true);
}
if (typeof first.messageIndex !== 'undefined') {
this.messageIndex = first.messageIndex;
}
if (typeof first.keyIndex !== 'undefined') {
this.keyIndex = first.keyIndex;
}
}
return {
['Program:exit']: () => {
this._checkProgramEnd(context);
},
['Literal']: (node) => {
if (typeof node.value === 'string') {
this._checkStringLiteral(context, node);
}
},
};
}
_checkProgramEnd(context) {
Object.keys(this.usedKeys).forEach(key => {
// Keys are quoted.
const identifier = key.substr(1, key.length - 2);
const occurrences = this.usedKeys[key];
// bad key
if (!NoUnexternalizedStringsRuleWalker.IDENTIFIER.test(identifier)) {
context.report({
loc: occurrences[0].key.loc,
messageId: 'badKey',
data: { key: occurrences[0].key.value }
});
}
// duplicates key
if (occurrences.length > 1) {
occurrences.forEach(occurrence => {
context.report({
loc: occurrence.key.loc,
messageId: 'duplicateKey',
data: { key: occurrence.key.value }
});
});
}
});
}
_checkStringLiteral(context, node) {
var _a;
const text = node.raw;
const doubleQuoted = text.length >= 2 && text[0] === NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE && text[text.length - 1] === NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE;
const info = this._findDescribingParent(node);
// Ignore strings in import and export nodes.
if (info && info.isImport && doubleQuoted) {
context.report({
loc: node.loc,
messageId: 'badQuotes'
});
return;
}
const callInfo = info ? info.callInfo : null;
const functionName = callInfo && isStringLiteral(callInfo.callExpression.callee)
? callInfo.callExpression.callee.value
: null;
if (functionName && this.ignores[functionName]) {
return;
}
if (doubleQuoted && (!callInfo || callInfo.argIndex === -1 || !this.signatures[functionName])) {
context.report({
loc: node.loc,
messageId: 'unexternalized'
});
return;
}
// We have a single quoted string outside a localize function name.
if (!doubleQuoted && !this.signatures[functionName]) {
return;
}
// We have a string that is a direct argument into the localize call.
const keyArg = callInfo && callInfo.argIndex === this.keyIndex
? callInfo.callExpression.arguments[this.keyIndex]
: null;
if (keyArg) {
if (isStringLiteral(keyArg)) {
this.recordKey(context, keyArg, this.messageIndex && callInfo ? callInfo.callExpression.arguments[this.messageIndex] : undefined);
}
else if (isObjectLiteral(keyArg)) {
for (const property of keyArg.properties) {
if (isPropertyAssignment(property)) {
const name = NoUnexternalizedStringsRuleWalker._getText(context.getSourceCode(), property.key);
if (name === 'key') {
const initializer = property.value;
if (isStringLiteral(initializer)) {
this.recordKey(context, initializer, this.messageIndex && callInfo ? callInfo.callExpression.arguments[this.messageIndex] : undefined);
}
break;
}
}
}
}
}
const messageArg = callInfo.callExpression.arguments[this.messageIndex];
if (messageArg && !isStringLiteral(messageArg)) {
context.report({
loc: messageArg.loc,
messageId: 'badMessage',
data: { message: NoUnexternalizedStringsRuleWalker._getText(context.getSourceCode(), (_a = callInfo) === null || _a === void 0 ? void 0 : _a.callExpression.callee) }
});
return;
}
}
recordKey(context, keyNode, messageNode) {
const text = keyNode.raw;
// We have an empty key
if (text.match(/(['"]) *\1/)) {
if (messageNode) {
context.report({
loc: keyNode.loc,
messageId: 'whitespaceKey'
});
}
else {
context.report({
loc: keyNode.loc,
messageId: 'emptyKey'
});
}
return;
}
let occurrences = this.usedKeys[text];
if (!occurrences) {
occurrences = [];
this.usedKeys[text] = occurrences;
}
if (messageNode) {
if (occurrences.some(pair => pair.message ? NoUnexternalizedStringsRuleWalker._getText(context.getSourceCode(), pair.message) === NoUnexternalizedStringsRuleWalker._getText(context.getSourceCode(), messageNode) : false)) {
return;
}
}
occurrences.push({ key: keyNode, message: messageNode });
}
_findDescribingParent(node) {
let parent;
while ((parent = node.parent)) {
const kind = parent.type;
if (kind === experimental_utils_1.AST_NODE_TYPES.CallExpression) {
const callExpression = parent;
return { callInfo: { callExpression: callExpression, argIndex: callExpression.arguments.indexOf(node) } };
}
else if (kind === experimental_utils_1.AST_NODE_TYPES.TSImportEqualsDeclaration || kind === experimental_utils_1.AST_NODE_TYPES.ImportDeclaration || kind === experimental_utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
return { isImport: true };
}
else if (kind === experimental_utils_1.AST_NODE_TYPES.VariableDeclaration || kind === experimental_utils_1.AST_NODE_TYPES.FunctionDeclaration || kind === experimental_utils_1.AST_NODE_TYPES.TSPropertySignature
|| kind === experimental_utils_1.AST_NODE_TYPES.TSMethodSignature || kind === experimental_utils_1.AST_NODE_TYPES.TSInterfaceDeclaration
|| kind === experimental_utils_1.AST_NODE_TYPES.ClassDeclaration || kind === experimental_utils_1.AST_NODE_TYPES.TSEnumDeclaration || kind === experimental_utils_1.AST_NODE_TYPES.TSModuleDeclaration
|| kind === experimental_utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration || kind === experimental_utils_1.AST_NODE_TYPES.Program) {
return null;
}
node = parent;
}
return null;
}
static _getText(source, node) {
if (node.type === experimental_utils_1.AST_NODE_TYPES.Literal) {
return String(node.value);
}
const start = source.getIndexFromLoc(node.loc.start);
const end = source.getIndexFromLoc(node.loc.end);
return source.getText().substring(start, end);
}
},
_a.DOUBLE_QUOTE = '"',
_a.IDENTIFIER = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/,
_a);