mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(ast/estree): include comments in TS Program span (#28326)
- Align TS ESTree `Program` spans with `@typescript-eslint/parser` v8.72.0, which includes leading and trailing comments/whitespace. - Remove the TS-only `Program.start` adjustment from the Rust serializer and raw-transfer deserializers. - Update affected coverage and oxlint fixture snapshots.
This commit is contained in:
1 parent
9510eac186
commit
ed7df7de92
12 files changed
+63
-4763
No files matched your search
@@ -64,7 +64,8 @@ export function resetBuffer() {
|
||||
}
|
||||
|
||||
function deserializeProgram(pos) {
|
||||
let end = deserializeI32(pos + 4),
|
||||
let start = deserializeI32(pos),
|
||||
end = deserializeI32(pos + 4),
|
||||
program = (parent = {
|
||||
__proto__: NodeProto,
|
||||
type: "Program",
|
||||
@@ -78,32 +79,12 @@ function deserializeProgram(pos) {
|
||||
tokens === null && initTokens();
|
||||
return tokens;
|
||||
},
|
||||
start: 0,
|
||||
start,
|
||||
end,
|
||||
range: [0, end],
|
||||
range: [start, end],
|
||||
parent: null,
|
||||
}),
|
||||
body = (program.body = deserializeVecDirective(pos + 88));
|
||||
body.push(...deserializeVecStatement(pos + 112));
|
||||
{
|
||||
let start;
|
||||
if (body.length > 0) {
|
||||
let first = body[0];
|
||||
start = first.start;
|
||||
if (first.type === "ExportNamedDeclaration" || first.type === "ExportDefaultDeclaration") {
|
||||
let { declaration } = first;
|
||||
if (
|
||||
declaration !== null
|
||||
&& declaration.type === "ClassDeclaration"
|
||||
&& declaration.decorators.length > 0
|
||||
) {
|
||||
let decoratorStart = declaration.decorators[0].start;
|
||||
decoratorStart < start && (start = decoratorStart);
|
||||
}
|
||||
}
|
||||
} else start = end;
|
||||
program.start = program.range[0] = start;
|
||||
}
|
||||
});
|
||||
(program.body = deserializeVecDirective(pos + 88)).push(...deserializeVecStatement(pos + 112));
|
||||
parent = null;
|
||||
return program;
|
||||
}
|
||||
|
||||
@@ -323,8 +323,7 @@ const LOC_DESCRIPTOR: PropertyDescriptor = {
|
||||
*/
|
||||
export function computeLoc(start: number, end: number): Location {
|
||||
// All AST nodes, tokens and comments have `start < end`, with only one exception:
|
||||
// `Program` node can have `start === end` if it has no directives or statements - either 0-length file,
|
||||
// or purely comments and/or whitespace and/or hashbang. But `start > end` is impossible.
|
||||
// `Program` node has `start === end` for a 0-length file. But `start > end` is impossible.
|
||||
debugAssert(start <= end, "`start` must be <= `end`");
|
||||
|
||||
if (lines.length === 0) initLines();
|
||||
@@ -418,8 +417,6 @@ export function getNodeByRangeIndex(offset: number): ESTreeNode | null {
|
||||
debugAssertIsNonNull(ast);
|
||||
|
||||
// If index is outside of `Program`, return `null`
|
||||
// TODO: Once `Program`'s span covers the entire file (as per ESLint v10), `index < ast.start` check can be removed
|
||||
// (or changed to `index < 0` if we want to check for negative indices)
|
||||
if (offset < ast.start || offset >= ast.end) return null;
|
||||
|
||||
// Search for the node containing the index
|
||||
|
||||
+4
-4
@@ -53,9 +53,9 @@
|
||||
x bom-plugin(bom):
|
||||
| hasBOM: true
|
||||
| sourceText: "/*x*/'é';/*y*/ऊ=1;debugger;\n// 😀🤪😆😎🤮\ndebugger;\ndebugger;"
|
||||
| Program span: 5-61
|
||||
| Program span: 0-61
|
||||
| comments: [{"start":0,"end":5,"text":"/*x*/"},{"start":9,"end":14,"text":"/*y*/"},{"start":28,"end":41,"text":"// 😀🤪😆😎🤮"}]
|
||||
,-[files/bom_unicode.js:1:9]
|
||||
,-[files/bom_unicode.js:1:4]
|
||||
1 | ,-> /*x*/'é';/*y*/ऊ=1;debugger;
|
||||
2 | | // 😀🤪😆😎🤮
|
||||
3 | | debugger;
|
||||
@@ -134,9 +134,9 @@
|
||||
x bom-plugin(bom):
|
||||
| hasBOM: false
|
||||
| sourceText: "/*x*/'é';/*y*/ऊ=1;debugger;\n// 😀🤪😆😎🤮\ndebugger;\ndebugger;"
|
||||
| Program span: 5-61
|
||||
| Program span: 0-61
|
||||
| comments: [{"start":0,"end":5,"text":"/*x*/"},{"start":9,"end":14,"text":"/*y*/"},{"start":28,"end":41,"text":"// 😀🤪😆😎🤮"}]
|
||||
,-[files/no_bom_unicode.js:1:6]
|
||||
,-[files/no_bom_unicode.js:1:1]
|
||||
1 | ,-> /*x*/'é';/*y*/ऊ=1;debugger;
|
||||
2 | | // 😀🤪😆😎🤮
|
||||
3 | | debugger;
|
||||
|
||||
+14
-12
@@ -283,6 +283,20 @@
|
||||
2 | // Line comment after hashbang
|
||||
`----
|
||||
|
||||
x test-comments(test-comments): getAllComments: 3 comments
|
||||
| [0] Shebang: "/usr/bin/env node" at [0, 19]
|
||||
| [1] Line: " Line comment after hashbang" at [20, 50]
|
||||
| [2] Block: " Block comment after hashbang " at [51, 85]
|
||||
,-[files/hashbang.js:1:1]
|
||||
1 | ,-> #!/usr/bin/env node
|
||||
2 | | // Line comment after hashbang
|
||||
3 | | /* Block comment after hashbang */
|
||||
4 | | const topLevelVariable1 = 1;
|
||||
5 | | const topLevelVariable2 = 2;
|
||||
6 | |
|
||||
7 | `-> export function topLevelFunction() {}
|
||||
`----
|
||||
|
||||
x test-comments(test-comments): VariableDeclaration(topLevelVariable1):
|
||||
| getCommentsBefore: 3 comments
|
||||
| [0] Shebang: "/usr/bin/env node" at [0, 19]
|
||||
@@ -298,18 +312,6 @@
|
||||
5 | const topLevelVariable2 = 2;
|
||||
`----
|
||||
|
||||
x test-comments(test-comments): getAllComments: 3 comments
|
||||
| [0] Shebang: "/usr/bin/env node" at [0, 19]
|
||||
| [1] Line: " Line comment after hashbang" at [20, 50]
|
||||
| [2] Block: " Block comment after hashbang " at [51, 85]
|
||||
,-[files/hashbang.js:4:1]
|
||||
3 | /* Block comment after hashbang */
|
||||
4 | ,-> const topLevelVariable1 = 1;
|
||||
5 | | const topLevelVariable2 = 2;
|
||||
6 | |
|
||||
7 | `-> export function topLevelFunction() {}
|
||||
`----
|
||||
|
||||
x test-comments(test-comments): VariableDeclaration(topLevelVariable2):
|
||||
| getCommentsBefore: 0 comments
|
||||
| getCommentsInside: 0 comments
|
||||
|
||||
+13
-9
@@ -33,9 +33,11 @@
|
||||
| * TSUnionType:exit: (types: TSStringKeyword, TSNumberKeyword)
|
||||
| * TSTypeAliasDeclaration:exit: (typeAnnotation: TSUnionType)
|
||||
| * Program:exit
|
||||
,-[files/index.ts:4:1]
|
||||
3 | // All `Identifier`s
|
||||
4 | ,-> let a = { x: y };
|
||||
,-[files/index.ts:1:1]
|
||||
1 | ,-> // @ts-nocheck
|
||||
2 | |
|
||||
3 | | // All `Identifier`s
|
||||
4 | | let a = { x: y };
|
||||
5 | |
|
||||
6 | | // No `ParenthesizedExpression`s in AST
|
||||
7 | | // prettier-ignore
|
||||
@@ -50,12 +52,14 @@
|
||||
`----
|
||||
|
||||
x estree-check(check): program:
|
||||
| start/end: [37,281]
|
||||
| range: [37,281]
|
||||
| loc: [{"start":{"line":4,"column":0},"end":{"line":16,"column":0}}]
|
||||
,-[files/index.ts:4:1]
|
||||
3 | // All `Identifier`s
|
||||
4 | ,-> let a = { x: y };
|
||||
| start/end: [0,281]
|
||||
| range: [0,281]
|
||||
| loc: [{"start":{"line":1,"column":0},"end":{"line":16,"column":0}}]
|
||||
,-[files/index.ts:1:1]
|
||||
1 | ,-> // @ts-nocheck
|
||||
2 | |
|
||||
3 | | // All `Identifier`s
|
||||
4 | | let a = { x: y };
|
||||
5 | |
|
||||
6 | | // No `ParenthesizedExpression`s in AST
|
||||
7 | | // prettier-ignore
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
# stdout
|
||||
```
|
||||
x getNode-plugin(getNode): type: null
|
||||
x getNode-plugin(getNode): type: Program
|
||||
,-[files/index.ts:1:1]
|
||||
1 | // Comment
|
||||
: ^
|
||||
2 |
|
||||
`----
|
||||
|
||||
x getNode-plugin(getNode): type: null
|
||||
x getNode-plugin(getNode): type: Program
|
||||
,-[files/index.ts:2:1]
|
||||
1 | // Comment
|
||||
2 |
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use std::cmp;
|
||||
|
||||
use oxc_ast_macros::ast_meta;
|
||||
use oxc_estree::{
|
||||
CompactFixesSerializer, CompactSerializer, Concat2, ESTree, JsonSafeString,
|
||||
PrettyFixesSerializer, PrettySerializer, Serializer, StructSerializer,
|
||||
};
|
||||
use oxc_span::GetSpan;
|
||||
|
||||
use crate::ast::*;
|
||||
|
||||
@@ -81,19 +78,11 @@ impl Program<'_> {
|
||||
|
||||
/// Serializer for `Program`.
|
||||
///
|
||||
/// In TS AST, set start span to start of first directive or statement.
|
||||
/// This is required because unlike Acorn, TS-ESLint excludes whitespace and comments
|
||||
/// from the `Program` start span.
|
||||
/// See <https://github.com/oxc-project/oxc/pull/10134> for more info.
|
||||
///
|
||||
/// Special case where first statement is an `ExportDeclaration` or `ExportDefaultDeclaration`
|
||||
/// exporting a class with decorators, where one of the decorators is before `export`.
|
||||
/// In these cases, the span of the statement starts after the span of the decorators.
|
||||
/// e.g. `@dec export class C {}` - `ExportDeclaration` span start is 5, `Decorator` span start is 0.
|
||||
/// `Program` span start is 0 (not 5).
|
||||
/// Serializes directives and statements together as `body`.
|
||||
/// The span covers the entire source text, including leading and trailing comments and whitespace.
|
||||
#[ast_meta]
|
||||
#[estree(raw_deser = "
|
||||
const start = IS_TS ? 0 : DESER[i32](POS_OFFSET.span.start),
|
||||
const start = DESER[i32](POS_OFFSET.span.start),
|
||||
end = DESER[i32](POS_OFFSET.span.end);
|
||||
|
||||
const program = parent = {
|
||||
@@ -124,32 +113,6 @@ impl Program<'_> {
|
||||
const body = program.body = DESER[Vec<Directive>](POS_OFFSET.directives);
|
||||
body.push(...DESER[Vec<Statement>](POS_OFFSET.body));
|
||||
|
||||
if (IS_TS) {
|
||||
let start;
|
||||
if (body.length > 0) {
|
||||
const first = body[0];
|
||||
start = first.start;
|
||||
if (first.type === 'ExportNamedDeclaration' || first.type === 'ExportDefaultDeclaration') {
|
||||
const { declaration } = first;
|
||||
if (
|
||||
declaration !== null && declaration.type === 'ClassDeclaration'
|
||||
&& declaration.decorators.length > 0
|
||||
) {
|
||||
const decoratorStart = declaration.decorators[0].start;
|
||||
if (decoratorStart < start) start = decoratorStart;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
start = end;
|
||||
}
|
||||
|
||||
if (RANGE) {
|
||||
program.start = program.range[0] = start;
|
||||
} else {
|
||||
program.start = start;
|
||||
}
|
||||
}
|
||||
|
||||
if (PARENT) parent = null;
|
||||
|
||||
program
|
||||
@@ -165,49 +128,12 @@ impl ESTree for ProgramConverter<'_, '_> {
|
||||
state.serialize_field("body", &Concat2(&program.directives, &program.body));
|
||||
state.serialize_field("sourceType", &program.source_type.module_kind());
|
||||
state.serialize_field("hashbang", &program.hashbang);
|
||||
|
||||
let span = if state.include_ts_fields() {
|
||||
Span::new(get_ts_start_span(program), program.span.end)
|
||||
} else {
|
||||
program.span
|
||||
};
|
||||
state.serialize_span(span);
|
||||
state.serialize_span(program.span);
|
||||
|
||||
state.end();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ts_start_span(program: &Program<'_>) -> u32 {
|
||||
if let Some(first_directive) = program.directives.first() {
|
||||
return first_directive.span.start;
|
||||
}
|
||||
|
||||
let Some(first_stmt) = program.body.first() else {
|
||||
// Program contains no statements or directives. Span start = span end.
|
||||
return program.span.end;
|
||||
};
|
||||
|
||||
let start = first_stmt.span().start;
|
||||
match first_stmt {
|
||||
Statement::ExportDeclaration(decl) => {
|
||||
if let Declaration::ClassDeclaration(class) = &decl.declaration
|
||||
&& let Some(decorator) = class.decorators.first()
|
||||
{
|
||||
return cmp::min(start, decorator.span.start);
|
||||
}
|
||||
}
|
||||
Statement::ExportDefaultDeclaration(decl) => {
|
||||
if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &decl.declaration
|
||||
&& let Some(decorator) = class.decorators.first()
|
||||
{
|
||||
return cmp::min(start, decorator.span.start);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
start
|
||||
}
|
||||
|
||||
/// Serialize `value` field of `Comment`.
|
||||
///
|
||||
/// This serializer does not work for JSON serializer, because there's no access to source text
|
||||
|
||||
@@ -55,37 +55,18 @@ export function resetBuffer() {
|
||||
}
|
||||
|
||||
function deserializeProgram(pos) {
|
||||
let end = deserializeI32(pos + 4),
|
||||
let start = deserializeI32(pos),
|
||||
end = deserializeI32(pos + 4),
|
||||
program = {
|
||||
type: "Program",
|
||||
body: null,
|
||||
sourceType: deserializeModuleKind(pos + 137),
|
||||
hashbang: null,
|
||||
start: 0,
|
||||
start,
|
||||
end,
|
||||
};
|
||||
program.hashbang = deserializeOptionHashbang(pos + 56);
|
||||
let body = (program.body = deserializeVecDirective(pos + 88));
|
||||
body.push(...deserializeVecStatement(pos + 112));
|
||||
{
|
||||
let start;
|
||||
if (body.length > 0) {
|
||||
let first = body[0];
|
||||
start = first.start;
|
||||
if (first.type === "ExportNamedDeclaration" || first.type === "ExportDefaultDeclaration") {
|
||||
let { declaration } = first;
|
||||
if (
|
||||
declaration !== null
|
||||
&& declaration.type === "ClassDeclaration"
|
||||
&& declaration.decorators.length > 0
|
||||
) {
|
||||
let decoratorStart = declaration.decorators[0].start;
|
||||
decoratorStart < start && (start = decoratorStart);
|
||||
}
|
||||
}
|
||||
} else start = end;
|
||||
program.start = start;
|
||||
}
|
||||
(program.body = deserializeVecDirective(pos + 88)).push(...deserializeVecStatement(pos + 112));
|
||||
return program;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,38 +56,19 @@ export function resetBuffer() {
|
||||
}
|
||||
|
||||
function deserializeProgram(pos) {
|
||||
let end = deserializeI32(pos + 4),
|
||||
let start = deserializeI32(pos),
|
||||
end = deserializeI32(pos + 4),
|
||||
program = (parent = {
|
||||
type: "Program",
|
||||
body: null,
|
||||
sourceType: deserializeModuleKind(pos + 137),
|
||||
hashbang: null,
|
||||
start: 0,
|
||||
start,
|
||||
end,
|
||||
parent: null,
|
||||
});
|
||||
program.hashbang = deserializeOptionHashbang(pos + 56);
|
||||
let body = (program.body = deserializeVecDirective(pos + 88));
|
||||
body.push(...deserializeVecStatement(pos + 112));
|
||||
{
|
||||
let start;
|
||||
if (body.length > 0) {
|
||||
let first = body[0];
|
||||
start = first.start;
|
||||
if (first.type === "ExportNamedDeclaration" || first.type === "ExportDefaultDeclaration") {
|
||||
let { declaration } = first;
|
||||
if (
|
||||
declaration !== null
|
||||
&& declaration.type === "ClassDeclaration"
|
||||
&& declaration.decorators.length > 0
|
||||
) {
|
||||
let decoratorStart = declaration.decorators[0].start;
|
||||
decoratorStart < start && (start = decoratorStart);
|
||||
}
|
||||
}
|
||||
} else start = end;
|
||||
program.start = start;
|
||||
}
|
||||
(program.body = deserializeVecDirective(pos + 88)).push(...deserializeVecStatement(pos + 112));
|
||||
parent = null;
|
||||
return program;
|
||||
}
|
||||
|
||||
@@ -55,38 +55,19 @@ export function resetBuffer() {
|
||||
}
|
||||
|
||||
function deserializeProgram(pos) {
|
||||
let end = deserializeI32(pos + 4),
|
||||
let start = deserializeI32(pos),
|
||||
end = deserializeI32(pos + 4),
|
||||
program = {
|
||||
type: "Program",
|
||||
body: null,
|
||||
sourceType: deserializeModuleKind(pos + 137),
|
||||
hashbang: null,
|
||||
start: 0,
|
||||
start,
|
||||
end,
|
||||
range: [0, end],
|
||||
range: [start, end],
|
||||
};
|
||||
program.hashbang = deserializeOptionHashbang(pos + 56);
|
||||
let body = (program.body = deserializeVecDirective(pos + 88));
|
||||
body.push(...deserializeVecStatement(pos + 112));
|
||||
{
|
||||
let start;
|
||||
if (body.length > 0) {
|
||||
let first = body[0];
|
||||
start = first.start;
|
||||
if (first.type === "ExportNamedDeclaration" || first.type === "ExportDefaultDeclaration") {
|
||||
let { declaration } = first;
|
||||
if (
|
||||
declaration !== null
|
||||
&& declaration.type === "ClassDeclaration"
|
||||
&& declaration.decorators.length > 0
|
||||
) {
|
||||
let decoratorStart = declaration.decorators[0].start;
|
||||
decoratorStart < start && (start = decoratorStart);
|
||||
}
|
||||
}
|
||||
} else start = end;
|
||||
program.start = program.range[0] = start;
|
||||
}
|
||||
(program.body = deserializeVecDirective(pos + 88)).push(...deserializeVecStatement(pos + 112));
|
||||
return program;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,39 +56,20 @@ export function resetBuffer() {
|
||||
}
|
||||
|
||||
function deserializeProgram(pos) {
|
||||
let end = deserializeI32(pos + 4),
|
||||
let start = deserializeI32(pos),
|
||||
end = deserializeI32(pos + 4),
|
||||
program = (parent = {
|
||||
type: "Program",
|
||||
body: null,
|
||||
sourceType: deserializeModuleKind(pos + 137),
|
||||
hashbang: null,
|
||||
start: 0,
|
||||
start,
|
||||
end,
|
||||
range: [0, end],
|
||||
range: [start, end],
|
||||
parent: null,
|
||||
});
|
||||
program.hashbang = deserializeOptionHashbang(pos + 56);
|
||||
let body = (program.body = deserializeVecDirective(pos + 88));
|
||||
body.push(...deserializeVecStatement(pos + 112));
|
||||
{
|
||||
let start;
|
||||
if (body.length > 0) {
|
||||
let first = body[0];
|
||||
start = first.start;
|
||||
if (first.type === "ExportNamedDeclaration" || first.type === "ExportDefaultDeclaration") {
|
||||
let { declaration } = first;
|
||||
if (
|
||||
declaration !== null
|
||||
&& declaration.type === "ClassDeclaration"
|
||||
&& declaration.decorators.length > 0
|
||||
) {
|
||||
let decoratorStart = declaration.decorators[0].start;
|
||||
decoratorStart < start && (start = decoratorStart);
|
||||
}
|
||||
}
|
||||
} else start = end;
|
||||
program.start = program.range[0] = start;
|
||||
}
|
||||
(program.body = deserializeVecDirective(pos + 88)).push(...deserializeVecStatement(pos + 112));
|
||||
parent = null;
|
||||
return program;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user