From d7fefba3741a526ff2b58dd713995c3ee5603962 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 21 Jul 2020 11:20:28 -0700 Subject: [PATCH] fix(typescript-estree): correct AST regression introduced by TS4.0 upgrade (#2316) --- packages/typescript-estree/src/convert.ts | 18 ++++++++++-- packages/typescript-estree/src/node-utils.ts | 11 ++------ .../typescript-estree/src/version-check.ts | 28 +++++++++++++++++++ 3 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 packages/typescript-estree/src/version-check.ts diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 470a9108aef..7a137c3335f 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -29,6 +29,7 @@ import { TSNode, TSESTreeToTSNode, } from './ts-estree'; +import { typescriptVersionIsAtLeast } from './version-check'; const SyntaxKind = ts.SyntaxKind; @@ -1998,12 +1999,20 @@ export class Converter { raw: 'false', }); - case SyntaxKind.NullKeyword: + case SyntaxKind.NullKeyword: { + if (!typescriptVersionIsAtLeast['4.0'] && this.inTypeMode) { + // 4.0 started nesting null types inside a LiteralType node, but we still need to support pre-4.0 + return this.createNode(node, { + type: AST_NODE_TYPES.TSNullKeyword, + }); + } + return this.createNode(node, { type: AST_NODE_TYPES.Literal, value: null, raw: 'null', }); + } case SyntaxKind.EmptyStatement: return this.createNode(node, { @@ -2672,8 +2681,11 @@ export class Converter { }); } case SyntaxKind.LiteralType: { - if (node.literal.kind === SyntaxKind.NullKeyword) { - // 4.0.0 started nesting null types inside a LiteralType node + if ( + typescriptVersionIsAtLeast['4.0'] && + node.literal.kind === SyntaxKind.NullKeyword + ) { + // 4.0 started nesting null types inside a LiteralType node // but our AST is designed around the old way of null being a keyword return this.createNode( node.literal as ts.NullLiteral, diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index b97f78b8dbd..82b98b29301 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -1,7 +1,7 @@ import unescape from 'lodash/unescape'; -import * as semver from 'semver'; import * as ts from 'typescript'; import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from './ts-estree'; +import { typescriptVersionIsAtLeast } from './version-check'; const SyntaxKind = ts.SyntaxKind; @@ -452,13 +452,6 @@ export function isOptionalChain( ); } -/** - * Returns true if the current TS version is TS 3.9 - */ -function isTSv3dot9(): boolean { - return !semver.satisfies(ts.version, '< 3.9.0 || < 3.9.1-rc || < 3.9.0-beta'); -} - /** * Returns true of the child of property access expression is an optional chain */ @@ -477,7 +470,7 @@ export function isChildOptionalChain( return true; } - if (!isTSv3dot9()) { + if (!typescriptVersionIsAtLeast['3.9']) { return false; } diff --git a/packages/typescript-estree/src/version-check.ts b/packages/typescript-estree/src/version-check.ts new file mode 100644 index 00000000000..d26f96bc31a --- /dev/null +++ b/packages/typescript-estree/src/version-check.ts @@ -0,0 +1,28 @@ +import * as semver from 'semver'; +import * as ts from 'typescript'; + +function semverCheck(version: string): boolean { + return semver.satisfies( + ts.version, + `>= ${version}.0 || >= ${version}.1-rc || >= ${version}.0-beta`, + { + includePrerelease: true, + }, + ); +} + +const versions = [ + // + '3.7', + '3.8', + '3.9', + '4.0', +] as const; +type Versions = typeof versions extends ArrayLike ? U : never; + +const typescriptVersionIsAtLeast = {} as Record; +for (const version of versions) { + typescriptVersionIsAtLeast[version] = semverCheck(version); +} + +export { typescriptVersionIsAtLeast };