Skip to content

Commit c035216

Browse files
authoredFeb 9, 2024
Add no-anonymous-default-export rule (#2273)
1 parent d76f8a2 commit c035216

19 files changed

+2051
-12
lines changed
 

‎configs/recommended.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
'unicorn/import-style': 'error',
1515
'unicorn/new-for-builtins': 'error',
1616
'unicorn/no-abusive-eslint-disable': 'error',
17+
'unicorn/no-anonymous-default-export': 'error',
1718
'unicorn/no-array-callback-reference': 'error',
1819
'unicorn/no-array-for-each': 'error',
1920
'unicorn/no-array-method-this-argument': 'error',
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Disallow anonymous functions and classes as the default export
2+
3+
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs).
4+
5+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
6+
7+
<!-- end auto-generated rule header -->
8+
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
9+
10+
Naming default exports improves codebase searchability by ensuring consistent identifier use for a module's default export, both where it's declared and where it's imported.
11+
12+
## Fail
13+
14+
```js
15+
export default class {}
16+
```
17+
18+
```js
19+
export default function () {}
20+
```
21+
22+
```js
23+
export default () => {};
24+
```
25+
26+
```js
27+
module.exports = class {};
28+
```
29+
30+
```js
31+
module.exports = function () {};
32+
```
33+
34+
```js
35+
module.exports = () => {};
36+
```
37+
38+
## Pass
39+
40+
```js
41+
export default class Foo {}
42+
```
43+
44+
```js
45+
export default function foo () {}
46+
```
47+
48+
```js
49+
const foo = () => {};
50+
export default foo;
51+
```
52+
53+
```js
54+
module.exports = class Foo {};
55+
```
56+
57+
```js
58+
module.exports = function foo () {};
59+
```
60+
61+
```js
62+
const foo = () => {};
63+
module.exports = foo;
64+
```

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@
134134
]
135135
}
136136
],
137-
"import/order": "off"
137+
"import/order": "off",
138+
"func-names": "off"
138139
},
139140
"overrides": [
140141
{

‎readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
124124
| [import-style](docs/rules/import-style.md) | Enforce specific import styles per module. || | |
125125
| [new-for-builtins](docs/rules/new-for-builtins.md) | Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. || 🔧 | |
126126
| [no-abusive-eslint-disable](docs/rules/no-abusive-eslint-disable.md) | Enforce specifying rules to disable in `eslint-disable` comments. || | |
127+
| [no-anonymous-default-export](docs/rules/no-anonymous-default-export.md) | Disallow anonymous functions and classes as the default export. || | 💡 |
127128
| [no-array-callback-reference](docs/rules/no-array-callback-reference.md) | Prevent passing a function reference directly to iterator methods. || | 💡 |
128129
| [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `for…of` over the `forEach` method. || 🔧 | 💡 |
129130
| [no-array-method-this-argument](docs/rules/no-array-method-this-argument.md) | Disallow using the `this` argument in array methods. || 🔧 | 💡 |

‎rules/no-anonymous-default-export.js

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
'use strict';
2+
3+
const path = require('node:path');
4+
const {
5+
getFunctionHeadLocation,
6+
getFunctionNameWithKind,
7+
isOpeningParenToken,
8+
} = require('@eslint-community/eslint-utils');
9+
const {
10+
isIdentifierName,
11+
} = require('@babel/helper-validator-identifier');
12+
const getClassHeadLocation = require('./utils/get-class-head-location.js');
13+
const {upperFirst, camelCase} = require('./utils/lodash.js');
14+
const {getParenthesizedRange} = require('./utils/parentheses.js');
15+
const {
16+
getScopes,
17+
avoidCapture,
18+
} = require('./utils/index.js');
19+
const {isMemberExpression} = require('./ast/index.js');
20+
21+
const MESSAGE_ID_ERROR = 'no-anonymous-default-export/error';
22+
const MESSAGE_ID_SUGGESTION = 'no-anonymous-default-export/suggestion';
23+
const messages = {
24+
[MESSAGE_ID_ERROR]: 'The {{description}} should be named.',
25+
[MESSAGE_ID_SUGGESTION]: 'Name it as `{{name}}`.',
26+
};
27+
28+
const isClassKeywordToken = token => token.type === 'Keyword' && token.value === 'class';
29+
const isAnonymousClassOrFunction = node =>
30+
(
31+
(
32+
node.type === 'FunctionDeclaration'
33+
|| node.type === 'FunctionExpression'
34+
|| node.type === 'ClassDeclaration'
35+
|| node.type === 'ClassExpression'
36+
)
37+
&& !node.id
38+
)
39+
|| node.type === 'ArrowFunctionExpression';
40+
41+
function getSuggestionName(node, filename, sourceCode) {
42+
if (filename === '<input>' || filename === '<text>') {
43+
return;
44+
}
45+
46+
let [name] = path.basename(filename).split('.');
47+
name = camelCase(name);
48+
49+
if (!isIdentifierName(name)) {
50+
return;
51+
}
52+
53+
name = node.type === 'ClassDeclaration' ? upperFirst(name) : name;
54+
name = avoidCapture(name, getScopes(sourceCode.getScope(node)));
55+
56+
return name;
57+
}
58+
59+
function addName(fixer, node, name, sourceCode) {
60+
switch (node.type) {
61+
case 'ClassDeclaration':
62+
case 'ClassExpression': {
63+
const lastDecorator = node.decorators?.at(-1);
64+
const classToken = lastDecorator
65+
? sourceCode.getTokenAfter(lastDecorator, isClassKeywordToken)
66+
: sourceCode.getFirstToken(node, isClassKeywordToken);
67+
return fixer.insertTextAfter(classToken, ` ${name}`);
68+
}
69+
70+
case 'FunctionDeclaration':
71+
case 'FunctionExpression': {
72+
const openingParenthesisToken = sourceCode.getFirstToken(
73+
node,
74+
isOpeningParenToken,
75+
);
76+
return fixer.insertTextBefore(
77+
openingParenthesisToken,
78+
`${sourceCode.text.charAt(openingParenthesisToken.range[0] - 1) === ' ' ? '' : ' '}${name} `,
79+
);
80+
}
81+
82+
case 'ArrowFunctionExpression': {
83+
const [exportDeclarationStart, exportDeclarationEnd]
84+
= node.parent.type === 'ExportDefaultDeclaration'
85+
? node.parent.range
86+
: node.parent.parent.range;
87+
const [arrowFunctionStart, arrowFunctionEnd] = getParenthesizedRange(node, sourceCode);
88+
89+
let textBefore = sourceCode.text.slice(exportDeclarationStart, arrowFunctionStart);
90+
let textAfter = sourceCode.text.slice(arrowFunctionEnd, exportDeclarationEnd);
91+
92+
textBefore = `\n${textBefore}`;
93+
if (!/\s$/.test(textBefore)) {
94+
textBefore = `${textBefore} `;
95+
}
96+
97+
if (!textAfter.endsWith(';')) {
98+
textAfter = `${textAfter};`;
99+
}
100+
101+
return [
102+
fixer.replaceTextRange(
103+
[exportDeclarationStart, arrowFunctionStart],
104+
`const ${name} = `,
105+
),
106+
fixer.replaceTextRange(
107+
[arrowFunctionEnd, exportDeclarationEnd],
108+
';',
109+
),
110+
fixer.insertTextAfterRange(
111+
[exportDeclarationEnd, exportDeclarationEnd],
112+
`${textBefore}${name}${textAfter}`,
113+
),
114+
];
115+
}
116+
117+
// No default
118+
}
119+
}
120+
121+
function getProblem(node, context) {
122+
const {sourceCode, physicalFilename} = context;
123+
124+
const suggestionName = getSuggestionName(node, physicalFilename, sourceCode);
125+
126+
let loc;
127+
let description;
128+
if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
129+
loc = getClassHeadLocation(node, sourceCode);
130+
description = 'class';
131+
} else {
132+
loc = getFunctionHeadLocation(node, sourceCode);
133+
// [TODO: @fisker]: Ask `@eslint-community/eslint-utils` to expose `getFunctionKind`
134+
const nameWithKind = getFunctionNameWithKind(node);
135+
description = nameWithKind.replace(/ '.*?'$/, '');
136+
}
137+
138+
const problem = {
139+
node,
140+
loc,
141+
messageId: MESSAGE_ID_ERROR,
142+
data: {
143+
description,
144+
},
145+
};
146+
147+
if (!suggestionName) {
148+
return problem;
149+
}
150+
151+
problem.suggest = [
152+
{
153+
messageId: MESSAGE_ID_SUGGESTION,
154+
data: {
155+
name: suggestionName,
156+
},
157+
fix: fixer => addName(fixer, node, suggestionName, sourceCode),
158+
},
159+
];
160+
161+
return problem;
162+
}
163+
164+
/** @param {import('eslint').Rule.RuleContext} context */
165+
const create = context => {
166+
context.on('ExportDefaultDeclaration', node => {
167+
if (!isAnonymousClassOrFunction(node.declaration)) {
168+
return;
169+
}
170+
171+
return getProblem(node.declaration, context);
172+
});
173+
174+
context.on('AssignmentExpression', node => {
175+
if (
176+
!isAnonymousClassOrFunction(node.right)
177+
|| !(
178+
node.parent.type === 'ExpressionStatement'
179+
&& node.parent.expression === node
180+
)
181+
|| !(
182+
isMemberExpression(node.left, {
183+
object: 'module',
184+
property: 'exports',
185+
computed: false,
186+
optional: false,
187+
})
188+
|| (
189+
node.left.type === 'Identifier',
190+
node.left.name === 'exports'
191+
)
192+
)
193+
) {
194+
return;
195+
}
196+
197+
return getProblem(node.right, context);
198+
});
199+
};
200+
201+
/** @type {import('eslint').Rule.RuleModule} */
202+
module.exports = {
203+
create,
204+
meta: {
205+
type: 'suggestion',
206+
docs: {
207+
description: 'Disallow anonymous functions and classes as the default export.',
208+
},
209+
hasSuggestions: true,
210+
messages,
211+
},
212+
};

‎rules/utils/avoid-capture.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ Useful when you want to rename a variable (or create a new variable) while being
129129
@param {isSafe} [isSafe] - Rule-specific name check function.
130130
@returns {string} - Either `name` as is, or a string like `${name}_` suffixed with underscores to make the name unique.
131131
*/
132-
module.exports = (name, scopes, isSafe = alwaysTrue) => {
132+
module.exports = function avoidCapture(name, scopes, isSafe = alwaysTrue) {
133133
if (!isValidIdentifier(name)) {
134134
name += '_';
135135

@@ -144,3 +144,4 @@ module.exports = (name, scopes, isSafe = alwaysTrue) => {
144144

145145
return name;
146146
};
147+

‎rules/utils/cartesian-product-samples.js

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

3-
module.exports = (combinations, length = Number.POSITIVE_INFINITY) => {
3+
module.exports = function cartesianProductSamples(combinations, length = Number.POSITIVE_INFINITY) {
44
const total = combinations.reduce((total, {length}) => total * length, 1);
55

66
const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => {

‎rules/utils/escape-string.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Escape string and wrap the result in quotes.
99
@param {string} [quote] - The quote character.
1010
@returns {string} - The quoted and escaped string.
1111
*/
12-
module.exports = (string, quote = '\'') => {
12+
module.exports = function escapeString(string, quote = '\'') {
1313
/* c8 ignore start */
1414
if (typeof string !== 'string') {
1515
throw new TypeError('Unexpected string.');
+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

3-
module.exports = string => string.replaceAll(
3+
const escapeTemplateElementRaw = string => string.replaceAll(
44
/(?<=(?:^|[^\\])(?:\\\\)*)(?<symbol>(?:`|\$(?={)))/g,
55
'\\$<symbol>',
66
);
7+
module.exports = escapeTemplateElementRaw;

‎rules/utils/get-documentation-url.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const packageJson = require('../../package.json');
44

55
const repoUrl = 'https://github.com/sindresorhus/eslint-plugin-unicorn';
66

7-
module.exports = filename => {
7+
module.exports = function getDocumentationUrl(filename) {
88
const ruleName = path.basename(filename, '.js');
99
return `${repoUrl}/blob/v${packageJson.version}/docs/rules/${ruleName}.md`;
1010
};
+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use strict';
22

33
// Get identifiers of given variable
4-
module.exports = ({identifiers, references}) => [...new Set([
4+
const getVariableIdentifiers = ({identifiers, references}) => [...new Set([
55
...identifiers,
66
...references.map(({identifier}) => identifier),
77
])];
8+
module.exports = getVariableIdentifiers;

‎rules/utils/has-same-range.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use strict';
22

3-
module.exports = (node1, node2) =>
3+
const hasSameRange = (node1, node2) =>
44
node1
55
&& node2
66
&& node1.range[0] === node2.range[0]
77
&& node1.range[1] === node2.range[1];
8+
module.exports = hasSameRange;

‎rules/utils/is-object-method.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use strict';
2-
module.exports = (node, object, method) => {
2+
module.exports = function isObjectMethod(node, object, method) {
33
const {callee} = node;
44
return (
55
callee.type === 'MemberExpression'

‎rules/utils/is-value-not-usable.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
const {isExpressionStatement} = require('../ast/index.js');
44

5-
module.exports = node => isExpressionStatement(node.parent);
5+
const isValueNotUsable = node => isExpressionStatement(node.parent);
6+
module.exports = isValueNotUsable;

‎rules/utils/resolve-variable-name.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Finds a variable named `name` in the scope `scope` (or it's parents).
77
@param {Scope} scope - The scope to look for the variable in.
88
@returns {Variable?} - The found variable, if any.
99
*/
10-
module.exports = (name, scope) => {
10+
module.exports = function resolveVariableName(name, scope) {
1111
while (scope) {
1212
const variable = scope.set.get(name);
1313

‎scripts/internal-rules/fix-snapshot-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ function checkInvalidCases(node, context) {
101101
}
102102

103103
for (const propertyNode of testCaseNode.properties) {
104-
if (propertyNode.computed || propertyNode.key.type !== 'Identifier') {
104+
if (propertyNode.type !== 'Property' || propertyNode.computed || propertyNode.key.type !== 'Identifier') {
105105
continue;
106106
}
107107

‎test/no-anonymous-default-export.mjs

+320
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
import outdent from 'outdent';
2+
import {getTester, parsers} from './utils/test.mjs';
3+
4+
const {test} = getTester(import.meta);
5+
6+
test.snapshot({
7+
valid: [
8+
'export default function named() {}',
9+
'export default class named {}',
10+
'export default []',
11+
'export default {}',
12+
'export default 1',
13+
'export default false',
14+
'export default 0n',
15+
'notExports = class {}',
16+
'notModule.exports = class {}',
17+
'module.notExports = class {}',
18+
'module.exports.foo = class {}',
19+
'alert(exports = class {})',
20+
'foo = module.exports = class {}',
21+
],
22+
invalid: [
23+
'export default function () {}',
24+
'export default class {}',
25+
'export default () => {}',
26+
'export default function * () {}',
27+
'export default async function () {}',
28+
'export default async function * () {}',
29+
'export default async () => {}',
30+
31+
// `ClassDeclaration`
32+
{
33+
code: 'export default class {}',
34+
filename: '/path/to/foo.js',
35+
},
36+
{
37+
code: 'export default class extends class {} {}',
38+
filename: '/path/to/foo.js',
39+
},
40+
{
41+
code: 'export default class{}',
42+
filename: '/path/to/foo.js',
43+
},
44+
{
45+
code: 'export default class {}',
46+
filename: '/path/to/foo-bar.js',
47+
},
48+
{
49+
code: 'export default class {}',
50+
filename: '/path/to/foo_bar.js',
51+
},
52+
{
53+
code: 'export default class {}',
54+
filename: '/path/to/foo+bar.js',
55+
},
56+
{
57+
code: 'export default class {}',
58+
filename: '/path/to/foo+bar123.js',
59+
},
60+
{
61+
code: 'export default class {}',
62+
filename: '/path/to/foo*.js',
63+
},
64+
{
65+
code: 'export default class {}',
66+
filename: '/path/to/[foo].js',
67+
},
68+
{
69+
code: 'export default class {}',
70+
filename: '/path/to/class.js',
71+
},
72+
{
73+
code: 'export default class {}',
74+
filename: '/path/to/foo.helper.js',
75+
},
76+
{
77+
code: 'export default class {}',
78+
filename: '/path/to/foo.bar.js',
79+
},
80+
{
81+
code: 'export default class {}',
82+
filename: '/path/to/foo.test.js',
83+
},
84+
{
85+
code: 'export default class {}',
86+
filename: '/path/to/.foo.js',
87+
},
88+
{
89+
code: outdent`
90+
let Foo, Foo_, foo, foo_
91+
export default class {}
92+
`,
93+
filename: '/path/to/foo.js',
94+
},
95+
96+
// `ClassExpression`
97+
{
98+
code: outdent`
99+
let Foo, Foo_, foo, foo_
100+
export default (class{})
101+
`,
102+
filename: '/path/to/foo.js',
103+
},
104+
{
105+
code: 'export default (class extends class {} {})',
106+
filename: '/path/to/foo.js',
107+
},
108+
{
109+
code: outdent`
110+
let Exports, Exports_, exports, exports_
111+
exports = class {}
112+
`,
113+
filename: '/path/to/exports.js',
114+
},
115+
{
116+
code: 'module.exports = class {}',
117+
filename: '/path/to/module.js',
118+
},
119+
120+
// `FunctionDeclaration`
121+
{
122+
code: 'export default function () {}',
123+
filename: '/path/to/foo.js',
124+
},
125+
{
126+
code: 'export default function* () {}',
127+
filename: '/path/to/foo.js',
128+
},
129+
{
130+
code: 'export default async function* () {}',
131+
filename: '/path/to/foo.js',
132+
},
133+
{
134+
code: 'export default async function*() {}',
135+
filename: '/path/to/foo.js',
136+
},
137+
{
138+
code: 'export default async function *() {}',
139+
filename: '/path/to/foo.js',
140+
},
141+
{
142+
code: 'export default async function * () {}',
143+
filename: '/path/to/foo.js',
144+
},
145+
{
146+
code: 'export default async function * /* comment */ () {}',
147+
filename: '/path/to/foo.js',
148+
},
149+
{
150+
code: outdent`
151+
export default async function * // comment
152+
() {}
153+
`,
154+
filename: '/path/to/foo.js',
155+
},
156+
{
157+
code: outdent`
158+
let Foo, Foo_, foo, foo_
159+
export default async function * () {}
160+
`,
161+
filename: '/path/to/foo.js',
162+
},
163+
164+
// `FunctionExpression`
165+
{
166+
code: outdent`
167+
let Foo, Foo_, foo, foo_
168+
export default (async function * () {})
169+
`,
170+
filename: '/path/to/foo.js',
171+
},
172+
{
173+
code: outdent`
174+
let Exports, Exports_, exports, exports_
175+
exports = function() {}
176+
`,
177+
filename: '/path/to/exports.js',
178+
},
179+
{
180+
code: 'module.exports = function() {}',
181+
filename: '/path/to/module.js',
182+
},
183+
184+
// `ArrowFunctionExpression`
185+
{
186+
code: 'export default () => {}',
187+
filename: '/path/to/foo.js',
188+
},
189+
{
190+
code: 'export default async () => {}',
191+
filename: '/path/to/foo.js',
192+
},
193+
{
194+
code: 'export default () => {};',
195+
filename: '/path/to/foo.js',
196+
},
197+
{
198+
code: 'export default() => {}',
199+
filename: '/path/to/foo.js',
200+
},
201+
{
202+
code: 'export default foo => {}',
203+
filename: '/path/to/foo.js',
204+
},
205+
{
206+
code: 'export default (( () => {} ))',
207+
filename: '/path/to/foo.js',
208+
},
209+
{
210+
code: '/* comment 1 */ export /* comment 2 */ default /* comment 3 */ () => {}',
211+
filename: '/path/to/foo.js',
212+
},
213+
{
214+
code: outdent`
215+
// comment 1
216+
export
217+
// comment 2
218+
default
219+
// comment 3
220+
() => {}
221+
`,
222+
filename: '/path/to/foo.js',
223+
},
224+
{
225+
code: outdent`
226+
let Foo, Foo_, foo, foo_
227+
export default async () => {}
228+
`,
229+
filename: '/path/to/foo.js',
230+
},
231+
{
232+
code: outdent`
233+
let Exports, Exports_, exports, exports_
234+
exports = (( () => {} ))
235+
`,
236+
filename: '/path/to/exports.js',
237+
},
238+
{
239+
code: outdent`
240+
// comment 1
241+
module
242+
// comment 2
243+
.exports
244+
// comment 3
245+
=
246+
// comment 4
247+
() => {};
248+
`,
249+
filename: '/path/to/module.js',
250+
},
251+
{
252+
code: '(( exports = (( () => {} )) ))',
253+
filename: '/path/to/foo.js',
254+
},
255+
{
256+
code: '(( module.exports = (( () => {} )) ))',
257+
filename: '/path/to/foo.js',
258+
},
259+
{
260+
code: '(( exports = (( () => {} )) ));',
261+
filename: '/path/to/foo.js',
262+
},
263+
{
264+
code: '(( module.exports = (( () => {} )) ));',
265+
filename: '/path/to/foo.js',
266+
},
267+
],
268+
});
269+
270+
// Decorators
271+
const decoratorsBeforeExportOptions = {
272+
parser: parsers.babel,
273+
parserOptions: {
274+
babelOptions: {
275+
parserOpts: {
276+
plugins: [
277+
['decorators', {decoratorsBeforeExport: true}],
278+
],
279+
},
280+
},
281+
},
282+
};
283+
const decoratorsAfterExportOptions = {
284+
parser: parsers.babel,
285+
parserOptions: {
286+
babelOptions: {
287+
parserOpts: {
288+
plugins: [
289+
['decorators', {decoratorsBeforeExport: false}],
290+
],
291+
},
292+
},
293+
},
294+
};
295+
test.snapshot({
296+
valid: [],
297+
invalid: [
298+
{
299+
code: '@decorator export default class {}',
300+
filename: '/path/to/foo.js',
301+
...decoratorsBeforeExportOptions,
302+
},
303+
{
304+
code: 'export default @decorator(class {}) class extends class {} {}',
305+
filename: '/path/to/foo.js',
306+
...decoratorsAfterExportOptions,
307+
},
308+
{
309+
code: 'module.exports = @decorator(class {}) class extends class {} {}',
310+
filename: '/path/to/foo.js',
311+
...decoratorsAfterExportOptions,
312+
},
313+
{
314+
code: '@decorator @decorator(class {}) export default class {}',
315+
filename: '/path/to/foo.js',
316+
...decoratorsBeforeExportOptions,
317+
},
318+
],
319+
});
320+

‎test/snapshots/no-anonymous-default-export.mjs.md

+1,435
Large diffs are not rendered by default.
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.