From cbe8cc93bd6977c9483f257e96e1e2d9c38b9f5a Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 16:22:04 +0900 Subject: [PATCH 01/27] 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 02/27] 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 03/27] 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 04/27] 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 05/27] 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", From cd7550fbe4fc20099c358cd6baa31599a34efe4d Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 16:22:04 +0900 Subject: [PATCH 06/27] 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 156b3844a11521c3c22b61b404da53c9eb0e98fb Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 16:52:51 +0900 Subject: [PATCH 07/27] 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 6103105154dfdba15c0fe3014b49daa9e01e0d30 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 17:55:50 +0900 Subject: [PATCH 08/27] 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 cbeb8d7afd937ddee7bd9f063874e67fce86b64e Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 22:37:28 +0900 Subject: [PATCH 09/27] 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 954e84e62f20eceb828b9dfa0df97015ab18c497 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 28 Jun 2025 23:17:59 +0900 Subject: [PATCH 10/27] 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", From 8fdb4dbb240fb5356e59c6ec60b6c84c242a6149 Mon Sep 17 00:00:00 2001 From: JamesHenry Date: Tue, 1 Jul 2025 17:47:54 +0400 Subject: [PATCH 11/27] chore: try with SKIP_AST_SPEC_REBUILD false --- .github/actions/prepare-build/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/prepare-build/action.yml b/.github/actions/prepare-build/action.yml index d191358d6503..1c3356230272 100644 --- a/.github/actions/prepare-build/action.yml +++ b/.github/actions/prepare-build/action.yml @@ -24,7 +24,7 @@ runs: run: | yarn nx run types:build env: - SKIP_AST_SPEC_REBUILD: true + SKIP_AST_SPEC_REBUILD: false - name: Build if: steps['build-cache'].outputs.cache-hit != 'true' @@ -33,4 +33,4 @@ runs: run: | yarn nx run-many --target=build --parallel --exclude=website --exclude=website-eslint env: - SKIP_AST_SPEC_REBUILD: true + SKIP_AST_SPEC_REBUILD: false From 2e5857a4467a24b7849eac75b693a4ac8acbf297 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 23 Jul 2025 16:47:42 +0900 Subject: [PATCH 12/27] fix: fallback when cooked is null --- .../src/rules/plugin-test-formatting.ts | 10 +++++++++- .../src/rules/no-duplicate-enum-values.ts | 2 +- .../eslint-plugin/src/rules/no-unsafe-assignment.ts | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts index 26fcc60d8d2b..78eef0741c86 100644 --- a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts +++ b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts @@ -284,6 +284,10 @@ export default createRule({ const text = literal.quasis[0].value.cooked; + if (text == null) { + return; + } + if (literal.loc.end.line === literal.loc.start.line) { // don't use template strings for single line tests return context.report({ @@ -448,9 +452,13 @@ export default createRule({ } function checkForUnnecesaryNoFormat( - text: string, + text: string | null, expr: TSESTree.TaggedTemplateExpression, ): void { + if (text == null) { + return; + } + const formatted = getCodeFormatted(text); if (formatted === text) { context.report({ diff --git a/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts b/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts index c5a858dbdcaa..e413656136d5 100644 --- a/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts +++ b/packages/eslint-plugin/src/rules/no-duplicate-enum-values.ts @@ -56,7 +56,7 @@ export default createRule({ return; } - let value: number | string | undefined; + let value: number | string | null | undefined; if (isStringLiteral(member.initializer)) { value = member.initializer.value; } else if (isNumberLiteral(member.initializer)) { diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index 2dd9ca6b5d2e..b7f55ecabad2 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -200,7 +200,11 @@ export default createRule({ receiverProperty.key.type === AST_NODE_TYPES.TemplateLiteral && receiverProperty.key.quasis.length === 1 ) { - key = receiverProperty.key.quasis[0].value.cooked; + const cooked = receiverProperty.key.quasis[0].value.cooked; + if (cooked == null) { + continue; + } + key = cooked; } else { // can't figure out the name, so skip it continue; From 49dcf05d3634804b8133a8106770a71b4c4115f9 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 23 Jul 2025 17:21:42 +0900 Subject: [PATCH 13/27] chore: temp commit to see ci result --- 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 dda44172c500..231ca2cb8210 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 | null; + cooked: string | null | undefined; raw: string; }; } From 84ec93c9a2dfd5e0460eda450dddb9f790497127 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 23 Jul 2025 18:01:34 +0900 Subject: [PATCH 14/27] fix: try to change input --- packages/ast-spec/src/special/TemplateElement/spec.ts | 2 +- packages/types/package.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ast-spec/src/special/TemplateElement/spec.ts b/packages/ast-spec/src/special/TemplateElement/spec.ts index 231ca2cb8210..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 | null | undefined; + cooked: string | null; raw: string; }; } diff --git a/packages/types/package.json b/packages/types/package.json index ee0dc3efc5cb..7788a077f727 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -65,6 +65,9 @@ ], "targets": { "build": { + "inputs": [ + "src/generated" + ], "dependsOn": [ "copy-ast-spec" ] From 814604f555957b60b570ceff425fd519b947013f Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 23 Jul 2025 18:04:22 +0900 Subject: [PATCH 15/27] fix: mis config input --- packages/types/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/package.json b/packages/types/package.json index 7788a077f727..773395e08575 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -66,7 +66,7 @@ "targets": { "build": { "inputs": [ - "src/generated" + "{projectRoot}/src/generated" ], "dependsOn": [ "copy-ast-spec" From 888e312725b5983cf1de4b80b6b653cc220fbff1 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Wed, 23 Jul 2025 18:07:43 +0900 Subject: [PATCH 16/27] chore: off ast spec env --- .github/actions/prepare-build/action.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/actions/prepare-build/action.yml b/.github/actions/prepare-build/action.yml index 1c3356230272..575d5a38e76a 100644 --- a/.github/actions/prepare-build/action.yml +++ b/.github/actions/prepare-build/action.yml @@ -23,8 +23,6 @@ runs: shell: bash run: | yarn nx run types:build - env: - SKIP_AST_SPEC_REBUILD: false - name: Build if: steps['build-cache'].outputs.cache-hit != 'true' @@ -32,5 +30,3 @@ runs: # Website will be built by the Netlify GitHub App run: | yarn nx run-many --target=build --parallel --exclude=website --exclude=website-eslint - env: - SKIP_AST_SPEC_REBUILD: false From 4c1891c7ae888cae03e5aebf613fd74a6b95194b Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 24 Jul 2025 17:09:38 +0900 Subject: [PATCH 17/27] fix: lint err --- .../src/rules/plugin-test-formatting.ts | 6 +++++- packages/eslint-plugin/src/rules/no-unsafe-assignment.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts index 78eef0741c86..352362e7d7f0 100644 --- a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts +++ b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts @@ -277,6 +277,10 @@ export default createRule({ isErrorTest: boolean, isNoFormatTagged = false, ): void { + function isNull(value: unknown): value is null { + return value == null; + } + if (literal.quasis.length > 1) { // ignore template literals with ${expressions} for simplicity return; @@ -284,7 +288,7 @@ export default createRule({ const text = literal.quasis[0].value.cooked; - if (text == null) { + if (isNull(text)) { return; } diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index b7f55ecabad2..eb7cdeb99e4c 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -172,6 +172,10 @@ export default createRule({ senderType: ts.Type, senderNode: ts.Node, ): boolean { + function isNull(value: unknown): value is null { + return value == null; + } + const properties = new Map( senderType .getProperties() @@ -201,7 +205,7 @@ export default createRule({ receiverProperty.key.quasis.length === 1 ) { const cooked = receiverProperty.key.quasis[0].value.cooked; - if (cooked == null) { + if (isNull(cooked)) { continue; } key = cooked; From 2a3100621c5649c2490eea8f2a802ba02173696c Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 24 Jul 2025 17:23:42 +0900 Subject: [PATCH 18/27] chore: revert action.yml and package.json --- .github/actions/prepare-build/action.yml | 4 ++++ packages/types/package.json | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/actions/prepare-build/action.yml b/.github/actions/prepare-build/action.yml index 575d5a38e76a..d191358d6503 100644 --- a/.github/actions/prepare-build/action.yml +++ b/.github/actions/prepare-build/action.yml @@ -23,6 +23,8 @@ runs: shell: bash run: | yarn nx run types:build + env: + SKIP_AST_SPEC_REBUILD: true - name: Build if: steps['build-cache'].outputs.cache-hit != 'true' @@ -30,3 +32,5 @@ runs: # Website will be built by the Netlify GitHub App run: | yarn nx run-many --target=build --parallel --exclude=website --exclude=website-eslint + env: + SKIP_AST_SPEC_REBUILD: true diff --git a/packages/types/package.json b/packages/types/package.json index 773395e08575..ee0dc3efc5cb 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -65,9 +65,6 @@ ], "targets": { "build": { - "inputs": [ - "{projectRoot}/src/generated" - ], "dependsOn": [ "copy-ast-spec" ] From 27e9b9abeb3b576ca31b15a8289c6e409b9e19af Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 31 Jul 2025 15:45:33 +0900 Subject: [PATCH 19/27] chore: workspace sync --- packages/parser/tsconfig.build.json | 3 --- packages/parser/tsconfig.json | 3 --- packages/project-service/tsconfig.build.json | 3 --- packages/project-service/tsconfig.json | 3 --- packages/scope-manager/tsconfig.build.json | 3 --- packages/scope-manager/tsconfig.json | 3 --- packages/type-utils/tsconfig.build.json | 3 --- packages/type-utils/tsconfig.json | 3 --- packages/utils/tsconfig.build.json | 3 --- packages/utils/tsconfig.json | 3 --- packages/visitor-keys/tsconfig.build.json | 6 +----- packages/visitor-keys/tsconfig.json | 3 --- packages/website/tsconfig.build.json | 3 --- packages/website/tsconfig.json | 3 --- 14 files changed, 1 insertion(+), 44 deletions(-) diff --git a/packages/parser/tsconfig.build.json b/packages/parser/tsconfig.build.json index ca2c74a7ac78..1c35eaea1cc2 100644 --- a/packages/parser/tsconfig.build.json +++ b/packages/parser/tsconfig.build.json @@ -8,9 +8,6 @@ { "path": "../typescript-estree/tsconfig.build.json" }, - { - "path": "../types/tsconfig.build.json" - }, { "path": "../scope-manager/tsconfig.build.json" } diff --git a/packages/parser/tsconfig.json b/packages/parser/tsconfig.json index 8122a5aaab8a..5f1cd2b68000 100644 --- a/packages/parser/tsconfig.json +++ b/packages/parser/tsconfig.json @@ -9,9 +9,6 @@ { "path": "../typescript-estree" }, - { - "path": "../types" - }, { "path": "../scope-manager" }, diff --git a/packages/project-service/tsconfig.build.json b/packages/project-service/tsconfig.build.json index d42ee3d2bef0..4496a35e63c2 100644 --- a/packages/project-service/tsconfig.build.json +++ b/packages/project-service/tsconfig.build.json @@ -4,9 +4,6 @@ "references": [ { "path": "../tsconfig-utils/tsconfig.build.json" - }, - { - "path": "../types/tsconfig.build.json" } ] } diff --git a/packages/project-service/tsconfig.json b/packages/project-service/tsconfig.json index 8029bc0ac19d..51673a2ff68e 100644 --- a/packages/project-service/tsconfig.json +++ b/packages/project-service/tsconfig.json @@ -6,9 +6,6 @@ { "path": "../tsconfig-utils" }, - { - "path": "../types" - }, { "path": "./tsconfig.build.json" }, diff --git a/packages/scope-manager/tsconfig.build.json b/packages/scope-manager/tsconfig.build.json index 51c7a6dbc40d..48ac0db13dc8 100644 --- a/packages/scope-manager/tsconfig.build.json +++ b/packages/scope-manager/tsconfig.build.json @@ -5,9 +5,6 @@ { "path": "../visitor-keys/tsconfig.build.json" }, - { - "path": "../types/tsconfig.build.json" - }, { "path": "../typescript-estree/tsconfig.build.json" } diff --git a/packages/scope-manager/tsconfig.json b/packages/scope-manager/tsconfig.json index 44cfc868c8d6..56d85c206c52 100644 --- a/packages/scope-manager/tsconfig.json +++ b/packages/scope-manager/tsconfig.json @@ -6,9 +6,6 @@ { "path": "../visitor-keys" }, - { - "path": "../types" - }, { "path": "../typescript-estree" }, diff --git a/packages/type-utils/tsconfig.build.json b/packages/type-utils/tsconfig.build.json index 2c29fdf0c620..1bb2abba12ab 100644 --- a/packages/type-utils/tsconfig.build.json +++ b/packages/type-utils/tsconfig.build.json @@ -2,9 +2,6 @@ "extends": "../../tsconfig.build.json", "compilerOptions": {}, "references": [ - { - "path": "../types/tsconfig.build.json" - }, { "path": "../utils/tsconfig.build.json" }, diff --git a/packages/type-utils/tsconfig.json b/packages/type-utils/tsconfig.json index a57d8d1814a8..2eb8e60114d8 100644 --- a/packages/type-utils/tsconfig.json +++ b/packages/type-utils/tsconfig.json @@ -3,9 +3,6 @@ "files": [], "include": [], "references": [ - { - "path": "../types" - }, { "path": "../utils" }, diff --git a/packages/utils/tsconfig.build.json b/packages/utils/tsconfig.build.json index f99a3f7e1b40..e1c25e0bedf6 100644 --- a/packages/utils/tsconfig.build.json +++ b/packages/utils/tsconfig.build.json @@ -5,9 +5,6 @@ { "path": "../typescript-estree/tsconfig.build.json" }, - { - "path": "../types/tsconfig.build.json" - }, { "path": "../scope-manager/tsconfig.build.json" } diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index bda7a07530ab..1a5d1d3a8729 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -6,9 +6,6 @@ { "path": "../typescript-estree" }, - { - "path": "../types" - }, { "path": "../scope-manager" }, diff --git a/packages/visitor-keys/tsconfig.build.json b/packages/visitor-keys/tsconfig.build.json index deb22cbe20a5..4a1f19accacd 100644 --- a/packages/visitor-keys/tsconfig.build.json +++ b/packages/visitor-keys/tsconfig.build.json @@ -1,9 +1,5 @@ { "extends": "../../tsconfig.build.json", "compilerOptions": {}, - "references": [ - { - "path": "../types/tsconfig.build.json" - } - ] + "references": [] } diff --git a/packages/visitor-keys/tsconfig.json b/packages/visitor-keys/tsconfig.json index d89a8a4ec896..d4d0929e1955 100644 --- a/packages/visitor-keys/tsconfig.json +++ b/packages/visitor-keys/tsconfig.json @@ -3,9 +3,6 @@ "files": [], "include": [], "references": [ - { - "path": "../types" - }, { "path": "./tsconfig.build.json" }, diff --git a/packages/website/tsconfig.build.json b/packages/website/tsconfig.build.json index 94033f10b0be..bdc90c5624ad 100644 --- a/packages/website/tsconfig.build.json +++ b/packages/website/tsconfig.build.json @@ -35,9 +35,6 @@ { "path": "../typescript-estree/tsconfig.build.json" }, - { - "path": "../types/tsconfig.build.json" - }, { "path": "../scope-manager/tsconfig.build.json" }, diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json index d3b0dd5bcc67..9e586ad2f711 100644 --- a/packages/website/tsconfig.json +++ b/packages/website/tsconfig.json @@ -12,9 +12,6 @@ { "path": "../typescript-estree" }, - { - "path": "../types" - }, { "path": "../scope-manager" }, From 30ed17f1b143295bef02cc9fc2fe1d88b648372b Mon Sep 17 00:00:00 2001 From: nayounsang Date: Thu, 31 Jul 2025 15:46:08 +0900 Subject: [PATCH 20/27] test: add test snapshot --- .../snapshots/2-TSESTree-Tokens.shot | 4 +- .../snapshots/6-AST-Alignment-Tokens.shot | 6 +-- .../snapshots/2-TSESTree-Tokens.shot | 4 +- .../snapshots/6-AST-Alignment-Tokens.shot | 6 +-- .../snapshots/1-TSESTree-AST.shot | 2 +- .../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 ++--- .../snapshots/2-TSESTree-Tokens.shot | 28 ++++++------- .../snapshots/6-AST-Alignment-Tokens.shot | 42 +++++++------------ .../snapshots/2-TSESTree-Tokens.shot | 8 ++-- .../snapshots/6-AST-Alignment-Tokens.shot | 12 ++---- .../tests/fixtures-with-differences-ast.shot | 1 - 16 files changed, 63 insertions(+), 86 deletions(-) diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/typed-this/snapshots/2-TSESTree-Tokens.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/typed-this/snapshots/2-TSESTree-Tokens.shot index 573d00dfabbf..c7bbf886221e 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/typed-this/snapshots/2-TSESTree-Tokens.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/typed-this/snapshots/2-TSESTree-Tokens.shot @@ -79,8 +79,8 @@ end: { column: 29, line: 4 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [124, 128], diff --git a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/typed-this/snapshots/6-AST-Alignment-Tokens.shot b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/typed-this/snapshots/6-AST-Alignment-Tokens.shot index cf45b4578a48..e60f93635f64 100644 --- a/packages/ast-spec/src/legacy-fixtures/basics/fixtures/typed-this/snapshots/6-AST-Alignment-Tokens.shot +++ b/packages/ast-spec/src/legacy-fixtures/basics/fixtures/typed-this/snapshots/6-AST-Alignment-Tokens.shot @@ -88,10 +88,8 @@ Snapshot Diff: end: { column: 29, line: 4 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [124, 128], diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/function-with-this/snapshots/2-TSESTree-Tokens.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/function-with-this/snapshots/2-TSESTree-Tokens.shot index 4ed3f2a15270..7384c5b71f9c 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/function-with-this/snapshots/2-TSESTree-Tokens.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/function-with-this/snapshots/2-TSESTree-Tokens.shot @@ -39,8 +39,8 @@ end: { column: 8, line: 3 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [81, 85], diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/function-with-this/snapshots/6-AST-Alignment-Tokens.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/function-with-this/snapshots/6-AST-Alignment-Tokens.shot index 4dd08bd918b5..f2753dc6195f 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/function-with-this/snapshots/6-AST-Alignment-Tokens.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/function-with-this/snapshots/6-AST-Alignment-Tokens.shot @@ -48,10 +48,8 @@ Snapshot Diff: end: { column: 8, line: 3 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [81, 85], 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 5e0c9090807d..88e028508e34 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": null, + "cooked": "foo", "raw": "foo", }, 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 5c59dc2dffde..1065df8f8712 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": null, + "cooked": "foo", "raw": "foo", }, @@ -37,7 +37,7 @@ Program { type: "TemplateElement", tail: true, value: { - "cooked": null, + "cooked": "", "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 b337acde0a87..dc7e97974018 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': null, +- 'cooked': 'foo', - 'raw': 'foo', - }, + typeAnnotation: TSLiteralType { @@ -55,7 +55,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: true, - value: Object { -- 'cooked': null, +- 'cooked': '', - '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 7c3b8088d938..86bdf5a0561f 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": null, + "cooked": "", "raw": "", }, @@ -183,7 +183,7 @@ Program { type: "TemplateElement", tail: true, value: { - "cooked": null, + "cooked": " fish", "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 d6e6c564df8d..5a6cfaa69dbb 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': null, +- 'cooked': '', - 'raw': '', - }, + typeAnnotation: TSLiteralType { @@ -205,7 +205,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: true, - value: Object { -- 'cooked': null, +- 'cooked': ' fish', - '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 51ecc6523886..5a2aee247757 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": null, + "cooked": "", "raw": "", }, @@ -37,7 +37,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": null, + "cooked": " - ", "raw": " - ", }, @@ -51,7 +51,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": null, + "cooked": " - ", "raw": " - ", }, @@ -65,7 +65,7 @@ Program { type: "TemplateElement", tail: false, value: { - "cooked": null, + "cooked": " - ", "raw": " - ", }, @@ -79,7 +79,7 @@ Program { type: "TemplateElement", tail: true, value: { - "cooked": null, + "cooked": "", "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 0e993488ce92..a2d68af67a6b 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': null, +- 'cooked': '', - 'raw': '', - }, - @@ -44,7 +44,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': null, +- 'cooked': ' - ', - 'raw': ' - ', - }, - @@ -58,7 +58,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': null, +- 'cooked': ' - ', - 'raw': ' - ', - }, - @@ -72,7 +72,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: false, - value: Object { -- 'cooked': null, +- 'cooked': ' - ', - 'raw': ' - ', - }, + typeAnnotation: TSLiteralType { @@ -98,7 +98,7 @@ Snapshot Diff: - type: 'TemplateElement', - tail: true, - value: Object { -- 'cooked': null, +- 'cooked': '', - 'raw': '', - }, + range: [124, 133], diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/this-type-expanded/snapshots/2-TSESTree-Tokens.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/this-type-expanded/snapshots/2-TSESTree-Tokens.shot index 1deb60e039ac..f41521a4d6ef 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/this-type-expanded/snapshots/2-TSESTree-Tokens.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/this-type-expanded/snapshots/2-TSESTree-Tokens.shot @@ -109,8 +109,8 @@ end: { column: 16, line: 6 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [120, 124], @@ -269,8 +269,8 @@ end: { column: 17, line: 10 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [183, 187], @@ -429,8 +429,8 @@ end: { column: 17, line: 14 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [241, 245], @@ -689,8 +689,8 @@ end: { column: 17, line: 19 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [329, 333], @@ -949,8 +949,8 @@ end: { column: 22, line: 24 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [419, 423], @@ -1089,8 +1089,8 @@ end: { column: 8, line: 28 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "typeof", range: [471, 477], @@ -1109,8 +1109,8 @@ end: { column: 16, line: 28 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [478, 482], diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/this-type-expanded/snapshots/6-AST-Alignment-Tokens.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/this-type-expanded/snapshots/6-AST-Alignment-Tokens.shot index a00c84b46b57..3abc427e91f4 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/this-type-expanded/snapshots/6-AST-Alignment-Tokens.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/this-type-expanded/snapshots/6-AST-Alignment-Tokens.shot @@ -120,10 +120,8 @@ Snapshot Diff: end: { column: 16, line: 6 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [120, 124], @@ -284,10 +282,8 @@ Snapshot Diff: end: { column: 17, line: 10 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [183, 187], @@ -448,10 +444,8 @@ Snapshot Diff: end: { column: 17, line: 14 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [241, 245], @@ -712,10 +706,8 @@ Snapshot Diff: end: { column: 17, line: 19 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [329, 333], @@ -974,10 +966,8 @@ Snapshot Diff: end: { column: 22, line: 24 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [419, 423], @@ -1116,10 +1106,8 @@ Snapshot Diff: end: { column: 8, line: 28 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'typeof', range: [471, 477], @@ -1138,10 +1126,8 @@ Snapshot Diff: end: { column: 16, line: 28 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [478, 482], diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/typeof-this/snapshots/2-TSESTree-Tokens.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/typeof-this/snapshots/2-TSESTree-Tokens.shot index accecbdf3e6c..9257d8476df2 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/typeof-this/snapshots/2-TSESTree-Tokens.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/typeof-this/snapshots/2-TSESTree-Tokens.shot @@ -39,8 +39,8 @@ end: { column: 16, line: 3 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [90, 94], @@ -99,8 +99,8 @@ end: { column: 15, line: 4 }, }, }, - Keyword { - type: "Keyword", + Identifier { + type: "Identifier", value: "this", range: [112, 116], diff --git a/packages/ast-spec/src/legacy-fixtures/types/fixtures/typeof-this/snapshots/6-AST-Alignment-Tokens.shot b/packages/ast-spec/src/legacy-fixtures/types/fixtures/typeof-this/snapshots/6-AST-Alignment-Tokens.shot index c76669826b20..fdcc7a211dbb 100644 --- a/packages/ast-spec/src/legacy-fixtures/types/fixtures/typeof-this/snapshots/6-AST-Alignment-Tokens.shot +++ b/packages/ast-spec/src/legacy-fixtures/types/fixtures/typeof-this/snapshots/6-AST-Alignment-Tokens.shot @@ -48,10 +48,8 @@ Snapshot Diff: end: { column: 16, line: 3 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [90, 94], @@ -112,10 +110,8 @@ Snapshot Diff: end: { column: 15, line: 4 }, }, }, -- Keyword { -- type: 'Keyword', -+ Identifier { -+ type: 'Identifier', + Identifier { + type: 'Identifier', value: 'this', range: [112, 116], diff --git a/packages/ast-spec/tests/fixtures-with-differences-ast.shot b/packages/ast-spec/tests/fixtures-with-differences-ast.shot index 254d24375d39..a64da89a8106 100644 --- a/packages/ast-spec/tests/fixtures-with-differences-ast.shot +++ b/packages/ast-spec/tests/fixtures-with-differences-ast.shot @@ -215,7 +215,6 @@ 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", From 3d5888d22a0c7ed0f441dc5452b06ab16e390514 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 2 Aug 2025 18:04:06 +0900 Subject: [PATCH 21/27] test: move test to convert and add validate tests --- .../tests/lib/convert.test.ts | 137 ++++++++++++++++++ .../typescript-estree/tests/lib/parse.test.ts | 54 ------- 2 files changed, 137 insertions(+), 54 deletions(-) diff --git a/packages/typescript-estree/tests/lib/convert.test.ts b/packages/typescript-estree/tests/lib/convert.test.ts index 77dea1f5f4b4..b7990a83acec 100644 --- a/packages/typescript-estree/tests/lib/convert.test.ts +++ b/packages/typescript-estree/tests/lib/convert.test.ts @@ -449,4 +449,141 @@ describe('convert', () => { expect(Object.keys(tsMappedType)).toContain('typeParameter'); }); }); + + describe('tagged template literal cooked', () => { + const getTemplateElement = (code: string): TSESTree.TemplateElement => { + const result = convertCode(code); + const converter = new Converter(result); + const program = converter.convertProgram(); + + const taggedTemplate = program.body.find( + b => b.type === AST_NODE_TYPES.ExpressionStatement, + ); + const expression = taggedTemplate?.expression; + if (expression?.type !== AST_NODE_TYPES.TaggedTemplateExpression) { + throw new Error('TaggedTemplateExpression not found'); + } + return expression.quasi.quasis[0]; + }; + + const invalidEscapeSequences = String.raw`\uXXXX`; + + it('should set cooked to null for invalid escape sequences in tagged template literals', () => { + const code = `String.raw\`${invalidEscapeSequences}\``; + const templateElement = getTemplateElement(code); + + expect(templateElement.value.cooked).toBeNull(); + }); + + it('should set cooked to null for mixed valid and invalid escape sequences', () => { + const code = `String.raw\`\n${invalidEscapeSequences}\t\``; + const templateElement = getTemplateElement(code); + + expect(templateElement.value.cooked).toBeNull(); + }); + + it('should not set cooked to null for text without invalid escape sequences', () => { + const code = `String.raw\`foo\n\u1111\t + bar + baz\``; + const templateElement = getTemplateElement(code); + + expect(templateElement.value.cooked).toBe(`foo\n\u1111\t + bar + baz`); + }); + + it('should not set cooked to null for untagged template literals', () => { + const code = `const foo = \`\c1\``; + const result = convertCode(code); + const converter = new Converter(result); + const program = converter.convertProgram(); + + const variableDeclaration = program.body.find( + b => b.type === AST_NODE_TYPES.VariableDeclaration, + ); + const variableDeclarator = variableDeclaration?.declarations[0]; + if (variableDeclarator?.type !== AST_NODE_TYPES.VariableDeclarator) { + throw new Error('VariableDeclarator not found'); + } + const init = variableDeclarator.init; + if (init?.type !== AST_NODE_TYPES.TemplateLiteral) { + throw new Error('TemplateLiteral not found'); + } + const templateElement = init.quasis[0]; + + expect(templateElement.value.cooked).toBe(`\c1`); + }); + + describe('validate escape sequences', () => { + it('should return false for invalid unicode', () => { + const invalidUnicodes = [String.raw`\uXXXX`, String.raw`\u12`]; + const codes = invalidUnicodes.map( + invalidUnicode => `String.raw\`${invalidUnicode}\``, + ); + const templateElements = codes.map(code => getTemplateElement(code)); + + expect(templateElements[0].value.cooked).toBeNull(); + expect(templateElements[1].value.cooked).toBeNull(); + }); + + it('should return false for invalid unicode with braces', () => { + const invalidUnicodes = [String.raw`\u{123`, String.raw`\u{12345678}`]; + const codes = invalidUnicodes.map( + invalidUnicode => `String.raw\`${invalidUnicode}\``, + ); + const templateElements = codes.map(code => getTemplateElement(code)); + + expect(templateElements[0].value.cooked).toBeNull(); + expect(templateElements[1].value.cooked).toBeNull(); + }); + + it('should return true for valid unicode', () => { + // 002E is . + const validUnicodes = [String.raw`\u{002E}`, String.raw`\u002E`]; + const codes = validUnicodes.map( + validUnicode => `String.raw\`${validUnicode}\``, + ); + const templateElements = codes.map(code => getTemplateElement(code)); + + expect(templateElements[0].value.cooked).toBe('.'); + expect(templateElements[1].value.cooked).toBe('.'); + }); + + it('should return false for invalid hex', () => { + const invalidHexes = [String.raw`\x1`, String.raw`\xZX`]; + const codes = invalidHexes.map( + invalidHex => `String.raw\`${invalidHex}\``, + ); + const templateElements = codes.map(code => getTemplateElement(code)); + + expect(templateElements[0].value.cooked).toBeNull(); + expect(templateElements[1].value.cooked).toBeNull(); + }); + + it('should return true for valid hex', () => { + const validHex = String.raw`\x2E`; + const code = `String.raw\`${validHex}\``; + const templateElement = getTemplateElement(code); + + expect(templateElement.value.cooked).toBe('.'); + }); + + it('should return false for invalid short', () => { + const invalidShort = String.raw`\1`; + const code = `String.raw\`${invalidShort}\``; + const templateElement = getTemplateElement(code); + + expect(templateElement.value.cooked).toBeNull(); + }); + + it('should return true for valid short', () => { + const validShort = String.raw`\"`; + const code = `String.raw\`${validShort}\``; + const templateElement = getTemplateElement(code); + + expect(templateElement.value.cooked).toBe(`"`); + }); + }); + }); }); diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index a44bb4952b5c..c371a6e9e7ac 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -926,58 +926,4 @@ 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 4d5d4133cb7da7c422ff19d56afabc495c9eb298 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 2 Aug 2025 18:04:19 +0900 Subject: [PATCH 22/27] feat: change validate logic --- packages/typescript-estree/src/convert.ts | 84 +++++++++++++++-------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 38fe026632ea..05d370ee2a80 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -402,35 +402,65 @@ 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; + #isValidEscape(text: string): boolean { + function isHex(hex: string): boolean { + return /^[0-9a-fA-F]+$/.test(hex); + } - const allEscapes = - /\\(u\{[^}]*\}|u[0-9a-fA-F]{0,4}|x[0-9a-fA-F]{0,2}|[^ux])/g; + const validShort = ['f', 'n', 'r', 't', 'v', 'b', '\\', '"', "'", '`', '0']; - let match: RegExpExecArray | null; - while ((match = allEscapes.exec(arg)) != null) { - const escape = match[0]; + for (let index = 0; index < text.length; index++) { + const char = text[index]; + if (char !== '\\') { + continue; + } - if ( - unicode.test(escape) || - (unicodeBracket.test(escape) && - (() => { - const cp = parseInt(escape.match(unicodeBracket)![1], 16); - return cp <= 0x10ffff; - })()) || - hex.test(escape) || - validShort.test(escape) - ) { + const nextChar = text[index + 1]; + if (nextChar == null) { + return false; + } + + if (validShort.includes(nextChar)) { + index += 1; + continue; + } + + // unicode + if (nextChar === 'u') { + if (text[index + 2] === '{') { + const closingBraceIndex = text.indexOf('}', index + 3); + if (closingBraceIndex === -1) { + return false; + } + + const hex = text.slice(index + 3, closingBraceIndex); + if (!isHex(hex) || hex.length === 0 || hex.length > 6) { + return false; + } + index += closingBraceIndex; + continue; + } else { + const hex = text.slice(index + 2, index + 6); + if (!isHex(hex) || hex.length !== 4) { + return false; + } + index += 5; + continue; + } + } + + // hex + if (nextChar === 'x') { + const hex = text.slice(index + 2, index + 4); + if (!isHex(hex) || hex.length !== 2) { + return false; + } + index += 3; continue; } return false; } - return true; } @@ -1923,9 +1953,9 @@ export class Converter { tail: true, value: { cooked: - this.#isValidEscape(node.text) && this.#isInTaggedTemplate - ? node.text - : null, + this.#isInTaggedTemplate && !this.#isValidEscape(node.text) + ? null + : node.text, raw: this.ast.text.slice( node.getStart(this.ast) + 1, node.end - 1, @@ -1987,9 +2017,9 @@ export class Converter { tail, value: { cooked: - this.#isValidEscape(node.text) && this.#isInTaggedTemplate - ? node.text - : null, + this.#isInTaggedTemplate && !this.#isValidEscape(node.text) + ? null + : node.text, raw: this.ast.text.slice( node.getStart(this.ast) + 1, node.end - (tail ? 1 : 2), From 5e6e6b7f7585afadebd8c4dd69254b507b52f7d8 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 2 Aug 2025 18:04:41 +0900 Subject: [PATCH 23/27] fix: (tmp) process cooked is null --- .../src/rules/plugin-test-formatting.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts index 352362e7d7f0..437512560b1b 100644 --- a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts +++ b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts @@ -277,20 +277,12 @@ export default createRule({ isErrorTest: boolean, isNoFormatTagged = false, ): void { - function isNull(value: unknown): value is null { - return value == null; - } - if (literal.quasis.length > 1) { // ignore template literals with ${expressions} for simplicity return; } - const text = literal.quasis[0].value.cooked; - - if (isNull(text)) { - return; - } + const text = literal.quasis[0].value.cooked ?? ''; if (literal.loc.end.line === literal.loc.start.line) { // don't use template strings for single line tests From a05152591eb8243ac0a4ae2f375245f1581b2e16 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 2 Aug 2025 23:00:11 +0900 Subject: [PATCH 24/27] test: fix lint error on convert test --- packages/typescript-estree/tests/lib/convert.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/typescript-estree/tests/lib/convert.test.ts b/packages/typescript-estree/tests/lib/convert.test.ts index b7990a83acec..ed4cb3313457 100644 --- a/packages/typescript-estree/tests/lib/convert.test.ts +++ b/packages/typescript-estree/tests/lib/convert.test.ts @@ -494,6 +494,7 @@ describe('convert', () => { }); it('should not set cooked to null for untagged template literals', () => { + // eslint-disable-next-line no-useless-escape const code = `const foo = \`\c1\``; const result = convertCode(code); const converter = new Converter(result); @@ -512,6 +513,7 @@ describe('convert', () => { } const templateElement = init.quasis[0]; + // eslint-disable-next-line no-useless-escape expect(templateElement.value.cooked).toBe(`\c1`); }); From a7311e64015d019523c9b9820e84900305dea1ae Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 2 Aug 2025 23:16:28 +0900 Subject: [PATCH 25/27] fix: add case for $ --- packages/typescript-estree/src/convert.ts | 15 ++++++++++++++- .../typescript-estree/tests/lib/convert.test.ts | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 05d370ee2a80..a368d878e151 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -407,7 +407,20 @@ export class Converter { return /^[0-9a-fA-F]+$/.test(hex); } - const validShort = ['f', 'n', 'r', 't', 'v', 'b', '\\', '"', "'", '`', '0']; + const validShort = [ + 'f', + 'n', + 'r', + 't', + 'v', + 'b', + '\\', + '"', + "'", + '`', + '0', + '$', + ]; for (let index = 0; index < text.length; index++) { const char = text[index]; diff --git a/packages/typescript-estree/tests/lib/convert.test.ts b/packages/typescript-estree/tests/lib/convert.test.ts index ed4cb3313457..4ff0301ddf73 100644 --- a/packages/typescript-estree/tests/lib/convert.test.ts +++ b/packages/typescript-estree/tests/lib/convert.test.ts @@ -476,14 +476,14 @@ describe('convert', () => { }); it('should set cooked to null for mixed valid and invalid escape sequences', () => { - const code = `String.raw\`\n${invalidEscapeSequences}\t\``; + const code = `String.raw\`\n${invalidEscapeSequences}\\\t\${}\``; const templateElement = getTemplateElement(code); expect(templateElement.value.cooked).toBeNull(); }); it('should not set cooked to null for text without invalid escape sequences', () => { - const code = `String.raw\`foo\n\u1111\t + const code = `String.raw\`foo\n\\\u1111\t bar baz\``; const templateElement = getTemplateElement(code); From 8a67d0508330d237f2aadda6667a2b1e5df99f75 Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sat, 2 Aug 2025 23:16:48 +0900 Subject: [PATCH 26/27] refactor: ignore with conditional statement --- .../src/rules/plugin-test-formatting.ts | 6 +++++- packages/eslint-plugin/src/rules/no-unsafe-assignment.ts | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts index 437512560b1b..78eef0741c86 100644 --- a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts +++ b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts @@ -282,7 +282,11 @@ export default createRule({ return; } - const text = literal.quasis[0].value.cooked ?? ''; + const text = literal.quasis[0].value.cooked; + + if (text == null) { + return; + } if (literal.loc.end.line === literal.loc.start.line) { // don't use template strings for single line tests diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index eb7cdeb99e4c..b7f55ecabad2 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -172,10 +172,6 @@ export default createRule({ senderType: ts.Type, senderNode: ts.Node, ): boolean { - function isNull(value: unknown): value is null { - return value == null; - } - const properties = new Map( senderType .getProperties() @@ -205,7 +201,7 @@ export default createRule({ receiverProperty.key.quasis.length === 1 ) { const cooked = receiverProperty.key.quasis[0].value.cooked; - if (isNull(cooked)) { + if (cooked == null) { continue; } key = cooked; From b85dda1e83078a2e94952f33072f924790038b3e Mon Sep 17 00:00:00 2001 From: nayounsang Date: Sun, 3 Aug 2025 22:48:04 +0900 Subject: [PATCH 27/27] fix: enhance test coverage and remove code for nextChar is null --- packages/typescript-estree/src/convert.ts | 3 -- .../tests/lib/convert.test.ts | 41 ++++++++----------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index a368d878e151..55c5a036c37f 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -429,9 +429,6 @@ export class Converter { } const nextChar = text[index + 1]; - if (nextChar == null) { - return false; - } if (validShort.includes(nextChar)) { index += 1; diff --git a/packages/typescript-estree/tests/lib/convert.test.ts b/packages/typescript-estree/tests/lib/convert.test.ts index 4ff0301ddf73..ce54f0222436 100644 --- a/packages/typescript-estree/tests/lib/convert.test.ts +++ b/packages/typescript-estree/tests/lib/convert.test.ts @@ -466,24 +466,24 @@ describe('convert', () => { return expression.quasi.quasis[0]; }; - const invalidEscapeSequences = String.raw`\uXXXX`; + const invalidEscapeSequences = [String.raw`\uXXXX`, String.raw`\xQW`]; it('should set cooked to null for invalid escape sequences in tagged template literals', () => { - const code = `String.raw\`${invalidEscapeSequences}\``; + const code = `tag\`${invalidEscapeSequences[0]}${invalidEscapeSequences[1]}\``; const templateElement = getTemplateElement(code); expect(templateElement.value.cooked).toBeNull(); }); it('should set cooked to null for mixed valid and invalid escape sequences', () => { - const code = `String.raw\`\n${invalidEscapeSequences}\\\t\${}\``; + const code = `tag\`\n${invalidEscapeSequences[0]}\u{1111}\t\${}${invalidEscapeSequences[1]}\``; const templateElement = getTemplateElement(code); expect(templateElement.value.cooked).toBeNull(); }); it('should not set cooked to null for text without invalid escape sequences', () => { - const code = `String.raw\`foo\n\\\u1111\t + const code = `tag\`foo\n\\\u1111\t bar baz\``; const templateElement = getTemplateElement(code); @@ -494,8 +494,7 @@ describe('convert', () => { }); it('should not set cooked to null for untagged template literals', () => { - // eslint-disable-next-line no-useless-escape - const code = `const foo = \`\c1\``; + const code = `const foo = \`${invalidEscapeSequences[0]}\``; const result = convertCode(code); const converter = new Converter(result); const program = converter.convertProgram(); @@ -513,15 +512,14 @@ describe('convert', () => { } const templateElement = init.quasis[0]; - // eslint-disable-next-line no-useless-escape - expect(templateElement.value.cooked).toBe(`\c1`); + expect(templateElement.value.cooked).toBe(`\\uXXXX`); }); describe('validate escape sequences', () => { it('should return false for invalid unicode', () => { const invalidUnicodes = [String.raw`\uXXXX`, String.raw`\u12`]; const codes = invalidUnicodes.map( - invalidUnicode => `String.raw\`${invalidUnicode}\``, + invalidUnicode => `tag\`${invalidUnicode}\``, ); const templateElements = codes.map(code => getTemplateElement(code)); @@ -532,7 +530,7 @@ describe('convert', () => { it('should return false for invalid unicode with braces', () => { const invalidUnicodes = [String.raw`\u{123`, String.raw`\u{12345678}`]; const codes = invalidUnicodes.map( - invalidUnicode => `String.raw\`${invalidUnicode}\``, + invalidUnicode => `tag\`${invalidUnicode}\``, ); const templateElements = codes.map(code => getTemplateElement(code)); @@ -543,20 +541,15 @@ describe('convert', () => { it('should return true for valid unicode', () => { // 002E is . const validUnicodes = [String.raw`\u{002E}`, String.raw`\u002E`]; - const codes = validUnicodes.map( - validUnicode => `String.raw\`${validUnicode}\``, - ); - const templateElements = codes.map(code => getTemplateElement(code)); + const code = `tag\`${validUnicodes[0]}${validUnicodes[1]}\``; + const templateElement = getTemplateElement(code); - expect(templateElements[0].value.cooked).toBe('.'); - expect(templateElements[1].value.cooked).toBe('.'); + expect(templateElement.value.cooked).toBe('..'); }); it('should return false for invalid hex', () => { const invalidHexes = [String.raw`\x1`, String.raw`\xZX`]; - const codes = invalidHexes.map( - invalidHex => `String.raw\`${invalidHex}\``, - ); + const codes = invalidHexes.map(invalidHex => `tag\`${invalidHex}\``); const templateElements = codes.map(code => getTemplateElement(code)); expect(templateElements[0].value.cooked).toBeNull(); @@ -565,7 +558,7 @@ describe('convert', () => { it('should return true for valid hex', () => { const validHex = String.raw`\x2E`; - const code = `String.raw\`${validHex}\``; + const code = `tag\`${validHex}\``; const templateElement = getTemplateElement(code); expect(templateElement.value.cooked).toBe('.'); @@ -573,18 +566,18 @@ describe('convert', () => { it('should return false for invalid short', () => { const invalidShort = String.raw`\1`; - const code = `String.raw\`${invalidShort}\``; + const code = `tag\`${invalidShort}\``; const templateElement = getTemplateElement(code); expect(templateElement.value.cooked).toBeNull(); }); it('should return true for valid short', () => { - const validShort = String.raw`\"`; - const code = `String.raw\`${validShort}\``; + const validShort = String.raw`\"\'`; + const code = `tag\`${validShort}\``; const templateElement = getTemplateElement(code); - expect(templateElement.value.cooked).toBe(`"`); + expect(templateElement.value.cooked).toBe(`"'`); }); }); });