deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
149
dev-packages/private-eslint-plugin/rules/localization-check.js
Normal file
149
dev-packages/private-eslint-plugin/rules/localization-check.js
Normal file
@@ -0,0 +1,149 @@
|
||||
// @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;
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user