1
- use std:: fmt;
2
-
3
1
use oxc_ast:: {
4
2
AstKind ,
5
3
ast:: { AssignmentExpression , AssignmentTarget , ExportDefaultDeclarationKind , Expression } ,
@@ -8,9 +6,14 @@ use oxc_diagnostics::OxcDiagnostic;
8
6
use oxc_macros:: declare_oxc_lint;
9
7
use oxc_span:: Span ;
10
8
11
- use crate :: { AstNode , context:: LintContext , rule:: Rule } ;
9
+ use crate :: { AstNode , ast_util , context:: LintContext , rule:: Rule } ;
12
10
13
11
fn no_anonymous_default_export_diagnostic ( span : Span , kind : ErrorNodeKind ) -> OxcDiagnostic {
12
+ let kind = match kind {
13
+ ErrorNodeKind :: Function => "function" ,
14
+ ErrorNodeKind :: Class => "class" ,
15
+ } ;
16
+
14
17
OxcDiagnostic :: warn ( format ! ( "This {kind} default export is missing a name" ) )
15
18
// TODO: suggest a name. https://github.com/sindresorhus/eslint-plugin-unicorn/blob/d3e4b805da31c6ed7275e2e2e770b6b0fbcf11c2/rules/no-anonymous-default-export.js#L41
16
19
. with_label ( span)
@@ -21,14 +24,15 @@ pub struct NoAnonymousDefaultExport;
21
24
22
25
declare_oxc_lint ! (
23
26
/// ### What it does
24
- /// Disallow anonymous functions and classes as the default export
27
+ ///
28
+ /// Disallows anonymous functions and classes as default exports.
25
29
///
26
30
/// ### Why is this bad?
27
- /// Naming default exports improves codebase searchability by ensuring
28
- /// consistent identifier use for a module's default export, both where it's
29
- /// declared and where it's imported.
30
31
///
31
- /// ### Example
32
+ /// Naming default exports improves searchability and ensures consistent
33
+ /// identifiers for a module’s default export in both declaration and import.
34
+ ///
35
+ /// ### Examples
32
36
///
33
37
/// Examples of **incorrect** code for this rule:
34
38
/// ```javascript
@@ -61,73 +65,66 @@ declare_oxc_lint!(
61
65
62
66
impl Rule for NoAnonymousDefaultExport {
63
67
fn run < ' a > ( & self , node : & AstNode < ' a > , ctx : & LintContext < ' a > ) {
64
- let problem_node = match node. kind ( ) {
68
+ let result = match node. kind ( ) {
65
69
// ESM: export default
66
70
AstKind :: ExportDefaultDeclaration ( export_decl) => match & export_decl. declaration {
67
- ExportDefaultDeclarationKind :: ClassDeclaration ( class_decl) => class_decl
68
- . id
69
- . as_ref ( )
70
- . map_or ( Some ( ( export_decl. span , ErrorNodeKind :: Class ) ) , |_| None ) ,
71
- ExportDefaultDeclarationKind :: FunctionDeclaration ( function_decl) => function_decl
72
- . id
73
- . as_ref ( )
74
- . map_or ( Some ( ( export_decl. span , ErrorNodeKind :: Function ) ) , |_| None ) ,
75
- ExportDefaultDeclarationKind :: ArrowFunctionExpression ( _) => {
76
- Some ( ( export_decl. span , ErrorNodeKind :: Function ) )
77
- }
78
- ExportDefaultDeclarationKind :: ParenthesizedExpression ( expr) => {
79
- let expr = expr. expression . get_inner_expression ( ) ;
80
- match expr {
81
- Expression :: ClassExpression ( class_expr) => class_expr
82
- . id
83
- . as_ref ( )
84
- . map_or ( Some ( ( class_expr. span , ErrorNodeKind :: Class ) ) , |_| None ) ,
85
- Expression :: FunctionExpression ( func_expr) => func_expr
86
- . id
87
- . as_ref ( )
88
- . map_or ( Some ( ( func_expr. span , ErrorNodeKind :: Function ) ) , |_| None ) ,
89
- _ => None ,
90
- }
71
+ ExportDefaultDeclarationKind :: ClassDeclaration ( class_decl)
72
+ if class_decl. id . is_none ( ) =>
73
+ {
74
+ Some ( ( class_decl. span , ErrorNodeKind :: Class ) )
91
75
}
92
- _ => None ,
93
- } ,
94
- // CommonJS: module.exports
95
- AstKind :: AssignmentExpression ( expr) if is_common_js_export ( expr) => match & expr. right {
96
- Expression :: ClassExpression ( class_expr) => {
97
- class_expr. id . as_ref ( ) . map_or ( Some ( ( expr. span , ErrorNodeKind :: Class ) ) , |_| None )
76
+ ExportDefaultDeclarationKind :: FunctionDeclaration ( function_decl)
77
+ if function_decl. id . is_none ( ) =>
78
+ {
79
+ Some ( ( function_decl. span , ErrorNodeKind :: Function ) )
98
80
}
99
- Expression :: FunctionExpression ( function_expr) => function_expr
100
- . id
101
- . as_ref ( )
102
- . map_or ( Some ( ( expr. span , ErrorNodeKind :: Function ) ) , |_| None ) ,
103
- Expression :: ArrowFunctionExpression ( _) => {
104
- Some ( ( expr. span , ErrorNodeKind :: Function ) )
81
+ _ => {
82
+ export_decl. declaration . as_expression ( ) . and_then ( is_anonymous_class_or_function)
105
83
}
106
- _ => None ,
107
84
} ,
108
- _ => None ,
85
+ // CommonJS: module.exports
86
+ AstKind :: AssignmentExpression ( expr)
87
+ if is_top_expr ( ctx, node) && is_common_js_export ( expr) =>
88
+ {
89
+ is_anonymous_class_or_function ( & expr. right )
90
+ }
91
+ _ => return ,
109
92
} ;
110
93
111
- if let Some ( ( span, error_kind) ) = problem_node {
94
+ if let Some ( ( span, error_kind) ) = result {
112
95
ctx. diagnostic ( no_anonymous_default_export_diagnostic ( span, error_kind) ) ;
113
96
} ;
114
97
}
115
98
}
116
99
117
- fn is_common_js_export ( expr : & AssignmentExpression ) -> bool {
118
- if let AssignmentTarget :: StaticMemberExpression ( member_expr) = & expr. left {
119
- if let Expression :: Identifier ( object_ident) = & member_expr. object {
120
- if object_ident. name != "module" {
121
- return false ;
122
- }
100
+ fn is_anonymous_class_or_function ( expr : & Expression ) -> Option < ( Span , ErrorNodeKind ) > {
101
+ Some ( match expr. get_inner_expression ( ) {
102
+ Expression :: ClassExpression ( expr) if expr. id . is_none ( ) => ( expr. span , ErrorNodeKind :: Class ) ,
103
+ Expression :: FunctionExpression ( expr) if expr. id . is_none ( ) => {
104
+ ( expr. span , ErrorNodeKind :: Function )
123
105
}
106
+ Expression :: ArrowFunctionExpression ( expr) => ( expr. span , ErrorNodeKind :: Function ) ,
107
+ _ => return None ,
108
+ } )
109
+ }
124
110
125
- if member_expr. property . name != "exports" {
126
- return false ;
111
+ fn is_common_js_export ( expr : & AssignmentExpression ) -> bool {
112
+ match & expr. left {
113
+ AssignmentTarget :: AssignmentTargetIdentifier ( id) => id. name == "exports" ,
114
+ AssignmentTarget :: StaticMemberExpression ( mem_expr) if !mem_expr. optional => {
115
+ mem_expr. object . is_specific_id ( "module" ) && mem_expr. property . name == "exports"
127
116
}
117
+ _ => false ,
128
118
}
119
+ }
120
+
121
+ fn is_top_expr ( ctx : & LintContext , node : & AstNode ) -> bool {
122
+ if !ctx. scoping ( ) . scope_flags ( node. scope_id ( ) ) . is_top ( ) {
123
+ return false ;
124
+ } ;
129
125
130
- true
126
+ let parent = ast_util:: iter_outer_expressions ( ctx, node. id ( ) ) . next ( ) ;
127
+ matches ! ( parent, Some ( AstKind :: ExpressionStatement ( _) ) )
131
128
}
132
129
133
130
#[ derive( Clone , Copy ) ]
@@ -136,39 +133,99 @@ enum ErrorNodeKind {
136
133
Class ,
137
134
}
138
135
139
- impl fmt:: Display for ErrorNodeKind {
140
- fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
141
- match self {
142
- Self :: Function => "function" . fmt ( f) ,
143
- Self :: Class => "class" . fmt ( f) ,
144
- }
145
- }
146
- }
147
-
148
136
#[ test]
149
137
fn test ( ) {
150
138
use crate :: tester:: Tester ;
151
139
152
140
let pass = vec ! [
153
- r"export default class Foo {}" ,
154
- r"export default function foo () {}" ,
155
141
r"const foo = () => {}; export default foo;" ,
156
142
r"module.exports = class Foo {};" ,
157
143
r"module.exports = function foo () {};" ,
158
144
r"const foo = () => {}; module.exports = foo;" ,
159
- // TODO: need handle this situation?
160
- // r"module['exports'] = function foo () {};",
145
+ ( "export default function named() {}" ) ,
146
+ ( "export default class named {}" ) ,
147
+ ( "export default []" ) ,
148
+ ( "export default {}" ) ,
149
+ ( "export default 1" ) ,
150
+ ( "export default false" ) ,
151
+ ( "export default 0n" ) ,
152
+ ( "notExports = class {}" ) ,
153
+ ( "notModule.exports = class {}" ) ,
154
+ ( "module.notExports = class {}" ) ,
155
+ ( "module.exports.foo = class {}" ) ,
156
+ ( "alert(exports = class {})" ) ,
157
+ ( "foo = module.exports = class {}" ) ,
161
158
] ;
162
159
163
160
let fail = vec ! [
164
- r"export default class {}" ,
165
- r"export default function () {}" ,
166
- r"export default () => {};" ,
167
- r"module.exports = class {}" ,
168
- r"module.exports = function () {}" ,
169
- r"module.exports = () => {}" ,
170
- "export default (async function * () {})" ,
171
- "export default (class extends class {} {})" ,
161
+ ( "export default function () {}" ) ,
162
+ ( "export default class {}" ) ,
163
+ ( "export default () => {}" ) ,
164
+ ( "export default function * () {}" ) ,
165
+ ( "export default async function () {}" ) ,
166
+ ( "export default async function * () {}" ) ,
167
+ ( "export default async () => {}" ) ,
168
+ ( "export default class {}" ) ,
169
+ ( "export default class extends class {} {}" ) ,
170
+ ( "export default class{}" ) ,
171
+ ( "export default class {}" ) ,
172
+ ( "let Foo, Foo_, foo, foo_
173
+ export default class {}" ) ,
174
+ ( "let Foo, Foo_, foo, foo_
175
+ export default (class{})" ) ,
176
+ ( "export default (class extends class {} {})" ) ,
177
+ ( "let Exports, Exports_, exports, exports_
178
+ exports = class {}" ) ,
179
+ ( "module.exports = class {}" ) ,
180
+ ( "export default function () {}" ) ,
181
+ ( "export default function* () {}" ) ,
182
+ ( "export default async function* () {}" ) ,
183
+ ( "export default async function*() {}" ) ,
184
+ ( "export default async function *() {}" ) ,
185
+ ( "export default async function * () {}" ) ,
186
+ ( "export default async function * /* comment */ () {}" ) ,
187
+ ( "export default async function * // comment
188
+ () {}" ) ,
189
+ ( "let Foo, Foo_, foo, foo_
190
+ export default async function * () {}" ) ,
191
+ ( "let Foo, Foo_, foo, foo_
192
+ export default (async function * () {})" ) ,
193
+ ( "let Exports, Exports_, exports, exports_
194
+ exports = function() {}" ) ,
195
+ ( "module.exports = function() {}" ) ,
196
+ ( "export default () => {}" ) ,
197
+ ( "export default async () => {}" ) ,
198
+ ( "export default () => {};" ) ,
199
+ ( "export default() => {}" ) ,
200
+ ( "export default foo => {}" ) ,
201
+ ( "export default (( () => {} ))" ) ,
202
+ ( "/* comment 1 */ export /* comment 2 */ default /* comment 3 */ () => {}" ) ,
203
+ ( "// comment 1
204
+ export
205
+ // comment 2
206
+ default
207
+ // comment 3
208
+ () => {}" ) ,
209
+ ( "let Foo, Foo_, foo, foo_
210
+ export default async () => {}" ) ,
211
+ ( "let Exports, Exports_, exports, exports_
212
+ exports = (( () => {} ))" ) ,
213
+ ( "// comment 1
214
+ module
215
+ // comment 2
216
+ .exports
217
+ // comment 3
218
+ =
219
+ // comment 4
220
+ () => {};" ) ,
221
+ ( "(( exports = (( () => {} )) ))" ) ,
222
+ ( "(( module.exports = (( () => {} )) ))" ) ,
223
+ ( "(( exports = (( () => {} )) ));" ) ,
224
+ ( "(( module.exports = (( () => {} )) ));" ) ,
225
+ ( "@decorator export default class {}" ) ,
226
+ ( "export default @decorator(class {}) class extends class {} {}" ) ,
227
+ ( "module.exports = @decorator(class {}) class extends class {} {}" ) ,
228
+ ( "@decorator @decorator(class {}) export default class {}" ) ,
172
229
] ;
173
230
174
231
Tester :: new ( NoAnonymousDefaultExport :: NAME , NoAnonymousDefaultExport :: PLUGIN , pass, fail)
0 commit comments