Skip to content

Commit

Permalink
Breaking: Support ESM rules
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed Sep 22, 2021
1 parent 30bb8e2 commit 084fcef
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 8 deletions.
2 changes: 1 addition & 1 deletion lib/rules/prefer-object-rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module.exports = {
// note - we intentionally don't worry about formatting here, as otherwise we have
// to indent the function correctly

if (ruleInfo.create.type === 'FunctionExpression') {
if (ruleInfo.create.type === 'FunctionExpression' || ruleInfo.create.type === 'FunctionDeclaration') {
const openParenToken = sourceCode.getFirstToken(
ruleInfo.create,
token => token.type === 'Punctuator' && token.value === '('
Expand Down
25 changes: 24 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,28 @@ module.exports = {
let exportsVarOverridden = false;
let exportsIsFunction = false;

const exportNodes = ast.body
// ESM
const exportNodesESM = ast.body
.filter(statement => statement.type === 'ExportDefaultDeclaration')
.map(statement => statement.declaration)
.reduce((currentExports, node) => {
if (node.type === 'ObjectExpression') {
return node.properties.reduce((parsedProps, prop) => {
const keyValue = module.exports.getKeyName(prop);
if (INTERESTING_KEYS.has(keyValue)) {
parsedProps[keyValue] = prop.value;
}
return parsedProps;
}, {});
} else if (isNormalFunctionExpression(node)) {
exportsIsFunction = true;
return { create: node, meta: null };
}
return currentExports;
}, null);

// CJS
const exportNodesCJS = ast.body
.filter(statement => statement.type === 'ExpressionStatement')
.map(statement => statement.expression)
.filter(expression => expression.type === 'AssignmentExpression')
Expand Down Expand Up @@ -150,6 +171,8 @@ module.exports = {
return currentExports;
}, {});

const exportNodes = exportNodesESM || exportNodesCJS;

const createExists = Object.prototype.hasOwnProperty.call(exportNodes, 'create');
if (!createExists) {
return null;
Expand Down
23 changes: 23 additions & 0 deletions tests/lib/rules/prefer-message-ids.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ ruleTester.run('prefer-message-ids', rule, {
}
};
`,
{
// ESM
code: `
export default {
create(context) {
context.report({ node, messageId: 'foo' });
}
};
`,
parserOptions: { sourceType: 'module' },
},
`
module.exports = {
create(context) {
Expand Down Expand Up @@ -91,6 +102,18 @@ ruleTester.run('prefer-message-ids', rule, {
`,
errors: [{ messageId: 'foundMessage', type: 'Property' }],
},
{
// ESM
code: `
export default {
create(context) {
context.report({ node, message: 'foo' });
}
};
`,
parserOptions: { sourceType: 'module' },
errors: [{ messageId: 'foundMessage', type: 'Property' }],
},
{
// With message in variable.
code: `
Expand Down
48 changes: 43 additions & 5 deletions tests/lib/rules/prefer-object-rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
const rule = require('../../../lib/rules/prefer-object-rule');
const RuleTester = require('eslint').RuleTester;

const ERROR = { messageId: 'preferObject', line: 2, column: 26 };

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -63,6 +61,18 @@ ruleTester.run('prefer-object-rule', rule, {
};
module.exports = rule;
`,

{
// ESM
code: `
export default {
create(context) {
return { Program() { context.report() } };
},
};
`,
parserOptions: { sourceType: 'module' },
},
],

invalid: [
Expand All @@ -77,7 +87,7 @@ ruleTester.run('prefer-object-rule', rule, {
return { Program() { context.report() } };
}};
`,
errors: [ERROR],
errors: [{ messageId: 'preferObject', line: 2, column: 26 }],
},
{
code: `
Expand All @@ -90,7 +100,7 @@ ruleTester.run('prefer-object-rule', rule, {
return { Program() { context.report() } };
}};
`,
errors: [ERROR],
errors: [{ messageId: 'preferObject', line: 2, column: 26 }],
},
{
code: `
Expand All @@ -103,7 +113,35 @@ ruleTester.run('prefer-object-rule', rule, {
return { Program() { context.report() } };
}};
`,
errors: [ERROR],
errors: [{ messageId: 'preferObject', line: 2, column: 26 }],
},

// ESM
{
code: `
export default function (context) {
return { Program() { context.report() } };
};
`,
output: `
export default {create(context) {
return { Program() { context.report() } };
}};
`,
parserOptions: { sourceType: 'module' },
errors: [{ messageId: 'preferObject', line: 2, column: 24 }],
},
{
code: 'export default function create() {};',
output: 'export default {create() {}};',
parserOptions: { sourceType: 'module' },
errors: [{ messageId: 'preferObject', line: 1, column: 16 }],
},
{
code: 'export default () => {};',
output: 'export default {create: () => {}};',
parserOptions: { sourceType: 'module' },
errors: [{ messageId: 'preferObject', line: 1, column: 16 }],
},
],
});
22 changes: 22 additions & 0 deletions tests/lib/rules/require-meta-docs-description.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ ruleTester.run('require-meta-docs-description', rule, {
create(context) {}
};
`,
{
// ESM
code: `
export default {
meta: { docs: { description: 'disallow unused variables' } },
create(context) {}
};
`,
parserOptions: { sourceType: 'module' },
},
`
module.exports = {
meta: { docs: { description: 'enforce a maximum line length' } },
Expand Down Expand Up @@ -104,6 +114,18 @@ ruleTester.run('require-meta-docs-description', rule, {
output: null,
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
},
{
// ESM
code: `
export default {
meta: {},
create(context) {}
};
`,
output: null,
parserOptions: { sourceType: 'module' },
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
},
{
code: `
module.exports = {
Expand Down
41 changes: 40 additions & 1 deletion tests/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,23 @@ describe('utils', () => {
});
});

describe('the file does not have a valid rule (ESM)', () => {
[
'',
'export const foo = { create() {} }',
'export default { foo: {} }',
'const foo = {}; export default foo',
].forEach(noRuleCase => {
it(`returns null for ${noRuleCase}`, () => {
const ast = espree.parse(noRuleCase, { ecmaVersion: 8, range: true, sourceType: 'module' });
assert.isNull(utils.getRuleInfo({ ast }), 'Expected no rule to be found');
});
});
});

describe('the file has a valid rule', () => {
const CASES = {
// CJS
'module.exports = { create: function foo() {} };': {
create: { type: 'FunctionExpression', id: { name: 'foo' } }, // (This property will actually contain the AST node.)
meta: null,
Expand Down Expand Up @@ -110,11 +125,35 @@ describe('utils', () => {
meta: null,
isNewStyle: false,
},

// ESM (object style)
'export default { create() {} }': {
create: { type: 'FunctionExpression' },
meta: null,
isNewStyle: true,
},
'export default { create() {}, meta: {} }': {
create: { type: 'FunctionExpression' },
meta: { type: 'ObjectExpression' },
isNewStyle: true,
},

// ESM (function style)
'export default function () {}': {
create: { type: 'FunctionDeclaration' },
meta: null,
isNewStyle: false,
},
'export default () => {}': {
create: { type: 'ArrowFunctionExpression' },
meta: null,
isNewStyle: false,
},
};

Object.keys(CASES).forEach(ruleSource => {
it(ruleSource, () => {
const ast = espree.parse(ruleSource, { ecmaVersion: 6, range: true });
const ast = espree.parse(ruleSource, { ecmaVersion: 6, range: true, sourceType: ruleSource.includes('export default') ? 'module' : 'script' });
const ruleInfo = utils.getRuleInfo({ ast });
assert(
lodash.isMatch(ruleInfo, CASES[ruleSource]),
Expand Down

0 comments on commit 084fcef

Please sign in to comment.