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 parse failures when using ?? just after the end of a type #721

Merged
merged 4 commits into from
Jul 14, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions spec-compliance-tests/babel-tests/check-babel-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ flow/scope/declare-module
flow/this-annotation/function-type
flow/typecasts/yield
jsx/basic/3
typescript/cast/as
typescript/export/as-namespace
typescript/import/export-import
typescript/import/export-import-require
Expand All @@ -76,7 +75,6 @@ typescript/import/export-import-type-require
typescript/import/import-default-id-type
typescript/import/type-asi
typescript/import/type-equals-require
typescript/type-arguments/instantiation-expression-binary-operator
`
.split("\n")
.filter((s) => s);
Expand Down
3 changes: 3 additions & 0 deletions src/parser/plugins/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,9 @@ export function tsParseSubscript(
// Tagged template with a type argument.
parseTemplate();
} else if (
// The remaining possible case is an instantiation expression, e.g.
// Array<number> . Check for a few cases that would disqualify it and
// cause us to bail out.
// a<b>>c is not (a<b>)>c, but a<(b>>c)
state.type === tt.greaterThan ||
// a<b>c is (a<b)>c
Expand Down
9 changes: 7 additions & 2 deletions src/parser/tokenizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ function readToken_gt(): void {

/**
* Called after `as` expressions in TS; we're switching from a type to a
* non-type context, so a > token may actually be >=
* non-type context, so a > token may actually be >= .
*/
export function rescan_gt(): void {
if (state.type === tt.greaterThan) {
Expand Down Expand Up @@ -565,7 +565,12 @@ function readToken_question(): void {
// '?'
const nextChar = input.charCodeAt(state.pos + 1);
const nextChar2 = input.charCodeAt(state.pos + 2);
if (nextChar === charCodes.questionMark && !state.isType) {
if (
nextChar === charCodes.questionMark &&
// In Flow (but not TypeScript), ??string is a valid type that should be
// tokenized as two individual ? tokens.
!(isFlowEnabled && state.isType)
) {
if (nextChar2 === charCodes.equalsTo) {
// '??='
finishOp(tt.assign, 3);
Expand Down
11 changes: 11 additions & 0 deletions test/flow-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,4 +688,15 @@ describe("transform flow", () => {
`,
);
});

it("handles two ? operators in a row", () => {
assertFlowResult(
`
type T = ??number;
`,
`"use strict";

`,
);
});
});
19 changes: 17 additions & 2 deletions test/typescript-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
IMPORT_DEFAULT_PREFIX,
IMPORT_WILDCARD_PREFIX,
JSX_PREFIX,
NULLISH_COALESCE_PREFIX,
OPTIONAL_CHAIN_PREFIX,
} from "./prefixes";
import {assertResult, devProps} from "./util";
Expand Down Expand Up @@ -2388,15 +2389,17 @@ describe("typescript transform", () => {
);
});

it("properly handles a >= symbol after an `as` cast", () => {
it("properly handles >= and ?? after `as`", () => {
assertTypeScriptResult(
`
const x: string | number = 1;
if (x as number >= 5) {}
if (y as unknown ?? false) {}
`,
`"use strict";
`"use strict";${NULLISH_COALESCE_PREFIX}
const x = 1;
if (x >= 5) {}
if (_nullishCoalesce(y , () => ( false))) {}
`,
);
});
Expand Down Expand Up @@ -3214,6 +3217,18 @@ describe("typescript transform", () => {
);
});

it("allows instantiation expressions followed by ??", () => {
assertResult(
`
const foo = a<b> ?? c;
`,
`${NULLISH_COALESCE_PREFIX}
const foo = _nullishCoalesce(a, () => ( c));
`,
{transforms: ["typescript"]},
);
});

it("allows extends constraints on infer type variables", () => {
assertResult(
`
Expand Down