Skip to content

Commit

Permalink
Port babel-parser changes from 2021-02-18 to 2021-03-14 (#635)
Browse files Browse the repository at this point in the history
Instructions: https://github.com/alangpierce/sucrase/wiki/Porting-changes-from-Babel's-parser

2f755ef052 v7.12.17
🚫 Release only.

16e9f1c8e5 Support Flow `this` parameter annotations (#12234)
🚫 Seems to be working already.

5b99b8f221 Flow Enums with unknown members support (#12193)
🚫 For now, Flow enums won't be supported in Sucrase.

e4588bed22 Support TypeScript 4.2 abstract constructor signatures (#12628)
✅ Ported with a similar implementation, but made a new enum value instead of passing a flag.

9c567baa9b Parse JS Module Blocks proposal (#12469)
✅ Added basic parsing support, though the transformer doesn't yet do anything special. Filed #634 as follow-up.

03d7911be6 Implement class features in estree (#12370)
🚫 Sucrase doesn't support entree.

c827193d9c v7.13.0
🚫 Release only.

c30039029a Don't enable class features by default in `estree` (#12867)
🚫 Sucrase doesn't support entree.

e940b8c4b9 v7.13.4
🚫 Release only.

b62fc3d44f babel-parser(flow): Set `this` property to `null` for FunctionTypeAnnotation without parens (#12930)
🚫 AST only.

efdca01409 fix: add tokens when tokens: true is passed to parseExpression (#12939)
🚫 Only applies to Babel-specific API.

2c0e8d0008 v7.13.9
🚫 Release only.

b416847b61 (ts) Raise syntax error for abstract methods with a body (#12687)
🚫 Validation only.

d04842a700 Avoid using CJS globals in internal source files (#12963)
🚫 Babel change not relevant for Sucrase.

d1d404b9a3 v7.13.10
🚫 Release only.

1a05b81387 Support multiple static blocks (#12738)
🚫 Only affects validation, multiple static blocks were already supported.

0988c471e9 Parse type imports in TSImportEqualsDeclaration (#12962)
✅ Implemented independently using similar logic as in Babel.
  • Loading branch information
alangpierce authored Jul 6, 2021
1 parent fa424bd commit 284bda6
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 8 deletions.
25 changes: 21 additions & 4 deletions src/parser/plugins/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,14 @@ function tsParseTemplateLiteralType(): void {
enum FunctionType {
TSFunctionType,
TSConstructorType,
TSAbstractConstructorType,
}

function tsParseFunctionOrConstructorType(type: FunctionType): void {
if (type === FunctionType.TSConstructorType) {
if (type === FunctionType.TSAbstractConstructorType) {
expectContextual(ContextualKeyword._abstract);
}
if (type === FunctionType.TSConstructorType || type === FunctionType.TSAbstractConstructorType) {
expect(tt._new);
}
tsFillSignature(tt.arrow);
Expand Down Expand Up @@ -710,6 +714,10 @@ export function tsParseType(): void {
tsParseType();
}

function isAbstractConstructorSignature(): boolean {
return isContextual(ContextualKeyword._abstract) && lookaheadType() === tt._new;
}

export function tsParseNonConditionalType(): void {
if (tsIsStartOfFunctionType()) {
tsParseFunctionOrConstructorType(FunctionType.TSFunctionType);
Expand All @@ -719,6 +727,10 @@ export function tsParseNonConditionalType(): void {
// As in `new () => Date`
tsParseFunctionOrConstructorType(FunctionType.TSConstructorType);
return;
} else if (isAbstractConstructorSignature()) {
// As in `abstract new () => Date`
tsParseFunctionOrConstructorType(FunctionType.TSAbstractConstructorType);
return;
}
tsParseUnionTypeOrHigher();
}
Expand Down Expand Up @@ -1198,9 +1210,14 @@ export function tsStartParseNewArguments(): void {
}

export function tsTryParseExport(): boolean {
if (match(tt._import)) {
// `export import A = B;`
expect(tt._import);
if (eat(tt._import)) {
// One of these cases:
// export import A = B;
// export import type A = require("A");
if (isContextual(ContextualKeyword._type) && lookaheadType() !== tt.eq) {
// Eat a `type` token, unless it's actually an identifier name.
expectContextual(ContextualKeyword._type);
}
tsParseImportEqualsDeclaration();
return true;
} else if (eat(tt.eq)) {
Expand Down
22 changes: 21 additions & 1 deletion src/parser/traverser/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
} from "./lval";
import {
parseBlock,
parseBlockBody,
parseClass,
parseDecorators,
parseFunction,
Expand All @@ -78,6 +79,8 @@ import {
canInsertSemicolon,
eatContextual,
expect,
expectContextual,
hasFollowingLineBreak,
hasPrecedingLineBreak,
isContextual,
unexpected,
Expand Down Expand Up @@ -237,7 +240,14 @@ export function parseMaybeUnary(): boolean {
tsParseTypeAssertion();
return false;
}

if (
isContextual(ContextualKeyword._module) &&
lookaheadCharCode() === charCodes.leftCurlyBrace &&
!hasFollowingLineBreak()
) {
parseModuleExpression();
return false;
}
if (state.type & TokenType.IS_PREFIX) {
next();
parseMaybeUnary();
Expand Down Expand Up @@ -989,3 +999,13 @@ function parseYield(): void {
parseMaybeAssign();
}
}

// https://github.com/tc39/proposal-js-module-blocks
function parseModuleExpression(): void {
expectContextual(ContextualKeyword._module);
expect(tt.braceL);
// For now, just call parseBlockBody to parse the block. In the future when we
// implement full support, we'll want to emit scopes and possibly other
// information.
parseBlockBody(tt.braceR);
}
23 changes: 21 additions & 2 deletions src/parser/traverser/statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1065,8 +1065,27 @@ export function parseImport(): void {
tsParseImportEqualsDeclaration();
return;
}
if (isTypeScriptEnabled) {
eatContextual(ContextualKeyword._type);
if (isTypeScriptEnabled && isContextual(ContextualKeyword._type)) {
const lookahead = lookaheadType();
if (lookahead === tt.name) {
// One of these `import type` cases:
// import type T = require('T');
// import type A from 'A';
expectContextual(ContextualKeyword._type);
if (lookaheadType() === tt.eq) {
tsParseImportEqualsDeclaration();
return;
}
// If this is an `import type...from` statement, then we already ate the
// type token, so proceed to the regular import parser.
} else if (lookahead === tt.star || lookahead === tt.braceL) {
// One of these `import type` cases, in which case we can eat the type token
// and proceed as normal:
// import type * as A from 'A';
// import type {a} from 'A';
expectContextual(ContextualKeyword._type);
}
// Otherwise, we are importing the name "type".
}

// import '...'
Expand Down
18 changes: 17 additions & 1 deletion src/parser/traverser/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {eat, finishToken, lookaheadTypeAndKeyword, match} from "../tokenizer/index";
import {eat, finishToken, lookaheadTypeAndKeyword, match, nextTokenStart} from "../tokenizer/index";
import type {ContextualKeyword} from "../tokenizer/keywords";
import {formatTokenType, TokenType, TokenType as tt} from "../tokenizer/types";
import {charCodes} from "../util/charcodes";
Expand Down Expand Up @@ -50,6 +50,22 @@ export function hasPrecedingLineBreak(): boolean {
return false;
}

export function hasFollowingLineBreak(): boolean {
const nextStart = nextTokenStart();
for (let i = state.end; i < nextStart; i++) {
const code = input.charCodeAt(i);
if (
code === charCodes.lineFeed ||
code === charCodes.carriageReturn ||
code === 0x2028 ||
code === 0x2029
) {
return true;
}
}
return false;
}

export function isLineTerminator(): boolean {
return eat(tt.semi) || canInsertSemicolon();
}
Expand Down
24 changes: 24 additions & 0 deletions src/transformers/ESMImportTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,34 @@ export default class ESMImportTransformer extends Transformer {
if (this.tokens.matches3(tt._import, tt.name, tt.eq)) {
return this.processImportEquals();
}
if (
this.tokens.matches4(tt._import, tt.name, tt.name, tt.eq) &&
this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, ContextualKeyword._type)
) {
// import type T = require('T')
this.tokens.removeInitialToken();
// This construct is always exactly 8 tokens long, so remove the 7 remaining tokens.
for (let i = 0; i < 7; i++) {
this.tokens.removeToken();
}
return true;
}
if (this.tokens.matches2(tt._export, tt.eq)) {
this.tokens.replaceToken("module.exports");
return true;
}
if (
this.tokens.matches5(tt._export, tt._import, tt.name, tt.name, tt.eq) &&
this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 2, ContextualKeyword._type)
) {
// export import type T = require('T')
this.tokens.removeInitialToken();
// This construct is always exactly 9 tokens long, so remove the 8 remaining tokens.
for (let i = 0; i < 8; i++) {
this.tokens.removeToken();
}
return true;
}
if (this.tokens.matches1(tt._import)) {
return this.processImport();
}
Expand Down
22 changes: 22 additions & 0 deletions test/sucrase-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1399,4 +1399,26 @@ describe("sucrase", () => {
{transforms: [], disableESTransforms: true},
);
});

it("parses module blocks (but doesn't transform them specially)", () => {
assertResult(
`
const workerBlock = module {
const x: number = 3;
console.log("Hello");
};
const worker: Worker = new Worker(workerBlock, {type: "module"});
console.log("World");
`,
`
const workerBlock = module {
const x = 3;
console.log("Hello");
};
const worker = new Worker(workerBlock, {type: "module"});
console.log("World");
`,
{transforms: ["typescript"]},
);
});
});
44 changes: 44 additions & 0 deletions test/typescript-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2538,6 +2538,50 @@ describe("typescript transform", () => {
);
});

it("handles abstract constructor signatures", () => {
assertTypeScriptESMResult(
`
let x: abstract new () => void = X;
`,
`
let x = X;
`,
);
});

it("handles import type =", () => {
assertTypeScriptESMResult(
`
import type A = require("A");
`,
`
;
`,
);
});

it("handles importing an identifier named type", () => {
assertTypeScriptESMResult(
`
import type = require("A");
`,
`
;
`,
);
});

it("handles export import type =", () => {
assertTypeScriptESMResult(
`
export import type B = require("B");
`,
`
;
`,
);
});

it("transforms constructor initializers even with disableESTransforms", () => {
assertTypeScriptESMResult(
`
Expand Down

0 comments on commit 284bda6

Please sign in to comment.