Skip to content

Commit 084fcef

Browse files
committed
Breaking: Support ESM rules
1 parent 30bb8e2 commit 084fcef

File tree

6 files changed

+153
-8
lines changed

6 files changed

+153
-8
lines changed

lib/rules/prefer-object-rule.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ module.exports = {
4646
// note - we intentionally don't worry about formatting here, as otherwise we have
4747
// to indent the function correctly
4848

49-
if (ruleInfo.create.type === 'FunctionExpression') {
49+
if (ruleInfo.create.type === 'FunctionExpression' || ruleInfo.create.type === 'FunctionDeclaration') {
5050
const openParenToken = sourceCode.getFirstToken(
5151
ruleInfo.create,
5252
token => token.type === 'Punctuator' && token.value === '('

lib/utils.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,28 @@ module.exports = {
9898
let exportsVarOverridden = false;
9999
let exportsIsFunction = false;
100100

101-
const exportNodes = ast.body
101+
// ESM
102+
const exportNodesESM = ast.body
103+
.filter(statement => statement.type === 'ExportDefaultDeclaration')
104+
.map(statement => statement.declaration)
105+
.reduce((currentExports, node) => {
106+
if (node.type === 'ObjectExpression') {
107+
return node.properties.reduce((parsedProps, prop) => {
108+
const keyValue = module.exports.getKeyName(prop);
109+
if (INTERESTING_KEYS.has(keyValue)) {
110+
parsedProps[keyValue] = prop.value;
111+
}
112+
return parsedProps;
113+
}, {});
114+
} else if (isNormalFunctionExpression(node)) {
115+
exportsIsFunction = true;
116+
return { create: node, meta: null };
117+
}
118+
return currentExports;
119+
}, null);
120+
121+
// CJS
122+
const exportNodesCJS = ast.body
102123
.filter(statement => statement.type === 'ExpressionStatement')
103124
.map(statement => statement.expression)
104125
.filter(expression => expression.type === 'AssignmentExpression')
@@ -150,6 +171,8 @@ module.exports = {
150171
return currentExports;
151172
}, {});
152173

174+
const exportNodes = exportNodesESM || exportNodesCJS;
175+
153176
const createExists = Object.prototype.hasOwnProperty.call(exportNodes, 'create');
154177
if (!createExists) {
155178
return null;

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

+23
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ ruleTester.run('prefer-message-ids', rule, {
2929
}
3030
};
3131
`,
32+
{
33+
// ESM
34+
code: `
35+
export default {
36+
create(context) {
37+
context.report({ node, messageId: 'foo' });
38+
}
39+
};
40+
`,
41+
parserOptions: { sourceType: 'module' },
42+
},
3243
`
3344
module.exports = {
3445
create(context) {
@@ -91,6 +102,18 @@ ruleTester.run('prefer-message-ids', rule, {
91102
`,
92103
errors: [{ messageId: 'foundMessage', type: 'Property' }],
93104
},
105+
{
106+
// ESM
107+
code: `
108+
export default {
109+
create(context) {
110+
context.report({ node, message: 'foo' });
111+
}
112+
};
113+
`,
114+
parserOptions: { sourceType: 'module' },
115+
errors: [{ messageId: 'foundMessage', type: 'Property' }],
116+
},
94117
{
95118
// With message in variable.
96119
code: `

tests/lib/rules/prefer-object-rule.js

+43-5
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
const rule = require('../../../lib/rules/prefer-object-rule');
1212
const RuleTester = require('eslint').RuleTester;
1313

14-
const ERROR = { messageId: 'preferObject', line: 2, column: 26 };
15-
1614
// ------------------------------------------------------------------------------
1715
// Tests
1816
// ------------------------------------------------------------------------------
@@ -63,6 +61,18 @@ ruleTester.run('prefer-object-rule', rule, {
6361
};
6462
module.exports = rule;
6563
`,
64+
65+
{
66+
// ESM
67+
code: `
68+
export default {
69+
create(context) {
70+
return { Program() { context.report() } };
71+
},
72+
};
73+
`,
74+
parserOptions: { sourceType: 'module' },
75+
},
6676
],
6777

6878
invalid: [
@@ -77,7 +87,7 @@ ruleTester.run('prefer-object-rule', rule, {
7787
return { Program() { context.report() } };
7888
}};
7989
`,
80-
errors: [ERROR],
90+
errors: [{ messageId: 'preferObject', line: 2, column: 26 }],
8191
},
8292
{
8393
code: `
@@ -90,7 +100,7 @@ ruleTester.run('prefer-object-rule', rule, {
90100
return { Program() { context.report() } };
91101
}};
92102
`,
93-
errors: [ERROR],
103+
errors: [{ messageId: 'preferObject', line: 2, column: 26 }],
94104
},
95105
{
96106
code: `
@@ -103,7 +113,35 @@ ruleTester.run('prefer-object-rule', rule, {
103113
return { Program() { context.report() } };
104114
}};
105115
`,
106-
errors: [ERROR],
116+
errors: [{ messageId: 'preferObject', line: 2, column: 26 }],
117+
},
118+
119+
// ESM
120+
{
121+
code: `
122+
export default function (context) {
123+
return { Program() { context.report() } };
124+
};
125+
`,
126+
output: `
127+
export default {create(context) {
128+
return { Program() { context.report() } };
129+
}};
130+
`,
131+
parserOptions: { sourceType: 'module' },
132+
errors: [{ messageId: 'preferObject', line: 2, column: 24 }],
133+
},
134+
{
135+
code: 'export default function create() {};',
136+
output: 'export default {create() {}};',
137+
parserOptions: { sourceType: 'module' },
138+
errors: [{ messageId: 'preferObject', line: 1, column: 16 }],
139+
},
140+
{
141+
code: 'export default () => {};',
142+
output: 'export default {create: () => {}};',
143+
parserOptions: { sourceType: 'module' },
144+
errors: [{ messageId: 'preferObject', line: 1, column: 16 }],
107145
},
108146
],
109147
});

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

+22
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ ruleTester.run('require-meta-docs-description', rule, {
2121
create(context) {}
2222
};
2323
`,
24+
{
25+
// ESM
26+
code: `
27+
export default {
28+
meta: { docs: { description: 'disallow unused variables' } },
29+
create(context) {}
30+
};
31+
`,
32+
parserOptions: { sourceType: 'module' },
33+
},
2434
`
2535
module.exports = {
2636
meta: { docs: { description: 'enforce a maximum line length' } },
@@ -104,6 +114,18 @@ ruleTester.run('require-meta-docs-description', rule, {
104114
output: null,
105115
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
106116
},
117+
{
118+
// ESM
119+
code: `
120+
export default {
121+
meta: {},
122+
create(context) {}
123+
};
124+
`,
125+
output: null,
126+
parserOptions: { sourceType: 'module' },
127+
errors: [{ messageId: 'missing', type: 'ObjectExpression' }],
128+
},
107129
{
108130
code: `
109131
module.exports = {

tests/lib/utils.js

+40-1
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,23 @@ describe('utils', () => {
3333
});
3434
});
3535

36+
describe('the file does not have a valid rule (ESM)', () => {
37+
[
38+
'',
39+
'export const foo = { create() {} }',
40+
'export default { foo: {} }',
41+
'const foo = {}; export default foo',
42+
].forEach(noRuleCase => {
43+
it(`returns null for ${noRuleCase}`, () => {
44+
const ast = espree.parse(noRuleCase, { ecmaVersion: 8, range: true, sourceType: 'module' });
45+
assert.isNull(utils.getRuleInfo({ ast }), 'Expected no rule to be found');
46+
});
47+
});
48+
});
49+
3650
describe('the file has a valid rule', () => {
3751
const CASES = {
52+
// CJS
3853
'module.exports = { create: function foo() {} };': {
3954
create: { type: 'FunctionExpression', id: { name: 'foo' } }, // (This property will actually contain the AST node.)
4055
meta: null,
@@ -110,11 +125,35 @@ describe('utils', () => {
110125
meta: null,
111126
isNewStyle: false,
112127
},
128+
129+
// ESM (object style)
130+
'export default { create() {} }': {
131+
create: { type: 'FunctionExpression' },
132+
meta: null,
133+
isNewStyle: true,
134+
},
135+
'export default { create() {}, meta: {} }': {
136+
create: { type: 'FunctionExpression' },
137+
meta: { type: 'ObjectExpression' },
138+
isNewStyle: true,
139+
},
140+
141+
// ESM (function style)
142+
'export default function () {}': {
143+
create: { type: 'FunctionDeclaration' },
144+
meta: null,
145+
isNewStyle: false,
146+
},
147+
'export default () => {}': {
148+
create: { type: 'ArrowFunctionExpression' },
149+
meta: null,
150+
isNewStyle: false,
151+
},
113152
};
114153

115154
Object.keys(CASES).forEach(ruleSource => {
116155
it(ruleSource, () => {
117-
const ast = espree.parse(ruleSource, { ecmaVersion: 6, range: true });
156+
const ast = espree.parse(ruleSource, { ecmaVersion: 6, range: true, sourceType: ruleSource.includes('export default') ? 'module' : 'script' });
118157
const ruleInfo = utils.getRuleInfo({ ast });
119158
assert(
120159
lodash.isMatch(ruleInfo, CASES[ruleSource]),

0 commit comments

Comments
 (0)