-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(es/react-compiler): recover source map spans from locations #12104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,19 +26,25 @@ use swc_ecma_ast as swc; | |
| use crate::preserved_ast::PreservedAst; | ||
|
|
||
| /// Convert with source text and preserved SWC nodes from the forward pass. | ||
| pub fn convert_program_to_swc(file: &File, preserved_ast: PreservedAst) -> swc::Program { | ||
| let ctx = ReverseCtx::new(preserved_ast); | ||
| pub fn convert_program_to_swc( | ||
| file: &File, | ||
| preserved_ast: PreservedAst, | ||
| source_file_start_pos: BytePos, | ||
| ) -> swc::Program { | ||
| let ctx = ReverseCtx::new(preserved_ast, source_file_start_pos); | ||
| ctx.convert_program(&file.program) | ||
| } | ||
|
|
||
| struct ReverseCtx { | ||
| preserved_ast: RefCell<PreservedAst>, | ||
| source_file_start_pos: BytePos, | ||
| } | ||
|
|
||
| impl ReverseCtx { | ||
| fn new(preserved_ast: PreservedAst) -> Self { | ||
| fn new(preserved_ast: PreservedAst, source_file_start_pos: BytePos) -> Self { | ||
| Self { | ||
| preserved_ast: RefCell::new(preserved_ast), | ||
| source_file_start_pos, | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -103,10 +109,27 @@ impl ReverseCtx { | |
| } | ||
|
|
||
| fn span_from_base(&self, base: &BaseNode) -> Span { | ||
| // `start` and `end` preserve absolute SWC `BytePos` values from the | ||
| // forward conversion, while `loc` indices are file-relative. | ||
| match (base.start, base.end) { | ||
| (Some(start), Some(end)) => Span::new(BytePos(start), BytePos(end)), | ||
| (Some(start), None) => Span::new(BytePos(start), BytePos(start)), | ||
| _ => DUMMY_SP, | ||
| _ => base.loc.as_ref().map_or(DUMMY_SP, |loc| { | ||
| let start = loc | ||
| .start | ||
| .index | ||
| .map(|index| self.source_file_start_pos + BytePos(index)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When callers parse multiple files with one Useful? React with 👍 / 👎. |
||
| let end = loc | ||
| .end | ||
| .index | ||
| .or(loc.start.index) | ||
| .map(|index| self.source_file_start_pos + BytePos(index)); | ||
|
|
||
| match (start, end) { | ||
| (Some(start), Some(end)) => Span::new(start, end), | ||
| _ => DUMMY_SP, | ||
| } | ||
| }), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -2916,3 +2939,81 @@ fn ts_type_operator_op(operator: &str) -> Option<swc::TsTypeOperatorOp> { | |
| _ => None, | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use react_compiler_ast::common::{Position, SourceLocation}; | ||
|
|
||
| use super::*; | ||
|
|
||
| fn base( | ||
| start: Option<u32>, | ||
| end: Option<u32>, | ||
| loc_start: Option<u32>, | ||
| loc_end: Option<u32>, | ||
| ) -> BaseNode { | ||
| BaseNode { | ||
| start, | ||
| end, | ||
| loc: loc_start.map(|index| SourceLocation { | ||
| start: Position { | ||
| line: 1, | ||
| column: index, | ||
| index: Some(index), | ||
| }, | ||
| end: Position { | ||
| line: 1, | ||
| column: loc_end.unwrap_or_default(), | ||
| index: loc_end, | ||
| }, | ||
| filename: None, | ||
| identifier_name: None, | ||
| }), | ||
| ..Default::default() | ||
| } | ||
| } | ||
|
|
||
| fn ctx() -> ReverseCtx { | ||
| ReverseCtx::new(Default::default(), BytePos(1_000)) | ||
| } | ||
|
|
||
| #[test] | ||
| fn span_from_base_preserves_start_and_end() { | ||
| assert_eq!( | ||
| ctx().span_from_base(&base(Some(10), Some(20), Some(1), Some(2))), | ||
| Span::new(BytePos(10), BytePos(20)) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn span_from_base_collapses_partial_start_and_end() { | ||
| assert_eq!( | ||
| ctx().span_from_base(&base(Some(10), None, Some(1), Some(2))), | ||
| Span::new(BytePos(10), BytePos(10)) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn span_from_base_uses_file_relative_loc_indices() { | ||
| assert_eq!( | ||
| ctx().span_from_base(&base(None, None, Some(10), Some(20))), | ||
| Span::new(BytePos(1_010), BytePos(1_020)) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn span_from_base_collapses_partial_loc_indices() { | ||
| assert_eq!( | ||
| ctx().span_from_base(&base(None, None, Some(10), None)), | ||
| Span::new(BytePos(1_010), BytePos(1_010)) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn span_from_base_without_positions_is_dummy() { | ||
| assert_eq!( | ||
| ctx().span_from_base(&base(None, None, None, None)), | ||
| DUMMY_SP | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This value is later added to Babel
loc.indexvalues to recover SWC spans, butprogram.span().lois the first parsed token rather than the beginning of the source file. For any input with leading whitespace or comments before the first token, a compiler-created node that has onlylocwill be shifted forward by that leading length (for example,loc.index == 0maps to the first token instead ofBytePos(1)), so emitted sourcemaps point at the wrong original text. Pass the actual file start, or derive it from the program base location, instead.Useful? React with 👍 / 👎.