feat(linter): eslint-plugin-unicorn(filename-case) (#978)

This commit is contained in:
Boshen 2023-10-11 15:23:17 +08:00 committed by GitHub
parent d5fb3d43ab
commit eaa0c58e24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 109 additions and 18 deletions

1
Cargo.lock generated
View file

@ -1590,6 +1590,7 @@ dependencies = [
name = "oxc_linter"
version = "0.0.0"
dependencies = [
"convert_case",
"dashmap",
"insta",
"itertools 0.11.0",

View file

@ -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"

View file

@ -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;
}

View file

@ -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,

View 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));
}
}

View file

@ -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)
}

View file

@ -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,

View file

@ -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);

View file

@ -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 {

View file

@ -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))
});
},
);
}