From eaa0c58e2470622aa2ae3cda9f2b6dfcd02488c0 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 11 Oct 2023 15:23:17 +0800 Subject: [PATCH] feat(linter): eslint-plugin-unicorn(filename-case) (#978) --- Cargo.lock | 1 + crates/oxc_linter/Cargo.toml | 19 ++--- crates/oxc_linter/src/context.rs | 11 ++- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/unicorn/filename_case.rs | 74 +++++++++++++++++++ crates/oxc_linter/src/service.rs | 3 +- crates/oxc_linter_plugin/src/test.rs | 3 +- crates/oxc_wasm/src/lib.rs | 7 +- editor/vscode/server/src/linter.rs | 3 +- tasks/benchmark/benches/linter.rs | 4 +- 10 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 crates/oxc_linter/src/rules/unicorn/filename_case.rs diff --git a/Cargo.lock b/Cargo.lock index 202d1e1bd1821..1803041246dad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1590,6 +1590,7 @@ dependencies = [ name = "oxc_linter" version = "0.0.0" dependencies = [ + "convert_case", "dashmap", "insta", "itertools 0.11.0", diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index 87f5fcbe057cf..3222b306f8bbc 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -27,15 +27,16 @@ oxc_syntax = { workspace = true } oxc_formatter = { workspace = true } oxc_resolver = { workspace = true } -rayon = { workspace = true } -lazy_static = { workspace = true } # used in oxc_macros -serde_json = { workspace = true } -regex = { workspace = true } -rustc-hash = { workspace = true } -phf = { workspace = true, features = ["macros"] } -num-traits = { workspace = true } -itertools = { workspace = true } -dashmap = { workspace = true } +rayon = { workspace = true } +lazy_static = { workspace = true } # used in oxc_macros +serde_json = { workspace = true } +regex = { workspace = true } +rustc-hash = { workspace = true } +phf = { workspace = true, features = ["macros"] } +num-traits = { workspace = true } +itertools = { workspace = true } +dashmap = { workspace = true } +convert_case = { workspace = true } rust-lapper = "1.1.0" once_cell = "1.18.0" diff --git a/crates/oxc_linter/src/context.rs b/crates/oxc_linter/src/context.rs index ba445b471a38b..17398e35f4fb0 100644 --- a/crates/oxc_linter/src/context.rs +++ b/crates/oxc_linter/src/context.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, path::Path, rc::Rc}; use oxc_diagnostics::Error; use oxc_formatter::{Formatter, FormatterOptions}; @@ -22,10 +22,12 @@ pub struct LintContext<'a> { fix: bool, current_rule_name: &'static str, + + file_path: Box, } impl<'a> LintContext<'a> { - pub fn new(semantic: &Rc>) -> Self { + pub fn new(file_path: Box, semantic: &Rc>) -> Self { let disable_directives = DisableDirectivesBuilder::new(semantic.source_text(), semantic.trivias()).build(); Self { @@ -34,6 +36,7 @@ impl<'a> LintContext<'a> { disable_directives, fix: false, current_rule_name: "", + file_path, } } @@ -55,6 +58,10 @@ impl<'a> LintContext<'a> { self.semantic().source_type() } + pub fn file_path(&self) -> &Path { + &self.file_path + } + pub fn with_rule_name(&mut self, name: &'static str) { self.current_rule_name = name; } diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 664a31fb0f19a..8333ff44d4df1 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -124,6 +124,7 @@ mod jest { } mod unicorn { + pub mod filename_case; pub mod no_instanceof_array; pub mod no_thenable; pub mod no_unnecessary_await; @@ -231,6 +232,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::no_instanceof_array, unicorn::no_unnecessary_await, unicorn::no_thenable, + unicorn::filename_case, import::named, import::no_cycle, import::no_self_import, diff --git a/crates/oxc_linter/src/rules/unicorn/filename_case.rs b/crates/oxc_linter/src/rules/unicorn/filename_case.rs new file mode 100644 index 0000000000000..a10402da87d95 --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/filename_case.rs @@ -0,0 +1,74 @@ +use convert_case::{Case, Casing}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::{self, Error}, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-unicorn(filename-case): Filename should not be in {1} case")] +#[diagnostic(severity(warning))] +struct FilenameCaseDiagnostic(#[label] pub Span, &'static str); + +#[allow(clippy::struct_excessive_bools)] +#[derive(Debug, Clone)] +pub struct FilenameCase { + kebab_case: bool, + camel_case: bool, + snake_case: bool, + pascal_case: bool, + underscore_case: bool, +} + +impl Default for FilenameCase { + fn default() -> Self { + Self { + kebab_case: false, + camel_case: true, + snake_case: false, + pascal_case: true, + underscore_case: false, + } + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// ### Why is this bad? + /// + /// ### Example + /// ``` + FilenameCase, + style +); + +impl Rule for FilenameCase { + fn run_once<'a>(&self, ctx: &LintContext<'_>) { + let Some(filename) = ctx.file_path().file_stem().and_then(|s| s.to_str()) else { return }; + + let mut case_name = ""; + + let cases = [ + (Case::Kebab, "kebab", self.kebab_case), + (Case::Camel, "camel", self.camel_case), + (Case::Snake, "snake", self.snake_case), + (Case::Pascal, "pascal", self.pascal_case), + (Case::Pascal, "underscore", self.underscore_case), + ]; + + for (case, name, condition) in cases { + if filename.to_case(case) == filename { + if condition { + return; + } + case_name = name; + } + } + + ctx.diagnostic(FilenameCaseDiagnostic(Span::default(), case_name)); + } +} diff --git a/crates/oxc_linter/src/service.rs b/crates/oxc_linter/src/service.rs index 33036f81a33bc..8f7ae3f780002 100644 --- a/crates/oxc_linter/src/service.rs +++ b/crates/oxc_linter/src/service.rs @@ -223,7 +223,8 @@ impl Runtime { return semantic_ret.errors.into_iter().map(|err| Message::new(err, None)).collect(); }; - let lint_ctx = LintContext::new(&Rc::new(semantic_ret.semantic)); + let lint_ctx = + LintContext::new(path.to_path_buf().into_boxed_path(), &Rc::new(semantic_ret.semantic)); self.linter.run(lint_ctx) } diff --git a/crates/oxc_linter_plugin/src/test.rs b/crates/oxc_linter_plugin/src/test.rs index b498791574bb1..92c05413bfc31 100644 --- a/crates/oxc_linter_plugin/src/test.rs +++ b/crates/oxc_linter_plugin/src/test.rs @@ -48,7 +48,8 @@ fn run_individual_test( let semantic = Rc::new(semantic); - let mut lint_ctx = LintContext::new(&Rc::clone(&semantic)); + let mut lint_ctx = + LintContext::new(PathBuf::from(file_path).into_boxed_path(), &Rc::clone(&semantic)); let result = plugin.lint_file_with_rule( &mut lint_ctx, diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index dfdd52be2e2ed..9bdcf11d56827 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -1,6 +1,6 @@ mod options; -use std::{cell::RefCell, collections::BTreeMap, rc::Rc, sync::Arc}; +use std::{cell::RefCell, collections::BTreeMap, path::PathBuf, rc::Rc, sync::Arc}; use oxc_allocator::Allocator; use oxc_diagnostics::Error; @@ -159,7 +159,8 @@ impl Oxc { let allocator = Allocator::default(); let source_text = &self.source_text; - let source_type = SourceType::from_path("test.tsx").unwrap_or_default(); + let path = PathBuf::from("test.tsx"); + let source_type = SourceType::from_path(&path).unwrap_or_default(); let ret = Parser::new(&allocator, source_text, source_type) .allow_return_outside_function(parser_options.allow_return_outside_function) @@ -184,7 +185,7 @@ impl Oxc { self.save_diagnostics(semantic_ret.errors); let semantic = Rc::new(semantic_ret.semantic); - let lint_ctx = LintContext::new(&semantic); + let lint_ctx = LintContext::new(path.into_boxed_path(), &semantic); let linter_ret = Linter::new().run(lint_ctx); let diagnostics = linter_ret.into_iter().map(|e| e.error).collect(); self.save_diagnostics(diagnostics); diff --git a/editor/vscode/server/src/linter.rs b/editor/vscode/server/src/linter.rs index 97dd971ccafc2..f757c2c5a5dbd 100644 --- a/editor/vscode/server/src/linter.rs +++ b/editor/vscode/server/src/linter.rs @@ -263,7 +263,8 @@ impl IsolatedLintHandler { return Some(Self::wrap_diagnostics(path, &source_text, reports)); }; - let mut lint_ctx = LintContext::new(&Rc::new(semantic_ret.semantic)); + let mut lint_ctx = + LintContext::new(path.to_path_buf().into_boxed_path(), &Rc::new(semantic_ret.semantic)); { if let Ok(guard) = plugin.read() { if let Some(plugin) = &*guard { diff --git a/tasks/benchmark/benches/linter.rs b/tasks/benchmark/benches/linter.rs index 1a728e59ff06b..ec06355d073c0 100644 --- a/tasks/benchmark/benches/linter.rs +++ b/tasks/benchmark/benches/linter.rs @@ -35,7 +35,9 @@ fn bench_linter(criterion: &mut Criterion) { LintOptions::default().with_filter(vec![(AllowWarnDeny::Deny, "all".into())]); let linter = Linter::from_options(lint_options); let semantic = Rc::new(semantic_ret.semantic); - b.iter(|| linter.run(LintContext::new(&semantic))); + b.iter(|| { + linter.run(LintContext::new(PathBuf::from("").into_boxed_path(), &semantic)) + }); }, ); }