From cbe8cc93bd6977c9483f257e96e1e2d9c38b9f5a Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 16:22:04 +0900 Subject: [PATCH 1/5] test: add test case --- .../typescript-estree/tests/lib/parse.test.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index c371a6e9e7ac..a44bb4952b5c 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -926,4 +926,58 @@ describe(parser.parseAndGenerateServices, () => { }); }, ); + + describe('template literal cooked values', () => { + const getTemplateElement = ( + code: string, + ): parser.TSESTree.TemplateElement | null => { + const result = parser.parse(code, { + comment: true, + loc: true, + range: true, + tokens: true, + }); + + const taggedTemplate = result.body.find( + b => b.type === parser.AST_NODE_TYPES.ExpressionStatement, + ); + const expression = taggedTemplate?.expression; + if (expression?.type !== parser.AST_NODE_TYPES.TaggedTemplateExpression) { + return null; + } + return expression.quasi.quasis[0]; + }; + + it('should set cooked to null for invalid escape sequences in tagged template literals', () => { + const code = 'String.raw`\\uXXXX`'; + const templateElement = getTemplateElement(code); + + expect(templateElement?.value.cooked).toBeNull(); + expect(templateElement?.value.raw).toBe('\\uXXXX'); + }); + + it('should set cooked to null for other invalid escape sequences', () => { + const code = 'String.raw`\\unicode and \\u{55}`'; + const templateElement = getTemplateElement(code); + + expect(templateElement?.value.cooked).toBeNull(); + expect(templateElement?.value.raw).toBe('\\unicode and \\u{55}'); + }); + + it('should set cooked to parsed value for valid escape sequences', () => { + const code = 'String.raw`\\n\\t\\u0041`'; + const templateElement = getTemplateElement(code); + + expect(templateElement?.value.cooked).toBe('\n\tA'); + expect(templateElement?.value.raw).toBe('\\n\\t\\u0041'); + }); + + it('should handle mixed valid and invalid escape sequences', () => { + const code = 'String.raw`\\n\\uXXXX\\t`'; + const templateElement = getTemplateElement(code); + + expect(templateElement?.value.cooked).toBeNull(); + expect(templateElement?.value.raw).toBe('\\n\\uXXXX\\t'); + }); + }); }); From a1902bbea23d02609502c9e60b502b0c15dc78f2 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 16:52:51 +0900 Subject: [PATCH 2/5] feat: make flag whether node is inside tag --- packages/typescript-estree/src/convert.ts | 33 ++++++++++++++--------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index f2a504aa4a1d..3e194027229c 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -93,6 +93,7 @@ export class Converter { private allowPattern = false; private readonly ast: ts.SourceFile; private readonly esTreeNodeToTSNodeMap = new WeakMap(); + private isInTaggedTemplate = false; private readonly options: ConverterOptions; private readonly tsNodeToESTreeNodeMap = new WeakMap(); @@ -1917,19 +1918,25 @@ export class Converter { return result; } - case SyntaxKind.TaggedTemplateExpression: - return this.createNode(node, { - type: AST_NODE_TYPES.TaggedTemplateExpression, - quasi: this.convertChild(node.template), - tag: this.convertChild(node.tag), - typeArguments: - node.typeArguments && - this.convertTypeArgumentsToTypeParameterInstantiation( - node.typeArguments, - node, - ), - }); - + case SyntaxKind.TaggedTemplateExpression: { + this.isInTaggedTemplate = true; + const result = this.createNode( + node, + { + type: AST_NODE_TYPES.TaggedTemplateExpression, + quasi: this.convertChild(node.template), + tag: this.convertChild(node.tag), + typeArguments: + node.typeArguments && + this.convertTypeArgumentsToTypeParameterInstantiation( + node.typeArguments, + node, + ), + }, + ); + this.isInTaggedTemplate = false; + return result; + } case SyntaxKind.TemplateHead: case SyntaxKind.TemplateMiddle: case SyntaxKind.TemplateTail: { From 4938a141b2e5f4439b7cb7cd25ee6f1e5b827979 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 17:55:50 +0900 Subject: [PATCH 3/5] fix: if template literal is tagged and the text has an invalid escape, cooked will be null --- packages/typescript-estree/src/convert.ts | 48 ++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 3e194027229c..d31f512eabed 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -90,10 +90,10 @@ function isEntityNameExpression( } export class Converter { + #isInTaggedTemplate = false; private allowPattern = false; private readonly ast: ts.SourceFile; private readonly esTreeNodeToTSNodeMap = new WeakMap(); - private isInTaggedTemplate = false; private readonly options: ConverterOptions; private readonly tsNodeToESTreeNodeMap = new WeakMap(); @@ -402,6 +402,38 @@ export class Converter { } } + #isValidEscape(arg: string): boolean { + const unicode = /\\u([0-9a-fA-F]{4})/g; + const unicodeBracket = /\\u\{([0-9a-fA-F]+)\}/g; // supports ES6+ + const hex = /\\x([0-9a-fA-F]{2})/g; + const validShort = /\\[nrtbfv0\\'"]/g; + + const allEscapes = + /\\(u\{[^}]*\}|u[0-9a-fA-F]{0,4}|x[0-9a-fA-F]{0,2}|[^ux])/g; + + let match: RegExpExecArray | null; + while ((match = allEscapes.exec(arg)) != null) { + const escape = match[0]; + + if ( + unicode.test(escape) || + (unicodeBracket.test(escape) && + (() => { + const cp = parseInt(escape.match(unicodeBracket)![1], 16); + return cp <= 0x10ffff; + })()) || + hex.test(escape) || + validShort.test(escape) + ) { + continue; + } + + return false; + } + + return true; + } + #throwError(node: number | ts.Node, message: string): asserts node is never { let start; let end; @@ -1890,7 +1922,10 @@ export class Converter { type: AST_NODE_TYPES.TemplateElement, tail: true, value: { - cooked: node.text, + cooked: + this.#isValidEscape(node.text) && this.#isInTaggedTemplate + ? node.text + : null, raw: this.ast.text.slice( node.getStart(this.ast) + 1, node.end - 1, @@ -1919,7 +1954,7 @@ export class Converter { } case SyntaxKind.TaggedTemplateExpression: { - this.isInTaggedTemplate = true; + this.#isInTaggedTemplate = true; const result = this.createNode( node, { @@ -1934,7 +1969,7 @@ export class Converter { ), }, ); - this.isInTaggedTemplate = false; + this.#isInTaggedTemplate = false; return result; } case SyntaxKind.TemplateHead: @@ -1945,7 +1980,10 @@ export class Converter { type: AST_NODE_TYPES.TemplateElement, tail, value: { - cooked: node.text, + cooked: + this.#isValidEscape(node.text) && this.#isInTaggedTemplate + ? node.text + : null, raw: this.ast.text.slice( node.getStart(this.ast) + 1, node.end - (tail ? 1 : 2), From 77960bf233ba411faed681f191d819ac219a92ed Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 22:37:28 +0900 Subject: [PATCH 4/5] fix: type error --- packages/ast-spec/src/special/TemplateElement/spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ast-spec/src/special/TemplateElement/spec.ts b/packages/ast-spec/src/special/TemplateElement/spec.ts index cb5d1c6e76f8..dda44172c500 100644 --- a/packages/ast-spec/src/special/TemplateElement/spec.ts +++ b/packages/ast-spec/src/special/TemplateElement/spec.ts @@ -5,7 +5,7 @@ export interface TemplateElement extends BaseNode { type: AST_NODE_TYPES.TemplateElement; tail: boolean; value: { - cooked: string; + cooked: string | null; raw: string; }; } From c11a244bb887b9dfca256960fedcacb32bcfd083 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 23:17:59 +0900 Subject: [PATCH 5/5] chore: add snapshot --- .../snapshots/1-TSESTree-AST.shot | 2 +- .../snapshots/5-AST-Alignment-AST.shot | 74 ++++++++++++++++++- .../snapshots/1-TSESTree-AST.shot | 4 +- .../snapshots/5-AST-Alignment-AST.shot | 4 +- .../snapshots/1-TSESTree-AST.shot | 4 +- .../snapshots/5-AST-Alignment-AST.shot | 4 +- .../snapshots/1-TSESTree-AST.shot | 10 +-- .../snapshots/5-AST-Alignment-AST.shot | 10 +-- .../tests/fixtures-with-differences-ast.shot | 1 + 9 files changed, 93 insertions(+), 20 deletions(-) diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-1/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-1/snapshots/1-TSESTree-AST.shot index 88e028508e34..5e0c9090807d 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-1/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-1/snapshots/1-TSESTree-AST.shot @@ -26,7 +26,7 @@ Program { type: "TemplateElement", tail: true, value: { - "cooked": "foo", + "cooked": null, "raw": "foo", }, diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-1/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-1/snapshots/5-AST-Alignment-AST.shot index cdda4e0f0b5b..a24c8bb9f5d6 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-1/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-1/snapshots/5-AST-Alignment-AST.shot @@ -2,4 +2,76 @@ exports[`AST Fixtures > legacy-fixtures > types > template-literal-type-1 > AST Alignment - AST`] Snapshot Diff: -Compared values have no visual difference. +- TSESTree ++ Babel + + Program { + type: 'Program', + body: Array [ + TSTypeAliasDeclaration { + type: 'TSTypeAliasDeclaration', + declare: false, + id: Identifier { + type: 'Identifier', + decorators: Array [], + name: 'T', + optional: false, + + range: [78, 79], + loc: { + start: { column: 5, line: 3 }, + end: { column: 6, line: 3 }, + }, + }, + typeAnnotation: TSLiteralType { + type: 'TSLiteralType', + literal: TemplateLiteral { + type: 'TemplateLiteral', + expressions: Array [], + quasis: Array [ + TemplateElement { + type: 'TemplateElement', + tail: true, + value: Object { +- 'cooked': null, ++ 'cooked': 'foo', + 'raw': 'foo', + }, + + range: [82, 87], + loc: { + start: { column: 9, line: 3 }, + end: { column: 14, line: 3 }, + }, + }, + ], + + range: [82, 87], + loc: { + start: { column: 9, line: 3 }, + end: { column: 14, line: 3 }, + }, + }, + + range: [82, 87], + loc: { + start: { column: 9, line: 3 }, + end: { column: 14, line: 3 }, + }, + }, + + range: [73, 88], + loc: { + start: { column: 0, line: 3 }, + end: { column: 15, line: 3 }, + }, + }, + ], + sourceType: 'script', + + range: [73, 89], + loc: { + start: { column: 0, line: 3 }, + end: { column: 0, line: 4 }, + }, + } diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-2/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-2/snapshots/1-TSESTree-AST.shot index 1065df8f8712..5c59dc2dffde 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-2/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-2/snapshots/1-TSESTree-AST.shot @@ -23,7 +23,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": "foo", + "cooked": null, "raw": "foo", }, @@ -37,7 +37,7 @@ Program { type: "TemplateElement", tail: true, value: { - "cooked": "", + "cooked": null, "raw": "", }, diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-2/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-2/snapshots/5-AST-Alignment-AST.shot index dc7e97974018..b337acde0a87 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-2/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-2/snapshots/5-AST-Alignment-AST.shot @@ -30,7 +30,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': 'foo', +- 'cooked': null, - 'raw': 'foo', - }, + typeAnnotation: TSLiteralType { @@ -55,7 +55,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: true, - value: Object { -- 'cooked': '', +- 'cooked': null, - 'raw': '', - }, + range: [88, 93], diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-3/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-3/snapshots/1-TSESTree-AST.shot index 86bdf5a0561f..7c3b8088d938 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-3/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-3/snapshots/1-TSESTree-AST.shot @@ -169,7 +169,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": "", + "cooked": null, "raw": "", }, @@ -183,7 +183,7 @@ Program { type: "TemplateElement", tail: true, value: { - "cooked": " fish", + "cooked": null, "raw": " fish", }, diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-3/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-3/snapshots/5-AST-Alignment-AST.shot index 5a6cfaa69dbb..d6e6c564df8d 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-3/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-3/snapshots/5-AST-Alignment-AST.shot @@ -176,7 +176,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': '', +- 'cooked': null, - 'raw': '', - }, + typeAnnotation: TSLiteralType { @@ -205,7 +205,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: true, - value: Object { -- 'cooked': ' fish', +- 'cooked': null, - 'raw': ' fish', - }, - diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-4/snapshots/1-TSESTree-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-4/snapshots/1-TSESTree-AST.shot index 5a2aee247757..51ecc6523886 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-4/snapshots/1-TSESTree-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-4/snapshots/1-TSESTree-AST.shot @@ -23,7 +23,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": "", + "cooked": null, "raw": "", }, @@ -37,7 +37,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": " - ", + "cooked": null, "raw": " - ", }, @@ -51,7 +51,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": " - ", + "cooked": null, "raw": " - ", }, @@ -65,7 +65,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": " - ", + "cooked": null, "raw": " - ", }, @@ -79,7 +79,7 @@ Program { type: "TemplateElement", tail: true, value: { - "cooked": "", + "cooked": null, "raw": "", }, diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-4/snapshots/5-AST-Alignment-AST.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-4/snapshots/5-AST-Alignment-AST.shot index a2d68af67a6b..0e993488ce92 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-4/snapshots/5-AST-Alignment-AST.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/template-literal-type-4/snapshots/5-AST-Alignment-AST.shot @@ -30,7 +30,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': '', +- 'cooked': null, - 'raw': '', - }, - @@ -44,7 +44,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': ' - ', +- 'cooked': null, - 'raw': ' - ', - }, - @@ -58,7 +58,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': ' - ', +- 'cooked': null, - 'raw': ' - ', - }, - @@ -72,7 +72,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': ' - ', +- 'cooked': null, - 'raw': ' - ', - }, + typeAnnotation: TSLiteralType { @@ -98,7 +98,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: true, - value: Object { -- 'cooked': '', +- 'cooked': null, - 'raw': '', - }, + range: [124, 133], diff --git a/packages/ast-spec/tests/fixtures-with-differences-ast.shot b/packages/ast-spec/tests/fixtures-with-differences-ast.shot index a64da89a8106..254d24375d39 100644 --- a/packages/ast-spec/tests/fixtures-with-differences-ast.shot +++ b/packages/ast-spec/tests/fixtures-with-differences-ast.shot @@ -215,6 +215,7 @@ exports[`AST Fixtures > List fixtures with AST differences`] "legacy-fixtures/types/fixtures/optional-variance-out/fixture.ts", "legacy-fixtures/types/fixtures/reference-generic-nested/fixture.ts", "legacy-fixtures/types/fixtures/reference-generic/fixture.ts", + "legacy-fixtures/types/fixtures/template-literal-type-1/fixture.ts", "legacy-fixtures/types/fixtures/template-literal-type-2/fixture.ts", "legacy-fixtures/types/fixtures/template-literal-type-3/fixture.ts", "legacy-fixtures/types/fixtures/template-literal-type-4/fixture.ts", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy