Skip to content

Commit b979413

Browse files
authored
Merge pull request #24982 from Microsoft/sourceMapBase
Fix off-by-one error with sourcemaps
2 parents 374cbd6 + 575ab61 commit b979413

File tree

8 files changed

+121
-233
lines changed

8 files changed

+121
-233
lines changed

src/compiler/sourcemap.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ namespace ts {
6464

6565
// Used for initialize lastEncodedSourceMapSpan and reset lastEncodedSourceMapSpan when updateLastEncodedAndRecordedSpans
6666
const defaultLastEncodedSourceMapSpan: SourceMapSpan = {
67-
emittedLine: 1,
68-
emittedColumn: 1,
69-
sourceLine: 1,
70-
sourceColumn: 1,
67+
emittedLine: 0,
68+
emittedColumn: 0,
69+
sourceLine: 0,
70+
sourceColumn: 0,
7171
sourceIndex: 0
7272
};
7373

@@ -258,6 +258,11 @@ namespace ts {
258258
return;
259259
}
260260

261+
Debug.assert(lastRecordedSourceMapSpan.emittedColumn >= 0, "lastEncodedSourceMapSpan.emittedColumn was negative");
262+
Debug.assert(lastRecordedSourceMapSpan.sourceIndex >= 0, "lastEncodedSourceMapSpan.sourceIndex was negative");
263+
Debug.assert(lastRecordedSourceMapSpan.sourceLine >= 0, "lastEncodedSourceMapSpan.sourceLine was negative");
264+
Debug.assert(lastRecordedSourceMapSpan.sourceColumn >= 0, "lastEncodedSourceMapSpan.sourceColumn was negative");
265+
261266
let prevEncodedEmittedColumn = lastEncodedSourceMapSpan!.emittedColumn;
262267
// Line/Comma delimiters
263268
if (lastEncodedSourceMapSpan!.emittedLine === lastRecordedSourceMapSpan.emittedLine) {
@@ -271,7 +276,7 @@ namespace ts {
271276
for (let encodedLine = lastEncodedSourceMapSpan!.emittedLine; encodedLine < lastRecordedSourceMapSpan.emittedLine; encodedLine++) {
272277
sourceMapData.sourceMapMappings += ";";
273278
}
274-
prevEncodedEmittedColumn = 1;
279+
prevEncodedEmittedColumn = 0;
275280
}
276281

277282
// 1. Relative Column 0 based
@@ -316,10 +321,6 @@ namespace ts {
316321

317322
const sourceLinePos = getLineAndCharacterOfPosition(currentSource, pos);
318323

319-
// Convert the location to be one-based.
320-
sourceLinePos.line++;
321-
sourceLinePos.character++;
322-
323324
const emittedLine = writer.getLine();
324325
const emittedColumn = writer.getColumn();
325326

@@ -402,16 +403,16 @@ namespace ts {
402403
host.getCanonicalFileName,
403404
/*isAbsolutePathAnUrl*/ true
404405
);
405-
const absolutePath = toPath(resolvedPath, sourcesDirectoryPath, host.getCanonicalFileName);
406+
const absolutePath = getNormalizedAbsolutePath(resolvedPath, sourcesDirectoryPath);
406407
// tslint:disable-next-line:no-null-keyword
407408
setupSourceEntry(absolutePath, originalMap.sourcesContent ? originalMap.sourcesContent[raw.sourceIndex] : null); // TODO: Lookup content for inlining?
408409
const newIndex = sourceMapData.sourceMapSources.indexOf(resolvedPath);
409410
// Then reencode all the updated spans into the overall map
410411
encodeLastRecordedSourceMapSpan();
411412
lastRecordedSourceMapSpan = {
412413
...raw,
413-
emittedLine: raw.emittedLine + offsetLine - 1,
414-
emittedColumn: raw.emittedLine === 0 ? (raw.emittedColumn + firstLineColumnOffset - 1) : raw.emittedColumn,
414+
emittedLine: raw.emittedLine + offsetLine,
415+
emittedColumn: raw.emittedLine === 0 ? (raw.emittedColumn + firstLineColumnOffset) : raw.emittedColumn,
415416
sourceIndex: newIndex,
416417
};
417418
});

src/compiler/sourcemapDecoder.ts

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,16 @@ namespace ts.sourcemaps {
148148
}
149149
}
150150

151-
export function calculateDecodedMappings<T>(map: SourceMapData, processPosition: (position: RawSourceMapPosition) => T, host?: { log?(s: string): void }): T[] {
152-
const state: DecoderState<T> = {
151+
/*@internal*/
152+
export interface MappingsDecoder extends Iterator<SourceMapSpan> {
153+
readonly decodingIndex: number;
154+
readonly error: string | undefined;
155+
readonly lastSpan: SourceMapSpan;
156+
}
157+
158+
/*@internal*/
159+
export function decodeMappings(map: SourceMapData): MappingsDecoder {
160+
const state: DecoderState = {
153161
encodedText: map.mappings,
154162
currentNameIndex: undefined,
155163
sourceMapNamesLength: map.names ? map.names.length : undefined,
@@ -158,20 +166,40 @@ namespace ts.sourcemaps {
158166
currentSourceColumn: 0,
159167
currentSourceLine: 0,
160168
currentSourceIndex: 0,
161-
positions: [],
162-
decodingIndex: 0,
163-
processPosition,
169+
decodingIndex: 0
164170
};
165-
while (!hasCompletedDecoding(state)) {
166-
decodeSinglePosition(state);
167-
if (state.error) {
168-
if (host && host.log) {
169-
host.log(`Encountered error while decoding sourcemap: ${state.error}`);
170-
}
171-
return [];
171+
function captureSpan(): SourceMapSpan {
172+
return {
173+
emittedColumn: state.currentEmittedColumn,
174+
emittedLine: state.currentEmittedLine,
175+
sourceColumn: state.currentSourceColumn,
176+
sourceIndex: state.currentSourceIndex,
177+
sourceLine: state.currentSourceLine,
178+
nameIndex: state.currentNameIndex
179+
};
180+
}
181+
return {
182+
get decodingIndex() { return state.decodingIndex; },
183+
get error() { return state.error; },
184+
get lastSpan() { return captureSpan(); },
185+
next() {
186+
if (hasCompletedDecoding(state) || state.error) return { done: true, value: undefined as never };
187+
if (!decodeSinglePosition(state)) return { done: true, value: undefined as never };
188+
return { done: false, value: captureSpan() };
172189
}
190+
};
191+
}
192+
193+
export function calculateDecodedMappings<T>(map: SourceMapData, processPosition: (position: RawSourceMapPosition) => T, host?: { log?(s: string): void }): T[] {
194+
const decoder = decodeMappings(map);
195+
const positions = arrayFrom(decoder, processPosition);
196+
if (decoder.error) {
197+
if (host && host.log) {
198+
host.log(`Encountered error while decoding sourcemap: ${decoder.error}`);
199+
}
200+
return [];
173201
}
174-
return state.positions;
202+
return positions;
175203
}
176204

177205
interface ProcessedSourceMapPosition {
@@ -189,7 +217,7 @@ namespace ts.sourcemaps {
189217
nameIndex?: number;
190218
}
191219

192-
interface DecoderState<T> {
220+
interface DecoderState {
193221
decodingIndex: number;
194222
currentEmittedLine: number;
195223
currentEmittedColumn: number;
@@ -200,15 +228,13 @@ namespace ts.sourcemaps {
200228
encodedText: string;
201229
sourceMapNamesLength?: number;
202230
error?: string;
203-
positions: T[];
204-
processPosition: (position: RawSourceMapPosition) => T;
205231
}
206232

207-
function hasCompletedDecoding(state: DecoderState<any>) {
233+
function hasCompletedDecoding(state: DecoderState) {
208234
return state.decodingIndex === state.encodedText.length;
209235
}
210236

211-
function decodeSinglePosition<T>(state: DecoderState<T>): void {
237+
function decodeSinglePosition(state: DecoderState): boolean {
212238
while (state.decodingIndex < state.encodedText.length) {
213239
const char = state.encodedText.charCodeAt(state.decodingIndex);
214240
if (char === CharacterCodes.semicolon) {
@@ -230,40 +256,40 @@ namespace ts.sourcemaps {
230256
state.currentEmittedColumn += base64VLQFormatDecode();
231257
// Incorrect emittedColumn dont support this map
232258
if (createErrorIfCondition(state.currentEmittedColumn < 0, "Invalid emittedColumn found")) {
233-
return;
259+
return false;
234260
}
235261
// Dont support reading mappings that dont have information about original source and its line numbers
236262
if (createErrorIfCondition(isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex), "Unsupported Error Format: No entries after emitted column")) {
237-
return;
263+
return false;
238264
}
239265

240266
// 2. Relative sourceIndex
241267
state.currentSourceIndex += base64VLQFormatDecode();
242268
// Incorrect sourceIndex dont support this map
243269
if (createErrorIfCondition(state.currentSourceIndex < 0, "Invalid sourceIndex found")) {
244-
return;
270+
return false;
245271
}
246272
// Dont support reading mappings that dont have information about original source position
247273
if (createErrorIfCondition(isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex), "Unsupported Error Format: No entries after sourceIndex")) {
248-
return;
274+
return false;
249275
}
250276

251277
// 3. Relative sourceLine 0 based
252278
state.currentSourceLine += base64VLQFormatDecode();
253279
// Incorrect sourceLine dont support this map
254280
if (createErrorIfCondition(state.currentSourceLine < 0, "Invalid sourceLine found")) {
255-
return;
281+
return false;
256282
}
257283
// Dont support reading mappings that dont have information about original source and its line numbers
258284
if (createErrorIfCondition(isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex), "Unsupported Error Format: No entries after emitted Line")) {
259-
return;
285+
return false;
260286
}
261287

262288
// 4. Relative sourceColumn 0 based
263289
state.currentSourceColumn += base64VLQFormatDecode();
264290
// Incorrect sourceColumn dont support this map
265291
if (createErrorIfCondition(state.currentSourceColumn < 0, "Invalid sourceLine found")) {
266-
return;
292+
return false;
267293
}
268294
// 5. Check if there is name:
269295
if (!isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex)) {
@@ -279,27 +305,15 @@ namespace ts.sourcemaps {
279305
}
280306
// Dont support reading mappings that dont have information about original source and its line numbers
281307
if (createErrorIfCondition(!isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex), "Unsupported Error Format: There are more entries after " + (state.currentNameIndex === undefined ? "sourceColumn" : "nameIndex"))) {
282-
return;
308+
return false;
283309
}
284310

285311
// Entry should be complete
286-
capturePosition();
287-
return;
312+
return true;
288313
}
289314

290315
createErrorIfCondition(/*condition*/ true, "No encoded entry found");
291-
return;
292-
293-
function capturePosition() {
294-
state.positions.push(state.processPosition({
295-
emittedColumn: state.currentEmittedColumn,
296-
emittedLine: state.currentEmittedLine,
297-
sourceColumn: state.currentSourceColumn,
298-
sourceIndex: state.currentSourceIndex,
299-
sourceLine: state.currentSourceLine,
300-
nameIndex: state.currentNameIndex
301-
}));
302-
}
316+
return false;
303317

304318
function createErrorIfCondition(condition: boolean, errormsg: string) {
305319
if (state.error) {

0 commit comments

Comments
 (0)