Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions crates/oxc_linter/src/rules/react/no_unknown_property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use phf::{phf_map, phf_set, Map, Set};
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use serde::Deserialize;

Expand Down Expand Up @@ -433,16 +432,30 @@ static DOM_PROPERTIES_LOWER_MAP: Lazy<FxHashMap<String, &'static str>> = Lazy::n
.collect::<FxHashMap<_, _>>()
});

///
/// Checks if an attribute name is a valid `data-*` attribute:
/// if the name starts with "data-" and has alphanumeric words (browsers require lowercase, but React and TS lowercase them),
/// not start with any casing of "xml", and separated by hyphens (-) (which is also called "kebab case" or "dash case"),
/// then the attribute is a valid data attribute.
///
/// - Name starts with "data-" and has alphanumeric words (browsers require lowercase, but React and TS lowercase them),
/// - Does not start with any casing of "xml"
/// - Words are separated by hyphens (-) (which is also called "kebab case" or "dash case")
fn is_valid_data_attr(name: &str) -> bool {
static DATA_ATTR_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^data(-?[^:]*)$").unwrap());
if !name.starts_with("data-") {
return false;
}

if name.cow_to_lowercase().starts_with("data-xml") {
return false;
}

let data_name = &name["data-".len()..];
if data_name.is_empty() {
return false;
}

data_name.chars().all(|c| c != ':')
}

!name.cow_to_lowercase().starts_with("data-xml") && DATA_ATTR_REGEX.is_match(name)
/// Checks if a tag name matches the HTML tag conventions.
fn matches_html_tag_conventions(tag: &str) -> bool {
tag.char_indices().all(|(i, c)| if i == 0 { c.is_ascii_lowercase() } else { c != '-' })
}

fn normalize_attribute_case(name: &str) -> &str {
Expand All @@ -465,8 +478,6 @@ impl Rule for NoUnknownProperty {
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
static HTML_TAG_CONVENTION: Lazy<Regex> = Lazy::new(|| Regex::new("^[a-z][^-]*$").unwrap());

let AstKind::JSXOpeningElement(el) = &node.kind() else {
return;
};
Expand All @@ -480,7 +491,7 @@ impl Rule for NoUnknownProperty {
return;
}

let is_valid_html_tag = HTML_TAG_CONVENTION.is_match(el_type)
let is_valid_html_tag = matches_html_tag_conventions(el_type)
&& el.attributes.iter().all(|attr| {
let JSXAttributeItem::Attribute(jsx_attr) = attr else {
return true;
Expand Down