-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add no-anonymous-default-export
rule
#712
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# no-anonymous-default-export | ||
|
||
Reports if a module's default export is unnamed. This includes several types of unnamed data types; literals, object expressions, arrays, anonymous functions, arrow functions, and anonymous class declarations. | ||
|
||
Ensuring that default exports are named helps improve the grepability of the codebase by encouraging the re-use of the same identifier for the module's default export at its declaration site and at its import sites. | ||
|
||
## Options | ||
|
||
By default, all types of anonymous default exports are forbidden, but any types can be selectively allowed by toggling them on in the options. | ||
|
||
The complete default configuration looks like this. | ||
|
||
```js | ||
"import/no-anonymous-default-export": ["error", { | ||
"allowArray": false, | ||
"allowArrowFunction": false, | ||
"allowAnonymousClass": false, | ||
"allowAnonymousFunction": false, | ||
"allowLiteral": false, | ||
"allowObject": false | ||
}] | ||
``` | ||
|
||
## Rule Details | ||
|
||
### Fail | ||
```js | ||
export default [] | ||
|
||
export default () => {} | ||
|
||
export default class {} | ||
|
||
export default function () {} | ||
|
||
export default 123 | ||
|
||
export default {} | ||
``` | ||
|
||
### Pass | ||
```js | ||
const foo = 123 | ||
export default foo | ||
|
||
export default class MyClass() {} | ||
|
||
export default function foo() {} | ||
|
||
/* eslint import/no-anonymous-default-export: [2, {"allowArray": true}] */ | ||
export default [] | ||
|
||
/* eslint import/no-anonymous-default-export: [2, {"allowArrowFunction": true}] */ | ||
export default () => {} | ||
|
||
/* eslint import/no-anonymous-default-export: [2, {"allowAnonymousClass": true}] */ | ||
export default class {} | ||
|
||
/* eslint import/no-anonymous-default-export: [2, {"allowAnonymousFunction": true}] */ | ||
export default function () {} | ||
|
||
/* eslint import/no-anonymous-default-export: [2, {"allowLiteral": true}] */ | ||
export default 123 | ||
|
||
/* eslint import/no-anonymous-default-export: [2, {"allowObject": true}] */ | ||
export default {} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* @fileoverview Rule to disallow anonymous default exports. | ||
* @author Duncan Beevers | ||
*/ | ||
|
||
const defs = { | ||
ArrayExpression: { | ||
option: 'allowArray', | ||
description: 'If `false`, will report default export of an array', | ||
message: 'Assign array to a variable before exporting as module default', | ||
}, | ||
ArrowFunctionExpression: { | ||
option: 'allowArrowFunction', | ||
description: 'If `false`, will report default export of an arrow function', | ||
message: 'Assign arrow function to a variable before exporting as module default', | ||
}, | ||
ClassDeclaration: { | ||
option: 'allowAnonymousClass', | ||
description: 'If `false`, will report default export of an anonymous class', | ||
message: 'Unexpected default export of anonymous class', | ||
forbid: (node) => !node.declaration.id, | ||
}, | ||
FunctionDeclaration: { | ||
option: 'allowAnonymousFunction', | ||
description: 'If `false`, will report default export of an anonymous function', | ||
message: 'Unexpected default export of anonymous function', | ||
forbid: (node) => !node.declaration.id, | ||
}, | ||
Literal: { | ||
option: 'allowLiteral', | ||
description: 'If `false`, will report default export of a literal', | ||
message: 'Assign literal to a variable before exporting as module default', | ||
}, | ||
ObjectExpression: { | ||
option: 'allowObject', | ||
description: 'If `false`, will report default export of an object expression', | ||
message: 'Assign object to a variable before exporting as module default', | ||
}, | ||
TemplateLiteral: { | ||
option: 'allowLiteral', | ||
description: 'If `false`, will report default export of a literal', | ||
message: 'Assign literal to a variable before exporting as module default', | ||
}, | ||
} | ||
|
||
const schemaProperties = Object.keys(defs). | ||
map((key) => defs[key]). | ||
reduce((acc, def) => { | ||
acc[def.option] = { | ||
description: def.description, | ||
type: 'boolean', | ||
default: false, | ||
} | ||
|
||
return acc | ||
}, {}) | ||
|
||
module.exports = { | ||
meta: { | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: schemaProperties, | ||
'additionalProperties': false, | ||
}, | ||
], | ||
}, | ||
|
||
create: function (context) { | ||
const options = Object.assign({}, context.options[0]) | ||
|
||
return { | ||
'ExportDefaultDeclaration': (node) => { | ||
const def = defs[node.declaration.type] | ||
|
||
// Recognized node type and allowed by configuration, | ||
// and has no forbid check, or forbid check return value is truthy | ||
if (def && !options[def.option] && (!def.forbid || def.forbid(node))) { | ||
context.report({ node, message: def.message }) | ||
} | ||
}, | ||
} | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { test, SYNTAX_CASES } from '../utils' | ||
|
||
import { RuleTester } from 'eslint' | ||
|
||
const ruleTester = new RuleTester() | ||
const rule = require('rules/no-anonymous-default-export') | ||
|
||
ruleTester.run('no-anonymous-default-export', rule, { | ||
valid: [ | ||
// Exports with identifiers are valid | ||
test({ code: 'const foo = 123\nexport default foo' }), | ||
test({ code: 'export default function foo() {}'}), | ||
test({ code: 'export default class MyClass {}'}), | ||
|
||
// Allow each forbidden type with appropriate option | ||
test({ code: 'export default []', options: [{ allowArray: true }] }), | ||
test({ code: 'export default () => {}', options: [{ allowArrowFunction: true }] }), | ||
test({ code: 'export default class {}', options: [{ allowAnonymousClass: true }] }), | ||
test({ code: 'export default function() {}', options: [{ allowAnonymousFunction: true }] }), | ||
test({ code: 'export default 123', options: [{ allowLiteral: true }] }), | ||
test({ code: 'export default \'foo\'', options: [{ allowLiteral: true }] }), | ||
test({ code: 'export default `foo`', options: [{ allowLiteral: true }] }), | ||
test({ code: 'export default {}', options: [{ allowObject: true }] }), | ||
|
||
// Allow forbidden types with multiple options | ||
test({ code: 'export default 123', options: [{ allowLiteral: true, allowObject: true }] }), | ||
test({ code: 'export default {}', options: [{ allowLiteral: true, allowObject: true }] }), | ||
|
||
// Sanity check unrelated export syntaxes | ||
test({ code: 'export * from \'foo\'' }), | ||
test({ code: 'const foo = 123\nexport { foo }' }), | ||
test({ code: 'const foo = 123\nexport { foo as default }' }), | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add few valid tests for other kind of exports (named exports, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a few specs for a couple of flavors of exports and labeled the section // Sanity check unrelated export syntaxes |
||
...SYNTAX_CASES, | ||
], | ||
|
||
invalid: [ | ||
test({ code: 'export default []', errors: [{ message: 'Assign array to a variable before exporting as module default' }] }), | ||
test({ code: 'export default () => {}', errors: [{ message: 'Assign arrow function to a variable before exporting as module default' }] }), | ||
test({ code: 'export default class {}', errors: [{ message: 'Unexpected default export of anonymous class' }] }), | ||
test({ code: 'export default function() {}', errors: [{ message: 'Unexpected default export of anonymous function' }] }), | ||
test({ code: 'export default 123', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), | ||
test({ code: 'export default \'foo\'', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), | ||
test({ code: 'export default `foo`', errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), | ||
test({ code: 'export default {}', errors: [{ message: 'Assign object to a variable before exporting as module default' }] }), | ||
|
||
// Test failure with non-covering exception | ||
test({ code: 'export default 123', options: [{ allowObject: true }], errors: [{ message: 'Assign literal to a variable before exporting as module default' }] }), | ||
], | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add a schema to the rule, so that invalid options are rejected.