Skip to content

Commit

Permalink
[Babel 8] Create TSTemplateLiteralType (#17066)
Browse files Browse the repository at this point in the history
* breaking: create TSTemplateLiteralType

* update test fixtures

* ignore TSTemplateLiteralType in printer test

* purge failing prettier tests

* rename expressions to types

* fix: generate TSLiteralType when the template does not contain types

* restore template literals transform

* update test fixtures

* define TSTemplateLiteralType for Babel 7

Since the generator requires the types information

* refactor: extract _printTemplate helper

* Apply template literal range fixes to TSTemplateLiteralType
  • Loading branch information
JLHwung authored Jan 27, 2025
1 parent 616f887 commit e02b0ff
Show file tree
Hide file tree
Showing 32 changed files with 553 additions and 128 deletions.
5 changes: 4 additions & 1 deletion eslint/babel-eslint-parser/src/convert/convertAST.cts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ const convertNodesVisitor = {
}

// template string range fixes
if (node.type === "TemplateLiteral") {
if (
node.type === "TemplateLiteral" ||
node.type === "TSTemplateLiteralType"
) {
for (let i = 0; i < node.quasis.length; i++) {
const q = node.quasis[i];
q.range[0] -= 1;
Expand Down
41 changes: 24 additions & 17 deletions packages/babel-generator/src/generators/template-literals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,34 @@ export function TemplateElement(this: Printer) {
throw new Error("TemplateElement printing is handled in TemplateLiteral");
}

export function TemplateLiteral(this: Printer, node: t.TemplateLiteral) {
const quasis = node.quasis;
export type TemplateLiteralBase = t.Node & {
quasis: t.TemplateElement[];
};

export function _printTemplate<T extends t.Node>(
this: Printer,
node: TemplateLiteralBase,
substitutions: T[],
) {
const quasis = node.quasis;
let partRaw = "`";

for (let i = 0; i < quasis.length; i++) {
for (let i = 0; i < quasis.length - 1; i++) {
partRaw += quasis[i].value.raw;

if (i + 1 < quasis.length) {
this.token(partRaw + "${", true);
this.print(node.expressions[i]);
partRaw = "}";

// In Babel 7 we have indivirual tokens for ${ and }, so the automatic
// catchup logic does not work. Manually look for those tokens.
if (!process.env.BABEL_8_BREAKING && this.tokenMap) {
const token = this.tokenMap.findMatching(node, "}", i);
if (token) this._catchUpTo(token.loc.start);
}
this.token(partRaw + "${", true);
this.print(substitutions[i]);
partRaw = "}";

// In Babel 7 we have individual tokens for ${ and }, so the automatic
// catchup logic does not work. Manually look for those tokens.
if (!process.env.BABEL_8_BREAKING && this.tokenMap) {
const token = this.tokenMap.findMatching(node, "}", i);
if (token) this._catchUpTo(token.loc.start);
}
}

partRaw += quasis[quasis.length - 1].value.raw;
this.token(partRaw + "`", true);
}

export function TemplateLiteral(this: Printer, node: t.TemplateLiteral) {
this._printTemplate(node, node.expressions);
}
7 changes: 7 additions & 0 deletions packages/babel-generator/src/generators/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,13 @@ function tokenIfPlusMinus(self: Printer, tok: true | "+" | "-") {
}
}

export function TSTemplateLiteralType(
this: Printer,
node: t.TSTemplateLiteralType,
) {
this._printTemplate(node, node.types);
}

export function TSLiteralType(this: Printer, node: t.TSLiteralType) {
this.print(node.literal);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/babel-generator/test/printer.skip-bundled.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ describe("Printer", () => {
if (
type === "TSClassImplements" ||
type === "TSEnumBody" ||
type === "TSInterfaceHeritage"
type === "TSInterfaceHeritage" ||
type === "TSTemplateLiteralType"
) {
return;
}
Expand Down
33 changes: 29 additions & 4 deletions packages/babel-parser/src/plugins/typescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1257,10 +1257,35 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
return this.finishNode(node, "TSLiteralType");
}

tsParseTemplateLiteralType(): N.TsLiteralType {
const node = this.startNode<N.TsLiteralType>();
node.literal = super.parseTemplate(false);
return this.finishNode(node, "TSLiteralType");
tsParseTemplateLiteralType(): N.TsTemplateLiteralType | N.TsLiteralType {
if (process.env.BABEL_8_BREAKING) {
const startLoc = this.state.startLoc;
let curElt = this.parseTemplateElement(false);
const quasis = [curElt];
if (curElt.tail) {
const node = this.startNodeAt<N.TsLiteralType>(startLoc);
const literal = this.startNodeAt<N.TemplateLiteral>(startLoc);
literal.expressions = [];
literal.quasis = quasis;
node.literal = this.finishNode(literal, "TemplateLiteral");
return this.finishNode(node, "TSLiteralType");
} else {
const substitutions: N.TsType[] = [];
while (!curElt.tail) {
substitutions.push(this.tsParseType());
this.readTemplateContinuation();
quasis.push((curElt = this.parseTemplateElement(false)));
}
const node = this.startNodeAt<N.TsTemplateLiteralType>(startLoc);
node.types = substitutions;
node.quasis = quasis;
return this.finishNode(node, "TSTemplateLiteralType");
}
} else {
const node = this.startNode<N.TsLiteralType>();
node.literal = super.parseTemplate(false);
return this.finishNode(node, "TSLiteralType");
}
}

parseTemplateSubstitution(): N.TsType | N.Expression {
Expand Down
8 changes: 8 additions & 0 deletions packages/babel-parser/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,7 @@ export type TsType =
| TsIndexedAccessType
| TsMappedType
| TsLiteralType // TODO: This probably shouldn't be included here.
| TsTemplateLiteralType
| TsImportType
| TsTypePredicate;

Expand Down Expand Up @@ -1755,6 +1756,12 @@ export interface TsMappedType extends TsTypeBase {
nameType: TsType | undefined | null;
}

export interface TsTemplateLiteralType extends TsTypeBase {
type: "TSTemplateLiteralType";
quasis: TemplateElement[];
types: TsType[];
}

export interface TsLiteralType extends TsTypeBase {
type: "TSLiteralType";
literal: NumericLiteral | StringLiteral | BooleanLiteral | TemplateLiteral;
Expand Down Expand Up @@ -2153,6 +2160,7 @@ export type Node =
| TsQualifiedName
| TsRestType
| TsSatisfiesExpression
| TsTemplateLiteralType
| TsThisType
| TsTupleType
| TsTypeAliasDeclaration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let x: `foo`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"BABEL_8_BREAKING": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"type": "File",
"start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}},
"program": {
"type": "Program",
"start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}},
"declarations": [
{
"type": "VariableDeclarator",
"start":4,"end":12,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":12,"index":12}},
"id": {
"type": "Identifier",
"start":4,"end":12,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":12,"index":12},"identifierName":"x"},
"name": "x",
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":5,"end":12,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":12,"index":12}},
"typeAnnotation": {
"type": "TSLiteralType",
"start":7,"end":12,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":12,"index":12}},
"literal": {
"type": "TemplateLiteral",
"start":7,"end":12,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":12,"index":12}},
"expressions": [],
"quasis": [
{
"type": "TemplateElement",
"start":8,"end":11,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":11,"index":11}},
"value": {
"raw": "foo",
"cooked": "foo"
},
"tail": true
}
]
}
}
}
},
"init": null
}
],
"kind": "let"
}
],
"directives": [],
"extra": {
"topLevelAwait": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"BABEL_8_BREAKING": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let x: `foo-${bar}`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"BABEL_8_BREAKING": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"type": "File",
"start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}},
"program": {
"type": "Program",
"start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "VariableDeclaration",
"start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}},
"declarations": [
{
"type": "VariableDeclarator",
"start":4,"end":19,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":19,"index":19}},
"id": {
"type": "Identifier",
"start":4,"end":19,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":19,"index":19},"identifierName":"x"},
"name": "x",
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":5,"end":19,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":19,"index":19}},
"typeAnnotation": {
"type": "TSLiteralType",
"start":7,"end":19,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":19,"index":19}},
"literal": {
"type": "TemplateLiteral",
"start":7,"end":19,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":19,"index":19}},
"expressions": [
{
"type": "TSTypeReference",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17}},
"typeName": {
"type": "Identifier",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17},"identifierName":"bar"},
"name": "bar"
}
}
],
"quasis": [
{
"type": "TemplateElement",
"start":8,"end":12,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":12,"index":12}},
"value": {
"raw": "foo-",
"cooked": "foo-"
},
"tail": false
},
{
"type": "TemplateElement",
"start":18,"end":18,"loc":{"start":{"line":1,"column":18,"index":18},"end":{"line":1,"column":18,"index":18}},
"value": {
"raw": "",
"cooked": ""
},
"tail": true
}
]
}
}
}
},
"init": null
}
],
"kind": "let"
}
],
"directives": [],
"extra": {
"topLevelAwait": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"BABEL_8_BREAKING": true
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,43 +22,39 @@
"type": "TSTypeAnnotation",
"start":5,"end":19,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":19,"index":19}},
"typeAnnotation": {
"type": "TSLiteralType",
"type": "TSTemplateLiteralType",
"start":7,"end":19,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":19,"index":19}},
"literal": {
"type": "TemplateLiteral",
"start":7,"end":19,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":19,"index":19}},
"expressions": [
{
"type": "TSTypeReference",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17}},
"typeName": {
"type": "Identifier",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17},"identifierName":"bar"},
"name": "bar"
}
"types": [
{
"type": "TSTypeReference",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17}},
"typeName": {
"type": "Identifier",
"start":14,"end":17,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":17,"index":17},"identifierName":"bar"},
"name": "bar"
}
],
"quasis": [
{
"type": "TemplateElement",
"start":8,"end":12,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":12,"index":12}},
"value": {
"raw": "foo-",
"cooked": "foo-"
},
"tail": false
}
],
"quasis": [
{
"type": "TemplateElement",
"start":8,"end":12,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":12,"index":12}},
"value": {
"raw": "foo-",
"cooked": "foo-"
},
{
"type": "TemplateElement",
"start":18,"end":18,"loc":{"start":{"line":1,"column":18,"index":18},"end":{"line":1,"column":18,"index":18}},
"value": {
"raw": "",
"cooked": ""
},
"tail": true
}
]
}
"tail": false
},
{
"type": "TemplateElement",
"start":18,"end":18,"loc":{"start":{"line":1,"column":18,"index":18},"end":{"line":1,"column":18,"index":18}},
"value": {
"raw": "",
"cooked": ""
},
"tail": true
}
]
}
}
},
Expand Down
Loading

0 comments on commit e02b0ff

Please sign in to comment.