diff --git a/crates/oxc_linter/src/loader/partial_loader/vue.rs b/crates/oxc_linter/src/loader/partial_loader/vue.rs index f86f9a5b70e6e..70caba8c0702e 100644 --- a/crates/oxc_linter/src/loader/partial_loader/vue.rs +++ b/crates/oxc_linter/src/loader/partial_loader/vue.rs @@ -51,15 +51,8 @@ impl<'a> VuePartialLoader<'a> { let content = &self.source_text[*pointer..*pointer + offset]; // parse `lang` - let lang = content.split_once("lang").map_or(Some("mjs"), |(_, s)| { - const QUOTES: [char; 2] = ['"', '\'']; - s.trim_start() - .trim_start_matches('=') - .trim_start() - .trim_start_matches(QUOTES) - .split_once(QUOTES) - .map(|(s, _)| s) - })?; + let lang = Self::extract_lang_attribute(content); + let Ok(mut source_type) = SourceType::from_extension(lang) else { return None }; if !lang.contains('x') { source_type = source_type.with_standard(true); @@ -79,6 +72,43 @@ impl<'a> VuePartialLoader<'a> { #[expect(clippy::cast_possible_truncation)] Some(JavaScriptSource::partial(source_text, source_type, js_start as u32)) } + + fn extract_lang_attribute(content: &str) -> &str { + let content = content.trim(); + + let Some(lang_index) = content.find("lang") else { return "mjs" }; + + // Move past "lang" + let mut rest = content[lang_index + 4..].trim_start(); + + if !rest.starts_with('=') { + return "mjs"; + } + + // Move past "=" + rest = rest[1..].trim_start(); + + let first_char = rest.chars().next(); + + match first_char { + Some('"' | '\'') => { + let quote = first_char.unwrap(); + rest = &rest[1..]; + match rest.find(quote) { + Some(end) => &rest[..end], + None => "mjs", // Unterminated quote + } + } + Some(_) => { + // Unquoted value: take until first whitespace or attribute separator + match rest.find(|c: char| c.is_whitespace() || c == '>') { + Some(end) => &rest[..end], + None => rest, // whole rest is the lang value + } + } + None => "mjs", // nothing after = + } + } } #[cfg(test)] @@ -257,11 +287,11 @@ mod test { ("", Some(SourceType::mjs())), ("", Some(SourceType::tsx())), (r#""#, Some(SourceType::cjs())), + ("", Some(SourceType::tsx())), ("", None), (r#""#, None), ("", None), (r#""#, None), - ("", None), // this is valid but too compliated to parse ]; for (source_text, source_type) in cases {