Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_js_analyze): noUselessEmptyExport
Browse files Browse the repository at this point in the history
  • Loading branch information
Conaclos committed Jul 13, 2023
1 parent 18b6a2a commit 792ffcd
Show file tree
Hide file tree
Showing 29 changed files with 597 additions and 39 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ if no error diagnostics are emitted.

This rule disallows `\8` and `\9` escape sequences in string literals.

- Add [`noUselessEmptyExport`](https://docs.rome.tools/lint/rules/noUselessEmptyExport/)

This rule disallows useless `export {}`.

#### Other changes

- Add new TypeScript globals (`AsyncDisposable`, `Awaited`, `DecoratorContext`, and others) [4643](https://github.com/rome/tools/issues/4643).
Expand Down
1 change: 1 addition & 0 deletions crates/rome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ define_categories! {
"lint/nursery/noRedundantRoles": "https://docs.rome.tools/lint/rules/noRedundantRoles",
"lint/nursery/noSelfAssign": "https://docs.rome.tools/lint/rules/noSelfAssign",
"lint/nursery/noStaticOnlyClass": "https://docs.rome.tools/lint/rules/noStaticOnlyClass",
"lint/nursery/noUselessEmptyExport": "https://docs.rome.tools/lint/rules/noUselessEmptyExport",
"lint/nursery/noVoid": "https://docs.rome.tools/lint/rules/noVoid",
"lint/nursery/useAriaPropTypes": "https://docs.rome.tools/lint/rules/useAriaPropTypes",
"lint/nursery/useArrowFunction": "https://docs.rome.tools/lint/rules/useArrowFunction",
Expand Down
2 changes: 2 additions & 0 deletions crates/rome_js_analyze/src/analyzers/nursery.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use rome_analyze::{context::RuleContext, declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic};
use rome_console::markup;
use rome_diagnostics::Applicability;
use rome_js_syntax::{AnyJsModuleItem, JsExport, JsModuleItemList, JsSyntaxToken};
use rome_rowan::{AstNode, AstSeparatedList, BatchMutationExt};

use crate::JsRuleAction;

declare_rule! {
/// Disallow empty exports that don't change anything in a module file.
///
/// An empty `export {}` is sometimes useful to turn a file that would otherwise be a script into a module.
/// Per the [TypeScript Handbook Modules page](https://www.typescriptlang.org/docs/handbook/modules.html):
///
/// > In TypeScript, just as in ECMAScript 2015,
/// > any file containing a top-level import or export is considered a module.
/// > Conversely, a file without any top-level import or export declarations is treated as a script
/// > whose contents are available in the global scope.
///
/// However, an `export {}` statement does nothing if there are any other top-level import or export in the file.
///
/// Source: https://typescript-eslint.io/rules/no-useless-empty-export/
///
/// ## Examples
///
/// ### Invalid
///
/// ```js,expect_diagnostic
/// import { A } from "module";
/// export {};
/// ```
///
/// ```js,expect_diagnostic
/// export const A = 0;
/// export {};
/// ```
///
/// ## Valid
///
/// ```js
/// export {};
/// ```
///
pub(crate) NoUselessEmptyExport {
version: "next",
name: "noUselessEmptyExport",
recommended: true,
}
}

impl Rule for NoUselessEmptyExport {
type Query = Ast<JsExport>;
/// The first import or export that makes useless the empty export.
type State = JsSyntaxToken;
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();
if is_empty_export(node) {
let module_item_list = JsModuleItemList::cast(node.syntax().parent()?)?;
// allow reporting an empty export that precedes another empty export.
let mut ignore_empty_export = true;
for module_item in module_item_list {
match module_item {
AnyJsModuleItem::AnyJsStatement(_) => {}
AnyJsModuleItem::JsImport(import) => return import.import_token().ok(),
AnyJsModuleItem::JsExport(export) => {
if !is_empty_export(&export) {
return export.export_token().ok();
}
if !ignore_empty_export {
return export.export_token().ok();
}
if node == &export {
ignore_empty_export = false
}
}
}
}
}
None
}

fn diagnostic(ctx: &RuleContext<Self>, token: &Self::State) -> Option<RuleDiagnostic> {
Some(
RuleDiagnostic::new(
rule_category!(),
ctx.query().range(),
markup! {
"This empty "<Emphasis>"export"</Emphasis>" is useless because there's another "<Emphasis>"export"</Emphasis>" or "<Emphasis>"import"</Emphasis>"."
},
).detail(token.text_trimmed_range(), markup! {
"This "<Emphasis>{token.text_trimmed()}</Emphasis>" makes useless the empty export."
}),
)
}

fn action(ctx: &RuleContext<Self>, _: &Self::State) -> Option<JsRuleAction> {
let mut mutation = ctx.root().begin();
mutation.remove_node(ctx.query().clone());
Some(JsRuleAction {
category: ActionCategory::QuickFix,
applicability: Applicability::Always,
message: markup! { "Remove this useless empty export." }.to_owned(),
mutation,
})
}
}

fn is_empty_export(export: &JsExport) -> bool {
(|| -> Option<bool> {
Some(
export
.export_clause()
.ok()?
.as_js_export_named_clause()?
.specifiers()
.iter()
.count()
== 0,
)
})()
.unwrap_or(false)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default {};
export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
source: crates/rome_js_analyze/tests/spec_tests.rs
expression: invalid_with_default_export.js
---
# Input
```js
export default {};
export {}
```

# Diagnostics
```
invalid_with_default_export.js:2:1 lint/nursery/noUselessEmptyExport FIXABLE ━━━━━━━━━━━━━━━━━━━━━
! This empty export is useless because there's another export or import.
1 │ export default {};
> 2 │ export {}
│ ^^^^^^^^^
i This export makes useless the empty export.
> 1 │ export default {};
│ ^^^^^^
2 │ export {}
i Safe fix: Remove this useless empty export.
1 1 │ export default {};
2 │ - export·{}
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {}
export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
source: crates/rome_js_analyze/tests/spec_tests.rs
expression: invalid_with_empty_export.js
---
# Input
```js
export {}
export {}
```

# Diagnostics
```
invalid_with_empty_export.js:1:1 lint/nursery/noUselessEmptyExport FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━
! This empty export is useless because there's another export or import.
> 1 │ export {}
│ ^^^^^^^^^
2 │ export {}
i This export makes useless the empty export.
1 │ export {}
> 2 │ export {}
│ ^^^^^^
i Safe fix: Remove this useless empty export.
1 │ export·{}
│ ---------
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function f() { return 0 }
export const A = f();
export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
source: crates/rome_js_analyze/tests/spec_tests.rs
expression: invalid_with_export.js
---
# Input
```js
function f() { return 0 }
export const A = f();
export {}
```

# Diagnostics
```
invalid_with_export.js:3:1 lint/nursery/noUselessEmptyExport FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! This empty export is useless because there's another export or import.
1 │ function f() { return 0 }
2 │ export const A = f();
> 3 │ export {}
│ ^^^^^^^^^
i This export makes useless the empty export.
1 │ function f() { return 0 }
> 2 │ export const A = f();
│ ^^^^^^
3 │ export {}
i Safe fix: Remove this useless empty export.
1 1 │ function f() { return 0 }
2 2 │ export const A = f();
3 │ - export·{}
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "mod";
export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
source: crates/rome_js_analyze/tests/spec_tests.rs
expression: invalid_with_export_from.js
---
# Input
```js
export * from "mod";
export {}
```

# Diagnostics
```
invalid_with_export_from.js:2:1 lint/nursery/noUselessEmptyExport FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━
! This empty export is useless because there's another export or import.
1 │ export * from "mod";
> 2 │ export {}
│ ^^^^^^^^^
i This export makes useless the empty export.
> 1 │ export * from "mod";
│ ^^^^^^
2 │ export {}
i Safe fix: Remove this useless empty export.
1 1 │ export * from "mod";
2 │ - export·{}
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { A } from "mod"
function f() { return 0 }
export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
source: crates/rome_js_analyze/tests/spec_tests.rs
expression: invalid_with_import.js
---
# Input
```js
import { A } from "mod"
function f() { return 0 }
export {}
```

# Diagnostics
```
invalid_with_import.js:3:1 lint/nursery/noUselessEmptyExport FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! This empty export is useless because there's another export or import.
1 │ import { A } from "mod"
2 │ function f() { return 0 }
> 3 │ export {}
│ ^^^^^^^^^
i This import makes useless the empty export.
> 1 │ import { A } from "mod"
│ ^^^^^^
2 │ function f() { return 0 }
3 │ export {}
i Safe fix: Remove this useless empty export.
1 1 │ import { A } from "mod"
2 2 │ function f() { return 0 }
3 │ - export·{}
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import "mod"
function f() { return 0 }
export {}
Loading

0 comments on commit 792ffcd

Please sign in to comment.