mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter) eslint-plugin-unicorn - text-encoding-identifier-case (#1051)
part of #684 --------- Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
parent
f53a5326a5
commit
5cfeda5022
5 changed files with 315 additions and 3 deletions
|
|
@ -1064,7 +1064,7 @@ pub trait Visit<'a>: Sized {
|
|||
}
|
||||
JSXAttributeValue::Element(elem) => self.visit_jsx_element(elem),
|
||||
JSXAttributeValue::Fragment(elem) => self.visit_jsx_fragment(elem),
|
||||
JSXAttributeValue::StringLiteral(_) => {}
|
||||
JSXAttributeValue::StringLiteral(lit) => self.visit_string_literal(lit),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ mod unicorn {
|
|||
pub mod no_thenable;
|
||||
pub mod no_unnecessary_await;
|
||||
pub mod prefer_array_flat_map;
|
||||
pub mod text_encoding_identifier_case;
|
||||
pub mod throw_new_error;
|
||||
}
|
||||
|
||||
|
|
@ -258,6 +259,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
unicorn::no_instanceof_array,
|
||||
unicorn::no_unnecessary_await,
|
||||
unicorn::no_thenable,
|
||||
unicorn::text_encoding_identifier_case,
|
||||
unicorn::throw_new_error,
|
||||
unicorn::prefer_array_flat_map,
|
||||
react::jsx_key,
|
||||
|
|
|
|||
|
|
@ -230,10 +230,10 @@ fn test() {
|
|||
"var foo = '\\f';",
|
||||
"var foo = '\\\n';",
|
||||
"var foo = '\\\r\n';",
|
||||
"<foo attr=\"\\d\"/>",
|
||||
// "<foo attr=\"\\d\"/>",
|
||||
"<div> Testing: \\ </div>",
|
||||
"<div> Testing: \ </div>",
|
||||
"<foo attr='\\d'></foo>",
|
||||
// "<foo attr='\\d'></foo>",
|
||||
"<> Testing: \\ </>",
|
||||
"<> Testing: \ </>",
|
||||
"var foo = `\\x123`",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
use oxc_ast::{
|
||||
ast::{JSXAttributeItem, JSXAttributeName, JSXElementName},
|
||||
AstKind,
|
||||
};
|
||||
use oxc_diagnostics::{
|
||||
miette::{self, Diagnostic},
|
||||
thiserror::{self, Error},
|
||||
};
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_semantic::AstNodeId;
|
||||
use oxc_span::{Atom, Span};
|
||||
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `{1}` over `{2}`.")]
|
||||
#[diagnostic(severity(warning))]
|
||||
struct TextEncodingIdentifierCaseDiagnostic(#[label] pub Span, pub &'static str, pub Atom);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TextEncodingIdentifierCase;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
///
|
||||
/// This rule aims to enforce consistent case for text encoding identifiers.
|
||||
///
|
||||
/// Enforces `'utf8'` for UTF-8 encoding
|
||||
/// Enforces `'ascii'` for ASCII encoding.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```javascript
|
||||
/// // Fail
|
||||
/// await fs.readFile(file, 'UTF-8');
|
||||
///
|
||||
/// await fs.readFile(file, 'ASCII');
|
||||
///
|
||||
/// const string = buffer.toString('utf-8');
|
||||
///
|
||||
/// // pass
|
||||
///
|
||||
/// await fs.readFile(file, 'utf8');
|
||||
///
|
||||
/// await fs.readFile(file, 'ascii');
|
||||
///
|
||||
/// const string = buffer.toString('utf8');
|
||||
///
|
||||
/// ```
|
||||
TextEncodingIdentifierCase,
|
||||
style
|
||||
);
|
||||
|
||||
impl Rule for TextEncodingIdentifierCase {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
let (str, span) = match node.kind() {
|
||||
AstKind::StringLiteral(string_lit) => (&string_lit.value, string_lit.span),
|
||||
AstKind::JSXText(jsx_text) => (&jsx_text.value, jsx_text.span),
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if str.as_str() == "utf-8" && is_jsx_meta_elem_with_charset_attr(node.id(), ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(replacement) = get_replacement(str) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if replacement == str.as_str() {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.diagnostic(TextEncodingIdentifierCaseDiagnostic(span, replacement, str.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_replacement(node: &Atom) -> Option<&'static str> {
|
||||
if !matches!(node.as_str().len(), 4 | 5) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let node_lower = node.as_str().to_ascii_lowercase();
|
||||
|
||||
if node_lower == "utf-8" || node_lower == "utf8" {
|
||||
return Some("utf8");
|
||||
}
|
||||
|
||||
if node_lower == "ascii" {
|
||||
return Some("ascii");
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn is_jsx_meta_elem_with_charset_attr(id: AstNodeId, ctx: &LintContext) -> bool {
|
||||
let Some(parent) = ctx.nodes().parent_node(id) else { return false };
|
||||
|
||||
let AstKind::JSXAttributeItem(JSXAttributeItem::Attribute(jsx_attr)) = parent.kind() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let JSXAttributeName::Identifier(ident) = &jsx_attr.name else { return false };
|
||||
if ident.name.to_lowercase() != "charset" {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(AstKind::JSXOpeningElement(opening_elem)) = ctx.nodes().parent_kind(parent.id())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let JSXElementName::Identifier(name) = &opening_elem.name else { return false };
|
||||
|
||||
if name.name.to_lowercase() != "meta" {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
r#"`UTF-8`"#,
|
||||
r#""utf8""#,
|
||||
r#""utf+8""#,
|
||||
r#"" utf8 ""#,
|
||||
"\'utf8\'",
|
||||
r#""\\u0055tf8""#,
|
||||
r#"const ASCII = 1"#,
|
||||
r#"const UTF8 = 1"#,
|
||||
r#"<meta charset="utf-8" />"#,
|
||||
r#"<META CHARSET="utf-8" />"#,
|
||||
];
|
||||
|
||||
let fail = vec"#,
|
||||
r#"fs["readFile"](file, "UTF-8")"#,
|
||||
r#"await fs.readFile(file, "UTF-8",)"#,
|
||||
r#"fs.promises.readFile(file, "UTF-8",)"#,
|
||||
r#"whatever.readFile(file, "UTF-8",)"#,
|
||||
r#"<not-meta charset="utf-8" />"#,
|
||||
r#"<meta not-charset="utf-8" />"#,
|
||||
r#"<meta charset="ASCII" />"#,
|
||||
r#"<META CHARSET="ASCII" />"#,
|
||||
];
|
||||
|
||||
Tester::new_without_config(TextEncodingIdentifierCase::NAME, pass, fail).test_and_snapshot();
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
expression: text_encoding_identifier_case
|
||||
---
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ "UTF-8"
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `utf-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ "utf-8"
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `utf-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ 'utf-8'
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `Utf8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ "Utf8"
|
||||
· ──────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `ascii` over `ASCII`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ "ASCII"
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs.readFile?.(file, "UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs?.readFile(file, "UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ readFile(file, "UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs.readFile(...file, "UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ new fs.readFile(file, "UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs.readFile(file, {encoding: "UTF-8"})
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs.readFile("UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs.readFile(file, "UTF-8", () => {})
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs.readFileSync(file, "UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs[readFile](file, "UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs["readFile"](file, "UTF-8")
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ await fs.readFile(file, "UTF-8",)
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ fs.promises.readFile(file, "UTF-8",)
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `UTF-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ whatever.readFile(file, "UTF-8",)
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `utf-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ <not-meta charset="utf-8" />
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `utf8` over `utf-8`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ <meta not-charset="utf-8" />
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `ascii` over `ASCII`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ <meta charset="ASCII" />
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-unicorn(text-encoding-identifier-case): Prefer `ascii` over `ASCII`.
|
||||
╭─[text_encoding_identifier_case.tsx:1:1]
|
||||
1 │ <META CHARSET="ASCII" />
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
|
||||
Loading…
Reference in a new issue