@samitouri / QOS-React / commits / ad4ecb6e6e

[DevTools] Fix symbolication with Index Source Maps (#34300)

Sebastian "Sebbie" Silbermann committed Aug 26, 2025 at 15:18 UTC ad4ecb6e6eb0663dcabb5967fe23a8c4331c18d0
2 files changed +35 -61
packages/react-devtools-shared/src/hooks/SourceMapConsumer.js
+34 -60
@@ -136,9 +136,9 @@ function BasicSourceMapConsumer(sourceMapJSON: BasicSourceMap) {
136 }
137
138 type Section = {
139 - +generatedColumn: number,
140 - +generatedLine: number,
141 - +map: MixedSourceMap,
139 + +offsetColumn0: number,
140 + +offsetLine0: number,
141 + +map: BasicSourceMap,
142
143 // Lazily parsed only when/as the section is needed.
144 sourceMapConsumer: SourceMapConsumerType | null,
@@ -160,12 +160,12 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
160 column: number,
161 ...
162 } = section.offset;
163 - const offsetLine = offset.line;
164 - const offsetColumn = offset.column;
163 + const offsetLine0 = offset.line;
164 + const offsetColumn0 = offset.column;
165
166 if (
167 - offsetLine < lastOffset.line ||
168 - (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)
167 + offsetLine0 < lastOffset.line ||
168 + (offsetLine0 === lastOffset.line && offsetColumn0 < lastOffset.column)
169 ) {
170 throw new Error('Section offsets must be ordered and non-overlapping.');
171 }
@@ -173,9 +173,8 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
173 lastOffset = offset;
174
175 return {
176 - // The offset fields are 0-based, but we use 1-based indices when encoding/decoding from VLQ.
177 - generatedLine: offsetLine + 1,
178 - generatedColumn: offsetColumn + 1,
176 + offsetLine0,
177 + offsetColumn0,
178 map: section.map,
179 sourceMapConsumer: null,
180 };
@@ -186,55 +185,29 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
185 lineNumber,
186 }: SearchPosition): ResultPosition {
187 // Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
189 - const targetColumnNumber = columnNumber - 1;
190 -
191 - let section = null;
192 -
193 - let startIndex = 0;
194 - let stopIndex = sections.length - 1;
195 - let index = -1;
196 - while (startIndex <= stopIndex) {
197 - index = Math.floor((stopIndex + startIndex) / 2);
198 - section = sections[index];
199 -
200 - const currentLine = section.generatedLine;
201 - if (currentLine === lineNumber) {
202 - const currentColumn = section.generatedColumn;
203 - if (currentColumn === lineNumber) {
204 - break;
205 - } else {
206 - if (currentColumn > targetColumnNumber) {
207 - if (stopIndex - index > 0) {
208 - stopIndex = index;
209 - } else {
210 - index = stopIndex;
211 - break;
212 - }
213 - } else {
214 - if (index - startIndex > 0) {
215 - startIndex = index;
216 - } else {
217 - index = startIndex;
218 - break;
219 - }
220 - }
221 - }
188 + const column0 = columnNumber - 1;
189 + const line0 = lineNumber - 1;
190 +
191 + // Sections must not overlap and must be sorted: https://tc39.es/source-map/#section-object
192 + // Therefore the last section that has an offset less than or equal to the frame is the applicable one.
193 + let left = 0;
194 + let right = sections.length - 1;
195 + let section: Section | null = null;
196 +
197 + while (left <= right) {
198 + // fast Math.floor
199 + const middle = ~~((left + right) / 2);
200 + const currentSection = sections[middle];
201 +
202 + if (
203 + currentSection.offsetLine0 < line0 ||
204 + (currentSection.offsetLine0 === line0 &&
205 + currentSection.offsetColumn0 <= column0)
206 + ) {
207 + section = currentSection;
208 + left = middle + 1;
209 } else {
223 - if (currentLine > lineNumber) {
224 - if (stopIndex - index > 0) {
225 - stopIndex = index;
226 - } else {
227 - index = stopIndex;
228 - break;
229 - }
230 - } else {
231 - if (index - startIndex > 0) {
232 - startIndex = index;
233 - } else {
234 - index = startIndex;
235 - break;
236 - }
237 - }
210 + right = middle - 1;
211 }
212 }
213
@@ -252,8 +225,9 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
225 }
226
227 return section.sourceMapConsumer.originalPositionFor({
255 - columnNumber,
256 - lineNumber,
228 + // The mappings in a Source Map section are relative to the section offset.
229 + columnNumber: columnNumber - section.offsetColumn0,
230 + lineNumber: lineNumber - section.offsetLine0,
231 });
232 }
233
packages/react-devtools-shared/src/hooks/SourceMapTypes.js
+1 -1
@@ -29,7 +29,7 @@ export type BasicSourceMap = {
29 };
30
31 export type IndexSourceMapSection = {
32 - map: IndexSourceMap | BasicSourceMap,
32 + map: BasicSourceMap,
33 offset: {
34 line: number,
35 column: number,