fix(linter): skip linting vue <script> where lang is not js / ts (#10740)

This commit is contained in:
Boshen committed 2025-05-01 08:18:44 +00:00
1 parent 44f19526aa
commit 48c542d4d4
2 files changed
+72 -52

No files matched your search

@@ -1,4 +1,5 @@
use memchr::memmem::Finder;
use oxc_span::SourceType;
use super::{JavaScriptSource, SCRIPT_END, SCRIPT_START, find_script_closing_angle};
@@ -48,8 +49,21 @@ impl<'a> VuePartialLoader<'a> {
// get ts and jsx attribute
let content = &self.source_text[*pointer..*pointer + offset];
let is_ts = content.contains("ts");
let is_jsx = content.contains("tsx") || content.contains("jsx");
// 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 Ok(mut source_type) = SourceType::from_extension(lang) else { return None };
if !lang.contains('x') {
source_type = source_type.with_standard(true);
}
*pointer += offset + 1;
let js_start = *pointer;
@@ -61,7 +75,6 @@ impl<'a> VuePartialLoader<'a> {
*pointer += offset + SCRIPT_END.len();
let source_text = &self.source_text[js_start..js_end];
let source_type = SourceType::mjs().with_typescript(is_ts).with_jsx(is_jsx);
// NOTE: loader checked that source_text.len() is less than u32::MAX
#[expect(clippy::cast_possible_truncation)]
Some(JavaScriptSource::partial(source_text, source_type, js_start as u32))
@@ -70,6 +83,8 @@ impl<'a> VuePartialLoader<'a> {
#[cfg(test)]
mod test {
use oxc_span::SourceType;
use super::{JavaScriptSource, VuePartialLoader};
fn parse_vue(source_text: &str) -> JavaScriptSource<'_> {
@@ -99,20 +114,7 @@ mod test {
"#;
let result = parse_vue(source_text);
assert!(result.source_type.is_typescript());
assert_eq!(result.source_text.trim(), "1/1");
}
#[test]
fn test_build_vue_with_ts_flag_2() {
let source_text = r"
<script lang=ts setup>
1/1
</script>
";
let result = parse_vue(source_text);
assert!(result.source_type.is_typescript());
assert_eq!(result.source_type, SourceType::ts());
assert_eq!(result.source_text.trim(), "1/1");
}
@@ -125,21 +127,7 @@ mod test {
";
let result = parse_vue(source_text);
assert!(result.source_type.is_typescript());
assert_eq!(result.source_text.trim(), "1/1");
}
#[test]
fn test_build_vue_with_tsx_flag() {
let source_text = r"
<script lang=tsx setup>
1/1
</script>
";
let result = parse_vue(source_text);
assert!(result.source_type.is_jsx());
assert!(result.source_type.is_typescript());
assert_eq!(result.source_type, SourceType::ts());
assert_eq!(result.source_text.trim(), "1/1");
}
@@ -252,4 +240,28 @@ mod test {
assert_eq!(sources.len(), 1);
assert_eq!(sources[0].source_text, "a");
}
#[test]
fn lang() {
let cases = [
("<script>debugger</script>", Some(SourceType::mjs())),
("<script lang = 'tsx' >debugger</script>", Some(SourceType::tsx())),
(r#"<script lang = "cjs" >debugger</script>"#, Some(SourceType::cjs())),
("<script lang = 'xxx'>debugger</script>", None),
(r#"<script lang = "xxx">debugger</script>"#, None),
("<script lang='xxx'>debugger</script>", None),
(r#"<script lang="xxx">debugger</script>"#, None),
("<script lang=tsx>debugger</script>", None), // this is valid but too compliated to parse
];
for (source_text, source_type) in cases {
let sources = VuePartialLoader::new(source_text).parse();
if let Some(expected) = source_type {
assert_eq!(sources.len(), 1);
assert_eq!(sources[0].source_type, expected);
} else {
assert_eq!(sources.len(), 0);
}
}
}
}
+28 -20
View File
@@ -462,36 +462,44 @@ impl SourceType {
)
})?;
let mut source_type = Self::from_extension(extension)?;
#[expect(clippy::case_sensitive_file_extension_comparisons)]
if match extension {
"ts" if file_name[..file_name.len() - 3].split('.').rev().take(2).any(|c| c == "d") => {
true
}
"mts" | "cts" if file_name[..file_name.len() - 4].ends_with(".d") => true,
_ => false,
} {
source_type.language = Language::TypeScriptDefinition;
}
Ok(source_type)
}
/// Converts a file extension to [`SourceType`].
///
/// # Errors
///
/// Returns [`UnknownExtension`] if:
/// * the file extension is not one of "js", "mjs", "cjs", "jsx", "ts",
/// "mts", "cts", "tsx". See [`VALID_EXTENSIONS`] for the list of valid
/// extensions.
pub fn from_extension(extension: &str) -> Result<Self, UnknownExtension> {
let module_kind = match extension {
"js" | "tsx" | "ts" | "jsx" | "mts" | "mjs" => ModuleKind::Module,
"cjs" | "cts" => ModuleKind::Script,
_ => unreachable!(),
_ => return Err(UnknownExtension::new("Unknown extension.")),
};
let language = match extension {
// https://www.typescriptlang.org/tsconfig/#allowArbitraryExtensions
// `{file basename}.d.{extension}.ts`
// https://github.com/microsoft/TypeScript/issues/50133
"ts" => {
if file_name[..file_name.len() - 3].split('.').rev().take(2).any(|c| c == "d") {
Language::TypeScriptDefinition
} else {
Language::TypeScript
}
}
"js" | "cjs" | "mjs" | "jsx" => Language::JavaScript,
"tsx" => Language::TypeScript,
#[expect(clippy::case_sensitive_file_extension_comparisons)]
"mts" | "cts" => {
if file_name[..file_name.len() - 4].ends_with(".d") {
Language::TypeScriptDefinition
} else {
Language::TypeScript
}
}
_ => {
unreachable!();
}
"ts" | "tsx" | "mts" | "cts" => Language::TypeScript,
_ => return Err(UnknownExtension::new("Unknown extension.")),
};
let variant = match extension {