diff --git a/.eslintrc.json b/.eslintrc.json index ed14c57a0db..ad255a10130 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -31,18 +31,7 @@ "code-translation-remind": "warn", "code-no-nls-in-standalone-editor": "warn", "code-no-standalone-editor": "warn", - "code-no-unexternalized-strings2": "warn", - "code-no-unexternalized-strings": [ - "off", - { - "signatures": [ - "localize", - "nls.localize" - ], - "keyIndex": 0, - "messageIndex": 1 - } - ], + "code-no-unexternalized-strings": "warn", "code-layering": [ "warn", { diff --git a/build/lib/eslint/code-no-unexternalized-strings.js b/build/lib/eslint/code-no-unexternalized-strings.js index f0f43450d1e..c4cdbe597ba 100644 --- a/build/lib/eslint/code-no-unexternalized-strings.js +++ b/build/lib/eslint/code-no-unexternalized-strings.js @@ -8,207 +8,105 @@ 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 isDoubleQuoted(node) { + return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; } -function isPropertyAssignment(node) { - return !!node && node.type === experimental_utils_1.AST_NODE_TYPES.Property; -} -module.exports = new (_a = class NoUnexternalizedStringsRuleWalker { +module.exports = new (_a = class NoUnexternalizedStrings { 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.` + doubleQuoted: 'Only use double-quoted strings for externalized strings.', + badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.', + duplicateKey: 'Duplicate key \'{{key}}\' with different message value.', + 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; + const externalizedStringLiterals = new Map(); + const doubleQuotedStringLiterals = new Set(); + function collectDoubleQuotedStrings(node) { + if (isStringLiteral(node) && isDoubleQuoted(node)) { + doubleQuotedStringLiterals.add(node); } } - 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 } - }); + function visitLocalizeCall(node) { + // localize(key, message) + const [keyNode, messageNode] = node.arguments; + // (1) + // extract key so that it can be checked later + let key; + if (isStringLiteral(keyNode)) { + doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider + key = keyNode.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); + else if (keyNode.type === experimental_utils_1.AST_NODE_TYPES.ObjectExpression) { + for (let property of keyNode.properties) { + if (property.type === experimental_utils_1.AST_NODE_TYPES.Property && !property.computed) { + if (property.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier && property.key.name === 'key') { + if (isStringLiteral(property.value)) { + doubleQuotedStringLiterals.delete(property.value); //todo@joh reconsider + key = property.value.value; + break; } - break; + } + } + } + } + if (typeof key === 'string') { + let array = externalizedStringLiterals.get(key); + if (!array) { + array = []; + externalizedStringLiterals.set(key, array); + } + array.push({ call: node, message: messageNode }); + } + // (2) + // remove message-argument from doubleQuoted list and make + // sure it is a string-literal + doubleQuotedStringLiterals.delete(messageNode); + if (!isStringLiteral(messageNode)) { + context.report({ + loc: messageNode.loc, + messageId: 'badMessage', + data: { message: context.getSourceCode().getText(node) } + }); + } + } + function reportBadStringsAndBadKeys() { + // (1) + // report all strings that are in double quotes + for (const node of doubleQuotedStringLiterals) { + context.report({ loc: node.loc, messageId: 'doubleQuoted' }); + } + for (const [key, values] of externalizedStringLiterals) { + // (2) + // report all invalid NLS keys + if (!key.match(NoUnexternalizedStrings._rNlsKeys)) { + for (let value of values) { + context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } }); + } + } + // (2) + // report all invalid duplicates (same key, different message) + if (values.length > 1) { + for (let i = 1; i < values.length; i++) { + if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { + context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); } } } } } - 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); + return { + ['Literal']: (node) => collectDoubleQuotedStrings(node), + ['CallExpression[callee.type="MemberExpression"][callee.property.name="localize"]:exit']: (node) => visitLocalizeCall(node), + ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node) => visitLocalizeCall(node), + ['Program:exit']: reportBadStringsAndBadKeys, + }; } }, - _a.DOUBLE_QUOTE = '"', - _a.IDENTIFIER = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/, + _a._rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/, _a); diff --git a/build/lib/eslint/code-no-unexternalized-strings.ts b/build/lib/eslint/code-no-unexternalized-strings.ts index 096f1b44802..3bc1156d397 100644 --- a/build/lib/eslint/code-no-unexternalized-strings.ts +++ b/build/lib/eslint/code-no-unexternalized-strings.ts @@ -6,250 +6,122 @@ import * as eslint from 'eslint'; import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; -interface Map { - [key: string]: V; -} - -interface UnexternalizedStringsOptions { - signatures?: string[]; - messageIndex?: number; - keyIndex?: number; - ignores?: string[]; -} - function isStringLiteral(node: TSESTree.Node | null | undefined): node is TSESTree.StringLiteral { return !!node && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string'; } -function isObjectLiteral(node: TSESTree.Node | null | undefined): node is TSESTree.ObjectExpression { - return !!node && node.type === AST_NODE_TYPES.ObjectExpression; +function isDoubleQuoted(node: TSESTree.StringLiteral): boolean { + return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; } -function isPropertyAssignment(node: TSESTree.Node | null | undefined): node is TSESTree.Property { - return !!node && node.type === AST_NODE_TYPES.Property; -} +export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { -interface KeyMessagePair { - key: TSESTree.StringLiteral; - message: TSESTree.Node | undefined; -} - -export = new class NoUnexternalizedStringsRuleWalker implements eslint.Rule.RuleModule { - - private static DOUBLE_QUOTE: string = '"'; - private static IDENTIFIER = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/; - - private signatures: Map = Object.create(null); - private messageIndex: number | undefined; - private keyIndex: number | undefined; - private ignores: Map = Object.create(null); - - private usedKeys: Map = Object.create(null); + private static _rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/; readonly 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.` + doubleQuoted: 'Only use double-quoted strings for externalized strings.', + badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.', + duplicateKey: 'Duplicate key \'{{key}}\' with different message value.', + badMessage: 'Message argument to \'{{message}}\' must be a string literal.' } }; create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - const first = context.options[0]; - if (first) { - if (Array.isArray(first.signatures)) { - first.signatures.forEach((signature: string) => this.signatures[signature] = true); - } - if (Array.isArray(first.ignores)) { - first.ignores.forEach((ignore: string) => this.ignores[ignore] = true); - } - if (typeof first.messageIndex !== 'undefined') { - this.messageIndex = first.messageIndex; - } - if (typeof first.keyIndex !== 'undefined') { - this.keyIndex = first.keyIndex; + const externalizedStringLiterals = new Map(); + const doubleQuotedStringLiterals = new Set(); + + function collectDoubleQuotedStrings(node: TSESTree.Literal) { + if (isStringLiteral(node) && isDoubleQuoted(node)) { + doubleQuotedStringLiterals.add(node); } } - return { - ['Program:exit']: () => { - this._checkProgramEnd(context); - }, - ['Literal']: (node: any) => { - if (typeof (node).value === 'string') { - this._checkStringLiteral(context, node); - } - }, - }; - } + function visitLocalizeCall(node: TSESTree.CallExpression) { - protected _checkProgramEnd(context: eslint.Rule.RuleContext): void { - Object.keys(this.usedKeys).forEach(key => { - // Keys are quoted. - const identifier = key.substr(1, key.length - 2); - const occurrences = this.usedKeys[key]; + // localize(key, message) + const [keyNode, messageNode] = (node).arguments; - // bad key - if (!NoUnexternalizedStringsRuleWalker.IDENTIFIER.test(identifier)) { - context.report({ - loc: occurrences[0].key.loc, - messageId: 'badKey', - data: { key: occurrences[0].key.value } - }); - } + // (1) + // extract key so that it can be checked later + let key: string | undefined; + if (isStringLiteral(keyNode)) { + doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider + key = keyNode.value; - // duplicates key - if (occurrences.length > 1) { - occurrences.forEach(occurrence => { - context.report({ - loc: occurrence.key.loc, - messageId: 'duplicateKey', - data: { key: occurrence.key.value } - }); - }); - } - }); - } - - private _checkStringLiteral(context: eslint.Rule.RuleContext, node: TSESTree.StringLiteral): void { - 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: TSESTree.Expression | null = 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); + } else if (keyNode.type === AST_NODE_TYPES.ObjectExpression) { + for (let property of keyNode.properties) { + if (property.type === AST_NODE_TYPES.Property && !property.computed) { + if (property.key.type === AST_NODE_TYPES.Identifier && property.key.name === 'key') { + if (isStringLiteral(property.value)) { + doubleQuotedStringLiterals.delete(property.value); //todo@joh reconsider + key = property.value.value; + break; } - break; + } + } + } + } + if (typeof key === 'string') { + let array = externalizedStringLiterals.get(key); + if (!array) { + array = []; + externalizedStringLiterals.set(key, array); + } + array.push({ call: node, message: messageNode }); + } + + // (2) + // remove message-argument from doubleQuoted list and make + // sure it is a string-literal + doubleQuotedStringLiterals.delete(messageNode); + if (!isStringLiteral(messageNode)) { + context.report({ + loc: messageNode.loc, + messageId: 'badMessage', + data: { message: context.getSourceCode().getText(node) } + }); + } + } + + function reportBadStringsAndBadKeys() { + // (1) + // report all strings that are in double quotes + for (const node of doubleQuotedStringLiterals) { + context.report({ loc: node.loc, messageId: 'doubleQuoted' }); + } + + for (const [key, values] of externalizedStringLiterals) { + + // (2) + // report all invalid NLS keys + if (!key.match(NoUnexternalizedStrings._rNlsKeys)) { + for (let value of values) { + context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } }); + } + } + + // (2) + // report all invalid duplicates (same key, different message) + if (values.length > 1) { + for (let i = 1; i < values.length; i++) { + if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { + context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); } } } } } - const messageArg = callInfo!.callExpression.arguments[this.messageIndex!]; - - if (messageArg && !isStringLiteral(messageArg)) { - context.report({ - loc: messageArg.loc, - messageId: 'badMessage', - data: { message: NoUnexternalizedStringsRuleWalker._getText(context.getSourceCode(), callInfo?.callExpression.callee!) } - }); - return; - } + return { + ['Literal']: (node: any) => collectDoubleQuotedStrings(node), + ['CallExpression[callee.type="MemberExpression"][callee.property.name="localize"]:exit']: (node: any) => visitLocalizeCall(node), + ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node), + ['Program:exit']: reportBadStringsAndBadKeys, + }; } - - private recordKey(context: eslint.Rule.RuleContext, keyNode: TSESTree.StringLiteral, messageNode: TSESTree.Node | undefined) { - 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: KeyMessagePair[] = 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 }); - } - - private _findDescribingParent(node: TSESTree.Node): { callInfo?: { callExpression: TSESTree.CallExpression, argIndex: number }, isImport?: boolean; } | null { - let parent: TSESTree.Node | undefined; - while ((parent = node.parent)) { - const kind = parent.type; - if (kind === AST_NODE_TYPES.CallExpression) { - const callExpression = parent as TSESTree.CallExpression; - return { callInfo: { callExpression: callExpression, argIndex: callExpression.arguments.indexOf(node) } }; - - } else if (kind === AST_NODE_TYPES.TSImportEqualsDeclaration || kind === AST_NODE_TYPES.ImportDeclaration || kind === AST_NODE_TYPES.ExportNamedDeclaration) { - return { isImport: true }; - - } else if (kind === AST_NODE_TYPES.VariableDeclaration || kind === AST_NODE_TYPES.FunctionDeclaration || kind === AST_NODE_TYPES.TSPropertySignature - || kind === AST_NODE_TYPES.TSMethodSignature || kind === AST_NODE_TYPES.TSInterfaceDeclaration - || kind === AST_NODE_TYPES.ClassDeclaration || kind === AST_NODE_TYPES.TSEnumDeclaration || kind === AST_NODE_TYPES.TSModuleDeclaration - || kind === AST_NODE_TYPES.TSTypeAliasDeclaration || kind === AST_NODE_TYPES.Program) { - return null; - } - node = parent; - } - return null; - } - - private static _getText(source: eslint.SourceCode, node: TSESTree.Node): string { - if (node.type === 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); - } - }; diff --git a/build/lib/eslint/code-no-unexternalized-strings2.js b/build/lib/eslint/code-no-unexternalized-strings2.js deleted file mode 100644 index c4cdbe597ba..00000000000 --- a/build/lib/eslint/code-no-unexternalized-strings2.js +++ /dev/null @@ -1,112 +0,0 @@ -"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 isDoubleQuoted(node) { - return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; -} -module.exports = new (_a = class NoUnexternalizedStrings { - constructor() { - this.meta = { - type: 'problem', - schema: {}, - messages: { - doubleQuoted: 'Only use double-quoted strings for externalized strings.', - badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.', - duplicateKey: 'Duplicate key \'{{key}}\' with different message value.', - badMessage: 'Message argument to \'{{message}}\' must be a string literal.' - } - }; - } - create(context) { - const externalizedStringLiterals = new Map(); - const doubleQuotedStringLiterals = new Set(); - function collectDoubleQuotedStrings(node) { - if (isStringLiteral(node) && isDoubleQuoted(node)) { - doubleQuotedStringLiterals.add(node); - } - } - function visitLocalizeCall(node) { - // localize(key, message) - const [keyNode, messageNode] = node.arguments; - // (1) - // extract key so that it can be checked later - let key; - if (isStringLiteral(keyNode)) { - doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider - key = keyNode.value; - } - else if (keyNode.type === experimental_utils_1.AST_NODE_TYPES.ObjectExpression) { - for (let property of keyNode.properties) { - if (property.type === experimental_utils_1.AST_NODE_TYPES.Property && !property.computed) { - if (property.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier && property.key.name === 'key') { - if (isStringLiteral(property.value)) { - doubleQuotedStringLiterals.delete(property.value); //todo@joh reconsider - key = property.value.value; - break; - } - } - } - } - } - if (typeof key === 'string') { - let array = externalizedStringLiterals.get(key); - if (!array) { - array = []; - externalizedStringLiterals.set(key, array); - } - array.push({ call: node, message: messageNode }); - } - // (2) - // remove message-argument from doubleQuoted list and make - // sure it is a string-literal - doubleQuotedStringLiterals.delete(messageNode); - if (!isStringLiteral(messageNode)) { - context.report({ - loc: messageNode.loc, - messageId: 'badMessage', - data: { message: context.getSourceCode().getText(node) } - }); - } - } - function reportBadStringsAndBadKeys() { - // (1) - // report all strings that are in double quotes - for (const node of doubleQuotedStringLiterals) { - context.report({ loc: node.loc, messageId: 'doubleQuoted' }); - } - for (const [key, values] of externalizedStringLiterals) { - // (2) - // report all invalid NLS keys - if (!key.match(NoUnexternalizedStrings._rNlsKeys)) { - for (let value of values) { - context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } }); - } - } - // (2) - // report all invalid duplicates (same key, different message) - if (values.length > 1) { - for (let i = 1; i < values.length; i++) { - if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { - context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); - } - } - } - } - } - return { - ['Literal']: (node) => collectDoubleQuotedStrings(node), - ['CallExpression[callee.type="MemberExpression"][callee.property.name="localize"]:exit']: (node) => visitLocalizeCall(node), - ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node) => visitLocalizeCall(node), - ['Program:exit']: reportBadStringsAndBadKeys, - }; - } - }, - _a._rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/, - _a); diff --git a/build/lib/eslint/code-no-unexternalized-strings2.ts b/build/lib/eslint/code-no-unexternalized-strings2.ts deleted file mode 100644 index 3bc1156d397..00000000000 --- a/build/lib/eslint/code-no-unexternalized-strings2.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as eslint from 'eslint'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; - -function isStringLiteral(node: TSESTree.Node | null | undefined): node is TSESTree.StringLiteral { - return !!node && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string'; -} - -function isDoubleQuoted(node: TSESTree.StringLiteral): boolean { - return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; -} - -export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { - - private static _rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/; - - readonly meta = { - type: 'problem', - schema: {}, - messages: { - doubleQuoted: 'Only use double-quoted strings for externalized strings.', - badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.', - duplicateKey: 'Duplicate key \'{{key}}\' with different message value.', - badMessage: 'Message argument to \'{{message}}\' must be a string literal.' - } - }; - - create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - - const externalizedStringLiterals = new Map(); - const doubleQuotedStringLiterals = new Set(); - - function collectDoubleQuotedStrings(node: TSESTree.Literal) { - if (isStringLiteral(node) && isDoubleQuoted(node)) { - doubleQuotedStringLiterals.add(node); - } - } - - function visitLocalizeCall(node: TSESTree.CallExpression) { - - // localize(key, message) - const [keyNode, messageNode] = (node).arguments; - - // (1) - // extract key so that it can be checked later - let key: string | undefined; - if (isStringLiteral(keyNode)) { - doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider - key = keyNode.value; - - } else if (keyNode.type === AST_NODE_TYPES.ObjectExpression) { - for (let property of keyNode.properties) { - if (property.type === AST_NODE_TYPES.Property && !property.computed) { - if (property.key.type === AST_NODE_TYPES.Identifier && property.key.name === 'key') { - if (isStringLiteral(property.value)) { - doubleQuotedStringLiterals.delete(property.value); //todo@joh reconsider - key = property.value.value; - break; - } - } - } - } - } - if (typeof key === 'string') { - let array = externalizedStringLiterals.get(key); - if (!array) { - array = []; - externalizedStringLiterals.set(key, array); - } - array.push({ call: node, message: messageNode }); - } - - // (2) - // remove message-argument from doubleQuoted list and make - // sure it is a string-literal - doubleQuotedStringLiterals.delete(messageNode); - if (!isStringLiteral(messageNode)) { - context.report({ - loc: messageNode.loc, - messageId: 'badMessage', - data: { message: context.getSourceCode().getText(node) } - }); - } - } - - function reportBadStringsAndBadKeys() { - // (1) - // report all strings that are in double quotes - for (const node of doubleQuotedStringLiterals) { - context.report({ loc: node.loc, messageId: 'doubleQuoted' }); - } - - for (const [key, values] of externalizedStringLiterals) { - - // (2) - // report all invalid NLS keys - if (!key.match(NoUnexternalizedStrings._rNlsKeys)) { - for (let value of values) { - context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } }); - } - } - - // (2) - // report all invalid duplicates (same key, different message) - if (values.length > 1) { - for (let i = 1; i < values.length; i++) { - if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { - context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); - } - } - } - } - } - - return { - ['Literal']: (node: any) => collectDoubleQuotedStrings(node), - ['CallExpression[callee.type="MemberExpression"][callee.property.name="localize"]:exit']: (node: any) => visitLocalizeCall(node), - ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node), - ['Program:exit']: reportBadStringsAndBadKeys, - }; - } -}; -