Skip to content

Commit 377e904

Browse files
authored
fix(linter): ignore script tag in code comment (#15202)
Partial loader tries parsing the `<script>` tag in the code comment incorrectly For example, we have a `.vue` file ```vue <!-- <script setup lang="tsx"> --> <script setup lang="ts"> const count = ref(0) </script> ``` And the loader will regard these as script part ``` --> <script setup lang="ts"> const count = ref(0) ``` So this PR added some `Finder`s for code comment to ignore the `<script>` in code comment, also works on `.svelte` and `.astro` files
1 parent 313e82e commit 377e904

File tree

4 files changed

+85
-16
lines changed

4 files changed

+85
-16
lines changed

crates/oxc_linter/src/loader/partial_loader/astro.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use memchr::memmem::Finder;
1+
use memchr::memmem::{Finder, FinderRev};
22

33
use oxc_span::{SourceType, Span};
44

55
use crate::loader::JavaScriptSource;
66

7-
use super::{SCRIPT_END, SCRIPT_START};
7+
use super::{COMMENT_END, COMMENT_START, SCRIPT_END, SCRIPT_START, find_script_start};
88

99
const ASTRO_SPLIT: &str = "---";
1010

@@ -55,6 +55,8 @@ impl<'a> AstroPartialLoader<'a> {
5555
fn parse_scripts(&self, start: usize) -> Vec<JavaScriptSource<'a>> {
5656
let script_start_finder = Finder::new(SCRIPT_START);
5757
let script_end_finder = Finder::new(SCRIPT_END);
58+
let comment_start_finder = FinderRev::new(COMMENT_START);
59+
let comment_end_finder = Finder::new(COMMENT_END);
5860

5961
let mut results = vec![];
6062
let mut pointer = start;
@@ -63,9 +65,14 @@ impl<'a> AstroPartialLoader<'a> {
6365
let js_start;
6466
let js_end;
6567
// find opening "<script"
66-
if let Some(offset) = script_start_finder.find(&self.source_text.as_bytes()[pointer..])
67-
{
68-
pointer += offset + SCRIPT_START.len();
68+
if let Some(offset) = find_script_start(
69+
self.source_text,
70+
pointer,
71+
&script_start_finder,
72+
&comment_start_finder,
73+
&comment_end_finder,
74+
) {
75+
pointer += offset;
6976
} else {
7077
break;
7178
}

crates/oxc_linter/src/loader/partial_loader/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use memchr::{memmem::Finder, memmem::FinderRev};
12
use oxc_span::VALID_EXTENSIONS;
23

34
use crate::loader::JavaScriptSource;
@@ -11,6 +12,8 @@ pub use vue::VuePartialLoader;
1112

1213
const SCRIPT_START: &str = "<script";
1314
const SCRIPT_END: &str = "</script>";
15+
const COMMENT_START: &str = "<!--";
16+
const COMMENT_END: &str = "-->";
1417

1518
/// File extensions that can contain JS/TS code in certain parts, such as in `<script>` tags, and can
1619
/// be loaded using the [`PartialLoader`].
@@ -69,3 +72,31 @@ fn find_script_closing_angle(source_text: &str, pointer: usize) -> Option<usize>
6972

7073
None
7174
}
75+
76+
fn find_script_start(
77+
source_text: &str,
78+
pointer: usize,
79+
script_start_finder: &Finder<'_>,
80+
comment_start_finder: &FinderRev<'_>,
81+
comment_end_finder: &Finder<'_>,
82+
) -> Option<usize> {
83+
let mut new_pointer = pointer;
84+
85+
loop {
86+
new_pointer +=
87+
script_start_finder.find(&source_text.as_bytes()[new_pointer..])? + SCRIPT_START.len();
88+
89+
if let Some(offset) = comment_start_finder.rfind(&source_text.as_bytes()[..new_pointer]) {
90+
if comment_end_finder
91+
.find(&source_text.as_bytes()[offset + COMMENT_START.len()..new_pointer])
92+
.is_some()
93+
{
94+
break;
95+
}
96+
} else {
97+
break;
98+
}
99+
}
100+
101+
Some(new_pointer - pointer)
102+
}

crates/oxc_linter/src/loader/partial_loader/svelte.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
use memchr::memmem::Finder;
1+
use memchr::memmem::{Finder, FinderRev};
22

33
use oxc_span::SourceType;
44

55
use crate::loader::JavaScriptSource;
66

7-
use super::{SCRIPT_END, SCRIPT_START, find_script_closing_angle};
7+
use super::{
8+
COMMENT_END, COMMENT_START, SCRIPT_END, SCRIPT_START, find_script_closing_angle,
9+
find_script_start,
10+
};
811

912
pub struct SveltePartialLoader<'a> {
1013
source_text: &'a str,
@@ -37,10 +40,16 @@ impl<'a> SveltePartialLoader<'a> {
3740
fn parse_script(&self, pointer: &mut usize) -> Option<JavaScriptSource<'a>> {
3841
let script_start_finder = Finder::new(SCRIPT_START);
3942
let script_end_finder = Finder::new(SCRIPT_END);
40-
43+
let comment_start_finder = FinderRev::new(COMMENT_START);
44+
let comment_end_finder: Finder<'_> = Finder::new(COMMENT_END);
4145
// find opening "<script"
42-
let offset = script_start_finder.find(&self.source_text.as_bytes()[*pointer..])?;
43-
*pointer += offset + SCRIPT_START.len();
46+
*pointer += find_script_start(
47+
self.source_text,
48+
*pointer,
49+
&script_start_finder,
50+
&comment_start_finder,
51+
&comment_end_finder,
52+
)?;
4453

4554
// find closing ">"
4655
let offset = find_script_closing_angle(self.source_text, *pointer)?;

crates/oxc_linter/src/loader/partial_loader/vue.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
use memchr::memmem::Finder;
1+
use memchr::memmem::{Finder, FinderRev};
22

33
use oxc_span::SourceType;
44

55
use crate::frameworks::FrameworkOptions;
66

7-
use super::{JavaScriptSource, SCRIPT_END, SCRIPT_START, find_script_closing_angle};
7+
use super::{
8+
COMMENT_END, COMMENT_START, JavaScriptSource, SCRIPT_END, SCRIPT_START,
9+
find_script_closing_angle, find_script_start,
10+
};
811

912
pub struct VuePartialLoader<'a> {
1013
source_text: &'a str,
@@ -36,10 +39,16 @@ impl<'a> VuePartialLoader<'a> {
3639

3740
fn parse_script(&self, pointer: &mut usize) -> Option<JavaScriptSource<'a>> {
3841
let script_start_finder = Finder::new(SCRIPT_START);
39-
42+
let comment_start_finder = FinderRev::new(COMMENT_START);
43+
let comment_end_finder: Finder<'_> = Finder::new(COMMENT_END);
4044
// find opening "<script"
41-
let offset = script_start_finder.find(&self.source_text.as_bytes()[*pointer..])?;
42-
*pointer += offset + SCRIPT_START.len();
45+
*pointer += find_script_start(
46+
self.source_text,
47+
*pointer,
48+
&script_start_finder,
49+
&comment_start_finder,
50+
&comment_end_finder,
51+
)?;
4352

4453
// skip `<script-`
4554
if !self.source_text[*pointer..].starts_with([' ', '>']) {
@@ -65,7 +74,7 @@ impl<'a> VuePartialLoader<'a> {
6574
let js_start = *pointer;
6675

6776
// find "</script>"
68-
let script_end_finder = Finder::new(SCRIPT_END);
77+
let script_end_finder: Finder<'_> = Finder::new(SCRIPT_END);
6978
let offset = script_end_finder.find(&self.source_text.as_bytes()[*pointer..])?;
7079
let js_end = *pointer + offset;
7180
*pointer += offset + SCRIPT_END.len();
@@ -289,6 +298,19 @@ mod test {
289298
assert_eq!(result.source_text, "a");
290299
}
291300

301+
#[test]
302+
fn test_script_inside_code_comment() {
303+
let source_text = r"
304+
<!-- <script>a</script> -->
305+
<!-- <script> -->
306+
<script>b</script>
307+
";
308+
309+
let result: JavaScriptSource<'_> = parse_vue(source_text);
310+
assert_eq!(result.source_text, "b");
311+
assert_eq!(result.start, 79);
312+
}
313+
292314
#[test]
293315
fn lang() {
294316
let cases = [

0 commit comments

Comments
 (0)