Skip to content
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

feat: add no-useless-rename rule #1370

Merged
merged 1 commit into from
Dec 1, 2024
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
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