mirror of
https://github.com/danbulant/oxc
synced 2026-05-21 05:08:45 +00:00
feat(linter): eslint-plugin-unicorn(filename-case) (#978)
This commit is contained in:
parent
d5fb3d43ab
commit
eaa0c58e24
10 changed files with 109 additions and 18 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1590,6 +1590,7 @@ dependencies = [
|
|||
name = "oxc_linter"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"dashmap",
|
||||
"insta",
|
||||
"itertools 0.11.0",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<Path>,
|
||||
}
|
||||
|
||||
impl<'a> LintContext<'a> {
|
||||
pub fn new(semantic: &Rc<Semantic<'a>>) -> Self {
|
||||
pub fn new(file_path: Box<Path>, semantic: &Rc<Semantic<'a>>) -> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
74
crates/oxc_linter/src/rules/unicorn/filename_case.rs
Normal file
74
crates/oxc_linter/src/rules/unicorn/filename_case.rs
Normal file
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue