Skip to content

Commit 8711dcb

Browse files
Fix offending l10n.t call and add an eslint rule to prevent it from happening (#277577)
ref #277576
1 parent 9f56e26 commit 8711dcb

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as eslint from 'eslint';
7+
import { TSESTree } from '@typescript-eslint/utils';
8+
9+
/**
10+
* Prevents the use of template literals in localization function calls.
11+
*
12+
* vscode.l10n.t() and nls.localize() cannot handle string templating.
13+
* Use placeholders instead: vscode.l10n.t('Message {0}', value)
14+
*
15+
* Examples:
16+
* ❌ vscode.l10n.t(`Message ${value}`)
17+
* ✅ vscode.l10n.t('Message {0}', value)
18+
*
19+
* ❌ nls.localize('key', `Message ${value}`)
20+
* ✅ nls.localize('key', 'Message {0}', value)
21+
*/
22+
export default new class NoLocalizationTemplateLiterals implements eslint.Rule.RuleModule {
23+
24+
readonly meta: eslint.Rule.RuleMetaData = {
25+
messages: {
26+
noTemplateLiteral: 'Template literals cannot be used in localization calls. Use placeholders like {0}, {1} instead.'
27+
},
28+
docs: {
29+
description: 'Prevents template literals in vscode.l10n.t() and nls.localize() calls',
30+
},
31+
schema: false,
32+
};
33+
34+
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
35+
36+
function checkCallExpression(node: TSESTree.CallExpression) {
37+
const callee = node.callee;
38+
let isLocalizationCall = false;
39+
let isNlsLocalize = false;
40+
41+
// Check for vscode.l10n.t()
42+
if (callee.type === 'MemberExpression') {
43+
const object = callee.object;
44+
const property = callee.property;
45+
46+
// vscode.l10n.t
47+
if (object.type === 'MemberExpression') {
48+
const outerObject = object.object;
49+
const outerProperty = object.property;
50+
if (outerObject.type === 'Identifier' && outerObject.name === 'vscode' &&
51+
outerProperty.type === 'Identifier' && outerProperty.name === 'l10n' &&
52+
property.type === 'Identifier' && property.name === 't') {
53+
isLocalizationCall = true;
54+
}
55+
}
56+
57+
// l10n.t or nls.localize or any *.localize
58+
if (object.type === 'Identifier' && property.type === 'Identifier') {
59+
if (object.name === 'l10n' && property.name === 't') {
60+
isLocalizationCall = true;
61+
} else if (property.name === 'localize') {
62+
isLocalizationCall = true;
63+
isNlsLocalize = true;
64+
}
65+
}
66+
}
67+
68+
if (!isLocalizationCall) {
69+
return;
70+
}
71+
72+
// For vscode.l10n.t(message, ...args) - check the first argument (message)
73+
// For nls.localize(key, message, ...args) - check first two arguments (key and message)
74+
const argsToCheck = isNlsLocalize ? 2 : 1;
75+
for (let i = 0; i < argsToCheck && i < node.arguments.length; i++) {
76+
const arg = node.arguments[i];
77+
if (arg && arg.type === 'TemplateLiteral' && arg.expressions.length > 0) {
78+
context.report({
79+
node: arg,
80+
messageId: 'noTemplateLiteral'
81+
});
82+
}
83+
}
84+
}
85+
86+
return {
87+
CallExpression: (node: any) => checkCallExpression(node as TSESTree.CallExpression)
88+
};
89+
}
90+
};

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export default tseslint.config(
9090
'local/code-no-reader-after-await': 'warn',
9191
'local/code-no-observable-get-in-reactive-context': 'warn',
9292
'local/code-policy-localization-key-match': 'warn',
93+
'local/code-no-localization-template-literals': 'error',
9394
'local/code-no-deep-import-of-internal': ['error', { '.*Internal': true, 'searchExtTypesInternal': false }],
9495
'local/code-layering': [
9596
'warn',

extensions/typescript-language-features/src/languageFeatures/quickFix.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
367367
let title = action.description;
368368
if (action.fixName === fixNames.classIncorrectlyImplementsInterface) {
369369
title = vscode.l10n.t('{0} with AI', action.description);
370-
message = vscode.l10n.t(`Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`);
370+
message = vscode.l10n.t('Implement the stubbed-out class members for {0} with a useful implementation.', document.getText(diagnostic.range));
371371
expand = { kind: 'code-action', action };
372372
} else if (action.fixName === fixNames.fixClassDoesntImplementInheritedAbstractMember) {
373373
title = vscode.l10n.t('{0} with AI', action.description);

0 commit comments

Comments
 (0)