Skip to content

Commit e388a3a

Browse files
authored
feat: detect function-style rules exported using a variable (#235)
1 parent c4fdffe commit e388a3a

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

Diff for: lib/utils.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ function getRuleExportsESM(ast, scopeManager) {
141141
INTERESTING_RULE_KEYS
142142
);
143143
} else if (node.type === 'Identifier') {
144+
// Rule could be stored in a variable before being exported.
144145
const possibleRule = findVariableValue(node, scopeManager);
145146
if (possibleRule) {
146147
if (possibleRule.type === 'ObjectExpression') {
@@ -149,6 +150,9 @@ function getRuleExportsESM(ast, scopeManager) {
149150
possibleRule.properties,
150151
INTERESTING_RULE_KEYS
151152
);
153+
} else if (isFunctionRule(possibleRule)) {
154+
// Check `const possibleRule = function(context) { return { ... } }; export default possibleRule;`
155+
return { create: possibleRule, meta: null, isNewStyle: false };
152156
} else if (isTypeScriptRuleHelper(possibleRule)) {
153157
// Check `const possibleRule = someTypeScriptHelper({ ... }); export default possibleRule;
154158
return collectInterestingProperties(
@@ -197,13 +201,19 @@ function getRuleExportsCJS(ast, scopeManager) {
197201
INTERESTING_RULE_KEYS
198202
);
199203
} else if (node.right.type === 'Identifier') {
204+
// Rule could be stored in a variable before being exported.
200205
const possibleRule = findVariableValue(node.right, scopeManager);
201-
if (possibleRule && possibleRule.type === 'ObjectExpression') {
202-
// Check `const possibleRule = { ... }; module.exports = possibleRule;
203-
return collectInterestingProperties(
204-
possibleRule.properties,
205-
INTERESTING_RULE_KEYS
206-
);
206+
if (possibleRule) {
207+
if (possibleRule.type === 'ObjectExpression') {
208+
// Check `const possibleRule = { ... }; module.exports = possibleRule;
209+
return collectInterestingProperties(
210+
possibleRule.properties,
211+
INTERESTING_RULE_KEYS
212+
);
213+
} else if (isFunctionRule(possibleRule)) {
214+
// Check `const possibleRule = function(context) { return { ... } }; module.exports = possibleRule;`
215+
return { create: possibleRule, meta: null, isNewStyle: false };
216+
}
207217
}
208218
}
209219
return {};

Diff for: tests/lib/rules/prefer-object-rule.js

+13
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ ruleTester.run('prefer-object-rule', rule, {
121121
`,
122122
errors: [{ messageId: 'preferObject', line: 2, column: 26 }],
123123
},
124+
{
125+
code: 'const rule = (context) => { return {}; }; module.exports = rule;',
126+
output:
127+
'const rule = {create: (context) => { return {}; }}; module.exports = rule;',
128+
errors: [{ messageId: 'preferObject', line: 1, column: 14 }],
129+
},
124130

125131
// ESM
126132
{
@@ -149,5 +155,12 @@ ruleTester.run('prefer-object-rule', rule, {
149155
parserOptions: { sourceType: 'module' },
150156
errors: [{ messageId: 'preferObject', line: 1, column: 16 }],
151157
},
158+
{
159+
code: 'const rule = (context) => { return {}; }; export default rule;',
160+
output:
161+
'const rule = {create: (context) => { return {}; }}; export default rule;',
162+
parserOptions: { sourceType: 'module' },
163+
errors: [{ messageId: 'preferObject', line: 1, column: 14 }],
164+
},
152165
],
153166
});

Diff for: tests/lib/rules/require-meta-docs-url.js

+30
Original file line numberDiff line numberDiff line change
@@ -1012,5 +1012,35 @@ url: "plugin-name/test.md"
10121012
],
10131013
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
10141014
},
1015+
{
1016+
// Function rule in variable.
1017+
filename: 'test.js',
1018+
code: `const rule = function(context) { return {}; }; module.exports = rule;`,
1019+
output: null,
1020+
options: [{ pattern: 'plugin-name/{{ name }}.md' }],
1021+
errors: [
1022+
{
1023+
message: '`meta.docs.url` property is missing.',
1024+
type: 'FunctionExpression',
1025+
},
1026+
],
1027+
},
1028+
{
1029+
// Object rule in variable.
1030+
filename: 'test.js',
1031+
code: `const rule = { create: function(context) { return {}; }, meta: {} }; module.exports = rule;`,
1032+
output: `const rule = { create: function(context) { return {}; }, meta: {
1033+
docs: {
1034+
url: "plugin-name/test.md"
1035+
}
1036+
} }; module.exports = rule;`,
1037+
options: [{ pattern: 'plugin-name/{{ name }}.md' }],
1038+
errors: [
1039+
{
1040+
message: '`meta.docs.url` property is missing.',
1041+
type: 'ObjectExpression',
1042+
},
1043+
],
1044+
},
10151045
],
10161046
});

Diff for: tests/lib/utils.js

+13
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ describe('utils', () => {
6969
'export default { foo: {} }',
7070
'const foo = {}; export default foo',
7171
'const foo = 123; export default foo',
72+
'const foo = function(){}; export default foo',
7273

7374
// Exports function but not default export.
7475
'export function foo (context) { return {}; }',
@@ -116,6 +117,8 @@ describe('utils', () => {
116117
'export default foo.bar<Options, MessageIds>(123);',
117118
'export default foo.bar()<Options, MessageIds>(123);',
118119
'const notRule = foo(); export default notRule;',
120+
'const notRule = function(){}; export default notRule;',
121+
'const notRule = {}; export default notRule;',
119122
].forEach((noRuleCase) => {
120123
it(`returns null for ${noRuleCase}`, () => {
121124
const ast = typescriptEslintParser.parse(noRuleCase, {
@@ -347,6 +350,11 @@ describe('utils', () => {
347350
meta: { type: 'ObjectExpression' },
348351
isNewStyle: true,
349352
},
353+
'const rule = function(context) {return{};}; module.exports = rule;': {
354+
create: { type: 'FunctionExpression' },
355+
meta: null,
356+
isNewStyle: false,
357+
},
350358
};
351359

352360
Object.keys(CASES).forEach((ruleSource) => {
@@ -421,6 +429,11 @@ describe('utils', () => {
421429
meta: null,
422430
isNewStyle: false,
423431
},
432+
'const rule = function(context) {return {};}; export default rule;': {
433+
create: { type: 'FunctionExpression' },
434+
meta: null,
435+
isNewStyle: false,
436+
},
424437
};
425438

426439
Object.keys(CASES).forEach((ruleSource) => {

0 commit comments

Comments
 (0)