Skip to content
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

Fix position of errors in template literals after newlines #14798

Merged
merged 2 commits into from Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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