Skip to content

Commit 1c30165

Browse files
authored
fix: false positives with violation reporting helper function in no-unused-message-ids rule (#290)
1 parent a1eaf30 commit 1c30165

File tree

5 files changed

+163
-2
lines changed

5 files changed

+163
-2
lines changed

Diff for: lib/rules/no-unused-message-ids.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ module.exports = {
120120

121121
if (
122122
values.length === 0 ||
123-
values.some((val) => val.type !== 'Literal')
123+
values.some((val) => val.type !== 'Literal') ||
124+
utils.isVariableFromParameter(node.value, scopeManager)
124125
) {
125126
// When a dynamic messageId is used and we can't detect its value, disable the rule to avoid false positives.
126127
hasSeenUnknownMessageId = true;

Diff for: lib/utils.js

+15
Original file line numberDiff line numberDiff line change
@@ -888,4 +888,19 @@ module.exports = {
888888
return [];
889889
});
890890
},
891+
892+
/**
893+
* Check whether a variable's definition is from a function parameter.
894+
* @param {Node} node - the Identifier node for the variable.
895+
* @param {ScopeManager} scopeManager
896+
* @returns {boolean} whether the variable comes from a function parameter
897+
*/
898+
isVariableFromParameter(node, scopeManager) {
899+
const variable = findVariable(
900+
scopeManager.acquire(node) || scopeManager.globalScope,
901+
node
902+
);
903+
904+
return variable?.defs[0]?.type === 'Parameter';
905+
},
891906
};

Diff for: tests/lib/rules/no-missing-message-ids.js

+66
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,48 @@ ruleTester.run('no-missing-message-ids', rule, {
196196
}
197197
};
198198
`,
199+
// Helper function with messageId parameter, outside rule.
200+
`
201+
function report(node, messageId) {
202+
context.report({node, messageId});
203+
}
204+
module.exports = {
205+
meta: { messages: { foo: 'hello' } },
206+
create(context) {
207+
report(node, 'foo');
208+
}
209+
};
210+
`,
211+
// Helper function with messageId parameter, inside rule, with parameter reassignment.
212+
`
213+
module.exports = {
214+
meta: { messages: { foo: 'hello', bar: 'world' } },
215+
create(context) {
216+
function report(node, messageId) {
217+
if (foo) {
218+
messageId = 'bar';
219+
}
220+
context.report({node, messageId});
221+
}
222+
report(node, 'foo');
223+
}
224+
};
225+
`,
226+
// Helper function with messageId parameter, inside rule, with missing messageId.
227+
// TODO: this should be an invalid test case because a non-existent `messageId` is used.
228+
// Eventually, we should be able to detect what values are passed to this function for its `messageId` parameter.
229+
`
230+
module.exports = {
231+
meta: { messages: { foo: 'hello' } },
232+
create(context) {
233+
function report(node, messageId) {
234+
context.report({node, messageId});
235+
}
236+
report(node, 'foo');
237+
report(node, 'bar');
238+
}
239+
};
240+
`,
199241
],
200242

201243
invalid: [
@@ -287,5 +329,29 @@ ruleTester.run('no-missing-message-ids', rule, {
287329
},
288330
],
289331
},
332+
{
333+
// Helper function with messageId parameter, inside rule, with missing messageId due to parameter reassignment.
334+
code: `
335+
module.exports = {
336+
meta: { messages: { foo: 'hello' } },
337+
create(context) {
338+
function report(node, messageId) {
339+
if (foo) {
340+
messageId = 'bar';
341+
}
342+
context.report({node, messageId});
343+
}
344+
report(node, 'foo');
345+
}
346+
};
347+
`,
348+
errors: [
349+
{
350+
messageId: 'missingMessage',
351+
data: { messageId: 'bar' },
352+
type: 'Literal',
353+
},
354+
],
355+
},
290356
],
291357
});

Diff for: tests/lib/rules/no-unused-message-ids.js

+45-1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,50 @@ ruleTester.run('no-unused-message-ids', rule, {
227227
create(context) {}
228228
};
229229
`,
230+
// Helper function messageId parameter, outside rule.
231+
`
232+
function reportFoo(node, messageId) {
233+
context.report({ node, messageId });
234+
}
235+
module.exports = {
236+
meta: { messages: { foo: 'hello', bar: 'world', baz: 'planet' } },
237+
create(context) {
238+
reportFoo(node, 'foo');
239+
reportFoo(node, 'bar');
240+
reportFoo(node, 'baz');
241+
}
242+
};
243+
`,
244+
// Helper function with messageId parameter, inside rule, parameter reassignment.
245+
`
246+
module.exports = {
247+
meta: { messages: { foo: 'hello', bar: 'world', baz: 'planet' } },
248+
create(context) {
249+
function reportFoo(node, messageId) {
250+
if (foo) {
251+
messageId = 'baz';
252+
}
253+
context.report({ node, messageId });
254+
}
255+
reportFoo(node, 'foo');
256+
reportFoo(node, 'bar');
257+
}
258+
};
259+
`,
260+
// Helper function with messageId parameter, outside rule, with an unused messageId.
261+
// TODO: this should be an invalid test case because a messageId is unused.
262+
// Eventually, we should be able to detect what values are passed to this function for its messageId parameter.
263+
`
264+
function reportFoo(node, messageId) {
265+
context.report({ node, messageId });
266+
}
267+
module.exports = {
268+
meta: { messages: { foo: 'hello', bar: 'world' } },
269+
create(context) {
270+
reportFoo(node, 'foo');
271+
}
272+
};
273+
`,
230274
],
231275

232276
invalid: [
@@ -363,7 +407,7 @@ ruleTester.run('no-unused-message-ids', rule, {
363407
context.report({ node, messageId });
364408
}
365409
module.exports = {
366-
meta: { messages: { foo: 'hello world' } },
410+
meta: { messages: { foo: 'hello world', bar: 'baz' } },
367411
create(context) {
368412
reportFoo(node);
369413
}

Diff for: tests/lib/utils.js

+35
Original file line numberDiff line numberDiff line change
@@ -1646,4 +1646,39 @@ describe('utils', () => {
16461646
);
16471647
});
16481648
});
1649+
1650+
describe('isVariableFromParameter', function () {
1651+
it('returns true for function parameter', () => {
1652+
const code =
1653+
'function myFunc(x) { if (foo) { x = "abc"; } console.log(x) }; myFunc("def");';
1654+
const ast = espree.parse(code, {
1655+
ecmaVersion: 9,
1656+
range: true,
1657+
});
1658+
1659+
const scopeManager = eslintScope.analyze(ast);
1660+
assert.ok(
1661+
utils.isVariableFromParameter(
1662+
ast.body[0].body.body[1].expression.arguments[0],
1663+
scopeManager
1664+
)
1665+
);
1666+
});
1667+
1668+
it('returns false for const variable', () => {
1669+
const code = 'const x = "abc"; console.log(x);';
1670+
const ast = espree.parse(code, {
1671+
ecmaVersion: 9,
1672+
range: true,
1673+
});
1674+
1675+
const scopeManager = eslintScope.analyze(ast);
1676+
assert.notOk(
1677+
utils.isVariableFromParameter(
1678+
ast.body[1].expression.arguments[0],
1679+
scopeManager
1680+
)
1681+
);
1682+
});
1683+
});
16491684
});

0 commit comments

Comments
 (0)