fix argument/parameter checking
This commit is contained in:
parent
dba3277200
commit
30d53de356
2 changed files with 69 additions and 11 deletions
|
@ -50,6 +50,36 @@ function findCallExpression(node) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function areArgumentsOneObject(node) {
|
||||||
|
return node.arguments.length === 1 &&
|
||||||
|
node.arguments[0].type === 'ObjectExpression';
|
||||||
|
}
|
||||||
|
|
||||||
|
// only call if `areArgumentsOneObject(node)` is true
|
||||||
|
function getArgumentObjectProperties(node) {
|
||||||
|
return new Set(node.arguments[0].properties.map(
|
||||||
|
p => {
|
||||||
|
if (p.key && p.key.type === 'Identifier') return p.key.name;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTranslationParameters(translation) {
|
||||||
|
return new Set(Array.from(translation.matchAll(/\{(\w+)\}/g)).map( m => m[1] ));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDifference(a,b) {
|
||||||
|
const result = [];
|
||||||
|
for (const element of a.values()) {
|
||||||
|
if (!b.has(element)) {
|
||||||
|
result.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/* the actual rule body
|
/* the actual rule body
|
||||||
*/
|
*/
|
||||||
function theRule(context) {
|
function theRule(context) {
|
||||||
|
@ -73,8 +103,8 @@ function theRule(context) {
|
||||||
const pathStr = `i18n.${method}.${path.join('.')}`;
|
const pathStr = `i18n.${method}.${path.join('.')}`;
|
||||||
|
|
||||||
// does that path point to a real translation?
|
// does that path point to a real translation?
|
||||||
const matchingNode = walkDown(locale, path);
|
const translation = walkDown(locale, path);
|
||||||
if (!matchingNode) {
|
if (!translation) {
|
||||||
context.report({
|
context.report({
|
||||||
node,
|
node,
|
||||||
message: `translation missing for ${pathStr}`,
|
message: `translation missing for ${pathStr}`,
|
||||||
|
@ -84,7 +114,7 @@ function theRule(context) {
|
||||||
|
|
||||||
// some more checks on how the translation is called
|
// some more checks on how the translation is called
|
||||||
if (method == 'ts') {
|
if (method == 'ts') {
|
||||||
if (matchingNode.match(/\{/)) {
|
if (translation.match(/\{/)) {
|
||||||
context.report({
|
context.report({
|
||||||
node,
|
node,
|
||||||
message: `translation for ${pathStr} is parametric, but called via 'ts'`,
|
message: `translation for ${pathStr} is parametric, but called via 'ts'`,
|
||||||
|
@ -101,7 +131,7 @@ function theRule(context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method == 'tsx') {
|
if (method == 'tsx') {
|
||||||
if (!matchingNode.match(/\{/)) {
|
if (!translation.match(/\{/)) {
|
||||||
context.report({
|
context.report({
|
||||||
node,
|
node,
|
||||||
message: `translation for ${pathStr} is not parametric, but called via 'tsx'`,
|
message: `translation for ${pathStr} is not parametric, but called via 'tsx'`,
|
||||||
|
@ -110,7 +140,6 @@ function theRule(context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const callExpression = findCallExpression(node);
|
const callExpression = findCallExpression(node);
|
||||||
|
|
||||||
if (!callExpression) {
|
if (!callExpression) {
|
||||||
context.report({
|
context.report({
|
||||||
node,
|
node,
|
||||||
|
@ -119,14 +148,42 @@ function theRule(context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameterCount = [...matchingNode.matchAll(/\{/g)].length ?? 0;
|
if (!areArgumentsOneObject(callExpression)) {
|
||||||
const argumentCount = callExpression.arguments.length;
|
context.report({
|
||||||
|
node,
|
||||||
|
message: `translation for ${pathStr} should be called with a single object as argument`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translationParameters = getTranslationParameters(translation);
|
||||||
|
const parameterCount = translationParameters.size;
|
||||||
|
const callArguments = getArgumentObjectProperties(callExpression);
|
||||||
|
const argumentCount = callArguments.size;
|
||||||
|
|
||||||
if (parameterCount !== argumentCount) {
|
if (parameterCount !== argumentCount) {
|
||||||
context.report({
|
context.report({
|
||||||
node,
|
node,
|
||||||
message: `translation for ${pathStr} has ${parameterCount} parameters, but is called with ${argumentCount} arguments`,
|
message: `translation for ${pathStr} has ${parameterCount} parameters, but is called with ${argumentCount} arguments`,
|
||||||
});
|
});
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
// node 20 doesn't have `Set.difference`...
|
||||||
|
const extraArguments = setDifference(callArguments, translationParameters);
|
||||||
|
const missingArguments = setDifference(translationParameters, callArguments);
|
||||||
|
|
||||||
|
if (extraArguments.length > 0) {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message: `translation for ${pathStr} passes unused arguments ${extraArguments.join(' ')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingArguments.length > 0) {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message: `translation for ${pathStr} does not pass arguments ${missingArguments.join(' ')}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@ ruleTester.run(
|
||||||
valid: [
|
valid: [
|
||||||
{code: 'i18n.ts.foo.bar', options: [locale] },
|
{code: 'i18n.ts.foo.bar', options: [locale] },
|
||||||
{code: 'i18n.ts.top', options: [locale] },
|
{code: 'i18n.ts.top', options: [locale] },
|
||||||
{code: 'i18n.tsx.foo.baz(1)', options: [locale] },
|
{code: 'i18n.tsx.foo.baz({x:1})', options: [locale] },
|
||||||
{code: 'whatever.i18n.ts.blah.blah', options: [locale] },
|
{code: 'whatever.i18n.ts.blah.blah', options: [locale] },
|
||||||
{code: 'whatever.i18n.tsx.does.not.matter', options: [locale] },
|
{code: 'whatever.i18n.tsx.does.not.matter', options: [locale] },
|
||||||
{code: 'whatever(i18n.ts.foo.bar)', options: [locale] },
|
{code: 'whatever(i18n.ts.foo.bar)', options: [locale] },
|
||||||
|
@ -20,10 +20,11 @@ ruleTester.run(
|
||||||
invalid: [
|
invalid: [
|
||||||
{code: 'i18n.ts.not', options: [locale], errors: 1 },
|
{code: 'i18n.ts.not', options: [locale], errors: 1 },
|
||||||
{code: 'i18n.tsx.deep.not', options: [locale], errors: 1 },
|
{code: 'i18n.tsx.deep.not', options: [locale], errors: 1 },
|
||||||
{code: 'i18n.tsx.deep.not(12)', options: [locale], errors: 1 },
|
{code: 'i18n.tsx.deep.not({x:12})', options: [locale], errors: 1 },
|
||||||
{code: 'i18n.tsx.top(1)', options: [locale], errors: 1 },
|
{code: 'i18n.tsx.top({x:1})', options: [locale], errors: 1 },
|
||||||
{code: 'i18n.ts.foo.baz', options: [locale], errors: 1 },
|
{code: 'i18n.ts.foo.baz', options: [locale], errors: 1 },
|
||||||
{code: 'i18n.tsx.foo.baz', options: [locale], errors: 1 },
|
{code: 'i18n.tsx.foo.baz', options: [locale], errors: 1 },
|
||||||
|
{code: 'i18n.tsx.foo.baz({y:2})', options: [locale], errors: 2 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue