Files
mawkone 8bb5110148
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled
deploy: current vibn theia state
Made-with: Cursor
2026-02-27 12:01:08 -08:00

150 lines
7.4 KiB
JavaScript

// @ts-check
// *****************************************************************************
// Copyright (C) 2021 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
const levenshtein = require('js-levenshtein');
// eslint-disable-next-line import/no-extraneous-dependencies
const metadata = require('@theia/core/src/common/i18n/nls.metadata.json');
const messages = new Set(Object.values(metadata.messages)
.reduceRight((prev, curr) => prev.concat(curr), [])
.map(e => e.replace(/&&/g, '')));
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
fixable: 'code',
docs: {
description: 'prevent incorrect use of \'nls.localize\'.',
},
},
create(context) {
return {
CallExpression(node) {
const callee = node.callee;
if (callee.type === 'Super') {
return;
}
const localizeResults = evaluateLocalize(node);
for (const { value, byDefault, node: localizeNode } of localizeResults) {
if (value !== undefined && localizeNode) {
if (byDefault && !messages.has(value)) {
let lowestDistance = Number.MAX_VALUE;
let lowestMessage = '';
for (const message of messages) {
const distance = levenshtein(value, message);
if (distance < lowestDistance) {
lowestDistance = distance;
lowestMessage = message;
}
}
if (lowestMessage) {
const replacementValue = `'${lowestMessage.replace(/'/g, "\\'").replace(/\n/g, '\\n')}'`;
context.report({
node: localizeNode,
message: `'${value}' is not a valid default value. Did you mean ${replacementValue}?`,
fix: function (fixer) {
return fixer.replaceText(localizeNode, replacementValue);
}
});
} else {
context.report({
node: localizeNode,
message: `'${value}' is not a valid default value.`
});
}
} else if (!byDefault && messages.has(value)) {
context.report({
node,
message: `'${value}' can be translated using the 'nls.localizeByDefault' function.`,
fix: function (fixer) {
const code = context.getSourceCode();
const args = node.arguments.slice(1);
const argsCode = args.map(e => code.getText(e)).join(', ');
const updatedCall = `nls.localizeByDefault(${argsCode})`;
return fixer.replaceText(node, updatedCall);
}
});
}
}
}
}
};
/**
* Evaluates a call expression and returns localization info.
* @param {import('estree').CallExpression} node
* @returns {Array<{value?: string, byDefault: boolean, node?: import('estree').Node}>}
*/
function evaluateLocalize(/** @type {import('estree').CallExpression} */ node) {
const callee = node.callee;
if ('object' in callee && 'name' in callee.object && 'property' in callee && 'name' in callee.property && callee.object.name === 'nls') {
if (callee.property.name === 'localize') {
const defaultTextNode = node.arguments[1]; // The default text node is the second argument for `nls.localize`
if (defaultTextNode && defaultTextNode.type === 'Literal' && typeof defaultTextNode.value === 'string') {
return [{
node: defaultTextNode,
value: defaultTextNode.value,
byDefault: false
}];
}
} else if (callee.property.name === 'localizeByDefault') {
const defaultTextNode = node.arguments[0]; // The default text node is the first argument for `nls.localizeByDefault`
if (defaultTextNode && defaultTextNode.type === 'Literal' && typeof defaultTextNode.value === 'string') {
return [{
node: defaultTextNode,
value: defaultTextNode.value,
byDefault: true
}];
}
}
}
// Check for Command.toDefaultLocalizedCommand
if ('object' in callee && 'name' in callee.object && 'property' in callee && 'name' in callee.property
&& callee.object.name === 'Command' && callee.property.name === 'toDefaultLocalizedCommand') {
const commandArg = node.arguments[0];
if (commandArg && commandArg.type === 'ObjectExpression') {
return extractDefaultLocalizedProperties(commandArg);
}
}
return [];
}
/**
* Extracts label and category properties from a Command object that will be passed to localizeByDefault.
* @param {import('estree').ObjectExpression} objectNode
* @returns {Array<{value?: string, byDefault: boolean, node?: import('estree').Node}>}
*/
function extractDefaultLocalizedProperties(objectNode) {
const results = [];
for (const property of objectNode.properties) {
if (property.type === 'Property' && property.key.type === 'Identifier') {
const keyName = property.key.name;
if ((keyName === 'label' || keyName === 'category') && property.value.type === 'Literal' && typeof property.value.value === 'string') {
results.push({
node: property.value,
value: property.value.value,
byDefault: true
});
}
}
}
return results;
}
}
};