Skip to content

Commit

Permalink
feat: add no-useless-rename rule
Browse files Browse the repository at this point in the history
  • Loading branch information
marvinhagemeister committed Nov 30, 2024
1 parent 3aca2e7 commit 3e4aa07
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 0 deletions.
19 changes: 19 additions & 0 deletions docs/rules/no_useless_rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Disallow useless rename operations where both the original and new name are
exactly the same. This is often a leftover from a refactoring procedure and can
be safely removed.

### Invalid:

```ts
import { foo as foo } from "foo";
const { foo: foo } = obj;
export { foo as foo };
```

### Valid:

```ts
import { foo as bar } from "foo";
const { foo: bar } = obj;
export { foo as bar };
```
1 change: 1 addition & 0 deletions schemas/rules.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"no-unsafe-negation",
"no-unused-labels",
"no-unused-vars",
"no-useless-rename",
"no-var",
"no-window",
"no-window-prefix",
Expand Down
2 changes: 2 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ pub mod no_unsafe_finally;
pub mod no_unsafe_negation;
pub mod no_unused_labels;
pub mod no_unused_vars;
pub mod no_useless_rename;
pub mod no_var;
pub mod no_window;
pub mod no_window_prefix;
Expand Down Expand Up @@ -354,6 +355,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
Box::new(no_unsafe_negation::NoUnsafeNegation),
Box::new(no_unused_labels::NoUnusedLabels),
Box::new(no_unused_vars::NoUnusedVars),
Box::new(no_useless_rename::NoUselessRename),
Box::new(no_var::NoVar),
Box::new(no_window::NoWindow),
Box::new(no_window_prefix::NoWindowPrefix),
Expand Down
135 changes: 135 additions & 0 deletions src/rules/no_useless_rename.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use super::{Context, LintRule};
use crate::handler::{Handler, Traverse};
use crate::tags::Tags;
use crate::Program;

use deno_ast::view::{
ExportNamedSpecifier, ImportNamedSpecifier, ModuleExportName, ObjectPat,
ObjectPatProp, Pat, PropName,
};
use deno_ast::SourceRanged;

#[derive(Debug)]
pub struct NoUselessRename;

const MESSAGE: &str = "The original name is exactly the same as the new name.";
const HINT: &str = "Remove the rename operation.";
const CODE: &str = "no-useless-rename";

impl LintRule for NoUselessRename {
fn tags(&self) -> Tags {
&[]
}

fn code(&self) -> &'static str {
CODE
}

fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: Program,
) {
NoUselessRenameHandler.traverse(program, context);
}

#[cfg(feature = "docs")]
fn docs(&self) -> &'static str {
include_str!("../../docs/rules/no_useless_rename.md")
}
}

struct NoUselessRenameHandler;

impl Handler for NoUselessRenameHandler {
fn import_named_specifier(
&mut self,
node: &ImportNamedSpecifier,
ctx: &mut Context,
) {
if let Some(ModuleExportName::Ident(imported_name)) = node.imported {
if imported_name.sym() == node.local.sym() {
ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT);
}
}
}

fn object_pat(&mut self, node: &ObjectPat, ctx: &mut Context) {
for prop in node.props {
let ObjectPatProp::KeyValue(key_val) = prop else {
return;
};

let PropName::Ident(prop_key) = key_val.key else {
return;
};

let Pat::Ident(prop_value) = key_val.value else {
return;
};

if prop_value.id.sym() == prop_key.sym() {
ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT);
}
}
}

fn export_named_specifier(
&mut self,
node: &ExportNamedSpecifier,
ctx: &mut Context,
) {
let Some(exported) = node.exported else {
return;
};

let ModuleExportName::Ident(exported_id) = exported else {
return;
};

let ModuleExportName::Ident(original) = node.orig else {
return;
};

if exported_id.sym() == original.sym() {
ctx.add_diagnostic_with_hint(node.range(), CODE, MESSAGE, HINT);
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn console_allowed() {
assert_lint_ok!(
NoUselessRename,
r#"import { foo as bar } from "foo";"#,
r#"const { foo: bar } = obj;"#,
r#"export { foo as bar };"#,
);
}

#[test]
fn no_console_invalid() {
assert_lint_err!(
NoUselessRename,
r#"import { foo as foo } from "foo";"#: [{
col: 9,
message: MESSAGE,
hint: HINT,
}],
r#"const { foo: foo } = obj;"#: [{
col: 6,
message: MESSAGE,
hint: HINT,
}],
r#"export { foo as foo };"#: [{
col: 9,
message: MESSAGE,
hint: HINT,
}]
);
}
}
5 changes: 5 additions & 0 deletions www/static/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,11 @@
"recommended"
]
},
{
"code": "no-useless-rename",
"docs": "Disallow useless rename operations where both the original and new name are\nexactly the same. This is often a leftover from a refactoring procedure and can\nbe safely removed.\n\n### Invalid:\n\n```ts\nimport { foo as foo } from \"foo\";\nconst { foo: foo } = obj;\nexport { foo as foo };\n```\n\n### Valid:\n\n```ts\nimport { foo as bar } from \"foo\";\nconst { foo: bar } = obj;\nexport { foo as bar };\n```\n",
"tags": []
},
{
"code": "no-var",
"docs": "Enforces the use of block scoped variables over more error prone function scoped\nvariables. Block scoped variables are defined using `const` and `let` keywords.\n\n`const` and `let` keywords ensure the variables defined using these keywords are\nnot accessible outside their block scope. On the other hand, variables defined\nusing `var` keyword are only limited by their function scope.\n\n### Invalid:\n\n```typescript\nvar foo = \"bar\";\n```\n\n### Valid:\n\n```typescript\nconst foo = 1;\nlet bar = 2;\n```\n",
Expand Down

0 comments on commit 3e4aa07

Please sign in to comment.