use super::{Context, LintRule};
use crate::handler::{Handler, Traverse};
use crate::swc_util::StringRepr;
use crate::tags::Tags;
use deno_ast::view::{Node, NodeKind, NodeTrait};
use deno_ast::{view as ast_view, SourceRange, SourceRanged};
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug)]
pub struct Camelcase;
const CODE: &str = "camelcase";
impl LintRule for Camelcase {
fn tags(&self) -> Tags {
&[]
}
fn code(&self) -> &'static str {
CODE
}
fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: ast_view::Program,
) {
let mut handler = CamelcaseHandler::default();
handler.traverse(program, context);
handler.report_errors(context);
}
}
fn is_underscored(ident_name: &str) -> bool {
let trimmed_ident = ident_name.trim_matches('_');
trimmed_ident.contains('_')
&& trimmed_ident != trimmed_ident.to_ascii_uppercase()
}
fn to_camelcase(ident_name: &str) -> String {
if !is_underscored(ident_name) {
return ident_name.to_string();
}
static UNDERSCORE_CHAR_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"([^_])_([a-z])").unwrap());
let result = UNDERSCORE_CHAR_RE.replace_all(ident_name, |caps: &Captures| {
format!("{}{}", &caps[1], caps[2].to_ascii_uppercase())
});
if result != ident_name {
return result.into_owned();
}
ident_name.to_ascii_uppercase()
}
enum IdentToCheck {
Variable(String),
ObjectKey {
key_name: String,
is_shorthand: bool,
},
Function(String),
Class(String),
TypeAlias(String),
Interface(String),
EnumName(String),
EnumVariant(String),
Namespace(String),
Module(String),
ObjectPat {
key_name: String,
value_name: Option<String>,
has_default: bool,
is_destructuring: bool,
},
NamedImport {
local: String,
imported: Option<String>,
},
}
impl IdentToCheck {
fn variable(name: impl AsRef<str>) -> Self {
Self::Variable(name.as_ref().to_string())
}
fn object_key(key_name: impl AsRef<str>, is_shorthand: bool) -> Self {
Self::ObjectKey {
key_name: key_name.as_ref().to_string(),
is_shorthand,
}
}
fn function(name: impl AsRef<str>) -> Self {
Self::Function(name.as_ref().to_string())
}
fn class(name: impl AsRef<str>) -> Self {
Self::Class(name.as_ref().to_string())
}
fn type_alias(name: impl AsRef<str>) -> Self {
Self::TypeAlias(name.as_ref().to_string())
}
fn interface(name: impl AsRef<str>) -> Self {
Self::Interface(name.as_ref().to_string())
}
fn enum_name(name: impl AsRef<str>) -> Self {
Self::EnumName(name.as_ref().to_string())
}
fn enum_variant(name: impl AsRef<str>) -> Self {
Self::EnumVariant(name.as_ref().to_string())
}
fn namespace(name: impl AsRef<str>) -> Self {
Self::Namespace(name.as_ref().to_string())
}
fn module(name: impl AsRef<str>) -> Self {
Self::Module(name.as_ref().to_string())
}
fn object_pat<K, V>(
key_name: &K,
value_name: Option<&V>,
has_default: bool,
is_destructuring: bool,
) -> Self
where
K: AsRef<str>,
V: AsRef<str>,
{
Self::ObjectPat {
key_name: key_name.as_ref().to_string(),
value_name: value_name.map(|v| v.as_ref().to_string()),
has_default,
is_destructuring,
}
}
fn named_import<L, I>(local: &L, imported: Option<&I>) -> Self
where
L: AsRef<str>,
I: AsRef<str>,
{
Self::NamedImport {
local: local.as_ref().to_string(),
imported: imported.map(|i| i.as_ref().to_string()),
}
}
fn get_ident_name(&self) -> &str {
match self {
IdentToCheck::Variable(name)
| IdentToCheck::Function(name)
| IdentToCheck::Class(name)
| IdentToCheck::TypeAlias(name)
| IdentToCheck::Interface(name)
| IdentToCheck::EnumName(name)
| IdentToCheck::EnumVariant(name)
| IdentToCheck::Namespace(name)
| IdentToCheck::Module(name) => name,
IdentToCheck::ObjectKey { ref key_name, .. } => key_name,
IdentToCheck::ObjectPat {
key_name,
value_name,
..
} => {
if let Some(value_name) = value_name {
value_name
} else {
key_name
}
}
IdentToCheck::NamedImport { local, .. } => local,
}
}
fn to_message(&self) -> String {
format!(
"Identifier '{}' is not in camel case.",
self.get_ident_name()
)
}
fn to_hint(&self) -> String {
match self {
IdentToCheck::Variable(name) | IdentToCheck::Function(name) => {
format!("Consider renaming `{}` to `{}`", name, to_camelcase(name))
}
IdentToCheck::ObjectKey {
ref key_name,
is_shorthand,
} => {
if *is_shorthand {
format!(
r#"Consider writing `{camel_cased}: {original}` or `"{original}": {original}`"#,
camel_cased = to_camelcase(key_name),
original = key_name
)
} else {
format!(
r#"Consider renaming `{original}` to `{camel_cased}`, or wrapping it in quotation mark like `"{original}"`"#,
camel_cased = to_camelcase(key_name),
original = key_name
)
}
}
IdentToCheck::Class(name)
| IdentToCheck::TypeAlias(name)
| IdentToCheck::Interface(name)
| IdentToCheck::EnumName(name)
| IdentToCheck::EnumVariant(name)
| IdentToCheck::Namespace(name)
| IdentToCheck::Module(name) => {
let camel_cased = to_camelcase(name);
static FIRST_CHAR_LOWERCASE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[a-z]").unwrap());
let pascal_cased = FIRST_CHAR_LOWERCASE
.replace(&camel_cased, |caps: &Captures| {
caps[0].to_ascii_uppercase()
});
format!("Consider renaming `{}` to `{}`", name, pascal_cased)
}
IdentToCheck::ObjectPat {
key_name,
value_name,
has_default,
is_destructuring: in_var_decl,
} => {
let rename_name = if let Some(value_name) = value_name {
Some(value_name)
} else if *in_var_decl {
None
} else {
Some(key_name)
};
if let Some(name) = rename_name {
return format!(
"Consider renaming `{}` to `{}`",
name,
to_camelcase(name),
);
}
if *has_default {
return format!(
"Consider replacing `{{ {key} = .. }}` with `{{ {key}: {value} = .. }}`",
key = key_name,
value = to_camelcase(key_name),
);
}
format!(
"Consider replacing `{{ {key} }}` with `{{ {key}: {value} }}`",
key = key_name,
value = to_camelcase(key_name),
)
}
IdentToCheck::NamedImport { local, imported } => {
if imported.is_some() {
format!("Consider renaming `{}` to `{}`", local, to_camelcase(local))
} else {
format!(
"Consider replacing `{{ {local} }}` with `{{ {local} as {camel_cased_local} }}`",
local = local,
camel_cased_local = to_camelcase(local),
)
}
}
}
}
}
#[derive(Default)]
struct CamelcaseHandler {
errors: BTreeMap<SourceRange, IdentToCheck>,
visited: BTreeSet<SourceRange>,
}
impl CamelcaseHandler {
fn report_errors(self, ctx: &mut Context) {
for (range, error_ident) in self.errors {
ctx.add_diagnostic_with_hint(
range,
CODE,
error_ident.to_message(),
error_ident.to_hint(),
);
}
}
fn check_ident<S: SourceRanged>(&mut self, range: &S, ident: IdentToCheck) {
let range = range.range();
if self.visited.insert(range) && is_underscored(ident.get_ident_name()) {
self.errors.insert(range, ident);
}
}
fn check_ts_type(&mut self, ty: &ast_view::TsType) {
if let ast_view::TsType::TsTypeLit(type_lit) = ty {
for member in type_lit.members {
self.check_ts_type_element(member);
}
}
}
fn check_ts_type_element(&mut self, ty_el: &ast_view::TsTypeElement) {
use deno_ast::view::TsTypeElement::*;
match ty_el {
TsPropertySignature(prop_sig) => {
if let ast_view::Expr::Ident(ident) = prop_sig.key {
self.check_ident(ident, IdentToCheck::object_key(ident.inner, false));
}
if let Some(type_ann) = &prop_sig.type_ann {
self.check_ts_type(&type_ann.type_ann);
}
}
TsMethodSignature(method_sig) => {
if let ast_view::Expr::Ident(ident) = method_sig.key {
self.check_ident(ident, IdentToCheck::function(ident.inner));
}
if let Some(type_ann) = &method_sig.type_ann {
self.check_ts_type(&type_ann.type_ann);
}
}
TsGetterSignature(getter_sig) => {
if let ast_view::Expr::Ident(ident) = getter_sig.key {
self.check_ident(ident, IdentToCheck::function(ident.inner));
}
if let Some(type_ann) = &getter_sig.type_ann {
self.check_ts_type(&type_ann.type_ann);
}
}
TsSetterSignature(setter_sig) => {
if let ast_view::Expr::Ident(ident) = setter_sig.key {
self.check_ident(ident, IdentToCheck::function(ident.inner));
}
}
TsIndexSignature(_)
| TsCallSignatureDecl(_)
| TsConstructSignatureDecl(_) => {}
}
}
fn check_pat(&mut self, pat: &ast_view::Pat) {
match pat {
ast_view::Pat::Ident(ident) => {
self.check_ident(ident, IdentToCheck::variable(ident.id.inner));
}
ast_view::Pat::Array(ast_view::ArrayPat { elems, .. }) => {
for pat in elems.iter().flatten() {
self.check_pat(pat);
}
}
ast_view::Pat::Rest(ast_view::RestPat { ref arg, .. }) => {
self.check_pat(arg);
}
ast_view::Pat::Object(ast_view::ObjectPat { props, .. }) => {
for prop in *props {
match prop {
ast_view::ObjectPatProp::KeyValue(ast_view::KeyValuePatProp {
ref key,
ref value,
..
}) => match value {
ast_view::Pat::Ident(value_ident) => {
self.check_ident(
value_ident,
IdentToCheck::object_pat(
&key.string_repr().unwrap_or_else(|| "[KEY]".to_string()),
Some(&value_ident.id.inner),
false,
pat_in_var_declarator(pat.into()),
),
);
}
ast_view::Pat::Assign(ast_view::AssignPat {
left: ast_view::Pat::Ident(value_ident),
..
}) => {
self.check_ident(
value_ident,
IdentToCheck::object_pat(
&key.string_repr().unwrap_or_else(|| "[KEY]".to_string()),
Some(&value_ident.id.inner),
true,
pat_in_var_declarator(pat.into()),
),
);
}
_ => {
self.check_pat(value);
}
},
ast_view::ObjectPatProp::Assign(ast_view::AssignPatProp {
ref key,
ref value,
..
}) => {
let has_default = value.is_some();
let in_var_declarator = pat_in_var_declarator(pat.into());
if !in_var_declarator {
self.check_ident(
key,
IdentToCheck::object_pat::<&str, &str>(
&key.inner.as_ref(),
None,
has_default,
in_var_declarator,
),
);
}
}
ast_view::ObjectPatProp::Rest(ast_view::RestPat {
ref arg,
..
}) => {
self.check_pat(arg);
}
}
}
}
ast_view::Pat::Assign(ast_view::AssignPat { ref left, .. }) => {
self.check_pat(left);
}
ast_view::Pat::Expr(expr) => {
if let ast_view::Expr::Ident(ident) = expr {
self.check_ident(ident, IdentToCheck::variable(ident.inner));
}
}
ast_view::Pat::Invalid(_) => {}
}
}
}
impl Handler for CamelcaseHandler {
fn fn_decl(&mut self, fn_decl: &ast_view::FnDecl, ctx: &mut Context) {
if fn_decl.declare() {
ctx.stop_traverse();
return;
}
self
.check_ident(&fn_decl.ident, IdentToCheck::function(fn_decl.ident.inner));
}
fn class_decl(
&mut self,
class_decl: &ast_view::ClassDecl,
ctx: &mut Context,
) {
if class_decl.declare() {
ctx.stop_traverse();
return;
}
self.check_ident(
&class_decl.ident,
IdentToCheck::class(class_decl.ident.inner),
);
}
fn var_decl(&mut self, var_decl: &ast_view::VarDecl, ctx: &mut Context) {
if var_decl.declare() {
ctx.stop_traverse();
return;
}
for decl in var_decl.decls {
self.check_pat(&decl.name);
if let Some(expr) = &decl.init {
match expr {
ast_view::Expr::Object(ast_view::ObjectLit { props, .. }) => {
for prop in *props {
if let ast_view::PropOrSpread::Prop(prop) = prop {
match prop {
ast_view::Prop::Shorthand(ident) => self.check_ident(
ident,
IdentToCheck::object_key(ident.inner, true),
),
ast_view::Prop::KeyValue(ast_view::KeyValueProp {
ref key,
..
})
| ast_view::Prop::Getter(ast_view::GetterProp {
ref key,
..
})
| ast_view::Prop::Setter(ast_view::SetterProp {
ref key,
..
})
| ast_view::Prop::Method(ast_view::MethodProp {
ref key,
..
}) => {
if let ast_view::PropName::Ident(ident) = key {
self.check_ident(
ident,
IdentToCheck::object_key(ident.inner, false),
);
}
}
ast_view::Prop::Assign(_) => {}
}
}
}
}
ast_view::Expr::Fn(ast_view::FnExpr {
ident: Some(ident), ..
}) => {
self.check_ident(ident, IdentToCheck::function(ident.inner));
}
ast_view::Expr::Class(ast_view::ClassExpr {
ident: Some(ident),
..
}) => {
self.check_ident(ident, IdentToCheck::class(ident.inner));
}
_ => {}
}
}
}
}
fn param(&mut self, param: &ast_view::Param, _ctx: &mut Context) {
self.check_pat(¶m.pat);
}
fn import_named_specifier(
&mut self,
import_named_specifier: &ast_view::ImportNamedSpecifier,
_ctx: &mut Context,
) {
let ast_view::ImportNamedSpecifier {
local, imported, ..
} = import_named_specifier;
if let Some(imported) = &imported {
self.check_ident(
local,
IdentToCheck::named_import(
local.inner,
Some(&match imported {
ast_view::ModuleExportName::Ident(ident) => ident.sym().to_string(),
ast_view::ModuleExportName::Str(str) => {
str.value().to_string_lossy().into_owned()
}
}),
),
);
}
}
fn import_default_specifier(
&mut self,
import_default_specifier: &ast_view::ImportDefaultSpecifier,
_ctx: &mut Context,
) {
let ast_view::ImportDefaultSpecifier { local, .. } =
import_default_specifier;
self.check_ident(local, IdentToCheck::variable(local.inner));
}
fn import_star_as_specifier(
&mut self,
import_star_as_specifier: &ast_view::ImportStarAsSpecifier,
_ctx: &mut Context,
) {
let ast_view::ImportStarAsSpecifier { local, .. } =
import_star_as_specifier;
self.check_ident(local, IdentToCheck::variable(local.inner));
}
fn export_namespace_specifier(
&mut self,
export_namespace_specifier: &ast_view::ExportNamespaceSpecifier,
_ctx: &mut Context,
) {
let ast_view::ExportNamespaceSpecifier { name, .. } =
export_namespace_specifier;
if let ast_view::ModuleExportName::Ident(name) = name {
self.check_ident(name, IdentToCheck::variable(name.inner));
}
}
fn ts_type_alias_decl(
&mut self,
type_alias: &ast_view::TsTypeAliasDecl,
ctx: &mut Context,
) {
if type_alias.declare() {
ctx.stop_traverse();
return;
}
self.check_ident(
&type_alias.id,
IdentToCheck::type_alias(type_alias.id.inner),
);
self.check_ts_type(&type_alias.type_ann);
}
fn ts_interface_decl(
&mut self,
interface_decl: &ast_view::TsInterfaceDecl,
ctx: &mut Context,
) {
if interface_decl.declare() {
ctx.stop_traverse();
return;
}
self.check_ident(
&interface_decl.id,
IdentToCheck::interface(interface_decl.id.inner),
);
for ty_el in interface_decl.body.body {
self.check_ts_type_element(ty_el);
}
}
fn ts_namespace_decl(
&mut self,
namespace_decl: &ast_view::TsNamespaceDecl,
ctx: &mut Context,
) {
if namespace_decl.declare() {
ctx.stop_traverse();
return;
}
self.check_ident(
&namespace_decl.id,
IdentToCheck::namespace(namespace_decl.id.inner),
);
}
fn ts_module_decl(
&mut self,
module_decl: &ast_view::TsModuleDecl,
ctx: &mut Context,
) {
if module_decl.declare() {
ctx.stop_traverse();
return;
}
if let ast_view::TsModuleName::Ident(id) = &module_decl.id {
self.check_ident(id, IdentToCheck::module(id.inner));
}
}
fn ts_enum_decl(
&mut self,
enum_decl: &ast_view::TsEnumDecl,
ctx: &mut Context,
) {
if enum_decl.declare() {
ctx.stop_traverse();
return;
}
self
.check_ident(&enum_decl.id, IdentToCheck::enum_name(enum_decl.id.inner));
for variant in enum_decl.members {
if let ast_view::TsEnumMemberId::Ident(id) = &variant.id {
self.check_ident(id, IdentToCheck::enum_variant(id.inner));
}
}
}
}
fn pat_in_var_declarator(pat: Node) -> bool {
for ancestor in pat.ancestors() {
match ancestor.kind() {
NodeKind::VarDeclarator => {
return true;
}
NodeKind::ArrayPat
| NodeKind::ObjectPat
| NodeKind::AssignPat
| NodeKind::AssignPatProp
| NodeKind::RestPat
| NodeKind::KeyValuePatProp => {
}
_ => {
return false;
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_underscored() {
let tests = [
("foo_bar", true),
("fooBar", false),
("FooBar", false),
("foo_bar_baz", true),
("_foo_bar_baz", true),
("__foo_bar_baz__", true),
("__fooBar_baz__", true),
("__fooBarBaz__", false),
("Sha3_224", true),
("SHA3_224", false),
];
for &(input, expected) in tests.iter() {
assert_eq!(expected, is_underscored(input));
}
}
#[test]
fn test_to_camelcase() {
let tests = [
("foo_bar", "fooBar"),
("fooBar", "fooBar"),
("FooBar", "FooBar"),
("foo_bar_baz", "fooBarBaz"),
("_foo_bar_baz", "_fooBarBaz"),
("__foo_bar_baz__", "__fooBarBaz__"),
("Sha3_224", "SHA3_224"),
("SHA3_224", "SHA3_224"),
("_leading", "_leading"),
("trailing_", "trailing_"),
("_bothEnds_", "_bothEnds_"),
];
for &(input, expected) in tests.iter() {
assert_eq!(expected, to_camelcase(input));
}
}
#[test]
fn test_to_hint() {
fn s(s: &str) -> String {
s.to_string()
}
let tests = [
(
IdentToCheck::Variable(s("foo_bar")),
"Consider renaming `foo_bar` to `fooBar`",
),
(
IdentToCheck::Function(s("foo_bar")),
"Consider renaming `foo_bar` to `fooBar`",
),
(
IdentToCheck::Class(s("foo_bar")),
"Consider renaming `foo_bar` to `FooBar`",
),
(
IdentToCheck::ObjectPat {
key_name: s("foo_bar"),
value_name: None,
has_default: false,
is_destructuring: true,
},
"Consider replacing `{ foo_bar }` with `{ foo_bar: fooBar }`",
),
(
IdentToCheck::ObjectPat {
key_name: s("foo_bar"),
value_name: Some(s("snake_case")),
has_default: false,
is_destructuring: true,
},
"Consider renaming `snake_case` to `snakeCase`",
),
(
IdentToCheck::ObjectPat {
key_name: s("foo_bar"),
value_name: None,
has_default: true,
is_destructuring: true,
},
"Consider replacing `{ foo_bar = .. }` with `{ foo_bar: fooBar = .. }`",
),
(
IdentToCheck::ObjectPat {
key_name: s("foo_bar"),
value_name: None,
has_default: true,
is_destructuring: false,
},
"Consider renaming `foo_bar` to `fooBar`",
),
(
IdentToCheck::ObjectPat {
key_name: s("foo_bar"),
value_name: Some(s("snake_case")),
has_default: true,
is_destructuring: true,
},
"Consider renaming `snake_case` to `snakeCase`",
),
(
IdentToCheck::NamedImport {
local: s("foo_bar"),
imported: None,
},
"Consider replacing `{ foo_bar }` with `{ foo_bar as fooBar }`",
),
(
IdentToCheck::NamedImport {
local: s("foo_bar"),
imported: Some(s("snake_case")),
},
"Consider renaming `foo_bar` to `fooBar`",
),
];
for (error_ident, expected) in tests.iter() {
assert_eq!(*expected, error_ident.to_hint());
}
}
#[test]
fn camelcase_valid() {
assert_lint_ok! {
Camelcase,
r#"firstName = "Ichigo""#,
r#"FIRST_NAME = "Ichigo""#,
r#"__myPrivateVariable = "Hoshimiya""#,
r#"myPrivateVariable_ = "Hoshimiya""#,
r#"function doSomething(){}"#,
r#"do_something()"#,
r#"new do_something"#,
r#"new do_something()"#,
r#"foo.do_something()"#,
r#"var foo = bar.baz_boom;"#,
r#"var foo = bar.baz_boom.something;"#,
r#"foo.boom_pow.qux = bar.baz_boom.something;"#,
r#"if (bar.baz_boom) {}"#,
r#"var obj = { key: foo.bar_baz };"#,
r#"var arr = [foo.bar_baz];"#,
r#"[foo.bar_baz]"#,
r#"var arr = [foo.bar_baz.qux];"#,
r#"[foo.bar_baz.nesting]"#,
r#"if (foo.bar_baz === boom.bam_pow) { [foo.baz_boom] }"#,
r#"var o = {key: 1}"#,
r#"var o = {_leading: 1}"#,
r#"var o = {trailing_: 1}"#,
r#"const { ['foo']: _foo } = obj;"#,
r#"const { [_foo_]: foo } = obj;"#,
r#"var { category_id: category } = query;"#,
r#"var { _leading } = query;"#,
r#"var { trailing_ } = query;"#,
r#"var { or_middle } = query;"#,
r#"var { category_id = 1 } = query;"#,
r#"var { category_id: { property_test } } = query;"#,
r#"const { no_camelcased = false } = bar;"#,
r#"import { camelCased } from "external module";"#,
r#"import { _leading } from "external module";"#,
r#"import { trailing_ } from "external module";"#,
r#"import { or_middle } from "external module";"#,
r#"import { no_camelcased as camelCased } from "external-module";"#,
r#"import { no_camelcased as _leading } from "external-module";"#,
r#"import { no_camelcased as trailing_ } from "external-module";"#,
r#"import { no_camelcased as camelCased, anotherCamelCased } from "external-module";"#,
r#"import { camelCased } from 'mod'"#,
r#"var _camelCased = aGlobalVariable"#,
r#"var camelCased = _aGlobalVariable"#,
r#"function foo({ no_camelcased: camelCased }) {};"#,
r#"function foo({ no_camelcased: _leading }) {};"#,
r#"function foo({ no_camelcased: trailing_ }) {};"#,
r#"function foo({ camelCased = 'default value' }) {};"#,
r#"function foo({ _leading = 'default value' }) {};"#,
r#"function foo({ trailing_ = 'default value' }) {};"#,
r#"function foo({ camelCased }) {};"#,
r#"function foo({ _leading }) {}"#,
r#"function foo({ trailing_ }) {}"#,
r#"({obj} = baz.fo_o);"#,
r#"([obj] = baz.fo_o);"#,
r#"([obj.foo = obj.fo_o] = bar);"#,
r#"const f = function camelCased() {};"#,
r#"const c = class camelCased {};"#,
r#"class camelCased {};"#,
r#"first_name = "Akari""#,
r#"__private_first_name = "Akari""#,
r#"obj.foo_bar = function(){};"#,
r#"bar_baz.foo = function(){};"#,
r#"[foo_bar.baz]"#,
r#"if (foo.bar_baz === boom.bam_pow) { [foo_bar.baz] }"#,
r#"foo.bar_baz = boom.bam_pow"#,
r#"foo.qux.boom_pow = { bar: boom.bam_pow }"#,
r#"obj.a_b = 2;"#,
r#"var { [category_id]: categoryId } = query;"#,
r#"a_global_variable.foo()"#,
r#"a_global_variable[undefined]"#,
r#"var camelCased = snake_cased"#,
r#"({ a: obj.fo_o } = bar);"#,
r#"({ a: obj.fo_o.b_ar } = baz);"#,
r#"({ a: { b: { c: obj.fo_o } } } = bar);"#,
r#"({ a: { b: { c: obj.fo_o.b_ar } } } = baz);"#,
r#"([obj.fo_o] = bar);"#,
r#"([obj.fo_o = 1] = bar);"#,
r#"({ a: [obj.fo_o] } = bar);"#,
r#"({ a: { b: [obj.fo_o] } } = bar);"#,
r#"([obj.fo_o.ba_r] = baz);"#,
r#"obj.o_k.non_camelcase = 0"#,
r#"(obj?.o_k).non_camelcase = 0"#,
r#"({...obj.fo_o} = baz);"#,
r#"({...obj.fo_o.ba_r} = baz);"#,
r#"({c: {...obj.fo_o }} = baz);"#,
r#"not_ignored_foo = 0;"#,
r#"const obj = { "created_at": "2020-10-30T13:16:45+09:00" }"#,
r#"const obj = { "created_at": created_at }"#,
r#"declare function foo_bar(a_b: number): void;"#,
r#"declare const foo_bar: number;"#,
r#"declare const foo_bar: number, snake_case: string;"#,
r#"declare let foo_bar: { some_property: string; };"#,
r#"declare var foo_bar: number;"#,
r#"declare class foo_bar { some_method(some_param: boolean): string; };"#,
r#"export declare const foo_bar: number;"#,
r#"declare type foo_bar = { some_var: string; };"#,
r#"declare interface foo_bar { some_var: string; }"#,
r#"declare namespace foo_bar {}"#,
r#"declare enum foo_bar { variant_one, variant_two }"#,
};
}
#[test]
fn camelcase_invalid() {
assert_lint_err! {
Camelcase,
r#"function foo_bar(){}"#: [
{
col: 9,
message: "Identifier 'foo_bar' is not in camel case.",
hint: "Consider renaming `foo_bar` to `fooBar`",
}
],
r#"var foo = { bar_baz: boom.bam_pow }"#: [
{
col: 12,
message: "Identifier 'bar_baz' is not in camel case.",
hint: r#"Consider renaming `bar_baz` to `barBaz`, or wrapping it in quotation mark like `"bar_baz"`"#,
}
],
r#"var o = { bar_baz: 1 }"#: [
{
col: 10,
message: "Identifier 'bar_baz' is not in camel case.",
hint: r#"Consider renaming `bar_baz` to `barBaz`, or wrapping it in quotation mark like `"bar_baz"`"#,
}
],
r#"var o = { bar_baz }"#: [
{
col: 10,
message: "Identifier 'bar_baz' is not in camel case.",
hint: r#"Consider writing `barBaz: bar_baz` or `"bar_baz": bar_baz`"#,
}
],
r#"var { category_id: category_alias } = query;"#: [
{
col: 19,
message: "Identifier 'category_alias' is not in camel case.",
hint: "Consider renaming `category_alias` to `categoryAlias`",
}
],
r#"var { category_id: category_id } = query;"#: [
{
col: 19,
message: "Identifier 'category_id' is not in camel case.",
hint: "Consider renaming `category_id` to `categoryId`",
}
],
r#"import * as no_camelcased from "external-module";"#: [
{
col: 12,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
}
],
r#"import { no_camelcased as no_camel_cased } from "external module";"#: [
{
col: 26,
message: "Identifier 'no_camel_cased' is not in camel case.",
hint: "Consider renaming `no_camel_cased` to `noCamelCased`",
}
],
r#"import { camelCased as no_camel_cased } from "external module";"#: [
{
col: 23,
message: "Identifier 'no_camel_cased' is not in camel case.",
hint: "Consider renaming `no_camel_cased` to `noCamelCased`",
}
],
r#"import no_camelcased, { another_no_camelcased as camelCased } from "external-module";"#: [
{
col: 7,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
}
],
r#"import snake_cased from 'mod'"#: [
{
col: 7,
message: "Identifier 'snake_cased' is not in camel case.",
hint: "Consider renaming `snake_cased` to `snakeCased`",
}
],
r#"import * as snake_cased from 'mod'"#: [
{
col: 12,
message: "Identifier 'snake_cased' is not in camel case.",
hint: "Consider renaming `snake_cased` to `snakeCased`",
}
],
r#"export * as snake_cased from 'mod'"#: [
{
col: 12,
message: "Identifier 'snake_cased' is not in camel case.",
hint: "Consider renaming `snake_cased` to `snakeCased`",
}
],
r#"function foo({ no_camelcased }) {};"#: [
{
col: 15,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
}
],
r#"function foo({ no_camelcased = 'default value' }) {};"#: [
{
col: 15,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
}
],
r#"const no_camelcased = 0; function foo({ camelcased_value = no_camelcased }) {}"#: [
{
col: 6,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
},
{
col: 40,
message: "Identifier 'camelcased_value' is not in camel case.",
hint: "Consider renaming `camelcased_value` to `camelcasedValue`",
}
],
r#"const { bar: no_camelcased } = foo;"#: [
{
col: 13,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
}
],
r#"function foo({ value_1: my_default }) {}"#: [
{
col: 24,
message: "Identifier 'my_default' is not in camel case.",
hint: "Consider renaming `my_default` to `myDefault`",
}
],
r#"function foo({ isCamelcased: no_camelcased }) {};"#: [
{
col: 29,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
}
],
r#"function foo({ isCamelcased: { no_camelcased } }) {};"#: [
{
col: 31,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
}
],
r#"var { foo: bar_baz = 1 } = quz;"#: [
{
col: 11,
message: "Identifier 'bar_baz' is not in camel case.",
hint: "Consider renaming `bar_baz` to `barBaz`",
}
],
r#"const f = function no_camelcased() {};"#: [
{
col: 19,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `noCamelcased`",
}
],
r#"const c = class no_camelcased {};"#: [
{
col: 16,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `NoCamelcased`",
}
],
r#"class no_camelcased {}"#: [
{
col: 6,
message: "Identifier 'no_camelcased' is not in camel case.",
hint: "Consider renaming `no_camelcased` to `NoCamelcased`",
}
],
r#"type foo_bar = string;"#: [
{
col: 5,
message: "Identifier 'foo_bar' is not in camel case.",
hint: "Consider renaming `foo_bar` to `FooBar`",
}
],
r#"type Foo = { snake_case: number; };"#: [
{
col: 13,
message: "Identifier 'snake_case' is not in camel case.",
hint: r#"Consider renaming `snake_case` to `snakeCase`, or wrapping it in quotation mark like `"snake_case"`"#,
}
],
r#"interface foo_bar { ok: string; };"#: [
{
col: 10,
message: "Identifier 'foo_bar' is not in camel case.",
hint: "Consider renaming `foo_bar` to `FooBar`",
}
],
r#"interface Foo { snake_case: number; };"#: [
{
col: 16,
message: "Identifier 'snake_case' is not in camel case.",
hint: r#"Consider renaming `snake_case` to `snakeCase`, or wrapping it in quotation mark like `"snake_case"`"#,
}
],
r#"namespace foo_bar {}"#: [
{
col: 10,
message: "Identifier 'foo_bar' is not in camel case.",
hint: "Consider renaming `foo_bar` to `FooBar`",
}
],
r#"namespace FooBar { const snake_case = 42; }"#: [
{
col: 25,
message: "Identifier 'snake_case' is not in camel case.",
hint: "Consider renaming `snake_case` to `snakeCase`",
}
],
r#"enum foo_bar { VariantOne }"#: [
{
col: 5,
message: "Identifier 'foo_bar' is not in camel case.",
hint: "Consider renaming `foo_bar` to `FooBar`",
}
],
r#"enum FooBar { variant_one }"#: [
{
col: 14,
message: "Identifier 'variant_one' is not in camel case.",
hint: "Consider renaming `variant_one` to `VariantOne`",
}
],
};
}
}