Skip to content

Commit

Permalink
Fix position of errors in template literals after newlines (#14798)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Jul 26, 2022
1 parent bfb4f98 commit b7e0945
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 95 deletions.
85 changes: 67 additions & 18 deletions packages/babel-helper-string-parser/src/index.ts
Expand Up @@ -142,12 +142,12 @@ function isStringEnd(
);
}

export type EscapedCharErrorHandlers = HexCharErrorHandlers &
type EscapedCharErrorHandlers = HexCharErrorHandlers &
CodePointErrorHandlers & {
strictNumericEscape(pos: number): void;
strictNumericEscape(pos: number, lineStart: number, curLine: number): void;
};

export function readEscapedChar(
function readEscapedChar(
input: string,
pos: number,
lineStart: number,
Expand All @@ -171,6 +171,8 @@ export function readEscapedChar(
({ code, pos } = readHexChar(
input,
pos,
lineStart,
curLine,
2,
false,
throwOnInvalid,
Expand All @@ -180,7 +182,14 @@ export function readEscapedChar(
}
case charCodes.lowercaseU: {
let code;
({ code, pos } = readCodePoint(input, pos, throwOnInvalid, errors));
({ code, pos } = readCodePoint(
input,
pos,
lineStart,
curLine,
throwOnInvalid,
errors,
));
return res(code === null ? null : String.fromCodePoint(code));
}
case charCodes.lowercaseT:
Expand Down Expand Up @@ -208,7 +217,7 @@ export function readEscapedChar(
if (inTemplate) {
return res(null);
} else {
errors.strictNumericEscape(pos - 1);
errors.strictNumericEscape(pos - 1, lineStart, curLine);
}
// fall through
default:
Expand All @@ -233,7 +242,7 @@ export function readEscapedChar(
if (inTemplate) {
return res(null);
} else {
errors.strictNumericEscape(startPos);
errors.strictNumericEscape(startPos, lineStart, curLine);
}
}

Expand All @@ -245,24 +254,36 @@ export function readEscapedChar(
}

type HexCharErrorHandlers = IntErrorHandlers & {
invalidEscapeSequence(pos: number, startPos: number): void;
invalidEscapeSequence(pos: number, lineStart: number, curLine: number): void;
};

// Used to read character escape sequences ('\x', '\u').
function readHexChar(
input: string,
pos: number,
lineStart: number,
curLine: number,
len: number,
forceLen: boolean,
throwOnInvalid: boolean,
errors: HexCharErrorHandlers,
) {
const initialPos = pos;
let n;
({ n, pos } = readInt(input, pos, 16, len, forceLen, false, errors));
({ n, pos } = readInt(
input,
pos,
lineStart,
curLine,
16,
len,
forceLen,
false,
errors,
));
if (n === null) {
if (throwOnInvalid) {
errors.invalidEscapeSequence(pos, initialPos);
errors.invalidEscapeSequence(initialPos, lineStart, curLine);
} else {
pos = initialPos - 1;
}
Expand All @@ -271,16 +292,31 @@ function readHexChar(
}

export type IntErrorHandlers = {
numericSeparatorInEscapeSequence(pos: number): void;
unexpectedNumericSeparator(pos: number): void;
numericSeparatorInEscapeSequence(
pos: number,
lineStart: number,
curLine: number,
): void;
unexpectedNumericSeparator(
pos: number,
lineStart: number,
curLine: number,
): void;
// It can return "true" to indicate that the error was handled
// and the int parsing should continue.
invalidDigit(pos: number, radix: number): boolean;
invalidDigit(
pos: number,
lineStart: number,
curLine: number,
radix: number,
): boolean;
};

export function readInt(
input: string,
pos: number,
lineStart: number,
curLine: number,
radix: number,
len: number | undefined,
forceLen: boolean,
Expand Down Expand Up @@ -313,14 +349,14 @@ export function readInt(
const next = input.charCodeAt(pos + 1);

if (!allowNumSeparator) {
errors.numericSeparatorInEscapeSequence(pos);
errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine);
} else if (
Number.isNaN(next) ||
!isAllowedSibling(next) ||
forbiddenSiblings.has(prev) ||
forbiddenSiblings.has(next)
) {
errors.unexpectedNumericSeparator(pos);
errors.unexpectedNumericSeparator(pos, lineStart, curLine);
}

// Ignore this _ character
Expand All @@ -340,7 +376,7 @@ export function readInt(
if (val >= radix) {
// If we found a digit which is too big, errors.invalidDigit can return true to avoid
// breaking the loop (this is used for error recovery).
if (val <= 9 && errors.invalidDigit(pos, radix)) {
if (val <= 9 && errors.invalidDigit(pos, lineStart, curLine, radix)) {
val = 0;
} else if (forceLen) {
val = 0;
Expand All @@ -360,12 +396,14 @@ export function readInt(
}

export type CodePointErrorHandlers = HexCharErrorHandlers & {
invalidCodePoint(pos: number): void;
invalidCodePoint(pos: number, lineStart: number, curLine: number): void;
};

export function readCodePoint(
input: string,
pos: number,
lineStart: number,
curLine: number,
throwOnInvalid: boolean,
errors: CodePointErrorHandlers,
) {
Expand All @@ -377,6 +415,8 @@ export function readCodePoint(
({ code, pos } = readHexChar(
input,
pos,
lineStart,
curLine,
input.indexOf("}", pos) - pos,
true,
throwOnInvalid,
Expand All @@ -385,13 +425,22 @@ export function readCodePoint(
++pos;
if (code !== null && code > 0x10ffff) {
if (throwOnInvalid) {
errors.invalidCodePoint(pos);
errors.invalidCodePoint(pos, lineStart, curLine);
} else {
return { code: null, pos };
}
}
} else {
({ code, pos } = readHexChar(input, pos, 4, false, throwOnInvalid, errors));
({ code, pos } = readHexChar(
input,
pos,
lineStart,
curLine,
4,
false,
throwOnInvalid,
errors,
));
}
return { code, pos };
}
120 changes: 43 additions & 77 deletions packages/babel-parser/src/tokenizer/index.ts
Expand Up @@ -36,16 +36,18 @@ import type { LookaheadState, DeferredStrictError } from "./state";
import {
readInt,
readCodePoint,
readEscapedChar,
readStringContents,
type IntErrorHandlers,
type CodePointErrorHandlers,
type EscapedCharErrorHandlers,
type StringContentsErrorHandlers,
} from "@babel/helper-string-parser";

import type { Plugin } from "../typings";

function buildPosition(pos: number, lineStart: number, curLine: number) {
return new Position(curLine, pos - lineStart, pos);
}

const VALID_REGEX_FLAGS = new Set([
charCodes.lowercaseG,
charCodes.lowercaseM,
Expand Down Expand Up @@ -1129,6 +1131,8 @@ export default class Tokenizer extends CommentsParser {
const { n, pos } = readInt(
this.input,
this.state.pos,
this.state.lineStart,
this.state.curLine,
radix,
len,
forceLen,
Expand Down Expand Up @@ -1284,6 +1288,8 @@ export default class Tokenizer extends CommentsParser {
const { code, pos } = readCodePoint(
this.input,
this.state.pos,
this.state.lineStart,
this.state.curLine,
throwOnInvalid,
this.errorHandlers_readCodePoint,
);
Expand Down Expand Up @@ -1348,11 +1354,7 @@ export default class Tokenizer extends CommentsParser {

recordStrictModeErrors(
toParseError: DeferredStrictError,
{
at,
}: {
at: Position;
},
{ at }: { at: Position },
) {
const index = at.index;

Expand All @@ -1363,22 +1365,6 @@ export default class Tokenizer extends CommentsParser {
}
}

// Used to read escaped characters
readEscapedChar(inTemplate: boolean): string | null {
const { ch, pos, lineStart, curLine } = readEscapedChar(
this.input,
this.state.pos,
this.state.lineStart,
this.state.curLine,
inTemplate,
this.errorHandlers_readEscapedChar,
);
this.state.pos = pos;
this.state.lineStart = lineStart;
this.state.curLine = curLine;
return ch;
}

// Read an identifier, and return it as a string. Sets `this.state.containsEsc`
// to whether the word contained a '\u' escape.
//
Expand Down Expand Up @@ -1552,80 +1538,60 @@ export default class Tokenizer extends CommentsParser {
}
}

errorHandlers_readInt: IntErrorHandlers = {
invalidDigit: (pos, radix) => {
if (this.options.errorRecovery) {
this.state.pos = pos;
this.raise(Errors.InvalidDigit, {
at: this.state.curPosition(),
radix,
});
// Continue parsing the number as if there was no invalid digit.
return true;
}
return false;
},
numericSeparatorInEscapeSequence: pos => {
this.state.pos = pos;
this.raise(Errors.NumericSeparatorInEscapeSequence, {
at: this.state.curPosition(),
errorBuilder(error: ParseErrorConstructor<{}>) {
return (pos: number, lineStart: number, curLine: number) => {
this.raise(error, {
at: buildPosition(pos, lineStart, curLine),
});
},
unexpectedNumericSeparator: pos => {
this.state.pos = pos;
this.raise(Errors.UnexpectedNumericSeparator, {
at: this.state.curPosition(),
};
}

errorHandlers_readInt: IntErrorHandlers = {
invalidDigit: (pos, lineStart, curLine, radix) => {
if (!this.options.errorRecovery) return false;

this.raise(Errors.InvalidDigit, {
at: buildPosition(pos, lineStart, curLine),
radix,
});
// Continue parsing the number as if there was no invalid digit.
return true;
},
numericSeparatorInEscapeSequence: this.errorBuilder(
Errors.NumericSeparatorInEscapeSequence,
),
unexpectedNumericSeparator: this.errorBuilder(
Errors.UnexpectedNumericSeparator,
),
};

errorHandlers_readCodePoint: CodePointErrorHandlers = {
...this.errorHandlers_readInt,
invalidEscapeSequence: (pos, startPos) => {
this.state.pos = pos;
this.raise(Errors.InvalidEscapeSequence, {
at: createPositionWithColumnOffset(
this.state.curPosition(),
startPos - pos,
),
});
},
invalidCodePoint: pos => {
this.state.pos = pos;
this.raise(Errors.InvalidCodePoint, { at: this.state.curPosition() });
},
invalidEscapeSequence: this.errorBuilder(Errors.InvalidEscapeSequence),
invalidCodePoint: this.errorBuilder(Errors.InvalidCodePoint),
};

errorHandlers_readEscapedChar: EscapedCharErrorHandlers = {
errorHandlers_readStringContents_string: StringContentsErrorHandlers = {
...this.errorHandlers_readCodePoint,
strictNumericEscape: pos => {
this.state.pos = pos;
strictNumericEscape: (pos, lineStart, curLine) => {
this.recordStrictModeErrors(Errors.StrictNumericEscape, {
at: this.state.curPosition(),
at: buildPosition(pos, lineStart, curLine),
});
},
};

errorHandlers_readStringContents_string: StringContentsErrorHandlers = {
...this.errorHandlers_readEscapedChar,
unterminated: (initialPos, initialLineStart, initialCurLine) => {
this.state.pos = initialPos - 1; // Report the error at the string quote
this.state.lineStart = initialLineStart;
this.state.curLine = initialCurLine;
unterminated: (pos, lineStart, curLine) => {
throw this.raise(Errors.UnterminatedString, {
at: this.state.curPosition(),
// Report the error at the string quote
at: buildPosition(pos - 1, lineStart, curLine),
});
},
};

errorHandlers_readStringContents_template: StringContentsErrorHandlers = {
...this.errorHandlers_readEscapedChar,
unterminated: (initialPos, initialLineStart, initialCurLine) => {
this.state.pos = initialPos; // TODO: For strings, we subtract 1
this.state.lineStart = initialLineStart;
this.state.curLine = initialCurLine;
...this.errorHandlers_readCodePoint,
strictNumericEscape: this.errorBuilder(Errors.StrictNumericEscape),
unterminated: (pos, lineStart, curLine) => {
throw this.raise(Errors.UnterminatedTemplate, {
at: this.state.curPosition(),
at: buildPosition(pos, lineStart, curLine),
});
},
};
Expand Down

0 comments on commit b7e0945

Please sign in to comment.