Skip to content

Commit

Permalink
feat: detect function-style rules exported using a variable (#235)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish authored Dec 14, 2021
1 parent c4fdffe commit e388a3a
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 6 deletions.
22 changes: 16 additions & 6 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ function getRuleExportsESM(ast, scopeManager) {
INTERESTING_RULE_KEYS
);
} else if (node.type === 'Identifier') {
// Rule could be stored in a variable before being exported.
const possibleRule = findVariableValue(node, scopeManager);
if (possibleRule) {
if (possibleRule.type === 'ObjectExpression') {
Expand All @@ -149,6 +150,9 @@ function getRuleExportsESM(ast, scopeManager) {
possibleRule.properties,
INTERESTING_RULE_KEYS
);
} else if (isFunctionRule(possibleRule)) {
// Check `const possibleRule = function(context) { return { ... } }; export default possibleRule;`
return { create: possibleRule, meta: null, isNewStyle: false };
} else if (isTypeScriptRuleHelper(possibleRule)) {
// Check `const possibleRule = someTypeScriptHelper({ ... }); export default possibleRule;
return collectInterestingProperties(
Expand Down Expand Up @@ -197,13 +201,19 @@ function getRuleExportsCJS(ast, scopeManager) {
INTERESTING_RULE_KEYS
);
} else if (node.right.type === 'Identifier') {
// Rule could be stored in a variable before being exported.
const possibleRule = findVariableValue(node.right, scopeManager);
if (possibleRule && possibleRule.type === 'ObjectExpression') {
// Check `const possibleRule = { ... }; module.exports = possibleRule;
return collectInterestingProperties(
possibleRule.properties,
INTERESTING_RULE_KEYS
);
if (possibleRule) {
if (possibleRule.type === 'ObjectExpression') {
// Check `const possibleRule = { ... }; module.exports = possibleRule;
return collectInterestingProperties(
possibleRule.properties,
INTERESTING_RULE_KEYS
);
} else if (isFunctionRule(possibleRule)) {
// Check `const possibleRule = function(context) { return { ... } }; module.exports = possibleRule;`
return { create: possibleRule, meta: null, isNewStyle: false };
}
}
}
return {};
Expand Down
13 changes: 13 additions & 0 deletions tests/lib/rules/prefer-object-rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ ruleTester.run('prefer-object-rule', rule, {
`,
errors: [{ messageId: 'preferObject', line: 2, column: 26 }],
},
{
code: 'const rule = (context) => { return {}; }; module.exports = rule;',
output:
'const rule = {create: (context) => { return {}; }}; module.exports = rule;',
errors: [{ messageId: 'preferObject', line: 1, column: 14 }],
},

// ESM
{
Expand Down Expand Up @@ -149,5 +155,12 @@ ruleTester.run('prefer-object-rule', rule, {
parserOptions: { sourceType: 'module' },
errors: [{ messageId: 'preferObject', line: 1, column: 16 }],
},
{
code: 'const rule = (context) => { return {}; }; export default rule;',
output:
'const rule = {create: (context) => { return {}; }}; export default rule;',
parserOptions: { sourceType: 'module' },
errors: [{ messageId: 'preferObject', line: 1, column: 14 }],
},
],
});
30 changes: 30 additions & 0 deletions tests/lib/rules/require-meta-docs-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -1012,5 +1012,35 @@ url: "plugin-name/test.md"
],
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
},
{
// Function rule in variable.
filename: 'test.js',
code: `const rule = function(context) { return {}; }; module.exports = rule;`,
output: null,
options: [{ pattern: 'plugin-name/{{ name }}.md' }],
errors: [
{
message: '`meta.docs.url` property is missing.',
type: 'FunctionExpression',
},
],
},
{
// Object rule in variable.
filename: 'test.js',
code: `const rule = { create: function(context) { return {}; }, meta: {} }; module.exports = rule;`,
output: `const rule = { create: function(context) { return {}; }, meta: {
docs: {
url: "plugin-name/test.md"
}
} }; module.exports = rule;`,
options: [{ pattern: 'plugin-name/{{ name }}.md' }],
errors: [
{
message: '`meta.docs.url` property is missing.',
type: 'ObjectExpression',
},
],
},
],
});
13 changes: 13 additions & 0 deletions tests/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ describe('utils', () => {
'export default { foo: {} }',
'const foo = {}; export default foo',
'const foo = 123; export default foo',
'const foo = function(){}; export default foo',

// Exports function but not default export.
'export function foo (context) { return {}; }',
Expand Down Expand Up @@ -116,6 +117,8 @@ describe('utils', () => {
'export default foo.bar<Options, MessageIds>(123);',
'export default foo.bar()<Options, MessageIds>(123);',
'const notRule = foo(); export default notRule;',
'const notRule = function(){}; export default notRule;',
'const notRule = {}; export default notRule;',
].forEach((noRuleCase) => {
it(`returns null for ${noRuleCase}`, () => {
const ast = typescriptEslintParser.parse(noRuleCase, {
Expand Down Expand Up @@ -347,6 +350,11 @@ describe('utils', () => {
meta: { type: 'ObjectExpression' },
isNewStyle: true,
},
'const rule = function(context) {return{};}; module.exports = rule;': {
create: { type: 'FunctionExpression' },
meta: null,
isNewStyle: false,
},
};

Object.keys(CASES).forEach((ruleSource) => {
Expand Down Expand Up @@ -421,6 +429,11 @@ describe('utils', () => {
meta: null,
isNewStyle: false,
},
'const rule = function(context) {return {};}; export default rule;': {
create: { type: 'FunctionExpression' },
meta: null,
isNewStyle: false,
},
};

Object.keys(CASES).forEach((ruleSource) => {
Expand Down

0 comments on commit e388a3a

Please sign in to comment.