From 3e4aa072c0012f78e29ceb3ff3c081e9a75020c4 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Sat, 30 Nov 2024 21:31:42 +0100 Subject: [PATCH] feat: add no-useless-rename rule --- docs/rules/no_useless_rename.md | 19 +++++ schemas/rules.v1.json | 1 + src/rules.rs | 2 + src/rules/no_useless_rename.rs | 135 ++++++++++++++++++++++++++++++++ www/static/docs.json | 5 ++ 5 files changed, 162 insertions(+) create mode 100644 docs/rules/no_useless_rename.md create mode 100644 src/rules/no_useless_rename.rs diff --git a/docs/rules/no_useless_rename.md b/docs/rules/no_useless_rename.md new file mode 100644 index 00000000..d36c8955 --- /dev/null +++ b/docs/rules/no_useless_rename.md @@ -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 }; +``` diff --git a/schemas/rules.v1.json b/schemas/rules.v1.json index ed892858..999d048b 100644 --- a/schemas/rules.v1.json +++ b/schemas/rules.v1.json @@ -103,6 +103,7 @@ "no-unsafe-negation", "no-unused-labels", "no-unused-vars", + "no-useless-rename", "no-var", "no-window", "no-window-prefix", diff --git a/src/rules.rs b/src/rules.rs index 1c7582ad..b02cce1d 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -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; @@ -354,6 +355,7 @@ fn get_all_rules_raw() -> Vec> { 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), diff --git a/src/rules/no_useless_rename.rs b/src/rules/no_useless_rename.rs new file mode 100644 index 00000000..0b44864a --- /dev/null +++ b/src/rules/no_useless_rename.rs @@ -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, + }] + ); + } +} diff --git a/www/static/docs.json b/www/static/docs.json index cd8788c7..bcaba751 100644 --- a/www/static/docs.json +++ b/www/static/docs.json @@ -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",