vscode/.eslint-plugin-local/code-no-localization-template-literals.ts

91 lines
3.0 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* 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 } from '@typescript-eslint/utils';
/**
* Prevents the use of template literals in localization function calls.
*
* vscode.l10n.t() and nls.localize() cannot handle string templating.
* Use placeholders instead: vscode.l10n.t('Message {0}', value)
*
* Examples:
* ❌ vscode.l10n.t(`Message ${value}`)
* ✅ vscode.l10n.t('Message {0}', value)
*
* ❌ nls.localize('key', `Message ${value}`)
* ✅ nls.localize('key', 'Message {0}', value)
*/
export default new class NoLocalizationTemplateLiterals implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
noTemplateLiteral: 'Template literals cannot be used in localization calls. Use placeholders like {0}, {1} instead.'
},
docs: {
description: 'Prevents template literals in vscode.l10n.t() and nls.localize() calls',
},
schema: false,
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
function checkCallExpression(node: TSESTree.CallExpression) {
const callee = node.callee;
let isLocalizationCall = false;
let isNlsLocalize = false;
// Check for vscode.l10n.t()
if (callee.type === 'MemberExpression') {
const object = callee.object;
const property = callee.property;
// vscode.l10n.t
if (object.type === 'MemberExpression') {
const outerObject = object.object;
const outerProperty = object.property;
if (outerObject.type === 'Identifier' && outerObject.name === 'vscode' &&
outerProperty.type === 'Identifier' && outerProperty.name === 'l10n' &&
property.type === 'Identifier' && property.name === 't') {
isLocalizationCall = true;
}
}
// l10n.t or nls.localize or any *.localize
if (object.type === 'Identifier' && property.type === 'Identifier') {
if (object.name === 'l10n' && property.name === 't') {
isLocalizationCall = true;
} else if (property.name === 'localize') {
isLocalizationCall = true;
isNlsLocalize = true;
}
}
}
if (!isLocalizationCall) {
return;
}
// For vscode.l10n.t(message, ...args) - check the first argument (message)
// For nls.localize(key, message, ...args) - check first two arguments (key and message)
const argsToCheck = isNlsLocalize ? 2 : 1;
for (let i = 0; i < argsToCheck && i < node.arguments.length; i++) {
const arg = node.arguments[i];
if (arg && arg.type === 'TemplateLiteral' && arg.expressions.length > 0) {
context.report({
node: arg,
messageId: 'noTemplateLiteral'
});
}
}
}
return {
CallExpression: (node: any) => checkCallExpression(node as TSESTree.CallExpression)
};
}
};