Skip to content

Commit 6ed4822

Browse files
committed
First part!
1 parent 6ab1a20 commit 6ed4822

File tree

4 files changed

+250
-0
lines changed

4 files changed

+250
-0
lines changed

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ impl RuleRunner for crate::rules::eslint::block_scoped_var::BlockScopedVar {
2626
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
2727
}
2828

29+
impl RuleRunner for crate::rules::eslint::camelcase::Camelcase {
30+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
31+
AstType::BindingIdentifier,
32+
AstType::IdentifierReference,
33+
AstType::ObjectProperty,
34+
]));
35+
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
36+
}
37+
2938
impl RuleRunner for crate::rules::eslint::class_methods_use_this::ClassMethodsUseThis {
3039
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
3140
AstType::AccessorProperty,

crates/oxc_linter/src/rules.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub(crate) mod eslint {
4343
pub mod array_callback_return;
4444
pub mod arrow_body_style;
4545
pub mod block_scoped_var;
46+
pub mod camelcase;
4647
pub mod class_methods_use_this;
4748
pub mod constructor_super;
4849
pub mod curly;
@@ -694,6 +695,7 @@ oxc_macros::declare_all_lint_rules! {
694695
eslint::max_nested_callbacks,
695696
eslint::max_params,
696697
eslint::new_cap,
698+
eslint::camelcase,
697699
eslint::no_useless_computed_key,
698700
eslint::no_unassigned_vars,
699701
eslint::no_extra_bind,
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
use oxc_ast::AstKind;
2+
use oxc_diagnostics::OxcDiagnostic;
3+
use oxc_macros::declare_oxc_lint;
4+
use oxc_span::{CompactStr, Span};
5+
use serde_json::Value;
6+
7+
use crate::{AstNode, context::LintContext, rule::Rule};
8+
9+
fn camelcase_diagnostic(span: Span, name: &str) -> OxcDiagnostic {
10+
OxcDiagnostic::warn(format!("Identifier '{}' is not in camel case.", name))
11+
.with_help("Use camelCase naming convention")
12+
.with_label(span)
13+
}
14+
15+
#[derive(Debug, Clone)]
16+
pub struct CamelcaseConfig {
17+
allow: Vec<CompactStr>,
18+
}
19+
20+
impl Default for CamelcaseConfig {
21+
fn default() -> Self {
22+
Self { allow: vec![] }
23+
}
24+
}
25+
26+
#[derive(Debug, Default, Clone)]
27+
pub struct Camelcase(Box<CamelcaseConfig>);
28+
29+
impl std::ops::Deref for Camelcase {
30+
type Target = CamelcaseConfig;
31+
32+
fn deref(&self) -> &Self::Target {
33+
&self.0
34+
}
35+
}
36+
37+
impl From<&Value> for Camelcase {
38+
fn from(raw: &Value) -> Self {
39+
let config_entry = raw.get(0);
40+
if config_entry.is_none() {
41+
return Self(Box::default());
42+
}
43+
44+
let config = config_entry.unwrap().as_object();
45+
if config.is_none() {
46+
return Self(Box::default());
47+
}
48+
49+
let config = config.unwrap();
50+
51+
let allow = if let Some(allow_value) = config.get("allow") {
52+
if let Some(allow_array) = allow_value.as_array() {
53+
allow_array.iter().filter_map(|v| v.as_str()).map(CompactStr::new).collect()
54+
} else {
55+
vec![]
56+
}
57+
} else {
58+
vec![]
59+
};
60+
61+
Self(Box::new(CamelcaseConfig { allow }))
62+
}
63+
}
64+
65+
declare_oxc_lint!(
66+
/// ### What it does
67+
///
68+
/// Enforce camelcase naming convention.
69+
///
70+
/// ### Why is this bad?
71+
///
72+
/// When it comes to naming variables, style guides generally fall into one of two camps:
73+
/// camelcase (`variableName`) and underscores (`variable_name`). This rule focuses on using
74+
/// the camelcase approach. If your style guide calls for camelcasing your variable names,
75+
/// then this rule is for you!
76+
///
77+
/// ### Examples
78+
///
79+
/// Examples of **incorrect** code for this rule:
80+
/// ```javascript
81+
/// import { no_camelcased } from "external-module"
82+
///
83+
/// var my_favorite_color = "#112C85";
84+
///
85+
/// function do_something() {
86+
/// // ...
87+
/// }
88+
///
89+
/// obj.do_something = function() {
90+
/// // ...
91+
/// };
92+
///
93+
/// function foo({ no_camelcased }) {
94+
/// // ...
95+
/// };
96+
///
97+
/// function foo({ isCamelcased: no_camelcased }) {
98+
/// // ...
99+
/// }
100+
///
101+
/// function foo({ no_camelcased = 'default value' }) {
102+
/// // ...
103+
/// };
104+
///
105+
/// var obj = {
106+
/// my_pref: 1
107+
/// };
108+
///
109+
/// var { category_id = 1 } = query;
110+
///
111+
/// var { category_id: category_alias } = query;
112+
///
113+
/// var { category_id: categoryId, ...other_params } = query;
114+
/// ```
115+
///
116+
/// Examples of **correct** code for this rule:
117+
/// ```javascript
118+
/// import { no_camelcased as camelCased } from "external-module";
119+
///
120+
/// var myFavoriteColor = "#112C85";
121+
/// var _myFavoriteColor = "#112C85";
122+
/// var myFavoriteColor_ = "#112C85";
123+
/// var MY_FAVORITE_COLOR = "#112C85";
124+
/// var foo = bar.baz_boom;
125+
/// var foo = { qux: bar.baz_boom };
126+
///
127+
/// obj.do_something();
128+
/// do_something();
129+
/// new do_something();
130+
///
131+
/// var { category_id: categoryId } = query;
132+
///
133+
/// function foo({ isCamelCased }) {
134+
/// // ...
135+
/// };
136+
///
137+
/// function foo({ isCamelCased = 'default value' }) {
138+
/// // ...
139+
/// };
140+
///
141+
/// var myObject = {
142+
/// isCamelCased: true
143+
/// };
144+
///
145+
/// var { categoryId } = query;
146+
///
147+
/// var { categoryId, ...otherParams } = query;
148+
/// ```
149+
Camelcase,
150+
eslint,
151+
style
152+
);
153+
154+
impl Rule for Camelcase {
155+
fn from_configuration(value: Value) -> Self {
156+
Self::from(&value)
157+
}
158+
159+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
160+
match node.kind() {
161+
AstKind::BindingIdentifier(binding_ident) => {
162+
let name = &binding_ident.name;
163+
let atom_str = name.as_str();
164+
165+
if !self.is_underscored(atom_str) || self.is_allowed(atom_str) {
166+
return;
167+
}
168+
169+
ctx.diagnostic(camelcase_diagnostic(binding_ident.span, atom_str));
170+
}
171+
_ => {}
172+
}
173+
}
174+
}
175+
176+
impl Camelcase {
177+
fn is_underscored(&self, name: &str) -> bool {
178+
// Remove leading and trailing underscores
179+
let name_body = name.trim_start_matches('_').trim_end_matches('_');
180+
181+
// If there's an underscore, it might be A_CONSTANT, which is okay
182+
name_body.contains('_') && name_body != name_body.to_uppercase()
183+
}
184+
185+
fn is_allowed(&self, name: &str) -> bool {
186+
self.allow.iter().any(|entry| {
187+
name == entry.as_str() || {
188+
// Try to match as regex - simplified, just exact match for now
189+
false
190+
}
191+
})
192+
}
193+
}
194+
195+
#[test]
196+
fn test() {
197+
use crate::tester::Tester;
198+
199+
let pass = vec![
200+
("var firstName = \"Ned\"", None),
201+
("var __myPrivateVariable = \"Patrick\"", None),
202+
("var myPrivateVariable_ = \"Patrick\"", None),
203+
("function doSomething(){}", None),
204+
("var MY_GLOBAL = 1", None),
205+
("var ANOTHER_GLOBAL = 1", None),
206+
("var foo_bar", Some(serde_json::json!([{ "allow": ["foo_bar"] }]))),
207+
];
208+
209+
let fail = vec![
210+
("var no_camelcased = 1;", None),
211+
("function no_camelcased(){}", None),
212+
("function bar( obj_name ){}", None),
213+
];
214+
215+
Tester::new(Camelcase::NAME, Camelcase::PLUGIN, pass, fail).test_and_snapshot();
216+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
---
4+
eslint(camelcase): Identifier 'no_camelcased' is not in camel case.
5+
╭─[camelcase.tsx:1:5]
6+
1var no_camelcased = 1;
7+
· ─────────────
8+
╰────
9+
help: Use camelCase naming convention
10+
11+
eslint(camelcase): Identifier 'no_camelcased' is not in camel case.
12+
╭─[camelcase.tsx:1:10]
13+
1function no_camelcased(){}
14+
· ─────────────
15+
╰────
16+
help: Use camelCase naming convention
17+
18+
eslint(camelcase): Identifier 'obj_name' is not in camel case.
19+
╭─[camelcase.tsx:1:15]
20+
1function bar( obj_name ){}
21+
· ────────
22+
╰────
23+
help: Use camelCase naming convention

0 commit comments

Comments
 (0)