From a4a596e5cdd57362f309ae50cc32a235d7817d34 Mon Sep 17 00:00:00 2001 From: Alan Pierce Date: Mon, 18 May 2020 07:49:17 -0700 Subject: [PATCH] Add support for parsing `declare` TS class fields (#537) Fixes #536 We already completely remove uninitialized class fields for now, which agrees with the `declare` behavior, so the only change needed here was to parse the syntax without crashing. In the future, when we remove the class fields transform, we'll need to distinguish declare vs non-declare fields. --- src/parser/plugins/typescript.ts | 26 +++++++++++++--------- src/parser/traverser/statement.ts | 2 ++ src/util/getClassInfo.ts | 1 + test/typescript-test.ts | 37 +++++++++++++++++++++++++++++++ tsconfig.json | 1 + 5 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/parser/plugins/typescript.ts b/src/parser/plugins/typescript.ts index f98e8b07..0a0ba4a6 100644 --- a/src/parser/plugins/typescript.ts +++ b/src/parser/plugins/typescript.ts @@ -113,6 +113,9 @@ export function tsParseModifier( case ContextualKeyword._protected: state.tokens[state.tokens.length - 1].type = tt._protected; break; + case ContextualKeyword._declare: + state.tokens[state.tokens.length - 1].type = tt._declare; + break; default: break; } @@ -1213,18 +1216,21 @@ export function tsTryParseClassMemberWithIsStatic( let isAbstract = false; let isReadonly = false; - const mod = tsParseModifier([ContextualKeyword._abstract, ContextualKeyword._readonly]); - switch (mod) { - case ContextualKeyword._readonly: - isReadonly = true; - isAbstract = !!tsParseModifier([ContextualKeyword._abstract]); + while (true) { + const mod = tsParseModifier([ + ContextualKeyword._abstract, + ContextualKeyword._readonly, + ContextualKeyword._declare, + ]); + if (mod == null) { break; - case ContextualKeyword._abstract: + } + if (mod === ContextualKeyword._readonly) { + isReadonly = true; + } + if (mod === ContextualKeyword._abstract) { isAbstract = true; - isReadonly = !!tsParseModifier([ContextualKeyword._readonly]); - break; - default: - break; + } } // We no longer check for public/private/etc, but tsTryParseIndexSignature should just return diff --git a/src/parser/traverser/statement.ts b/src/parser/traverser/statement.ts index 1ebb9156..f801b323 100644 --- a/src/parser/traverser/statement.ts +++ b/src/parser/traverser/statement.ts @@ -688,7 +688,9 @@ function parseClassBody(classContextId: number): void { function parseClassMember(memberStart: number, classContextId: number): void { if (isTypeScriptEnabled) { + eatContextual(ContextualKeyword._declare); tsParseAccessModifier(); + eatContextual(ContextualKeyword._declare); } let isStatic = false; if (match(tt.name) && state.contextualKeyword === ContextualKeyword._static) { diff --git a/src/util/getClassInfo.ts b/src/util/getClassInfo.ts index 0b2fb1c1..412006a5 100644 --- a/src/util/getClassInfo.ts +++ b/src/util/getClassInfo.ts @@ -263,6 +263,7 @@ function isAccessModifier(token: Token): boolean { tt._protected, tt._abstract, tt.star, + tt._declare, ].includes(token.type); } diff --git a/test/typescript-test.ts b/test/typescript-test.ts index d6f5771f..3f82e962 100644 --- a/test/typescript-test.ts +++ b/test/typescript-test.ts @@ -2107,4 +2107,41 @@ describe("typescript transform", () => { `, ); }); + + it("properly removes class fields with declare", () => { + assertTypeScriptResult( + ` + class Foo { + declare a: number; + public declare b: number; + declare public c: number; + static declare d: number; + declare static e: number; + declare public static f: number; + public declare static g: number; + public static declare h: number; + + constructor() { + console.log('Hi'); + } + } + `, + `"use strict"; + class Foo { + + + + + + + + + + constructor() { + console.log('Hi'); + } + } + `, + ); + }); }); diff --git a/tsconfig.json b/tsconfig.json index 2bec9952..26e53799 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "downlevelIteration": true, "noEmitHelpers": true, "importHelpers": true, + "useDefineForClassFields": true, "plugins": [ { "name": "typescript-tslint-plugin",