Skip to content

Commit 9bef705

Browse files
committed
fix: some edge cases
1 parent b2ea6ad commit 9bef705

48 files changed

Lines changed: 1636 additions & 871 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/oxlint/src-js/generated/deserialize.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ function deserializeAstroScript(pos) {
7070
return node;
7171
}
7272

73+
function deserializeAstroDoctype(pos) {
74+
let start, end;
75+
return {
76+
__proto__: NodeProto,
77+
type: "AstroDoctype",
78+
value: deserializeStr(pos + 8),
79+
start: (start = deserializeU32(pos)),
80+
end: (end = deserializeU32(pos + 4)),
81+
range: [start, end],
82+
parent,
83+
};
84+
}
85+
7386
function deserializeProgram(pos) {
7487
let localAstId = astId,
7588
end = deserializeU32(pos + 4),
@@ -3840,6 +3853,8 @@ function deserializeJSXChild(pos) {
38403853
return deserializeBoxJSXSpreadChild(pos + 8);
38413854
case 5:
38423855
return deserializeBoxAstroScript(pos + 8);
3856+
case 6:
3857+
return deserializeBoxAstroDoctype(pos + 8);
38433858
default:
38443859
throw Error(`Unexpected discriminant ${uint8[pos]} for JSXChild`);
38453860
}
@@ -6867,6 +6882,10 @@ function deserializeBoxAstroScript(pos) {
68676882
return deserializeAstroScript(uint32[pos >> 2]);
68686883
}
68696884

6885+
function deserializeBoxAstroDoctype(pos) {
6886+
return deserializeAstroDoctype(uint32[pos >> 2]);
6887+
}
6888+
68706889
function deserializeVecTSEnumMember(pos) {
68716890
let arr = [],
68726891
pos32 = pos >> 2;

apps/oxlint/src-js/generated/types.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export interface AstroScript extends Span {
2525
parent: Node;
2626
}
2727

28+
export interface AstroDoctype extends Span {
29+
type: "AstroDoctype";
30+
value: string;
31+
parent: Node;
32+
}
33+
2834
export interface Program extends Span {
2935
type: "Program";
3036
body: Array<Directive | Statement>;
@@ -1093,7 +1099,8 @@ export type JSXChild =
10931099
| JSXFragment
10941100
| JSXExpressionContainer
10951101
| JSXSpreadChild
1096-
| AstroScript;
1102+
| AstroScript
1103+
| AstroDoctype;
10971104

10981105
export interface JSXSpreadChild extends Span {
10991106
type: "JSXSpreadChild";
@@ -1756,6 +1763,7 @@ export type Node =
17561763
| AstroRoot
17571764
| AstroFrontmatter
17581765
| AstroScript
1766+
| AstroDoctype
17591767
| Program
17601768
| IdentifierName
17611769
| IdentifierReference

astro-parser-report.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Astro Parser Report
2+
3+
**Parser**: oxc `parseAstroSync` from `napi/parser`
4+
**Source**: `../docs` (79 Astro files)
5+
**Date**: 2026-02-04
6+
7+
## Summary
8+
9+
| Status | Count | Percentage |
10+
| --------- | ------ | ---------- |
11+
| Passed | 79 | 100% |
12+
| Failed | 0 | 0% |
13+
| **Total** | **79** | 100% |
14+
15+
All 79 Astro files from the `../docs` directory parse successfully.
16+
17+
---
18+
19+
## Passed Files (79)
20+
21+
- `src/components/BackendGuidesNav.astro`
22+
- `src/components/Badge.astro`
23+
- `src/components/BrandLogo.astro`
24+
- `src/components/Button.astro`
25+
- `src/components/CMSGuidesNav.astro`
26+
- `src/components/Checklist.astro`
27+
- `src/components/ContributorList.astro`
28+
- `src/components/DeployGuidesNav.astro`
29+
- `src/components/DontEditWarning.astro`
30+
- `src/components/FacePile.astro`
31+
- `src/components/FluidGrid.astro`
32+
- `src/components/FooterLinks.astro`
33+
- `src/components/IntegrationsNav.astro`
34+
- `src/components/IslandsDiagram.astro`
35+
- `src/components/Landing/Card.astro`
36+
- `src/components/Landing/Discord.astro`
37+
- `src/components/Landing/ListCard.astro`
38+
- `src/components/Landing/SplitCard.astro`
39+
- `src/components/LeftSidebar/Sponsors.astro`
40+
- `src/components/LoopingVideo.astro`
41+
- `src/components/MediaGuidesNav.astro`
42+
- `src/components/MigrationGuidesNav.astro`
43+
- `src/components/NavGrid/Card.astro`
44+
- `src/components/NavGrid/CardsNav.astro`
45+
- `src/components/NavGrid/Grid.astro`
46+
- `src/components/NotFound.astro`
47+
- `src/components/ReadMore.astro`
48+
- `src/components/RecipeLinks.astro`
49+
- `src/components/RecipesNav.astro`
50+
- `src/components/RightSidebar/LearnAstroAd.astro`
51+
- `src/components/RightSidebar/RandomizedAd.astro`
52+
- `src/components/RightSidebar/ScrimbaAd.astro`
53+
- `src/components/RightSidebar/StarlightBanner.astro`
54+
- `src/components/ShowcaseCard.astro`
55+
- `src/components/Since.astro`
56+
- `src/components/SourcePR.astro`
57+
- `src/components/Spoiler.astro`
58+
- `src/components/TabGroup/InstallGuideTabGroup.astro`
59+
- `src/components/Version.astro`
60+
- `src/components/starlight/EditLink.astro`
61+
- `src/components/starlight/Footer.astro`
62+
- `src/components/starlight/Hero.astro`
63+
- `src/components/starlight/Hero/FacePile.astro`
64+
- `src/components/starlight/MarkdownContent.astro`
65+
- `src/components/starlight/MobileMenuFooter.astro`
66+
- `src/components/starlight/MobileTableOfContents.astro`
67+
- `src/components/starlight/PageSidebar.astro`
68+
- `src/components/starlight/PageTitle.astro`
69+
- `src/components/starlight/Search.astro`
70+
- `src/components/starlight/Sidebar.astro`
71+
- `src/components/starlight/SiteTitle.astro`
72+
- `src/components/starlight/TableOfContents.astro`
73+
- `src/components/tabs/AstroJSXTabs.astro`
74+
- `src/components/tabs/AstroVueTabs.astro`
75+
- `src/components/tabs/JavascriptFlavorTabs.astro`
76+
- `src/components/tabs/PackageManagerTabs.astro`
77+
- `src/components/tabs/StaticSsrTabs.astro`
78+
- `src/components/tabs/TabListItem.astro`
79+
- `src/components/tabs/TabPanel.astro`
80+
- `src/components/tabs/TabbedContent.astro`
81+
- `src/components/tabs/UIFrameworkTabs.astro`
82+
- `src/components/tutorial/Blanks.astro`
83+
- `src/components/tutorial/Box.astro`
84+
- `src/components/tutorial/CompletionConfetti.astro`
85+
- `src/components/tutorial/Lede.astro`
86+
- `src/components/tutorial/MobileTutorialNav.astro`
87+
- `src/components/tutorial/MultipleChoice.astro`
88+
- `src/components/tutorial/Option.astro`
89+
- `src/components/tutorial/PreCheck.astro`
90+
- `src/components/tutorial/Progress.astro`
91+
- `src/components/tutorial/TutorialNav.astro`
92+
- `src/components/tutorial/UnitProgressIcon.astro`
93+
- `src/pages/404.astro`
94+
- `src/pages/[...enRedirectSlug].astro`
95+
- `src/pages/[lang]/404.astro`
96+
- `src/pages/[lang]/index.astro`
97+
- `src/pages/[lang]/install.astro`
98+
- `src/pages/[lang]/tutorial.astro`
99+
- `src/pages/index.astro`
100+
101+
---
102+
103+
## Fix Applied
104+
105+
A bug was found and fixed in `crates/oxc_parser/src/jsx/mod.rs` related to parsing `<style>` and `<script>` elements (raw text elements).
106+
107+
**Issue**: The parser was calling `next_jsx_child()` after parsing the opening tag's `>`, which would attempt to lex the raw content (CSS/JS) as JSX. This caused CSS selectors containing `>` (like `.foo > .bar`) to fail with "Unexpected token" errors.
108+
109+
**Fix**: Modified `parse_jsx_opening_element` to detect raw text elements (`<style>`, `<script>`) and skip the `next_jsx_child()` call for those elements. The `skip_raw_text_element_content` function now properly positions the lexer past the raw content without attempting to parse it as JSX.

astro-repo-parser-report.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Astro Parser Report - astro repository
2+
3+
**Date:** 2026-02-04
4+
5+
## Summary
6+
7+
- **Total files:** 1740
8+
- **Passed:** 1734
9+
- **Failed:** 6
10+
- **Pass rate:** 99.7%
11+
12+
## Failed Files (6)
13+
14+
### Unexpected token (3 files)
15+
16+
- `packages/astro/e2e/fixtures/errors/src/pages/astro-syntax-error.astro`
17+
- `packages/astro/e2e/fixtures/view-transitions/src/pages/non-html-anchor.astro`
18+
- `packages/astro/test/fixtures/astro-head/src/pages/head-own-component.astro`
19+
20+
### Expected `}` but found `EOF` (1 files)
21+
22+
- `packages/astro/e2e/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro`
23+
24+
### Expected corresponding JSX closing tag for 'body'., Unexpected token (1 files)
25+
26+
- `packages/astro/test/fixtures/core-image-ssg/src/pages/special-chars.astro`
27+
28+
### Expected `>` but found `EOF` (1 files)
29+
30+
- `packages/language-tools/language-server/test/fixture/cachingTest.astro`
31+
32+
## Passed Files
33+
34+
1734 files passed.

crates/oxc_ast/src/ast/astro.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use oxc_allocator::{Box, CloneIn, Dummy, TakeIn, UnstableAddress, Vec};
1111
use oxc_ast_macros::ast;
1212
use oxc_estree::ESTree;
13-
use oxc_span::{ContentEq, GetSpan, GetSpanMut, Span};
13+
use oxc_span::{Atom, ContentEq, GetSpan, GetSpanMut, Span};
1414

1515
use super::js::Program;
1616
use super::jsx::*;
@@ -86,3 +86,26 @@ pub struct AstroScript<'a> {
8686
/// The parsed TypeScript/JavaScript program from the script content
8787
pub program: Program<'a>,
8888
}
89+
90+
/// Astro Doctype Declaration
91+
///
92+
/// Represents an HTML doctype declaration like `<!doctype html>` or `<!DOCTYPE html>`.
93+
/// This is commonly used at the start of Astro pages to declare the document type.
94+
///
95+
/// ## Example
96+
///
97+
/// ```astro
98+
/// <!doctype html>
99+
/// <html>
100+
/// <body>Hello</body>
101+
/// </html>
102+
/// ```
103+
#[ast(visit)]
104+
#[derive(Debug)]
105+
#[generate_derive(CloneIn, Dummy, TakeIn, GetSpan, GetSpanMut, ContentEq, ESTree, UnstableAddress)]
106+
pub struct AstroDoctype<'a> {
107+
/// Node location in source code (includes `<!` and `>`)
108+
pub span: Span,
109+
/// The document type value, e.g., "html" from `<!doctype html>` or `<!DOCTYPE html>`
110+
pub value: Atom<'a>,
111+
}

crates/oxc_ast/src/ast/jsx.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ use oxc_ast_macros::ast;
99
use oxc_estree::ESTree;
1010
use oxc_span::{Atom, ContentEq, GetSpan, GetSpanMut, Span};
1111

12-
use super::{astro::AstroScript, inherit_variants, js::*, literal::*, ts::*};
12+
use super::{
13+
astro::{AstroDoctype, AstroScript},
14+
inherit_variants,
15+
js::*,
16+
literal::*,
17+
ts::*,
18+
};
1319

1420
// 1.2 JSX Elements
1521

@@ -449,6 +455,9 @@ pub enum JSXChild<'a> {
449455
/// Astro `<script>` element with parsed TypeScript content
450456
/// This is only used in Astro files.
451457
AstroScript(Box<'a, AstroScript<'a>>) = 5,
458+
/// Astro doctype declaration like `<!doctype html>`
459+
/// This is only used in Astro files.
460+
AstroDoctype(Box<'a, AstroDoctype<'a>>) = 6,
452461
}
453462

454463
/// JSX Spread Child.

crates/oxc_ast/src/ast_kind_impl.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ impl AstKind<'_> {
608608
Self::AstroRoot(_) => "AstroRoot".into(),
609609
Self::AstroFrontmatter(_) => "AstroFrontmatter".into(),
610610
Self::AstroScript(_) => "AstroScript".into(),
611+
Self::AstroDoctype(_) => "AstroDoctype".into(),
611612
}
612613
}
613614
}

crates/oxc_ast/src/generated/assert_layouts.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ const _: () = {
2828
assert!(offset_of!(AstroScript, span) == 0);
2929
assert!(offset_of!(AstroScript, program) == 8);
3030

31+
// Padding: 0 bytes
32+
assert!(size_of::<AstroDoctype>() == 24);
33+
assert!(align_of::<AstroDoctype>() == 8);
34+
assert!(offset_of!(AstroDoctype, span) == 0);
35+
assert!(offset_of!(AstroDoctype, value) == 8);
36+
3137
// Padding: 1 bytes
3238
assert!(size_of::<Program>() == 128);
3339
assert!(align_of::<Program>() == 8);
@@ -1669,6 +1675,12 @@ const _: () = if cfg!(target_family = "wasm") || align_of::<u64>() == 8 {
16691675
assert!(offset_of!(AstroScript, span) == 0);
16701676
assert!(offset_of!(AstroScript, program) == 8);
16711677

1678+
// Padding: 0 bytes
1679+
assert!(size_of::<AstroDoctype>() == 16);
1680+
assert!(align_of::<AstroDoctype>() == 4);
1681+
assert!(offset_of!(AstroDoctype, span) == 0);
1682+
assert!(offset_of!(AstroDoctype, value) == 8);
1683+
16721684
// Padding: 1 bytes
16731685
assert!(size_of::<Program>() == 88);
16741686
assert!(align_of::<Program>() == 4);

crates/oxc_ast/src/generated/ast_builder.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,38 @@ impl<'a> AstBuilder<'a> {
9797
Box::new_in(self.astro_script(span, program), self.allocator)
9898
}
9999

100+
/// Build an [`AstroDoctype`].
101+
///
102+
/// If you want the built node to be allocated in the memory arena,
103+
/// use [`AstBuilder::alloc_astro_doctype`] instead.
104+
///
105+
/// ## Parameters
106+
/// * `span`: Node location in source code (includes `<!` and `>`)
107+
/// * `value`: The document type value, e.g., "html" from `<!doctype html>` or `<!DOCTYPE html>`
108+
#[inline]
109+
pub fn astro_doctype<A1>(self, span: Span, value: A1) -> AstroDoctype<'a>
110+
where
111+
A1: Into<Atom<'a>>,
112+
{
113+
AstroDoctype { span, value: value.into() }
114+
}
115+
116+
/// Build an [`AstroDoctype`], and store it in the memory arena.
117+
///
118+
/// Returns a [`Box`] containing the newly-allocated node.
119+
/// If you want a stack-allocated node, use [`AstBuilder::astro_doctype`] instead.
120+
///
121+
/// ## Parameters
122+
/// * `span`: Node location in source code (includes `<!` and `>`)
123+
/// * `value`: The document type value, e.g., "html" from `<!doctype html>` or `<!DOCTYPE html>`
124+
#[inline]
125+
pub fn alloc_astro_doctype<A1>(self, span: Span, value: A1) -> Box<'a, AstroDoctype<'a>>
126+
where
127+
A1: Into<Atom<'a>>,
128+
{
129+
Box::new_in(self.astro_doctype(span, value), self.allocator)
130+
}
131+
100132
/// Build a [`Program`].
101133
///
102134
/// ## Parameters
@@ -9821,6 +9853,21 @@ impl<'a> AstBuilder<'a> {
98219853
JSXChild::AstroScript(self.alloc_astro_script(span, program))
98229854
}
98239855

9856+
/// Build a [`JSXChild::AstroDoctype`].
9857+
///
9858+
/// This node contains an [`AstroDoctype`] that will be stored in the memory arena.
9859+
///
9860+
/// ## Parameters
9861+
/// * `span`: Node location in source code (includes `<!` and `>`)
9862+
/// * `value`: The document type value, e.g., "html" from `<!doctype html>` or `<!DOCTYPE html>`
9863+
#[inline]
9864+
pub fn jsx_child_astro_doctype<A1>(self, span: Span, value: A1) -> JSXChild<'a>
9865+
where
9866+
A1: Into<Atom<'a>>,
9867+
{
9868+
JSXChild::AstroDoctype(self.alloc_astro_doctype(span, value))
9869+
}
9870+
98249871
/// Build a [`JSXSpreadChild`].
98259872
///
98269873
/// If you want the built node to be allocated in the memory arena,

0 commit comments

Comments
 (0)