Skip to content

Commit

Permalink
Support TypeScript 4.1 via typescript-estree (#9636)
Browse files Browse the repository at this point in the history
* Update typescript and typescript-estree

* Add AST transformation for TSTemplateLiteralType

* Update tests for template-literal-types

* Fix transformChainExpression

* Update tests for key-remapping-in-mapped-types

* Implement recoverInvalidDecorators

* Update changelog

* Avoid typecheck error

* Add os.platform() shim

* Update config.js

* Update config.js

* Shim os.cpus

* Update `process.versions.node`

* Attempt to fix build

* Update ts parserOptions

* Fix code style

* Remove `errorOnUnknownASTType`, fix code style

* Use `[] ||` instead of `[] //`

* Fix hack

* Add invalid tests

* checkMissingDecorators

* Update tests

* Fix tests

* Add comment

* Revert "Implement recoverInvalidDecorators"

This reverts commit 27e7b46.

* Add ref pr link

* Simplify logic

* Don't transform `TSTemplateLiteralType`

* Add `TSTemplateLiteralType` as simple type

* Fix tests

* Update comments

* Add comment

* Fix logic

* Minor tweak

* use multiple parsers

* Update @typescript-eslint/typescript-estree to v4.8.0

* Switch to `parseWithNodeMaps`

* Rename `esNode` -> `esTreeNode`

* Update @typescript-eslint/typescript-estree to 4.8.1-alpha.1

* Use @typescript-eslint/typescript-estree 4.8.1

Co-authored-by: fisker Cheung <lionkay@gmail.com>
  • Loading branch information
sosukesuzuki and fisker committed Nov 18, 2020
1 parent 702f543 commit 4cc1492
Show file tree
Hide file tree
Showing 32 changed files with 304 additions and 168 deletions.
4 changes: 1 addition & 3 deletions changelog_unreleased/typescript/pr-9473.md
@@ -1,6 +1,4 @@
#### [HIGHLIGHT]Support TypeScript 4.1 via Babel (#9473 by @sosukesuzuki)

`--parser=babel-ts` is required.
#### [HIGHLIGHT]Support TypeScript 4.1 (#9473, #9636 by @sosukesuzuki)

##### Key Remapping In Mapped Types

Expand Down
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -25,7 +25,7 @@
"@babel/parser": "7.12.5",
"@glimmer/syntax": "0.66.0",
"@iarna/toml": "2.2.5",
"@typescript-eslint/typescript-estree": "3.10.1",
"@typescript-eslint/typescript-estree": "4.8.1",
"angular-estree-parser": "2.2.1",
"angular-html-parser": "1.7.1",
"camelcase": "6.2.0",
Expand Down Expand Up @@ -78,7 +78,7 @@
"resolve": "1.19.0",
"semver": "7.3.2",
"string-width": "4.2.0",
"typescript": "4.0.5",
"typescript": "4.1.1-rc",
"unicode-regex": "3.0.0",
"unified": "9.2.0",
"vnopts": "1.0.2",
Expand Down
5 changes: 5 additions & 0 deletions scripts/build/config.js
Expand Up @@ -40,6 +40,11 @@ const parsers = [
// `TypeScript`, `toolsVersion`, `globalThis`
'typeof process === "undefined" || process.browser': "false",
'typeof globalThis === "object"': "true",
// `@typescript-eslint/typescript-estree` v4
'require("globby")': "{}",
"extra.projects = prepareAndTransformProjects(":
"extra.projects = [] || prepareAndTransformProjects(",
"process.versions.node": "'999.999.999'",
},
},
{
Expand Down
2 changes: 2 additions & 0 deletions scripts/build/shims/os.mjs
@@ -1,3 +1,5 @@
export default {
EOL: "\n",
platform: () => "browser",
cpus: () => [{ model: "Prettier" }],
};
11 changes: 2 additions & 9 deletions src/language-graphql/printer-graphql.js
@@ -1,15 +1,8 @@
"use strict";

const {
concat,
join,
hardline,
line,
softline,
group,
indent,
ifBreak,
} = require("../document").builders;
builders: { concat, join, hardline, line, softline, group, indent, ifBreak },
} = require("../document");
const { isNextLineEmpty } = require("../common/util");
const { insertPragma } = require("./pragma");
const { locStart, locEnd } = require("./loc");
Expand Down
11 changes: 2 additions & 9 deletions src/language-handlebars/printer-glimmer.js
@@ -1,15 +1,8 @@
"use strict";

const {
concat,
group,
hardline,
ifBreak,
indent,
join,
line,
softline,
} = require("../document").builders;
builders: { concat, group, hardline, ifBreak, indent, join, line, softline },
} = require("../document");
const { locStart, locEnd } = require("./loc");
const clean = require("./clean");
const {
Expand Down
59 changes: 56 additions & 3 deletions src/language-js/parse-postprocess.js
Expand Up @@ -5,6 +5,7 @@ const {
getNextNonSpaceNonCommentCharacter,
getShebang,
} = require("../common/util");
const createError = require("../common/parser-create-error");
const { composeLoc, locStart, locEnd } = require("./loc");
const { isTypeCastComment } = require("./comments");

Expand All @@ -17,6 +18,50 @@ function postprocess(ast, options) {
includeShebang(ast, options);
}

// Invalid decorators are removed since `@typescript-eslint/typescript-estree` v4
// https://github.com/typescript-eslint/typescript-eslint/pull/2375
if (options.parser === "typescript" && options.originalText.includes("@")) {
const {
esTreeNodeToTSNodeMap,
tsNodeToESTreeNodeMap,
} = options.tsParseResult;
ast = visitNode(ast, (node) => {
const tsNode = esTreeNodeToTSNodeMap.get(node);
if (!tsNode) {
return;
}
const tsDecorators = tsNode.decorators;
if (!Array.isArray(tsDecorators)) {
return;
}
// `esTreeNodeToTSNodeMap.get(ClassBody)` and `esTreeNodeToTSNodeMap.get(ClassDeclaration)` has the same tsNode
const esTreeNode = tsNodeToESTreeNodeMap.get(tsNode);
if (esTreeNode !== node) {
return;
}
const esTreeDecorators = esTreeNode.decorators;
if (
!Array.isArray(esTreeDecorators) ||
esTreeDecorators.length !== tsDecorators.length ||
tsDecorators.some((tsDecorator) => {
const esTreeDecorator = tsNodeToESTreeNodeMap.get(tsDecorator);
return (
!esTreeDecorator || !esTreeDecorators.includes(esTreeDecorator)
);
})
) {
const { start, end } = esTreeNode.loc;
throw createError(
"Leading decorators must be attached to a class declaration",
{
start: { line: start.line, column: start.column + 1 },
end: { line: end.line, column: end.column + 1 },
}
);
}
});
}

// Keep Babel's non-standard ParenthesizedExpression nodes only if they have Closure-style type cast comments.
if (
options.parser !== "typescript" &&
Expand Down Expand Up @@ -146,9 +191,9 @@ function postprocess(ast, options) {
}
}

// This is a workaround to transform `ChainExpression` from `espree` into
// `babel` shape AST, we should do the opposite, since `ChainExpression` is the
// standard `estree` AST for `optional chaining`
// This is a workaround to transform `ChainExpression` from `espree`, `meriyah`,
// and `typescript` into `babel` shape AST, we should do the opposite,
// since `ChainExpression` is the standard `estree` AST for `optional chaining`
// https://github.com/estree/estree/blob/master/es2020.md
function transformChainExpression(node) {
if (node.type === "CallExpression") {
Expand All @@ -158,6 +203,10 @@ function transformChainExpression(node) {
node.type = "OptionalMemberExpression";
node.object = transformChainExpression(node.object);
}
// typescript
else if (node.type === "TSNonNullExpression") {
node.expression = transformChainExpression(node.expression);
}
return node;
}

Expand All @@ -180,6 +229,10 @@ function visitNode(node, fn) {
node[key] = visitNode(child, fn);
}

if (Array.isArray(node)) {
return node;
}

return fn(node) || node;
}

Expand Down
17 changes: 11 additions & 6 deletions src/language-js/parser-typescript.js
Expand Up @@ -7,14 +7,14 @@ const postprocess = require("./parse-postprocess");

function parse(text, parsers, opts) {
const jsx = isProbablyJsx(text);
let ast;
let result;
try {
// Try passing with our best guess first.
ast = tryParseTypeScript(text, jsx);
result = tryParseTypeScript(text, jsx);
} catch (firstError) {
try {
// But if we get it wrong, try the opposite.
ast = tryParseTypeScript(text, !jsx);
result = tryParseTypeScript(text, !jsx);
} catch (secondError) {
// Suppose our guess is correct, throw the first error
const { message, lineNumber, column } = firstError;
Expand All @@ -30,12 +30,16 @@ function parse(text, parsers, opts) {
}
}

return postprocess(ast, { ...opts, originalText: text });
return postprocess(result.ast, {
...opts,
originalText: text,
tsParseResult: result,
});
}

function tryParseTypeScript(text, jsx) {
const parser = require("@typescript-eslint/typescript-estree");
return parser.parse(text, {
const { parseWithNodeMaps } = require("@typescript-eslint/typescript-estree");
return parseWithNodeMaps(text, {
// `jest@<=26.4.2` rely on `loc`
// https://github.com/facebook/jest/issues/10444
loc: true,
Expand All @@ -45,6 +49,7 @@ function tryParseTypeScript(text, jsx) {
jsx,
tokens: true,
loggerFn: false,
project: [],
});
}

Expand Down
15 changes: 11 additions & 4 deletions src/language-js/print/template-literal.js
Expand Up @@ -24,17 +24,24 @@ const {

function printTemplateLiteral(path, print, options) {
const node = path.getValue();
const parentNode = path.getParentNode();
const isTemplateLiteral = node.type === "TemplateLiteral";

if (isJestEachTemplateLiteral(node, parentNode)) {
if (
isTemplateLiteral &&
isJestEachTemplateLiteral(node, path.getParentNode())
) {
const printed = printJestEachTemplateLiteral(path, options, print);
if (printed) {
return printed;
}
}
let expressionsKey = "expressions";
if (node.type === "TSTemplateLiteralType") {
expressionsKey = "types";
}
const parts = [];

let expressions = path.map(print, "expressions");
let expressions = path.map(print, expressionsKey);
const isSimple = isSimpleTemplateLiteral(node);

if (isSimple) {
Expand Down Expand Up @@ -70,7 +77,7 @@ function printTemplateLiteral(path, print, options) {
let printed = expressions[i];

if (!isSimple) {
const expression = node.expressions[i];
const expression = node[expressionsKey][i];
// Breaks at the template element boundaries (${ and }) are preferred to breaking
// in the middle of a MemberExpression
if (
Expand Down
4 changes: 3 additions & 1 deletion src/language-js/printer-estree-json.js
@@ -1,6 +1,8 @@
"use strict";

const { concat, hardline, indent, join } = require("../document").builders;
const {
builders: { concat, hardline, indent, join },
} = require("../document");
const preprocess = require("./print-preprocess");

function genericPrint(path, options, print) {
Expand Down
1 change: 1 addition & 0 deletions src/language-js/printer-estree.js
Expand Up @@ -2238,6 +2238,7 @@ function printPathNoParens(path, options, print, args) {
return concat(parts);
case "TemplateElement":
return join(literalline, n.value.raw.split(/\r?\n/g));
case "TSTemplateLiteralType":
case "TemplateLiteral": {
return printTemplateLiteral(path, print, options);
}
Expand Down
11 changes: 9 additions & 2 deletions src/language-js/utils.js
Expand Up @@ -464,6 +464,7 @@ const simpleTypeAnnotations = new Set([
"BigIntLiteralTypeAnnotation",
"NumberLiteralTypeAnnotation",
"TSLiteralType",
"TSTemplateLiteralType",
// flow only, `empty`, `mixed`
"EmptyTypeAnnotation",
"MixedTypeAnnotation",
Expand Down Expand Up @@ -647,11 +648,17 @@ function isNgForOf(node, index, parentNode) {
* @returns {boolean}
*/
function isSimpleTemplateLiteral(node) {
if (node.expressions.length === 0) {
let expressionsKey = "expressions";
if (node.type === "TSTemplateLiteralType") {
expressionsKey = "types";
}
const expressions = node[expressionsKey];

if (expressions.length === 0) {
return false;
}

return node.expressions.every((expr) => {
return expressions.every((expr) => {
// Disallow comments since printDocToString can't print them here
if (expr.comments) {
return false;
Expand Down
20 changes: 11 additions & 9 deletions src/main/comments.js
Expand Up @@ -4,15 +4,17 @@
const assert = require("assert");

const {
concat,
line,
hardline,
breakParent,
indent,
lineSuffix,
join,
cursor,
} = require("../document").builders;
builders: {
concat,
line,
hardline,
breakParent,
indent,
lineSuffix,
join,
cursor,
},
} = require("../document");

const {
hasNewline,
Expand Down

0 comments on commit 4cc1492

Please sign in to comment.