Skip to content

Commit 0534276

Browse files
committed
fix: correctly handle rules that are missing meta or have meta / create defined in variables
1 parent 0fc8c45 commit 0534276

15 files changed

+200
-38
lines changed

lib/rules/prefer-message-ids.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ module.exports = {
3838
Program (ast) {
3939
contextIdentifiers = utils.getContextIdentifiers(context, ast);
4040

41-
if (info === null || info.meta === null) {
41+
if (info === null) {
4242
return;
4343
}
4444

@@ -49,7 +49,7 @@ module.exports = {
4949
metaNode.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'messages');
5050

5151
if (!messagesNode) {
52-
context.report({ node: metaNode, messageId: 'messagesMissing' });
52+
context.report({ node: metaNode || info.create, messageId: 'messagesMissing' });
5353
return;
5454
}
5555

lib/rules/require-meta-docs-description.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = {
4444

4545
return {
4646
Program () {
47-
if (info === null || info.meta === null) {
47+
if (info === null) {
4848
return;
4949
}
5050

@@ -62,7 +62,7 @@ module.exports = {
6262
docsNode.value.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'description');
6363

6464
if (!descriptionNode) {
65-
context.report({ node: docsNode ? docsNode : metaNode, messageId: 'missing' });
65+
context.report({ node: docsNode || metaNode || info.create, messageId: 'missing' });
6666
return;
6767
}
6868

lib/rules/require-meta-has-suggestions.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ module.exports = {
7171
if (!hasSuggestionsProperty) {
7272
// Rule reports suggestions but is missing the `meta.hasSuggestions` property altogether.
7373
context.report({
74-
node: metaNode ? metaNode : ruleInfo.create,
74+
node: metaNode || ruleInfo.create,
7575
messageId: 'shouldBeSuggestable',
7676
fix (fixer) {
7777
if (metaNode && metaNode.type === 'ObjectExpression') {

lib/rules/require-meta-schema.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ module.exports = {
4141
const sourceCode = context.getSourceCode();
4242
const { scopeManager } = sourceCode;
4343
const info = utils.getRuleInfo(sourceCode);
44-
if (info === null || info.meta === null) {
44+
if (info === null) {
4545
return {};
4646
}
4747

@@ -106,13 +106,15 @@ module.exports = {
106106
'Program:exit' () {
107107
if (!schemaNode && requireSchemaPropertyWhenOptionless) {
108108
context.report({
109-
node: metaNode,
109+
node: metaNode || info.create,
110110
messageId: 'missing',
111111
suggest: isUsingOptions ? [] : [
112112
{
113113
messageId: 'addEmptySchema',
114114
fix (fixer) {
115-
return utils.insertProperty(fixer, metaNode, 'schema: []', sourceCode);
115+
if (metaNode && metaNode.type === 'ObjectExpression') {
116+
return utils.insertProperty(fixer, metaNode, 'schema: []', sourceCode);
117+
}
116118
},
117119
},
118120
],
@@ -130,7 +132,7 @@ module.exports = {
130132
node.property.name === 'options'
131133
) {
132134
isUsingOptions = true;
133-
context.report({ node: schemaNode || metaNode, messageId: 'foundOptionsUsage' });
135+
context.report({ node: schemaNode || metaNode || info.create, messageId: 'foundOptionsUsage' });
134136
}
135137
},
136138
};

lib/rules/require-meta-type.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = {
4444

4545
return {
4646
Program () {
47-
if (info === null || info.meta === null) {
47+
if (info === null) {
4848
return;
4949
}
5050

@@ -55,7 +55,7 @@ module.exports = {
5555
metaNode.properties.find(p => p.type === 'Property' && utils.getKeyName(p) === 'type');
5656

5757
if (!typeNode) {
58-
context.report({ node: metaNode, messageId: 'missing' });
58+
context.report({ node: metaNode || info.create, messageId: 'missing' });
5959
return;
6060
}
6161

lib/utils.js

+35-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { getStaticValue } = require('eslint-utils');
3+
const { getStaticValue, findVariable } = require('eslint-utils');
44
const estraverse = require('estraverse');
55

66
/**
@@ -234,6 +234,29 @@ function findObjectPropertyValueByKeyName (obj, keyName) {
234234
return property ? property.value : undefined;
235235
}
236236

237+
/**
238+
* Get the first value that a variable is initialized to.
239+
* @param {Node} node - the Identifier node for the variable.
240+
* @param {ScopeManager} scopeManager
241+
* @returns the first value that the given variable is initialized to.
242+
*/
243+
function findVariableValue (node, scopeManager) {
244+
const variable = findVariable(
245+
scopeManager.acquire(node) || scopeManager.globalScope,
246+
node
247+
);
248+
if (
249+
variable &&
250+
variable.defs &&
251+
variable.defs[0] &&
252+
variable.defs[0].node &&
253+
variable.defs[0].node.type === 'VariableDeclarator' &&
254+
variable.defs[0].node.init
255+
) {
256+
return variable.defs[0].node.init;
257+
}
258+
}
259+
237260
module.exports = {
238261
/**
239262
* Performs static analysis on an AST to try to determine the final value of `module.exports`.
@@ -258,6 +281,16 @@ module.exports = {
258281
return null;
259282
}
260283

284+
// If create/meta are defined in variables, get their values.
285+
for (const key of Object.keys(exportNodes)) {
286+
if (exportNodes[key] && exportNodes[key].type === 'Identifier') {
287+
const value = findVariableValue(exportNodes[key], scopeManager);
288+
if (value) {
289+
exportNodes[key] = value;
290+
}
291+
}
292+
}
293+
261294
return Object.assign({ isNewStyle: true, meta: null }, exportNodes);
262295
},
263296

@@ -269,8 +302,7 @@ module.exports = {
269302
* @returns {Set<ASTNode>} A Set of all `Identifier` nodes that are references to the `context` value for the file
270303
*/
271304
getContextIdentifiers (context, ast) {
272-
const ruleInfo = module.exports.getRuleInfo({ ast });
273-
305+
const ruleInfo = module.exports.getRuleInfo({ ast, context });
274306
if (!ruleInfo || ruleInfo.create.params.length === 0 || ruleInfo.create.params[0].type !== 'Identifier') {
275307
return new Set();
276308
}

tests/lib/rules/meta-property-ordering.js

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ ruleTester.run('test-case-property-ordering', rule, {
7171
meta: {},
7272
create() {},
7373
};`,
74+
'module.exports = { create() {} };', // No `meta`.
7475
],
7576

7677
invalid: [

tests/lib/rules/prefer-message-ids.js

+35
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ ruleTester.run('prefer-message-ids', rule, {
1717
valid: [
1818
`
1919
module.exports = {
20+
meta: { messages: { foo: 'hello world' } },
2021
create(context) {
2122
context.report({ node });
2223
}
2324
};
2425
`,
2526
`
2627
module.exports = {
28+
meta: { messages: { foo: 'hello world' } },
2729
create(context) {
2830
context.report({ node, messageId: 'foo' });
2931
}
@@ -32,6 +34,7 @@ ruleTester.run('prefer-message-ids', rule, {
3234
// Suggestion
3335
`
3436
module.exports = {
37+
meta: { messages: { foo: 'hello world' } },
3538
create(context) {
3639
context.report({ node, suggest: [{messageId:'foo'}] });
3740
}
@@ -41,6 +44,7 @@ ruleTester.run('prefer-message-ids', rule, {
4144
// ESM
4245
code: `
4346
export default {
47+
meta: { messages: { foo: 'hello world' } },
4448
create(context) {
4549
context.report({ node, messageId: 'foo' });
4650
}
@@ -50,13 +54,15 @@ ruleTester.run('prefer-message-ids', rule, {
5054
},
5155
`
5256
module.exports = {
57+
meta: { messages: { foo: 'hello world' } },
5358
create(context) {
5459
foo.report({ node, message: 'foo' }); // unrelated function
5560
}
5661
};
5762
`,
5863
`
5964
module.exports = {
65+
meta: { messages: { foo: 'hello world' } },
6066
create(context) {
6167
context.foo({ node, message: 'foo' }); // unrelated function
6268
}
@@ -65,6 +71,7 @@ ruleTester.run('prefer-message-ids', rule, {
6571
`
6672
context.report({ node, message: 'foo' }); // outside rule
6773
module.exports = {
74+
meta: { messages: { foo: 'hello world' } },
6875
create(context) {
6976
}
7077
};
@@ -103,6 +110,7 @@ ruleTester.run('prefer-message-ids', rule, {
103110
{
104111
code: `
105112
module.exports = {
113+
meta: { messages: { foo: 'hello world' } },
106114
create(context) {
107115
context.report({ node, message: 'foo' });
108116
}
@@ -114,6 +122,7 @@ ruleTester.run('prefer-message-ids', rule, {
114122
// Suggestion
115123
code: `
116124
module.exports = {
125+
meta: { messages: { foo: 'hello world' } },
117126
create(context) {
118127
context.report({ node, suggest: [{desc:'foo'}] });
119128
}
@@ -125,6 +134,7 @@ ruleTester.run('prefer-message-ids', rule, {
125134
// ESM
126135
code: `
127136
export default {
137+
meta: { messages: { foo: 'hello world' } },
128138
create(context) {
129139
context.report({ node, message: 'foo' });
130140
}
@@ -138,6 +148,7 @@ ruleTester.run('prefer-message-ids', rule, {
138148
code: `
139149
const MESSAGE = \`\${foo} is bad.\`;
140150
module.exports = {
151+
meta: { messages: { foo: 'hello world' } },
141152
create(context) {
142153
context.report({
143154
node,
@@ -152,6 +163,7 @@ ruleTester.run('prefer-message-ids', rule, {
152163
// With constructed message.
153164
code: `
154165
module.exports = {
166+
meta: { messages: { foo: 'hello world' } },
155167
create(context) {
156168
context.report({
157169
node,
@@ -217,5 +229,28 @@ ruleTester.run('prefer-message-ids', rule, {
217229
{ messageId: 'foundMessage', type: 'Property' },
218230
],
219231
},
232+
{
233+
// `meta` missing.
234+
code: `
235+
module.exports = {
236+
create(context) {
237+
context.report({ node });
238+
}
239+
};
240+
`,
241+
errors: [{ messageId: 'messagesMissing', type: 'FunctionExpression' }],
242+
},
243+
{
244+
// `meta` in variable, `messages` missing, using `message`.
245+
code: `
246+
const meta = {};
247+
const create = function (context) { context.report({ node, message: 'foo' }); }
248+
module.exports = { meta, create };
249+
`,
250+
errors: [
251+
{ messageId: 'messagesMissing', type: 'ObjectExpression' },
252+
{ messageId: 'foundMessage', type: 'Property' },
253+
],
254+
},
220255
],
221256
});

tests/lib/rules/require-meta-docs-description.js

+26
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ ruleTester.run('require-meta-docs-description', rule, {
101101
`,
102102
options: [{ pattern: '.+' }], // any description allowed
103103
},
104+
// `meta` in variable, `description` present.
105+
`
106+
const meta = { docs: { description: 'enforce foo' } };
107+
module.exports = {
108+
meta,
109+
create(context) {}
110+
};
111+
`,
104112
],
105113

106114
invalid: [
@@ -114,6 +122,24 @@ ruleTester.run('require-meta-docs-description', rule, {
114122
output: null,
115123
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
116124
},
125+
{
126+
// No `meta`. Violation on `create`.
127+
code: 'module.exports = { create(context) {} };',
128+
output: null,
129+
errors: [{ messageId: 'missing', type: 'FunctionExpression' }],
130+
},
131+
{
132+
// `meta` in variable, `description` mismatch.
133+
code: `
134+
const meta = { docs: { description: 'foo' } };
135+
module.exports = {
136+
meta,
137+
create(context) {}
138+
};
139+
`,
140+
output: null,
141+
errors: [{ messageId: 'mismatch', type: 'Literal' }],
142+
},
117143
{
118144
// ESM
119145
code: `

tests/lib/rules/require-meta-docs-url.js

+6
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ tester.run('require-meta-docs-url', rule, {
130130
output: null,
131131
errors: [{ messageId: 'missing', type: 'FunctionExpression' }],
132132
},
133+
{
134+
// No `meta`. Violation on `create`.
135+
code: 'module.exports = { create() {} }',
136+
output: null,
137+
errors: [{ messageId: 'missing', type: 'FunctionExpression' }],
138+
},
133139
{
134140
code: `
135141
module.exports = {

0 commit comments

Comments
 (0)