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

feat(rome_js_analyze): noUselessEmptyExport #4693

Merged
merged 1 commit into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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