From cffc99d437a9659a352b9afb41f119dc08d03363 Mon Sep 17 00:00:00 2001 From: fisker Date: Tue, 16 Dec 2025 23:38:03 +0800 Subject: [PATCH 01/34] Minor refactor --- src/transform-node.ts | 12 +++--------- src/transform-template-binding.ts | 8 ++++---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index e852f541..63e6ba04 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -44,20 +44,14 @@ type NodeTransformOptions = { }; class Transformer extends Source { - #node; #text; - constructor(ast: angular.AST | undefined, text: string) { + constructor(text: string) { super(text); - this.#node = ast; this.#text = text; } - get node() { - return this.#transform(this.#node!); - } - - transformNode(node: angular.AST) { + transform(node: angular.AST) { return this.#transformNode(node) as T & LocationInformation; } @@ -636,7 +630,7 @@ type SupportedNodes = | angular.TaggedTemplateLiteral | angular.ParenthesizedExpression; function transform(node: SupportedNodes, text: string): NGNode { - return new Transformer(node, text).node; + return new Transformer(text).transform(node); } export { transform, Transformer }; diff --git a/src/transform-template-binding.ts b/src/transform-template-binding.ts index b0e89acd..509a9174 100644 --- a/src/transform-template-binding.ts +++ b/src/transform-template-binding.ts @@ -30,12 +30,12 @@ function isVariableBinding( return templateBinding instanceof NGVariableBinding; } -class Transformer extends NodeTransformer { +class TemplateBindingTransformer extends NodeTransformer { #rawTemplateBindings; #text; constructor(rawTemplateBindings: angular.TemplateBinding[], text: string) { - super(undefined, text); + super(text); this.#rawTemplateBindings = rawTemplateBindings; this.#text = text; @@ -61,7 +61,7 @@ class Transformer extends NodeTransformer { } #transform(node: angular.AST) { - return this.transformNode(node) as T; + return super.transform(node) as T; } #removePrefix(string: string) { @@ -281,7 +281,7 @@ function transform( rawTemplateBindings: angular.TemplateBinding[], text: string, ) { - return new Transformer(rawTemplateBindings, text).expressions; + return new TemplateBindingTransformer(rawTemplateBindings, text).expressions; } export { transform }; From 5dd042db173077ce2c636507f04e2921b8c67fb1 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 00:00:10 +0800 Subject: [PATCH 02/34] Swap method name --- src/transform-node.ts | 58 +++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 63e6ba04..692feaa1 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -52,7 +52,7 @@ class Transformer extends Source { } transform(node: angular.AST) { - return this.#transformNode(node) as T & LocationInformation; + return this.#transform(node) as T & LocationInformation; } #create( @@ -86,7 +86,7 @@ class Transformer extends Source { ) { return property; } - const object = this.#transform(receiver); + const object = this.#transformAndCast(receiver); const isOptionalObject = isOptionalObjectOrCallee(object); const commonProps = { @@ -129,14 +129,14 @@ class Transformer extends Source { ); } - #transform( + #transformAndCast( node: angular.AST, options?: NodeTransformOptions, ) { - return this.#transformNode(node, options) as T & LocationInformation; + return this.#transform(node, options) as T & LocationInformation; } - #transformNode(node: angular.AST, options?: NodeTransformOptions): NGNode { + #transform(node: angular.AST, options?: NodeTransformOptions): NGNode { const { isInParentParens } = { isInParentParens: false, ...options, @@ -158,7 +158,7 @@ class Transformer extends Source { { type: 'UnaryExpression', prefix: true, - argument: this.#transform(node.expr), + argument: this.#transformAndCast(node.expr), operator: node.operator as '-' | '+', ...node.sourceSpan, }, @@ -172,8 +172,8 @@ class Transformer extends Source { operation: operator, right: originalRight, } = node; - const left = this.#transform(originalLeft); - const right = this.#transform(originalRight); + const left = this.#transformAndCast(originalLeft); + const right = this.#transformAndCast(originalRight); const start = getOuterStart(left); const end = getOuterEnd(right); const properties = { left, right, start, end }; @@ -214,7 +214,7 @@ class Transformer extends Source { if (node instanceof angular.BindingPipe) { const { exp: expressionNode, name, args: originalArguments } = node; - const left = this.#transform(expressionNode); + const left = this.#transformAndCast(expressionNode); const start = getOuterStart(left); const leftEnd = getOuterEnd(left); const rightStart = this.getCharacterIndex( @@ -228,7 +228,7 @@ class Transformer extends Source { end: rightStart + name.length, }); const argumentNodes = originalArguments.map((node) => - this.#transform(node), + this.#transformAndCast(node), ); return this.#create( { @@ -251,7 +251,7 @@ class Transformer extends Source { { type: 'NGChainedExpression', expressions: node.expressions.map((node) => - this.#transform(node), + this.#transformAndCast(node), ), ...node.sourceSpan, }, @@ -261,9 +261,9 @@ class Transformer extends Source { if (node instanceof angular.Conditional) { const { condition, trueExp, falseExp } = node; - const test = this.#transform(condition); - const consequent = this.#transform(trueExp); - const alternate = this.#transform(falseExp); + const test = this.#transformAndCast(condition); + const consequent = this.#transformAndCast(trueExp); + const alternate = this.#transformAndCast(falseExp); return this.#create( { type: 'ConditionalExpression', @@ -296,7 +296,7 @@ class Transformer extends Source { { type: 'ArrayExpression', elements: node.expressions.map((node) => - this.#transform(node), + this.#transformAndCast(node), ), ...node.sourceSpan, }, @@ -307,7 +307,7 @@ class Transformer extends Source { if (node instanceof angular.LiteralMap) { const { keys, values } = node; const tValues = values.map((value) => - this.#transform(value), + this.#transformAndCast(value), ); const tProperties = keys.map(({ key, quoted }, index) => { const tValue = tValues[index]; @@ -415,14 +415,14 @@ class Transformer extends Source { const tArgs = args.length === 1 ? [ - this.#transform(args[0], { + this.#transformAndCast(args[0], { isInParentParens: true, }), ] : (args as angular.AST[]).map((node) => - this.#transform(node), + this.#transformAndCast(node), ); - const tReceiver = this.#transform(receiver!); + const tReceiver = this.#transformAndCast(receiver!); const isOptionalReceiver = isOptionalObjectOrCallee(tReceiver); const nodeType = isOptionalType || isOptionalReceiver @@ -444,7 +444,9 @@ class Transformer extends Source { } if (node instanceof angular.NonNullAssert) { - const expression = this.#transform(node.expression); + const expression = this.#transformAndCast( + node.expression, + ); return this.#create( { type: 'TSNonNullExpression', @@ -493,7 +495,9 @@ class Transformer extends Source { start = index; } - const expression = this.#transform(node.expression); + const expression = this.#transformAndCast( + node.expression, + ); return this.#create( { @@ -514,7 +518,7 @@ class Transformer extends Source { ) { return this.#transformReceiverAndName( node, - this.#transform(node.key), + this.#transformAndCast(node.key), { computed: true, optional: node instanceof angular.SafeKeyedRead, @@ -548,8 +552,8 @@ class Transformer extends Source { if (node instanceof angular.TaggedTemplateLiteral) { return this.#create({ type: 'TaggedTemplateExpression', - tag: this.#transform(node.tag) as babel.Expression, - quasi: this.#transform(node.template) as babel.TemplateLiteral, + tag: this.#transformAndCast(node.tag) as babel.Expression, + quasi: this.#transformAndCast(node.template) as babel.TemplateLiteral, ...node.sourceSpan, }); } @@ -560,10 +564,10 @@ class Transformer extends Source { return this.#create({ type: 'TemplateLiteral', quasis: elements.map((element) => - this.#transform(element, { parent: node }), + this.#transformAndCast(element, { parent: node }), ), expressions: expressions.map((expression) => - this.#transform(expression), + this.#transformAndCast(expression), ), ...node.sourceSpan, }); @@ -595,7 +599,7 @@ class Transformer extends Source { } if (node instanceof angular.ParenthesizedExpression) { - return this.#transformNode(node.expression); + return this.#transform(node.expression); } /* c8 ignore next */ From 34aec4e927844a80e835f1345ff8d98f275a52af Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 00:06:51 +0800 Subject: [PATCH 03/34] Swap method name --- src/transform-node.ts | 60 ++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 692feaa1..161bbf35 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -52,7 +52,7 @@ class Transformer extends Source { } transform(node: angular.AST) { - return this.#transform(node) as T & LocationInformation; + return this.#transformNode(node) as T & LocationInformation; } #create( @@ -86,7 +86,7 @@ class Transformer extends Source { ) { return property; } - const object = this.#transformAndCast(receiver); + const object = this.#transform(receiver); const isOptionalObject = isOptionalObjectOrCallee(object); const commonProps = { @@ -129,14 +129,14 @@ class Transformer extends Source { ); } - #transformAndCast( + #transform( node: angular.AST, options?: NodeTransformOptions, ) { - return this.#transform(node, options) as T & LocationInformation; + return this.#transformNode(node, options) as T & LocationInformation; } - #transform(node: angular.AST, options?: NodeTransformOptions): NGNode { + #transformNode(node: angular.AST, options?: NodeTransformOptions): NGNode { const { isInParentParens } = { isInParentParens: false, ...options, @@ -150,7 +150,7 @@ class Transformer extends Source { throw new Error("Unexpected 'Interpolation'"); } - return this.#transform(expressions[0]); + return this.#transformNode(expressions[0]); } if (node instanceof angular.Unary) { @@ -158,7 +158,7 @@ class Transformer extends Source { { type: 'UnaryExpression', prefix: true, - argument: this.#transformAndCast(node.expr), + argument: this.#transform(node.expr), operator: node.operator as '-' | '+', ...node.sourceSpan, }, @@ -172,8 +172,8 @@ class Transformer extends Source { operation: operator, right: originalRight, } = node; - const left = this.#transformAndCast(originalLeft); - const right = this.#transformAndCast(originalRight); + const left = this.#transform(originalLeft); + const right = this.#transform(originalRight); const start = getOuterStart(left); const end = getOuterEnd(right); const properties = { left, right, start, end }; @@ -214,7 +214,7 @@ class Transformer extends Source { if (node instanceof angular.BindingPipe) { const { exp: expressionNode, name, args: originalArguments } = node; - const left = this.#transformAndCast(expressionNode); + const left = this.#transform(expressionNode); const start = getOuterStart(left); const leftEnd = getOuterEnd(left); const rightStart = this.getCharacterIndex( @@ -228,7 +228,7 @@ class Transformer extends Source { end: rightStart + name.length, }); const argumentNodes = originalArguments.map((node) => - this.#transformAndCast(node), + this.#transform(node), ); return this.#create( { @@ -251,7 +251,7 @@ class Transformer extends Source { { type: 'NGChainedExpression', expressions: node.expressions.map((node) => - this.#transformAndCast(node), + this.#transform(node), ), ...node.sourceSpan, }, @@ -261,9 +261,9 @@ class Transformer extends Source { if (node instanceof angular.Conditional) { const { condition, trueExp, falseExp } = node; - const test = this.#transformAndCast(condition); - const consequent = this.#transformAndCast(trueExp); - const alternate = this.#transformAndCast(falseExp); + const test = this.#transform(condition); + const consequent = this.#transform(trueExp); + const alternate = this.#transform(falseExp); return this.#create( { type: 'ConditionalExpression', @@ -296,7 +296,7 @@ class Transformer extends Source { { type: 'ArrayExpression', elements: node.expressions.map((node) => - this.#transformAndCast(node), + this.#transform(node), ), ...node.sourceSpan, }, @@ -307,7 +307,7 @@ class Transformer extends Source { if (node instanceof angular.LiteralMap) { const { keys, values } = node; const tValues = values.map((value) => - this.#transformAndCast(value), + this.#transform(value), ); const tProperties = keys.map(({ key, quoted }, index) => { const tValue = tValues[index]; @@ -415,14 +415,14 @@ class Transformer extends Source { const tArgs = args.length === 1 ? [ - this.#transformAndCast(args[0], { + this.#transform(args[0], { isInParentParens: true, }), ] : (args as angular.AST[]).map((node) => - this.#transformAndCast(node), + this.#transform(node), ); - const tReceiver = this.#transformAndCast(receiver!); + const tReceiver = this.#transform(receiver!); const isOptionalReceiver = isOptionalObjectOrCallee(tReceiver); const nodeType = isOptionalType || isOptionalReceiver @@ -444,9 +444,7 @@ class Transformer extends Source { } if (node instanceof angular.NonNullAssert) { - const expression = this.#transformAndCast( - node.expression, - ); + const expression = this.#transform(node.expression); return this.#create( { type: 'TSNonNullExpression', @@ -495,9 +493,7 @@ class Transformer extends Source { start = index; } - const expression = this.#transformAndCast( - node.expression, - ); + const expression = this.#transform(node.expression); return this.#create( { @@ -518,7 +514,7 @@ class Transformer extends Source { ) { return this.#transformReceiverAndName( node, - this.#transformAndCast(node.key), + this.#transform(node.key), { computed: true, optional: node instanceof angular.SafeKeyedRead, @@ -552,8 +548,8 @@ class Transformer extends Source { if (node instanceof angular.TaggedTemplateLiteral) { return this.#create({ type: 'TaggedTemplateExpression', - tag: this.#transformAndCast(node.tag) as babel.Expression, - quasi: this.#transformAndCast(node.template) as babel.TemplateLiteral, + tag: this.#transform(node.tag), + quasi: this.#transform(node.template), ...node.sourceSpan, }); } @@ -564,10 +560,10 @@ class Transformer extends Source { return this.#create({ type: 'TemplateLiteral', quasis: elements.map((element) => - this.#transformAndCast(element, { parent: node }), + this.#transform(element, { parent: node }), ), expressions: expressions.map((expression) => - this.#transformAndCast(expression), + this.#transform(expression), ), ...node.sourceSpan, }); @@ -599,7 +595,7 @@ class Transformer extends Source { } if (node instanceof angular.ParenthesizedExpression) { - return this.#transform(node.expression); + return this.#transformNode(node.expression); } /* c8 ignore next */ From ade69f004bcf35aec6cbd12c95cf2c87b64afed4 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 00:11:02 +0800 Subject: [PATCH 04/34] Remove `#transform` --- src/transform-node.ts | 62 ++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 161bbf35..86496b7c 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -51,8 +51,11 @@ class Transformer extends Source { this.#text = text; } - transform(node: angular.AST) { - return this.#transformNode(node) as T & LocationInformation; + transform( + node: angular.AST, + options?: NodeTransformOptions, + ): T & LocationInformation { + return this.#transformNode(node, options) as T & LocationInformation; } #create( @@ -86,7 +89,7 @@ class Transformer extends Source { ) { return property; } - const object = this.#transform(receiver); + const object = this.transform(receiver); const isOptionalObject = isOptionalObjectOrCallee(object); const commonProps = { @@ -129,13 +132,6 @@ class Transformer extends Source { ); } - #transform( - node: angular.AST, - options?: NodeTransformOptions, - ) { - return this.#transformNode(node, options) as T & LocationInformation; - } - #transformNode(node: angular.AST, options?: NodeTransformOptions): NGNode { const { isInParentParens } = { isInParentParens: false, @@ -150,7 +146,7 @@ class Transformer extends Source { throw new Error("Unexpected 'Interpolation'"); } - return this.#transformNode(expressions[0]); + return this.transform(expressions[0]); } if (node instanceof angular.Unary) { @@ -158,7 +154,7 @@ class Transformer extends Source { { type: 'UnaryExpression', prefix: true, - argument: this.#transform(node.expr), + argument: this.transform(node.expr), operator: node.operator as '-' | '+', ...node.sourceSpan, }, @@ -172,8 +168,8 @@ class Transformer extends Source { operation: operator, right: originalRight, } = node; - const left = this.#transform(originalLeft); - const right = this.#transform(originalRight); + const left = this.transform(originalLeft); + const right = this.transform(originalRight); const start = getOuterStart(left); const end = getOuterEnd(right); const properties = { left, right, start, end }; @@ -214,7 +210,7 @@ class Transformer extends Source { if (node instanceof angular.BindingPipe) { const { exp: expressionNode, name, args: originalArguments } = node; - const left = this.#transform(expressionNode); + const left = this.transform(expressionNode); const start = getOuterStart(left); const leftEnd = getOuterEnd(left); const rightStart = this.getCharacterIndex( @@ -228,7 +224,7 @@ class Transformer extends Source { end: rightStart + name.length, }); const argumentNodes = originalArguments.map((node) => - this.#transform(node), + this.transform(node), ); return this.#create( { @@ -251,7 +247,7 @@ class Transformer extends Source { { type: 'NGChainedExpression', expressions: node.expressions.map((node) => - this.#transform(node), + this.transform(node), ), ...node.sourceSpan, }, @@ -261,9 +257,9 @@ class Transformer extends Source { if (node instanceof angular.Conditional) { const { condition, trueExp, falseExp } = node; - const test = this.#transform(condition); - const consequent = this.#transform(trueExp); - const alternate = this.#transform(falseExp); + const test = this.transform(condition); + const consequent = this.transform(trueExp); + const alternate = this.transform(falseExp); return this.#create( { type: 'ConditionalExpression', @@ -296,7 +292,7 @@ class Transformer extends Source { { type: 'ArrayExpression', elements: node.expressions.map((node) => - this.#transform(node), + this.transform(node), ), ...node.sourceSpan, }, @@ -307,7 +303,7 @@ class Transformer extends Source { if (node instanceof angular.LiteralMap) { const { keys, values } = node; const tValues = values.map((value) => - this.#transform(value), + this.transform(value), ); const tProperties = keys.map(({ key, quoted }, index) => { const tValue = tValues[index]; @@ -415,14 +411,14 @@ class Transformer extends Source { const tArgs = args.length === 1 ? [ - this.#transform(args[0], { + this.transform(args[0], { isInParentParens: true, }), ] : (args as angular.AST[]).map((node) => - this.#transform(node), + this.transform(node), ); - const tReceiver = this.#transform(receiver!); + const tReceiver = this.transform(receiver!); const isOptionalReceiver = isOptionalObjectOrCallee(tReceiver); const nodeType = isOptionalType || isOptionalReceiver @@ -444,7 +440,7 @@ class Transformer extends Source { } if (node instanceof angular.NonNullAssert) { - const expression = this.#transform(node.expression); + const expression = this.transform(node.expression); return this.#create( { type: 'TSNonNullExpression', @@ -493,7 +489,7 @@ class Transformer extends Source { start = index; } - const expression = this.#transform(node.expression); + const expression = this.transform(node.expression); return this.#create( { @@ -514,7 +510,7 @@ class Transformer extends Source { ) { return this.#transformReceiverAndName( node, - this.#transform(node.key), + this.transform(node.key), { computed: true, optional: node instanceof angular.SafeKeyedRead, @@ -548,8 +544,8 @@ class Transformer extends Source { if (node instanceof angular.TaggedTemplateLiteral) { return this.#create({ type: 'TaggedTemplateExpression', - tag: this.#transform(node.tag), - quasi: this.#transform(node.template), + tag: this.transform(node.tag), + quasi: this.transform(node.template), ...node.sourceSpan, }); } @@ -560,10 +556,10 @@ class Transformer extends Source { return this.#create({ type: 'TemplateLiteral', quasis: elements.map((element) => - this.#transform(element, { parent: node }), + this.transform(element, { parent: node }), ), expressions: expressions.map((expression) => - this.#transform(expression), + this.transform(expression), ), ...node.sourceSpan, }); @@ -595,7 +591,7 @@ class Transformer extends Source { } if (node instanceof angular.ParenthesizedExpression) { - return this.#transformNode(node.expression); + return this.transform(node.expression); } /* c8 ignore next */ From 7bcd379dc510a8ece99f845f5da92824a09c5bef Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 00:13:23 +0800 Subject: [PATCH 05/34] Minor tweak --- src/transform-node.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 86496b7c..e40b8a96 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -54,8 +54,8 @@ class Transformer extends Source { transform( node: angular.AST, options?: NodeTransformOptions, - ): T & LocationInformation { - return this.#transformNode(node, options) as T & LocationInformation; + ) { + return this.#transform(node, options) as T & LocationInformation; } #create( @@ -132,7 +132,10 @@ class Transformer extends Source { ); } - #transformNode(node: angular.AST, options?: NodeTransformOptions): NGNode { + #transform( + node: angular.AST, + options?: NodeTransformOptions, + ): NGNode { const { isInParentParens } = { isInParentParens: false, ...options, From c9752454a37dd832453b839ca68a86b447858f6e Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 00:24:12 +0800 Subject: [PATCH 06/34] Use `ancestors` instead of parent --- src/transform-node.ts | 66 +++++++++++++++++++++---------- tests/transform.test.ts | 88 +---------------------------------------- 2 files changed, 47 insertions(+), 107 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index e40b8a96..541e30a6 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -40,7 +40,7 @@ function isImplicitThis(node: angular.AST, text: string): boolean { type NodeTransformOptions = { isInParentParens?: boolean; - parent?: angular.AST; + ancestors: angular.AST[]; }; class Transformer extends Source { @@ -55,7 +55,8 @@ class Transformer extends Source { node: angular.AST, options?: NodeTransformOptions, ) { - return this.#transform(node, options) as T & LocationInformation; + return this.#transform(node, options ?? { ancestors: [] }) as T & + LocationInformation; } #create( @@ -132,10 +133,13 @@ class Transformer extends Source { ); } - #transform( - node: angular.AST, - options?: NodeTransformOptions, - ): NGNode { + #transform(node: angular.AST, options: NodeTransformOptions): NGNode { + const ancestors = options.ancestors; + const childTransformOptions = { + ...options, + ancestors: [...ancestors, node], + }; + const { isInParentParens } = { isInParentParens: false, ...options, @@ -149,7 +153,7 @@ class Transformer extends Source { throw new Error("Unexpected 'Interpolation'"); } - return this.transform(expressions[0]); + return this.transform(expressions[0], childTransformOptions); } if (node instanceof angular.Unary) { @@ -171,8 +175,14 @@ class Transformer extends Source { operation: operator, right: originalRight, } = node; - const left = this.transform(originalLeft); - const right = this.transform(originalRight); + const left = this.transform( + originalLeft, + childTransformOptions, + ); + const right = this.transform( + originalRight, + childTransformOptions, + ); const start = getOuterStart(left); const end = getOuterEnd(right); const properties = { left, right, start, end }; @@ -213,7 +223,10 @@ class Transformer extends Source { if (node instanceof angular.BindingPipe) { const { exp: expressionNode, name, args: originalArguments } = node; - const left = this.transform(expressionNode); + const left = this.transform( + expressionNode, + childTransformOptions, + ); const start = getOuterStart(left); const leftEnd = getOuterEnd(left); const rightStart = this.getCharacterIndex( @@ -227,7 +240,7 @@ class Transformer extends Source { end: rightStart + name.length, }); const argumentNodes = originalArguments.map((node) => - this.transform(node), + this.transform(node, childTransformOptions), ); return this.#create( { @@ -250,7 +263,7 @@ class Transformer extends Source { { type: 'NGChainedExpression', expressions: node.expressions.map((node) => - this.transform(node), + this.transform(node, childTransformOptions), ), ...node.sourceSpan, }, @@ -260,9 +273,18 @@ class Transformer extends Source { if (node instanceof angular.Conditional) { const { condition, trueExp, falseExp } = node; - const test = this.transform(condition); - const consequent = this.transform(trueExp); - const alternate = this.transform(falseExp); + const test = this.transform( + condition, + childTransformOptions, + ); + const consequent = this.transform( + trueExp, + childTransformOptions, + ); + const alternate = this.transform( + falseExp, + childTransformOptions, + ); return this.#create( { type: 'ConditionalExpression', @@ -295,7 +317,7 @@ class Transformer extends Source { { type: 'ArrayExpression', elements: node.expressions.map((node) => - this.transform(node), + this.transform(node, childTransformOptions), ), ...node.sourceSpan, }, @@ -415,11 +437,12 @@ class Transformer extends Source { args.length === 1 ? [ this.transform(args[0], { + ...childTransformOptions, isInParentParens: true, }), ] : (args as angular.AST[]).map((node) => - this.transform(node), + this.transform(node, childTransformOptions), ); const tReceiver = this.transform(receiver!); const isOptionalReceiver = isOptionalObjectOrCallee(tReceiver); @@ -559,17 +582,18 @@ class Transformer extends Source { return this.#create({ type: 'TemplateLiteral', quasis: elements.map((element) => - this.transform(element, { parent: node }), + this.transform(element, childTransformOptions), ), expressions: expressions.map((expression) => - this.transform(expression), + this.transform(expression, childTransformOptions), ), ...node.sourceSpan, }); } if (node instanceof angular.TemplateLiteralElement) { - const { elements } = options!.parent! as angular.TemplateLiteral; + const [parent] = ancestors; + const { elements } = parent as angular.TemplateLiteral; const elementIndex = elements.indexOf(node); const isFirst = elementIndex === 0; const isLast = elementIndex === elements.length - 1; @@ -594,7 +618,7 @@ class Transformer extends Source { } if (node instanceof angular.ParenthesizedExpression) { - return this.transform(node.expression); + return this.transform(node.expression, childTransformOptions); } /* c8 ignore next */ diff --git a/tests/transform.test.ts b/tests/transform.test.ts index 13334d56..001146f4 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -26,92 +26,8 @@ const PARSE_METHODS = [ ] as const; describe.each` - expectedAngularType | expectedEstreeType | text | parseAction | parseBinding | parseSimpleBinding | parseInterpolationExpression - ${'Binary'} | ${'BinaryExpression'} | ${' 0 - 1 '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'BinaryExpression'} | ${' a ** b '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'BinaryExpression'} | ${' ( ( ( ( a ) ) in ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'LogicalExpression'} | ${' a && b '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'LogicalExpression'} | ${' a ?? b '} | ${true} | ${true} | ${true} | ${true} - ${'Unary'} | ${'UnaryExpression'} | ${' - 1 '} | ${true} | ${true} | ${true} | ${true} - ${'Unary'} | ${'UnaryExpression'} | ${' + 1 '} | ${true} | ${true} | ${true} | ${true} - ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b '} | ${false} | ${true} | ${false} | ${true} - ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b : c '} | ${false} | ${true} | ${false} | ${true} - ${'Chain'} | ${'NGChainedExpression'} | ${' a ; b '} | ${true} | ${false} | ${false} | ${false} - ${'Conditional'} | ${'ConditionalExpression'} | ${' a ? 1 : 2 '} | ${true} | ${true} | ${true} | ${true} - ${'EmptyExpr'} | ${'NGEmptyExpression'} | ${''} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' ( a . b ) ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( a . b )?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' ( a ) ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( a )?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' a ( 1 ) ( 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ( 1 )?.( 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'KeyedRead'} | ${'MemberExpression'} | ${' a [ b ] '} | ${true} | ${true} | ${true} | ${true} - ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. [ b ] '} | ${true} | ${true} | ${true} | ${true} - ${'KeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b [ c ] '} | ${true} | ${true} | ${true} | ${true} - ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b ?. [ c ] '} | ${true} | ${true} | ${true} | ${true} - ${'KeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b () [ c ] '} | ${true} | ${true} | ${true} | ${true} - ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b () ?. [ c ] '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'AssignmentExpression'} | ${' a [ b ] = 1 '} | ${true} | ${true} | ${true} | ${true} - ${'ImplicitReceiver'} | ${'ThisExpression'} | ${' this '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralArray'} | ${'ArrayExpression'} | ${' [ 1 ] '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { "a" : 1 } )'} | ${true} | ${true} | ${true} | ${true} - ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { a : 1 } ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' f ( { a : 1 } ) '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( {a, b: 2} ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' f ( {a, b: 2} ) '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( {a, b} ) '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { a, b} ) '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralPrimitive'} | ${'BooleanLiteral'} | ${' true '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralPrimitive'} | ${'Identifier'} | ${' undefined '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralPrimitive'} | ${'NullLiteral'} | ${' null '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralPrimitive'} | ${'NumericLiteral'} | ${' ( 1 ) '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralPrimitive'} | ${'NumericLiteral'} | ${' 1 '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralPrimitive'} | ${'StringLiteral'} | ${' ( "hello" ) '} | ${true} | ${true} | ${true} | ${true} - ${'RegularExpressionLiteral'} | ${'RegExpLiteral'} | ${' ( ( /\\d+/ ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'RegularExpressionLiteral'} | ${'RegExpLiteral'} | ${' ( ( /\\d+/g ) )'} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' a ( this ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?.( this ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' a ( b) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?.( b) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' a . b ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a . b ?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' a ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b . c ( ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b . c ?. ( ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) . c ( ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) . c ?.( ) '} | ${true} | ${true} | ${true} | ${true} - ${'NonNullAssert'} | ${'TSNonNullExpression'} | ${' x ! '} | ${true} | ${true} | ${true} | ${true} - ${'PrefixNot'} | ${'UnaryExpression'} | ${' ! x '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'Identifier'} | ${' ( ( a ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'Identifier'} | ${' a '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'Identifier'} | ${' a // hello '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'MemberExpression'} | ${' a . b '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'MemberExpression'} | ${' this . a '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'OptionalMemberExpression'} | ${' a ?. b . c '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'OptionalMemberExpression'} | ${' a ?. b ( ) . c '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'OptionalMemberExpression'} | ${' foo?.bar!.bam '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'MemberExpression'} | ${' (foo?.bar)!.bam '} | ${true} | ${true} | ${true} | ${true} - ${'PropertyRead'} | ${'MemberExpression'} | ${' (foo?.bar!).bam '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'AssignmentExpression'} | ${' a . b = 1 '} | ${true} | ${false} | ${false} | ${false} - ${'Binary'} | ${'AssignmentExpression'} | ${' a = 1 '} | ${true} | ${false} | ${false} | ${false} - ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b ?. ( ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafePropertyRead'} | ${'OptionalMemberExpression'} | ${' a ?. b '} | ${true} | ${true} | ${true} | ${true} - ${'TypeofExpression'} | ${'UnaryExpression'} | ${' ( ( typeof {} ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'BinaryExpression'} | ${' typeof {} === "object" '} | ${true} | ${true} | ${true} | ${true} - ${'PrefixNot'} | ${'UnaryExpression'} | ${' ! ( typeof {} === "" ) '} | ${true} | ${true} | ${true} | ${true} - ${'VoidExpression'} | ${'UnaryExpression'} | ${' ( ( void ( ( a() ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ` a ${ b } \\u0063 ` '} | ${true} | ${true} | ${true} | ${true} - ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ( ( ` a ${ b } \\u0063 ` ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ` \\u0063 ` '} | ${true} | ${true} | ${true} | ${true} - ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ( ( ` ` ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' `` '} | ${true} | ${true} | ${true} | ${true} - ${'TaggedTemplateLiteral'} | ${'TaggedTemplateExpression'} | ${' tag ` a ${ b } \\u0063 ` '} | ${true} | ${true} | ${true} | ${true} - ${'TaggedTemplateLiteral'} | ${'TaggedTemplateExpression'} | ${' ( ( ( ( tag ) ) ` a ${ b } \\u0063 ` ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( ( {foo: ` a ${ b } ` } ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( ( {foo: tag ` a ${ b } ` } ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'AssignmentExpression'} | ${' a ??= b '} | ${true} | ${false} | ${false} | ${false} + expectedAngularType | expectedEstreeType | text | parseAction | parseBinding | parseSimpleBinding | parseInterpolationExpression + ${'Binary'} | ${'BinaryExpression'} | ${' ( ( ( ( a ) ) in ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} `('($expectedAngularType -> $expectedEstreeType)', (fields) => { for (const method of PARSE_METHODS) { testSection(method, fields); From a3ad5ab4420a2e599fed6e612e87c8ee6ca8d056 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 00:25:53 +0800 Subject: [PATCH 07/34] `hasParentParens` -> `isInParentParens` --- src/source.ts | 8 +++--- src/transform-node.ts | 60 +++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/source.ts b/src/source.ts index e639fc36..1ec64228 100644 --- a/src/source.ts +++ b/src/source.ts @@ -25,7 +25,7 @@ export class Source { transformSpan( span: RawNGSpan, - { stripSpaces = false, hasParentParens = false } = {}, + { stripSpaces = false, isInParentParens = false } = {}, ): LocationInformation { if (!stripSpaces) { return sourceSpanToLocationInformation(span); @@ -34,7 +34,7 @@ export class Source { const { outerSpan, innerSpan, hasParens } = fitSpans( span, this.text, - hasParentParens, + isInParentParens, ); const locationInformation = sourceSpanToLocationInformation(innerSpan); if (hasParens) { @@ -51,7 +51,7 @@ export class Source { createNode( properties: Partial & { type: T['type'] } & RawNGSpan, // istanbul ignore next - { stripSpaces = true, hasParentParens = false } = {}, + { stripSpaces = true, isInParentParens = false } = {}, ) { const { type, start, end } = properties; const node = { @@ -60,7 +60,7 @@ export class Source { { start, end }, { stripSpaces, - hasParentParens, + isInParentParens, }, ), } as T & LocationInformation; diff --git a/src/transform-node.ts b/src/transform-node.ts index 541e30a6..2a1000a9 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -61,9 +61,9 @@ class Transformer extends Source { #create( properties: Partial & { type: T['type'] } & RawNGSpan, - { stripSpaces = true, hasParentParens = false } = {}, + { stripSpaces = true, isInParentParens = false } = {}, ) { - return this.createNode(properties, { stripSpaces, hasParentParens }); + return this.createNode(properties, { stripSpaces, isInParentParens }); } #transformReceiverAndName( @@ -76,11 +76,11 @@ class Transformer extends Source { { computed, optional, - hasParentParens = false, + isInParentParens = false, }: { computed: boolean; optional: boolean; - hasParentParens?: boolean; + isInParentParens?: boolean; }, ) { const { receiver } = node; @@ -107,7 +107,7 @@ class Transformer extends Source { optional: optional || !isOptionalObject, ...commonProps, }, - { hasParentParens }, + { isInParentParens }, ); } @@ -118,7 +118,7 @@ class Transformer extends Source { ...commonProps, computed: true, }, - { hasParentParens }, + { isInParentParens }, ); } @@ -129,7 +129,7 @@ class Transformer extends Source { computed: false, property: property as babel.MemberExpressionNonComputed['property'], }, - { hasParentParens }, + { isInParentParens }, ); } @@ -165,7 +165,7 @@ class Transformer extends Source { operator: node.operator as '-' | '+', ...node.sourceSpan, }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -194,7 +194,7 @@ class Transformer extends Source { type: 'LogicalExpression', operator: operator as babel.LogicalExpression['operator'], }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -207,7 +207,7 @@ class Transformer extends Source { operator: operator as babel.AssignmentExpression['operator'], ...node.sourceSpan, }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -217,7 +217,7 @@ class Transformer extends Source { type: 'BinaryExpression', operator: operator as babel.BinaryExpression['operator'], }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -254,7 +254,7 @@ class Transformer extends Source { argumentNodes.length === 0 ? right : argumentNodes.at(-1)!, ), }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -267,7 +267,7 @@ class Transformer extends Source { ), ...node.sourceSpan, }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -294,21 +294,21 @@ class Transformer extends Source { start: getOuterStart(test), end: getOuterEnd(alternate), }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } if (node instanceof angular.EmptyExpr) { return this.#create( { type: 'NGEmptyExpression', ...node.sourceSpan }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } if (node instanceof angular.ImplicitReceiver) { return this.#create( { type: 'ThisExpression', ...node.sourceSpan }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -321,7 +321,7 @@ class Transformer extends Source { ), ...node.sourceSpan, }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -378,7 +378,7 @@ class Transformer extends Source { properties: tProperties, ...node.sourceSpan, }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -388,27 +388,27 @@ class Transformer extends Source { case 'boolean': return this.#create( { type: 'BooleanLiteral', value, ...node.sourceSpan }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); case 'number': return this.#create( { type: 'NumericLiteral', value, ...node.sourceSpan }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); case 'object': return this.#create( { type: 'NullLiteral', ...node.sourceSpan }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); case 'string': return this.#create( { type: 'StringLiteral', value, ...node.sourceSpan }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); case 'undefined': return this.#create( { type: 'Identifier', name: 'undefined', ...node.sourceSpan }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); /* c8 ignore next 4 */ default: @@ -426,7 +426,7 @@ class Transformer extends Source { flags: node.flags ?? '', ...node.sourceSpan, }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -461,7 +461,7 @@ class Transformer extends Source { start: getOuterStart(tReceiver), end: node.sourceSpan.end, // `)` }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -474,7 +474,7 @@ class Transformer extends Source { start: getOuterStart(expression), end: node.sourceSpan.end, // `!` }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -526,7 +526,7 @@ class Transformer extends Source { start, end: getOuterEnd(expression), }, - { hasParentParens: isInParentParens }, + { isInParentParens: isInParentParens }, ); } @@ -540,7 +540,7 @@ class Transformer extends Source { { computed: true, optional: node instanceof angular.SafeKeyedRead, - hasParentParens: isInParentParens, + isInParentParens: isInParentParens, }, ); } @@ -557,13 +557,13 @@ class Transformer extends Source { ...node.nameSpan, }, isImplicitThis(receiver, this.#text) - ? { hasParentParens: isInParentParens } + ? { isInParentParens: isInParentParens } : {}, ); return this.#transformReceiverAndName(node, tName, { computed: false, optional: node instanceof angular.SafePropertyRead, - hasParentParens: isInParentParens, + isInParentParens: isInParentParens, }); } From 02e724703aebd5220bfbc8f8c5cdfb446565854d Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 00:32:33 +0800 Subject: [PATCH 08/34] Pass `ancestors` around --- src/transform-node.ts | 130 ++++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 42 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 2a1000a9..3d488a43 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -61,6 +61,7 @@ class Transformer extends Source { #create( properties: Partial & { type: T['type'] } & RawNGSpan, + ancestors: angular.AST[], { stripSpaces = true, isInParentParens = false } = {}, ) { return this.createNode(properties, { stripSpaces, isInParentParens }); @@ -73,6 +74,7 @@ class Transformer extends Source { | angular.PropertyRead | angular.SafePropertyRead, property: babel.Expression, + ancestors: angular.AST[], { computed, optional, @@ -107,6 +109,7 @@ class Transformer extends Source { optional: optional || !isOptionalObject, ...commonProps, }, + ancestors, { isInParentParens }, ); } @@ -118,6 +121,7 @@ class Transformer extends Source { ...commonProps, computed: true, }, + ancestors, { isInParentParens }, ); } @@ -129,6 +133,7 @@ class Transformer extends Source { computed: false, property: property as babel.MemberExpressionNonComputed['property'], }, + ancestors, { isInParentParens }, ); } @@ -165,6 +170,7 @@ class Transformer extends Source { operator: node.operator as '-' | '+', ...node.sourceSpan, }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -194,6 +200,7 @@ class Transformer extends Source { type: 'LogicalExpression', operator: operator as babel.LogicalExpression['operator'], }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -207,6 +214,7 @@ class Transformer extends Source { operator: operator as babel.AssignmentExpression['operator'], ...node.sourceSpan, }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -217,6 +225,7 @@ class Transformer extends Source { type: 'BinaryExpression', operator: operator as babel.BinaryExpression['operator'], }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -233,12 +242,15 @@ class Transformer extends Source { /\S/, this.getCharacterIndex('|', leftEnd) + 1, ); - const right = this.#create({ - type: 'Identifier', - name, - start: rightStart, - end: rightStart + name.length, - }); + const right = this.#create( + { + type: 'Identifier', + name, + start: rightStart, + end: rightStart + name.length, + }, + ancestors, + ); const argumentNodes = originalArguments.map((node) => this.transform(node, childTransformOptions), ); @@ -254,6 +266,7 @@ class Transformer extends Source { argumentNodes.length === 0 ? right : argumentNodes.at(-1)!, ), }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -267,6 +280,7 @@ class Transformer extends Source { ), ...node.sourceSpan, }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -294,6 +308,7 @@ class Transformer extends Source { start: getOuterStart(test), end: getOuterEnd(alternate), }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -301,6 +316,7 @@ class Transformer extends Source { if (node instanceof angular.EmptyExpr) { return this.#create( { type: 'NGEmptyExpression', ...node.sourceSpan }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -308,6 +324,7 @@ class Transformer extends Source { if (node instanceof angular.ImplicitReceiver) { return this.#create( { type: 'ThisExpression', ...node.sourceSpan }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -321,6 +338,7 @@ class Transformer extends Source { ), ...node.sourceSpan, }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -350,27 +368,36 @@ class Transformer extends Source { ) + 1; const keySpan = { start: keyStart, end: keyEnd }; const tKey = quoted - ? this.#create({ - type: 'StringLiteral', - value: key, - ...keySpan, - }) - : this.#create({ - type: 'Identifier', - name: key, - ...keySpan, - }); + ? this.#create( + { + type: 'StringLiteral', + value: key, + ...keySpan, + }, + ancestors, + ) + : this.#create( + { + type: 'Identifier', + name: key, + ...keySpan, + }, + ancestors, + ); const shorthand = tKey.end < tKey.start || keyStart === valueStart; - return this.#create({ - type: 'ObjectProperty', - key: tKey, - value: tValue, - shorthand, - computed: false, - start: getOuterStart(tKey), - end: valueEnd, - }); + return this.#create( + { + type: 'ObjectProperty', + key: tKey, + value: tValue, + shorthand, + computed: false, + start: getOuterStart(tKey), + end: valueEnd, + }, + ancestors, + ); }); return this.#create( { @@ -378,6 +405,7 @@ class Transformer extends Source { properties: tProperties, ...node.sourceSpan, }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -388,26 +416,31 @@ class Transformer extends Source { case 'boolean': return this.#create( { type: 'BooleanLiteral', value, ...node.sourceSpan }, + ancestors, { isInParentParens: isInParentParens }, ); case 'number': return this.#create( { type: 'NumericLiteral', value, ...node.sourceSpan }, + ancestors, { isInParentParens: isInParentParens }, ); case 'object': return this.#create( { type: 'NullLiteral', ...node.sourceSpan }, + ancestors, { isInParentParens: isInParentParens }, ); case 'string': return this.#create( { type: 'StringLiteral', value, ...node.sourceSpan }, + ancestors, { isInParentParens: isInParentParens }, ); case 'undefined': return this.#create( { type: 'Identifier', name: 'undefined', ...node.sourceSpan }, + ancestors, { isInParentParens: isInParentParens }, ); /* c8 ignore next 4 */ @@ -426,6 +459,7 @@ class Transformer extends Source { flags: node.flags ?? '', ...node.sourceSpan, }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -461,6 +495,7 @@ class Transformer extends Source { start: getOuterStart(tReceiver), end: node.sourceSpan.end, // `)` }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -474,6 +509,7 @@ class Transformer extends Source { start: getOuterStart(expression), end: node.sourceSpan.end, // `!` }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -526,6 +562,7 @@ class Transformer extends Source { start, end: getOuterEnd(expression), }, + ancestors, { isInParentParens: isInParentParens }, ); } @@ -537,6 +574,7 @@ class Transformer extends Source { return this.#transformReceiverAndName( node, this.transform(node.key), + ancestors, { computed: true, optional: node instanceof angular.SafeKeyedRead, @@ -556,11 +594,12 @@ class Transformer extends Source { name, ...node.nameSpan, }, + ancestors, isImplicitThis(receiver, this.#text) ? { isInParentParens: isInParentParens } : {}, ); - return this.#transformReceiverAndName(node, tName, { + return this.#transformReceiverAndName(node, tName, ancestors, { computed: false, optional: node instanceof angular.SafePropertyRead, isInParentParens: isInParentParens, @@ -568,27 +607,33 @@ class Transformer extends Source { } if (node instanceof angular.TaggedTemplateLiteral) { - return this.#create({ - type: 'TaggedTemplateExpression', - tag: this.transform(node.tag), - quasi: this.transform(node.template), - ...node.sourceSpan, - }); + return this.#create( + { + type: 'TaggedTemplateExpression', + tag: this.transform(node.tag), + quasi: this.transform(node.template), + ...node.sourceSpan, + }, + ancestors, + ); } if (node instanceof angular.TemplateLiteral) { const { elements, expressions } = node; - return this.#create({ - type: 'TemplateLiteral', - quasis: elements.map((element) => - this.transform(element, childTransformOptions), - ), - expressions: expressions.map((expression) => - this.transform(expression, childTransformOptions), - ), - ...node.sourceSpan, - }); + return this.#create( + { + type: 'TemplateLiteral', + quasis: elements.map((element) => + this.transform(element, childTransformOptions), + ), + expressions: expressions.map((expression) => + this.transform(expression, childTransformOptions), + ), + ...node.sourceSpan, + }, + ancestors, + ); } if (node instanceof angular.TemplateLiteralElement) { @@ -613,6 +658,7 @@ class Transformer extends Source { end: end, tail: isLast, }, + ancestors, { stripSpaces: false }, ); } From dbd9f44fddb053222f0d567ef320badd71ce144a Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 01:18:57 +0800 Subject: [PATCH 09/34] Save --- src/transform-node.ts | 100 ++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 33 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 3d488a43..069c944c 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -59,7 +59,7 @@ class Transformer extends Source { LocationInformation; } - #create( + #createLegacy( properties: Partial & { type: T['type'] } & RawNGSpan, ancestors: angular.AST[], { stripSpaces = true, isInParentParens = false } = {}, @@ -67,6 +67,63 @@ class Transformer extends Source { return this.createNode(properties, { stripSpaces, isInParentParens }); } + #create( + properties: Partial & { type: T['type'] } & RawNGSpan, + ancestors: angular.AST[], + ) { + const node = { + ...properties, + range: [properties.start, properties.end], + } as T & LocationInformation; + + const parenthesizedExpression = getOutermostParenthesizedExpression(); + + if (parenthesizedExpression) { + node.extra = { + ...node.extra, + parenthesized: true, + parenStart: parenthesizedExpression.sourceSpan.start, + parenEnd: parenthesizedExpression.sourceSpan.end, + }; + } + + function getOutermostParenthesizedExpression() { + for (const [index, node] of ancestors.entries()) { + if (!(node instanceof angular.ParenthesizedExpression)) { + return; + } + + if ( + !(ancestors[index + 1] instanceof angular.ParenthesizedExpression) + ) { + return node; + } + } + } + + switch (node.type) { + case 'NumericLiteral': + case 'StringLiteral': + case 'RegExpLiteral': { + const raw = this.text.slice(node.start, node.end); + const { value } = node as unknown as + | babel.NumericLiteral + | babel.StringLiteral; + node.extra = { ...node.extra, raw, rawValue: value }; + break; + } + case 'ObjectProperty': { + const { shorthand } = node as unknown as babel.ObjectProperty; + if (shorthand) { + node.extra = { ...node.extra, shorthand }; + } + break; + } + } + + return node; + } + #transformReceiverAndName( node: | angular.KeyedRead @@ -78,7 +135,6 @@ class Transformer extends Source { { computed, optional, - isInParentParens = false, }: { computed: boolean; optional: boolean; @@ -110,7 +166,6 @@ class Transformer extends Source { ...commonProps, }, ancestors, - { isInParentParens }, ); } @@ -122,7 +177,6 @@ class Transformer extends Source { computed: true, }, ancestors, - { isInParentParens }, ); } @@ -134,7 +188,6 @@ class Transformer extends Source { property: property as babel.MemberExpressionNonComputed['property'], }, ancestors, - { isInParentParens }, ); } @@ -142,7 +195,7 @@ class Transformer extends Source { const ancestors = options.ancestors; const childTransformOptions = { ...options, - ancestors: [...ancestors, node], + ancestors: [node, ...ancestors], }; const { isInParentParens } = { @@ -171,7 +224,6 @@ class Transformer extends Source { ...node.sourceSpan, }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -201,7 +253,6 @@ class Transformer extends Source { operator: operator as babel.LogicalExpression['operator'], }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -215,7 +266,6 @@ class Transformer extends Source { ...node.sourceSpan, }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -226,7 +276,6 @@ class Transformer extends Source { operator: operator as babel.BinaryExpression['operator'], }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -267,7 +316,6 @@ class Transformer extends Source { ), }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -281,7 +329,6 @@ class Transformer extends Source { ...node.sourceSpan, }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -309,7 +356,6 @@ class Transformer extends Source { end: getOuterEnd(alternate), }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -317,7 +363,6 @@ class Transformer extends Source { return this.#create( { type: 'NGEmptyExpression', ...node.sourceSpan }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -325,7 +370,6 @@ class Transformer extends Source { return this.#create( { type: 'ThisExpression', ...node.sourceSpan }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -339,7 +383,6 @@ class Transformer extends Source { ...node.sourceSpan, }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -348,7 +391,8 @@ class Transformer extends Source { const tValues = values.map((value) => this.transform(value), ); - const tProperties = keys.map(({ key, quoted }, index) => { + const tProperties = keys.map((property, index) => { + const { key, quoted } = property; const tValue = tValues[index]; const valueStart = getOuterStart(tValue); const valueEnd = getOuterEnd(tValue); @@ -368,7 +412,7 @@ class Transformer extends Source { ) + 1; const keySpan = { start: keyStart, end: keyEnd }; const tKey = quoted - ? this.#create( + ? this.#createLegacy( { type: 'StringLiteral', value: key, @@ -376,7 +420,7 @@ class Transformer extends Source { }, ancestors, ) - : this.#create( + : this.#createLegacy( { type: 'Identifier', name: key, @@ -386,7 +430,7 @@ class Transformer extends Source { ); const shorthand = tKey.end < tKey.start || keyStart === valueStart; - return this.#create( + return this.#createLegacy( { type: 'ObjectProperty', key: tKey, @@ -406,7 +450,6 @@ class Transformer extends Source { ...node.sourceSpan, }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -417,16 +460,14 @@ class Transformer extends Source { return this.#create( { type: 'BooleanLiteral', value, ...node.sourceSpan }, ancestors, - { isInParentParens: isInParentParens }, ); case 'number': return this.#create( { type: 'NumericLiteral', value, ...node.sourceSpan }, ancestors, - { isInParentParens: isInParentParens }, ); case 'object': - return this.#create( + return this.#createLegacy( { type: 'NullLiteral', ...node.sourceSpan }, ancestors, { isInParentParens: isInParentParens }, @@ -435,13 +476,11 @@ class Transformer extends Source { return this.#create( { type: 'StringLiteral', value, ...node.sourceSpan }, ancestors, - { isInParentParens: isInParentParens }, ); case 'undefined': - return this.#create( + return this.#createLegacy( { type: 'Identifier', name: 'undefined', ...node.sourceSpan }, ancestors, - { isInParentParens: isInParentParens }, ); /* c8 ignore next 4 */ default: @@ -460,7 +499,6 @@ class Transformer extends Source { ...node.sourceSpan, }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -496,7 +534,6 @@ class Transformer extends Source { end: node.sourceSpan.end, // `)` }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -510,7 +547,6 @@ class Transformer extends Source { end: node.sourceSpan.end, // `!` }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -563,7 +599,6 @@ class Transformer extends Source { end: getOuterEnd(expression), }, ancestors, - { isInParentParens: isInParentParens }, ); } @@ -588,7 +623,7 @@ class Transformer extends Source { node instanceof angular.SafePropertyRead ) { const { receiver, name } = node; - const tName = this.#create( + const tName = this.#createLegacy( { type: 'Identifier', name, @@ -659,7 +694,6 @@ class Transformer extends Source { tail: isLast, }, ancestors, - { stripSpaces: false }, ); } From 63ad368fb544dc8601d7c485771a8a178a0eccc9 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 01:20:33 +0800 Subject: [PATCH 10/34] Save work --- src/transform-node.ts | 5 +-- tests/transform.test.ts | 88 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 069c944c..0041ef5a 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -467,10 +467,9 @@ class Transformer extends Source { ancestors, ); case 'object': - return this.#createLegacy( + return this.#create( { type: 'NullLiteral', ...node.sourceSpan }, ancestors, - { isInParentParens: isInParentParens }, ); case 'string': return this.#create( @@ -478,7 +477,7 @@ class Transformer extends Source { ancestors, ); case 'undefined': - return this.#createLegacy( + return this.#create( { type: 'Identifier', name: 'undefined', ...node.sourceSpan }, ancestors, ); diff --git a/tests/transform.test.ts b/tests/transform.test.ts index 001146f4..13334d56 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -26,8 +26,92 @@ const PARSE_METHODS = [ ] as const; describe.each` - expectedAngularType | expectedEstreeType | text | parseAction | parseBinding | parseSimpleBinding | parseInterpolationExpression - ${'Binary'} | ${'BinaryExpression'} | ${' ( ( ( ( a ) ) in ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + expectedAngularType | expectedEstreeType | text | parseAction | parseBinding | parseSimpleBinding | parseInterpolationExpression + ${'Binary'} | ${'BinaryExpression'} | ${' 0 - 1 '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'BinaryExpression'} | ${' a ** b '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'BinaryExpression'} | ${' ( ( ( ( a ) ) in ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'LogicalExpression'} | ${' a && b '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'LogicalExpression'} | ${' a ?? b '} | ${true} | ${true} | ${true} | ${true} + ${'Unary'} | ${'UnaryExpression'} | ${' - 1 '} | ${true} | ${true} | ${true} | ${true} + ${'Unary'} | ${'UnaryExpression'} | ${' + 1 '} | ${true} | ${true} | ${true} | ${true} + ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b '} | ${false} | ${true} | ${false} | ${true} + ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b : c '} | ${false} | ${true} | ${false} | ${true} + ${'Chain'} | ${'NGChainedExpression'} | ${' a ; b '} | ${true} | ${false} | ${false} | ${false} + ${'Conditional'} | ${'ConditionalExpression'} | ${' a ? 1 : 2 '} | ${true} | ${true} | ${true} | ${true} + ${'EmptyExpr'} | ${'NGEmptyExpression'} | ${''} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' ( a . b ) ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( a . b )?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' ( a ) ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( a )?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' a ( 1 ) ( 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ( 1 )?.( 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'KeyedRead'} | ${'MemberExpression'} | ${' a [ b ] '} | ${true} | ${true} | ${true} | ${true} + ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. [ b ] '} | ${true} | ${true} | ${true} | ${true} + ${'KeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b [ c ] '} | ${true} | ${true} | ${true} | ${true} + ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b ?. [ c ] '} | ${true} | ${true} | ${true} | ${true} + ${'KeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b () [ c ] '} | ${true} | ${true} | ${true} | ${true} + ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b () ?. [ c ] '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'AssignmentExpression'} | ${' a [ b ] = 1 '} | ${true} | ${true} | ${true} | ${true} + ${'ImplicitReceiver'} | ${'ThisExpression'} | ${' this '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralArray'} | ${'ArrayExpression'} | ${' [ 1 ] '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { "a" : 1 } )'} | ${true} | ${true} | ${true} | ${true} + ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { a : 1 } ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' f ( { a : 1 } ) '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( {a, b: 2} ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' f ( {a, b: 2} ) '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( {a, b} ) '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { a, b} ) '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralPrimitive'} | ${'BooleanLiteral'} | ${' true '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralPrimitive'} | ${'Identifier'} | ${' undefined '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralPrimitive'} | ${'NullLiteral'} | ${' null '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralPrimitive'} | ${'NumericLiteral'} | ${' ( 1 ) '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralPrimitive'} | ${'NumericLiteral'} | ${' 1 '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralPrimitive'} | ${'StringLiteral'} | ${' ( "hello" ) '} | ${true} | ${true} | ${true} | ${true} + ${'RegularExpressionLiteral'} | ${'RegExpLiteral'} | ${' ( ( /\\d+/ ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'RegularExpressionLiteral'} | ${'RegExpLiteral'} | ${' ( ( /\\d+/g ) )'} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' a ( this ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?.( this ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' a ( b) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?.( b) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' a . b ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a . b ?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' a ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b . c ( ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b . c ?. ( ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) . c ( ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) . c ?.( ) '} | ${true} | ${true} | ${true} | ${true} + ${'NonNullAssert'} | ${'TSNonNullExpression'} | ${' x ! '} | ${true} | ${true} | ${true} | ${true} + ${'PrefixNot'} | ${'UnaryExpression'} | ${' ! x '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'Identifier'} | ${' ( ( a ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'Identifier'} | ${' a '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'Identifier'} | ${' a // hello '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'MemberExpression'} | ${' a . b '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'MemberExpression'} | ${' this . a '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'OptionalMemberExpression'} | ${' a ?. b . c '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'OptionalMemberExpression'} | ${' a ?. b ( ) . c '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'OptionalMemberExpression'} | ${' foo?.bar!.bam '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'MemberExpression'} | ${' (foo?.bar)!.bam '} | ${true} | ${true} | ${true} | ${true} + ${'PropertyRead'} | ${'MemberExpression'} | ${' (foo?.bar!).bam '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'AssignmentExpression'} | ${' a . b = 1 '} | ${true} | ${false} | ${false} | ${false} + ${'Binary'} | ${'AssignmentExpression'} | ${' a = 1 '} | ${true} | ${false} | ${false} | ${false} + ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b ?. ( ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafePropertyRead'} | ${'OptionalMemberExpression'} | ${' a ?. b '} | ${true} | ${true} | ${true} | ${true} + ${'TypeofExpression'} | ${'UnaryExpression'} | ${' ( ( typeof {} ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'BinaryExpression'} | ${' typeof {} === "object" '} | ${true} | ${true} | ${true} | ${true} + ${'PrefixNot'} | ${'UnaryExpression'} | ${' ! ( typeof {} === "" ) '} | ${true} | ${true} | ${true} | ${true} + ${'VoidExpression'} | ${'UnaryExpression'} | ${' ( ( void ( ( a() ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ` a ${ b } \\u0063 ` '} | ${true} | ${true} | ${true} | ${true} + ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ( ( ` a ${ b } \\u0063 ` ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ` \\u0063 ` '} | ${true} | ${true} | ${true} | ${true} + ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ( ( ` ` ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' `` '} | ${true} | ${true} | ${true} | ${true} + ${'TaggedTemplateLiteral'} | ${'TaggedTemplateExpression'} | ${' tag ` a ${ b } \\u0063 ` '} | ${true} | ${true} | ${true} | ${true} + ${'TaggedTemplateLiteral'} | ${'TaggedTemplateExpression'} | ${' ( ( ( ( tag ) ) ` a ${ b } \\u0063 ` ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( ( {foo: ` a ${ b } ` } ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( ( {foo: tag ` a ${ b } ` } ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'AssignmentExpression'} | ${' a ??= b '} | ${true} | ${false} | ${false} | ${false} `('($expectedAngularType -> $expectedEstreeType)', (fields) => { for (const method of PARSE_METHODS) { testSection(method, fields); From a80ab6a5637fa2b77c29362562ff703ba5f46b72 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 01:26:30 +0800 Subject: [PATCH 11/34] Fix object key --- src/transform-node.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 0041ef5a..6a9904de 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -389,7 +389,7 @@ class Transformer extends Source { if (node instanceof angular.LiteralMap) { const { keys, values } = node; const tValues = values.map((value) => - this.transform(value), + this.transform(value, childTransformOptions), ); const tProperties = keys.map((property, index) => { const { key, quoted } = property; @@ -412,21 +412,21 @@ class Transformer extends Source { ) + 1; const keySpan = { start: keyStart, end: keyEnd }; const tKey = quoted - ? this.#createLegacy( + ? this.#create( { type: 'StringLiteral', value: key, ...keySpan, }, - ancestors, + [], ) - : this.#createLegacy( + : this.#create( { type: 'Identifier', name: key, ...keySpan, }, - ancestors, + [], ); const shorthand = tKey.end < tKey.start || keyStart === valueStart; From 990f9270b92eaa8436e0428f8d23d762d56ed87d Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 01:27:09 +0800 Subject: [PATCH 12/34] Fix --- src/transform-node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 6a9904de..56cb3286 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -430,7 +430,7 @@ class Transformer extends Source { ); const shorthand = tKey.end < tKey.start || keyStart === valueStart; - return this.#createLegacy( + return this.#create( { type: 'ObjectProperty', key: tKey, @@ -440,7 +440,7 @@ class Transformer extends Source { start: getOuterStart(tKey), end: valueEnd, }, - ancestors, + [], ); }); return this.#create( From 031e8467fad74434532f2a4c259a5224a9f46ca8 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 01:29:49 +0800 Subject: [PATCH 13/34] Save work --- src/transform-node.ts | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 56cb3286..3b8a335d 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -39,7 +39,6 @@ function isImplicitThis(node: angular.AST, text: string): boolean { } type NodeTransformOptions = { - isInParentParens?: boolean; ancestors: angular.AST[]; }; @@ -59,14 +58,6 @@ class Transformer extends Source { LocationInformation; } - #createLegacy( - properties: Partial & { type: T['type'] } & RawNGSpan, - ancestors: angular.AST[], - { stripSpaces = true, isInParentParens = false } = {}, - ) { - return this.createNode(properties, { stripSpaces, isInParentParens }); - } - #create( properties: Partial & { type: T['type'] } & RawNGSpan, ancestors: angular.AST[], @@ -138,7 +129,6 @@ class Transformer extends Source { }: { computed: boolean; optional: boolean; - isInParentParens?: boolean; }, ) { const { receiver } = node; @@ -198,11 +188,6 @@ class Transformer extends Source { ancestors: [node, ...ancestors], }; - const { isInParentParens } = { - isInParentParens: false, - ...options, - }; - if (node instanceof angular.Interpolation) { const { expressions } = node; @@ -509,7 +494,6 @@ class Transformer extends Source { ? [ this.transform(args[0], { ...childTransformOptions, - isInParentParens: true, }), ] : (args as angular.AST[]).map((node) => @@ -612,7 +596,6 @@ class Transformer extends Source { { computed: true, optional: node instanceof angular.SafeKeyedRead, - isInParentParens: isInParentParens, }, ); } @@ -622,21 +605,17 @@ class Transformer extends Source { node instanceof angular.SafePropertyRead ) { const { receiver, name } = node; - const tName = this.#createLegacy( + const tName = this.#create( { type: 'Identifier', name, ...node.nameSpan, }, - ancestors, - isImplicitThis(receiver, this.#text) - ? { isInParentParens: isInParentParens } - : {}, + isImplicitThis(receiver, this.#text) ? ancestors : [], ); return this.#transformReceiverAndName(node, tName, ancestors, { computed: false, optional: node instanceof angular.SafePropertyRead, - isInParentParens: isInParentParens, }); } From abcad99486583514b5aabd50bf0bcc80da0fd9ea Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 01:37:29 +0800 Subject: [PATCH 14/34] Replace `Source.createNode()` --- src/source.ts | 13 ++------ src/transform-node.ts | 75 ++++++++++++++++++------------------------- 2 files changed, 33 insertions(+), 55 deletions(-) diff --git a/src/source.ts b/src/source.ts index 1ec64228..daf53a00 100644 --- a/src/source.ts +++ b/src/source.ts @@ -50,22 +50,13 @@ export class Source { createNode( properties: Partial & { type: T['type'] } & RawNGSpan, - // istanbul ignore next - { stripSpaces = true, isInParentParens = false } = {}, ) { - const { type, start, end } = properties; const node = { ...properties, - ...this.transformSpan( - { start, end }, - { - stripSpaces, - isInParentParens, - }, - ), + range: [properties.start, properties.end], } as T & LocationInformation; - switch (type) { + switch (node.type) { case 'NumericLiteral': case 'StringLiteral': case 'RegExpLiteral': { diff --git a/src/transform-node.ts b/src/transform-node.ts index 3b8a335d..80937f42 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -38,6 +38,33 @@ function isImplicitThis(node: angular.AST, text: string): boolean { return start >= end || /^\s+$/.test(text.slice(start, end)); } +function getParenthesizedInformation(ancestors: angular.AST[]) { + const parenthesizedExpression = + getOutermostParenthesizedExpression(ancestors); + + if (!parenthesizedExpression) { + return; + } + + return { + parenthesized: true, + parenStart: parenthesizedExpression.sourceSpan.start, + parenEnd: parenthesizedExpression.sourceSpan.end, + }; +} + +function getOutermostParenthesizedExpression(ancestors: angular.AST[]) { + for (const [index, node] of ancestors.entries()) { + if (!(node instanceof angular.ParenthesizedExpression)) { + return; + } + + if (!(ancestors[index + 1] instanceof angular.ParenthesizedExpression)) { + return node; + } + } +} + type NodeTransformOptions = { ancestors: angular.AST[]; }; @@ -62,56 +89,16 @@ class Transformer extends Source { properties: Partial & { type: T['type'] } & RawNGSpan, ancestors: angular.AST[], ) { - const node = { - ...properties, - range: [properties.start, properties.end], - } as T & LocationInformation; - - const parenthesizedExpression = getOutermostParenthesizedExpression(); + const node = this.createNode(properties); - if (parenthesizedExpression) { + const parenthesizedInformation = getParenthesizedInformation(ancestors); + if (parenthesizedInformation) { node.extra = { ...node.extra, - parenthesized: true, - parenStart: parenthesizedExpression.sourceSpan.start, - parenEnd: parenthesizedExpression.sourceSpan.end, + ...parenthesizedInformation, }; } - function getOutermostParenthesizedExpression() { - for (const [index, node] of ancestors.entries()) { - if (!(node instanceof angular.ParenthesizedExpression)) { - return; - } - - if ( - !(ancestors[index + 1] instanceof angular.ParenthesizedExpression) - ) { - return node; - } - } - } - - switch (node.type) { - case 'NumericLiteral': - case 'StringLiteral': - case 'RegExpLiteral': { - const raw = this.text.slice(node.start, node.end); - const { value } = node as unknown as - | babel.NumericLiteral - | babel.StringLiteral; - node.extra = { ...node.extra, raw, rawValue: value }; - break; - } - case 'ObjectProperty': { - const { shorthand } = node as unknown as babel.ObjectProperty; - if (shorthand) { - node.extra = { ...node.extra, shorthand }; - } - break; - } - } - return node; } From 8a452b4ace6faecd641fcde68ee2cc8462dc51ee Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 01:52:41 +0800 Subject: [PATCH 15/34] Save --- src/transform-node.ts | 159 ++++++++++++++++++------------------------ 1 file changed, 69 insertions(+), 90 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 80937f42..9aa5633d 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -102,72 +102,6 @@ class Transformer extends Source { return node; } - #transformReceiverAndName( - node: - | angular.KeyedRead - | angular.SafeKeyedRead - | angular.PropertyRead - | angular.SafePropertyRead, - property: babel.Expression, - ancestors: angular.AST[], - { - computed, - optional, - }: { - computed: boolean; - optional: boolean; - }, - ) { - const { receiver } = node; - if ( - isImplicitThis(receiver, this.#text) || - receiver.sourceSpan.start === property.start - ) { - return property; - } - const object = this.transform(receiver); - const isOptionalObject = isOptionalObjectOrCallee(object); - - const commonProps = { - property, - object, - computed, - ...node.sourceSpan, - }; - - if (optional || isOptionalObject) { - return this.#create( - { - type: 'OptionalMemberExpression', - optional: optional || !isOptionalObject, - ...commonProps, - }, - ancestors, - ); - } - - if (computed) { - return this.#create( - { - type: 'MemberExpression', - ...commonProps, - computed: true, - }, - ancestors, - ); - } - - return this.#create( - { - type: 'MemberExpression', - ...commonProps, - computed: false, - property: property as babel.MemberExpressionNonComputed['property'], - }, - ancestors, - ); - } - #transform(node: angular.AST, options: NodeTransformOptions): NGNode { const ancestors = options.ancestors; const childTransformOptions = { @@ -574,36 +508,81 @@ class Transformer extends Source { if ( node instanceof angular.KeyedRead || - node instanceof angular.SafeKeyedRead - ) { - return this.#transformReceiverAndName( - node, - this.transform(node.key), - ancestors, - { - computed: true, - optional: node instanceof angular.SafeKeyedRead, - }, - ); - } - - if ( + node instanceof angular.SafeKeyedRead || node instanceof angular.PropertyRead || node instanceof angular.SafePropertyRead ) { - const { receiver, name } = node; - const tName = this.#create( + const isComputed = + node instanceof angular.KeyedRead || + node instanceof angular.SafeKeyedRead; + const isOptional = + node instanceof angular.SafeKeyedRead || + node instanceof angular.SafePropertyRead; + + const { receiver } = node; + + const implicit = isImplicitThis(receiver, this.#text); + + let property; + if (isComputed) { + property = this.transform(node.key); + } else { + const { name } = node; + property = this.#create( + { + type: 'Identifier', + name, + ...node.nameSpan, + }, + implicit ? ancestors : [], + ); + } + + if (implicit || receiver.sourceSpan.start === property.start) { + return property; + } + + const object = this.transform(receiver); + const isOptionalObject = isOptionalObjectOrCallee(object); + + const commonProps = { + property, + object, + ...node.sourceSpan, + }; + + if (isOptional || isOptionalObject) { + return this.#create( + { + type: 'OptionalMemberExpression', + optional: isOptional || !isOptionalObject, + computed: isComputed, + ...commonProps, + }, + ancestors, + ); + } + + if (isComputed) { + return this.#create( + { + type: 'MemberExpression', + ...commonProps, + computed: true, + }, + ancestors, + ); + } + + return this.#create( { - type: 'Identifier', - name, - ...node.nameSpan, + type: 'MemberExpression', + ...commonProps, + computed: false, + property: property as babel.MemberExpressionNonComputed['property'], }, - isImplicitThis(receiver, this.#text) ? ancestors : [], + ancestors, ); - return this.#transformReceiverAndName(node, tName, ancestors, { - computed: false, - optional: node instanceof angular.SafePropertyRead, - }); } if (node instanceof angular.TaggedTemplateLiteral) { From f64ca6c9f8a7de5cd78c4ebd29ee6bca924e67bc Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 01:55:54 +0800 Subject: [PATCH 16/34] Conditional --- src/transform-node.ts | 3 +-- tests/transform.test.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 9aa5633d..b3d52fbb 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -258,8 +258,7 @@ class Transformer extends Source { test, consequent, alternate, - start: getOuterStart(test), - end: getOuterEnd(alternate), + ...node.sourceSpan, }, ancestors, ); diff --git a/tests/transform.test.ts b/tests/transform.test.ts index 13334d56..af9de4c0 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -37,7 +37,7 @@ describe.each` ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b '} | ${false} | ${true} | ${false} | ${true} ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b : c '} | ${false} | ${true} | ${false} | ${true} ${'Chain'} | ${'NGChainedExpression'} | ${' a ; b '} | ${true} | ${false} | ${false} | ${false} - ${'Conditional'} | ${'ConditionalExpression'} | ${' a ? 1 : 2 '} | ${true} | ${true} | ${true} | ${true} + ${'Conditional'} | ${'ConditionalExpression'} | ${' ( ( ( ( a ) ) ? ( ( 1 ) ) : ( ( 2 ) ) ))'} | ${true} | ${true} | ${true} | ${true} ${'EmptyExpr'} | ${'NGEmptyExpression'} | ${''} | ${true} | ${true} | ${true} | ${true} ${'Call'} | ${'CallExpression'} | ${' ( a . b ) ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( a . b )?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} From f34164611769efb798504f1742c2f19b14ba05d0 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:02:18 +0800 Subject: [PATCH 17/34] UnaryExpression --- src/transform-node.ts | 2 +- tests/transform.test.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index b3d52fbb..d9fe0e22 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -499,7 +499,7 @@ class Transformer extends Source { operator, argument: expression, start, - end: getOuterEnd(expression), + end: node.sourceSpan.end, }, ancestors, ); diff --git a/tests/transform.test.ts b/tests/transform.test.ts index af9de4c0..6db2d5af 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -82,7 +82,6 @@ describe.each` ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) . c ( ) '} | ${true} | ${true} | ${true} | ${true} ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) . c ?.( ) '} | ${true} | ${true} | ${true} | ${true} ${'NonNullAssert'} | ${'TSNonNullExpression'} | ${' x ! '} | ${true} | ${true} | ${true} | ${true} - ${'PrefixNot'} | ${'UnaryExpression'} | ${' ! x '} | ${true} | ${true} | ${true} | ${true} ${'PropertyRead'} | ${'Identifier'} | ${' ( ( a ) ) '} | ${true} | ${true} | ${true} | ${true} ${'PropertyRead'} | ${'Identifier'} | ${' a '} | ${true} | ${true} | ${true} | ${true} ${'PropertyRead'} | ${'Identifier'} | ${' a // hello '} | ${true} | ${true} | ${true} | ${true} @@ -98,10 +97,10 @@ describe.each` ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) '} | ${true} | ${true} | ${true} | ${true} ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b ?. ( ) '} | ${true} | ${true} | ${true} | ${true} ${'SafePropertyRead'} | ${'OptionalMemberExpression'} | ${' a ?. b '} | ${true} | ${true} | ${true} | ${true} - ${'TypeofExpression'} | ${'UnaryExpression'} | ${' ( ( typeof {} ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'PrefixNot'} | ${'UnaryExpression'} | ${' ( ( ! ( ( x ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'TypeofExpression'} | ${'UnaryExpression'} | ${' ( ( typeof ( ( x ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'VoidExpression'} | ${'UnaryExpression'} | ${' ( ( void ( ( x ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} ${'Binary'} | ${'BinaryExpression'} | ${' typeof {} === "object" '} | ${true} | ${true} | ${true} | ${true} - ${'PrefixNot'} | ${'UnaryExpression'} | ${' ! ( typeof {} === "" ) '} | ${true} | ${true} | ${true} | ${true} - ${'VoidExpression'} | ${'UnaryExpression'} | ${' ( ( void ( ( a() ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ` a ${ b } \\u0063 ` '} | ${true} | ${true} | ${true} | ${true} ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ( ( ` a ${ b } \\u0063 ` ) ) '} | ${true} | ${true} | ${true} | ${true} ${'TemplateLiteral'} | ${'TemplateLiteral'} | ${' ` \\u0063 ` '} | ${true} | ${true} | ${true} | ${true} From cbb18d5c8e9354b94e3f259776519d79bbe180c7 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:10:40 +0800 Subject: [PATCH 18/34] Binary --- src/transform-node.ts | 13 +++++++------ tests/transform.test.ts | 17 +++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index d9fe0e22..8083f030 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -147,16 +147,15 @@ class Transformer extends Source { originalRight, childTransformOptions, ); - const start = getOuterStart(left); - const end = getOuterEnd(right); - const properties = { left, right, start, end }; if (operator === '&&' || operator === '||' || operator === '??') { return this.#create( { - ...properties, type: 'LogicalExpression', operator: operator as babel.LogicalExpression['operator'], + left, + right, + ...node.sourceSpan, }, ancestors, ); @@ -165,9 +164,9 @@ class Transformer extends Source { if (angular.Binary.isAssignmentOperation(operator)) { return this.#create( { - ...properties, type: 'AssignmentExpression', left: left as babel.MemberExpression, + right, operator: operator as babel.AssignmentExpression['operator'], ...node.sourceSpan, }, @@ -177,9 +176,11 @@ class Transformer extends Source { return this.#create( { - ...properties, + left, + right, type: 'BinaryExpression', operator: operator as babel.BinaryExpression['operator'], + ...node.sourceSpan, }, ancestors, ); diff --git a/tests/transform.test.ts b/tests/transform.test.ts index 6db2d5af..cfd6ecc0 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -27,11 +27,16 @@ const PARSE_METHODS = [ describe.each` expectedAngularType | expectedEstreeType | text | parseAction | parseBinding | parseSimpleBinding | parseInterpolationExpression - ${'Binary'} | ${'BinaryExpression'} | ${' 0 - 1 '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'BinaryExpression'} | ${' a ** b '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'BinaryExpression'} | ${' ( ( ( ( 0 ) ) - ( ( 1 ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'BinaryExpression'} | ${' ( ( ( ( a ) ) ** ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} ${'Binary'} | ${'BinaryExpression'} | ${' ( ( ( ( a ) ) in ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'LogicalExpression'} | ${' a && b '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'LogicalExpression'} | ${' a ?? b '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'LogicalExpression'} | ${' ( ( ( ( a ) ) && ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'LogicalExpression'} | ${' ( ( ( ( a ) ) || ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'LogicalExpression'} | ${' ( ( ( ( a ) ) ?? ( ( b ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'AssignmentExpression'} | ${' ( ( a . b = ( ( 1 ) ) ) ) '} | ${true} | ${false} | ${false} | ${false} + ${'Binary'} | ${'AssignmentExpression'} | ${' ( ( a = ( ( 1 ) ) ) ) '} | ${true} | ${false} | ${false} | ${false} + ${'Binary'} | ${'AssignmentExpression'} | ${' a [ b ] = 1 '} | ${true} | ${true} | ${true} | ${true} + ${'Binary'} | ${'AssignmentExpression'} | ${' ( ( a ??= ( ( 1 ) ) ) ) '} | ${true} | ${false} | ${false} | ${false} ${'Unary'} | ${'UnaryExpression'} | ${' - 1 '} | ${true} | ${true} | ${true} | ${true} ${'Unary'} | ${'UnaryExpression'} | ${' + 1 '} | ${true} | ${true} | ${true} | ${true} ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b '} | ${false} | ${true} | ${false} | ${true} @@ -51,7 +56,6 @@ describe.each` ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b ?. [ c ] '} | ${true} | ${true} | ${true} | ${true} ${'KeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b () [ c ] '} | ${true} | ${true} | ${true} | ${true} ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b () ?. [ c ] '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'AssignmentExpression'} | ${' a [ b ] = 1 '} | ${true} | ${true} | ${true} | ${true} ${'ImplicitReceiver'} | ${'ThisExpression'} | ${' this '} | ${true} | ${true} | ${true} | ${true} ${'LiteralArray'} | ${'ArrayExpression'} | ${' [ 1 ] '} | ${true} | ${true} | ${true} | ${true} ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { "a" : 1 } )'} | ${true} | ${true} | ${true} | ${true} @@ -92,8 +96,6 @@ describe.each` ${'PropertyRead'} | ${'OptionalMemberExpression'} | ${' foo?.bar!.bam '} | ${true} | ${true} | ${true} | ${true} ${'PropertyRead'} | ${'MemberExpression'} | ${' (foo?.bar)!.bam '} | ${true} | ${true} | ${true} | ${true} ${'PropertyRead'} | ${'MemberExpression'} | ${' (foo?.bar!).bam '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'AssignmentExpression'} | ${' a . b = 1 '} | ${true} | ${false} | ${false} | ${false} - ${'Binary'} | ${'AssignmentExpression'} | ${' a = 1 '} | ${true} | ${false} | ${false} | ${false} ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) '} | ${true} | ${true} | ${true} | ${true} ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b ?. ( ) '} | ${true} | ${true} | ${true} | ${true} ${'SafePropertyRead'} | ${'OptionalMemberExpression'} | ${' a ?. b '} | ${true} | ${true} | ${true} | ${true} @@ -110,7 +112,6 @@ describe.each` ${'TaggedTemplateLiteral'} | ${'TaggedTemplateExpression'} | ${' ( ( ( ( tag ) ) ` a ${ b } \\u0063 ` ) ) '} | ${true} | ${true} | ${true} | ${true} ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( ( {foo: ` a ${ b } ` } ) ) '} | ${true} | ${true} | ${true} | ${true} ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( ( {foo: tag ` a ${ b } ` } ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'Binary'} | ${'AssignmentExpression'} | ${' a ??= b '} | ${true} | ${false} | ${false} | ${false} `('($expectedAngularType -> $expectedEstreeType)', (fields) => { for (const method of PARSE_METHODS) { testSection(method, fields); From 75783fe8c696e31b21cc3419026e813a3e2c7dd3 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:11:54 +0800 Subject: [PATCH 19/34] Simplify --- src/transform-node.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 8083f030..bf3c2504 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -134,18 +134,9 @@ class Transformer extends Source { } if (node instanceof angular.Binary) { - const { - left: originalLeft, - operation: operator, - right: originalRight, - } = node; - const left = this.transform( - originalLeft, - childTransformOptions, - ); - const right = this.transform( - originalRight, - childTransformOptions, + const { operation: operator } = node; + const [left, right] = [node.left, node.right].map((node) => + this.transform(node, childTransformOptions), ); if (operator === '&&' || operator === '||' || operator === '??') { From 535fb7f21da24bf21f674efdb0d927f38cd17a19 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:17:32 +0800 Subject: [PATCH 20/34] DRY --- src/transform-node.ts | 19 +++++++------------ tests/transform.test.ts | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index bf3c2504..13722a95 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -231,19 +231,14 @@ class Transformer extends Source { } if (node instanceof angular.Conditional) { - const { condition, trueExp, falseExp } = node; - const test = this.transform( - condition, - childTransformOptions, - ); - const consequent = this.transform( - trueExp, - childTransformOptions, - ); - const alternate = this.transform( - falseExp, - childTransformOptions, + const [test, consequent, alternate] = [ + node.condition, + node.trueExp, + node.falseExp, + ].map((node) => + this.transform(node, childTransformOptions), ); + return this.#create( { type: 'ConditionalExpression', diff --git a/tests/transform.test.ts b/tests/transform.test.ts index cfd6ecc0..0ae046f6 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -39,7 +39,7 @@ describe.each` ${'Binary'} | ${'AssignmentExpression'} | ${' ( ( a ??= ( ( 1 ) ) ) ) '} | ${true} | ${false} | ${false} | ${false} ${'Unary'} | ${'UnaryExpression'} | ${' - 1 '} | ${true} | ${true} | ${true} | ${true} ${'Unary'} | ${'UnaryExpression'} | ${' + 1 '} | ${true} | ${true} | ${true} | ${true} - ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b '} | ${false} | ${true} | ${false} | ${true} + ${'BindingPipe'} | ${'NGPipeExpression'} | ${' ( ( ( ( a ) ) | b )) '} | ${false} | ${true} | ${false} | ${true} ${'BindingPipe'} | ${'NGPipeExpression'} | ${' a | b : c '} | ${false} | ${true} | ${false} | ${true} ${'Chain'} | ${'NGChainedExpression'} | ${' a ; b '} | ${true} | ${false} | ${false} | ${false} ${'Conditional'} | ${'ConditionalExpression'} | ${' ( ( ( ( a ) ) ? ( ( 1 ) ) : ( ( 2 ) ) ))'} | ${true} | ${true} | ${true} | ${true} From 8018ac0131b5f371e88211d6d5e47b56e9fe6e8d Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:22:27 +0800 Subject: [PATCH 21/34] Call --- src/transform-node.ts | 31 ++++----- tests/__snapshots__/transform.test.ts.snap | 76 ++++++++++++++++++++++ tests/transform.test.ts | 8 +-- 3 files changed, 91 insertions(+), 24 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 13722a95..94e29c83 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -394,34 +394,25 @@ class Transformer extends Source { } if (node instanceof angular.Call || node instanceof angular.SafeCall) { - const isOptionalType = node instanceof angular.SafeCall; - const { receiver, args } = node; - const tArgs = - args.length === 1 - ? [ - this.transform(args[0], { - ...childTransformOptions, - }), - ] - : (args as angular.AST[]).map((node) => - this.transform(node, childTransformOptions), - ); - const tReceiver = this.transform(receiver!); - const isOptionalReceiver = isOptionalObjectOrCallee(tReceiver); + const arguments_ = node.args.map((node) => + this.transform(node, childTransformOptions), + ); + const callee = this.transform(node.receiver); + const isOptionalReceiver = isOptionalObjectOrCallee(callee); + const isOptional = node instanceof angular.SafeCall; const nodeType = - isOptionalType || isOptionalReceiver + isOptional || isOptionalReceiver ? 'OptionalCallExpression' : 'CallExpression'; return this.#create( { type: nodeType, - callee: tReceiver, - arguments: tArgs, + callee, + arguments: arguments_, ...(nodeType === 'OptionalCallExpression' - ? { optional: isOptionalType } + ? { optional: isOptional } : undefined), - start: getOuterStart(tReceiver), - end: node.sourceSpan.end, // `)` + ...node.sourceSpan, }, ancestors, ); diff --git a/tests/__snapshots__/transform.test.ts.snap b/tests/__snapshots__/transform.test.ts.snap index 47486d8d..5e1b1f18 100644 --- a/tests/__snapshots__/transform.test.ts.snap +++ b/tests/__snapshots__/transform.test.ts.snap @@ -1,5 +1,43 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`('BindingPipe' -> 'NGPipeExpression') > parseBinding(" ( ( ( ( a ) ) | b )) ") 1`] = ` +NGPipeExpression { + "left": "Identifier", + "right": "Identifier", + "arguments": [], + "extra": { + "parenthesized": true, + "parenStart": 1, + "parenEnd": 23 + }, + "comments": [] +} +> 1 | ( ( ( ( a ) ) | b )) + | ^^^^^^^^^^^^^^^^^^ +-------------------------------------------------------------------------------- +Identifier { + "name": "a", + "extra": { + "parenthesized": true, + "parenStart": 5, + "parenEnd": 14 + } +} +> 1 | ( ( ( ( a ) ) | b )) + | ^ +-------------------------------------------------------------------------------- +Identifier { + "name": "b", + "extra": { + "parenthesized": true, + "parenStart": 1, + "parenEnd": 23 + } +} +> 1 | ( ( ( ( a ) ) | b )) + | ^ +`; + exports[`('BindingPipe' -> 'NGPipeExpression') > parseBinding(" a | b ") 1`] = ` NGPipeExpression { "left": "Identifier", @@ -54,6 +92,44 @@ Identifier { | ^ `; +exports[`('BindingPipe' -> 'NGPipeExpression') > parseInterpolationExpression(" ( ( ( ( a ) ) | b )) ") 1`] = ` +NGPipeExpression { + "left": "Identifier", + "right": "Identifier", + "arguments": [], + "extra": { + "parenthesized": true, + "parenStart": 1, + "parenEnd": 23 + }, + "comments": [] +} +> 1 | ( ( ( ( a ) ) | b )) + | ^^^^^^^^^^^^^^^^^^ +-------------------------------------------------------------------------------- +Identifier { + "name": "a", + "extra": { + "parenthesized": true, + "parenStart": 5, + "parenEnd": 14 + } +} +> 1 | ( ( ( ( a ) ) | b )) + | ^ +-------------------------------------------------------------------------------- +Identifier { + "name": "b", + "extra": { + "parenthesized": true, + "parenStart": 1, + "parenEnd": 23 + } +} +> 1 | ( ( ( ( a ) ) | b )) + | ^ +`; + exports[`('BindingPipe' -> 'NGPipeExpression') > parseInterpolationExpression(" a | b ") 1`] = ` NGPipeExpression { "left": "Identifier", diff --git a/tests/transform.test.ts b/tests/transform.test.ts index 0ae046f6..bd8d9d51 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -44,10 +44,10 @@ describe.each` ${'Chain'} | ${'NGChainedExpression'} | ${' a ; b '} | ${true} | ${false} | ${false} | ${false} ${'Conditional'} | ${'ConditionalExpression'} | ${' ( ( ( ( a ) ) ? ( ( 1 ) ) : ( ( 2 ) ) ))'} | ${true} | ${true} | ${true} | ${true} ${'EmptyExpr'} | ${'NGEmptyExpression'} | ${''} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' ( a . b ) ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( a . b )?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' ( a ) ( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} - ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( a )?.( 1 , 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' ( ( ( ( a . b ) ) ( 1 , 2 ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( ( ( ( a . b ) )?.( 1 , 2 ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' ( ( ( ( a ) ) ( 1 , 2 ) ) ) '} | ${true} | ${true} | ${true} | ${true} + ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( ( ( ( a ) ) ?. ( 1 , 2 ) ) ) '} | ${true} | ${true} | ${true} | ${true} ${'Call'} | ${'CallExpression'} | ${' a ( 1 ) ( 2 ) '} | ${true} | ${true} | ${true} | ${true} ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ( 1 )?.( 2 ) '} | ${true} | ${true} | ${true} | ${true} ${'KeyedRead'} | ${'MemberExpression'} | ${' a [ b ] '} | ${true} | ${true} | ${true} | ${true} From 36f8d6a206bc199314adc136290446f8e764b79d Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:23:15 +0800 Subject: [PATCH 22/34] Call --- tests/transform.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/transform.test.ts b/tests/transform.test.ts index bd8d9d51..be7ccbba 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -48,7 +48,7 @@ describe.each` ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( ( ( ( a . b ) )?.( 1 , 2 ) ) ) '} | ${true} | ${true} | ${true} | ${true} ${'Call'} | ${'CallExpression'} | ${' ( ( ( ( a ) ) ( 1 , 2 ) ) ) '} | ${true} | ${true} | ${true} | ${true} ${'SafeCall'} | ${'OptionalCallExpression'} | ${' ( ( ( ( a ) ) ?. ( 1 , 2 ) ) ) '} | ${true} | ${true} | ${true} | ${true} - ${'Call'} | ${'CallExpression'} | ${' a ( 1 ) ( 2 ) '} | ${true} | ${true} | ${true} | ${true} + ${'Call'} | ${'CallExpression'} | ${' ( ( a ( ( ( 1 ) ) ) ( ( ( 1 ) ) ) ) ) '} | ${true} | ${true} | ${true} | ${true} ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ( 1 )?.( 2 ) '} | ${true} | ${true} | ${true} | ${true} ${'KeyedRead'} | ${'MemberExpression'} | ${' a [ b ] '} | ${true} | ${true} | ${true} | ${true} ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. [ b ] '} | ${true} | ${true} | ${true} | ${true} From c5a8776aace47d51dee92e02e4584ca17febd520 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:24:43 +0800 Subject: [PATCH 23/34] NonNullAssert --- src/transform-node.ts | 3 +-- tests/transform.test.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 94e29c83..9b670f48 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -424,8 +424,7 @@ class Transformer extends Source { { type: 'TSNonNullExpression', expression: expression, - start: getOuterStart(expression), - end: node.sourceSpan.end, // `!` + ...node.sourceSpan, }, ancestors, ); diff --git a/tests/transform.test.ts b/tests/transform.test.ts index be7ccbba..8f24735b 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -85,7 +85,7 @@ describe.each` ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b . c ?. ( ) '} | ${true} | ${true} | ${true} | ${true} ${'Call'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) . c ( ) '} | ${true} | ${true} | ${true} | ${true} ${'SafeCall'} | ${'OptionalCallExpression'} | ${' a ?. b ( ) . c ?.( ) '} | ${true} | ${true} | ${true} | ${true} - ${'NonNullAssert'} | ${'TSNonNullExpression'} | ${' x ! '} | ${true} | ${true} | ${true} | ${true} + ${'NonNullAssert'} | ${'TSNonNullExpression'} | ${' ( ( ( ( x ) ) ! ) ) '} | ${true} | ${true} | ${true} | ${true} ${'PropertyRead'} | ${'Identifier'} | ${' ( ( a ) ) '} | ${true} | ${true} | ${true} | ${true} ${'PropertyRead'} | ${'Identifier'} | ${' a '} | ${true} | ${true} | ${true} | ${true} ${'PropertyRead'} | ${'Identifier'} | ${' a // hello '} | ${true} | ${true} | ${true} | ${true} From 3e0e968c7d3768d950f33ec33507be2031d7b0dd Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:35:54 +0800 Subject: [PATCH 24/34] Fix coverage --- src/transform-node.ts | 16 ++++---- src/transform-template-binding.ts | 3 +- tests/__snapshots__/transform.test.ts.snap | 46 ---------------------- 3 files changed, 8 insertions(+), 57 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 9b670f48..bbbbe975 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -112,7 +112,7 @@ class Transformer extends Source { if (node instanceof angular.Interpolation) { const { expressions } = node; - /* c8 ignore next 3 */ + /* c8 ignore next 3 @preserve */ if (expressions.length !== 1) { throw new Error("Unexpected 'Interpolation'"); } @@ -442,10 +442,10 @@ class Transformer extends Source { ? 'typeof' : node instanceof angular.VoidExpression ? 'void' - : /* c8 ignore next */ + : /* c8 ignore next @preserve */ undefined; - /* c8 ignore next 3 */ + /* c8 ignore next 3 @preserve */ if (!operator) { throw new Error('Unexpected expression.'); } @@ -455,7 +455,7 @@ class Transformer extends Source { if (operator === 'typeof' || operator === 'void') { const index = this.text.lastIndexOf(operator, start); - /* c8 ignore next 7 */ + /* c8 ignore next 7 @preserve */ if (index === -1) { throw new Error( `Cannot find operator '${operator}' from index ${start} in ${JSON.stringify( @@ -574,15 +574,13 @@ class Transformer extends Source { } if (node instanceof angular.TemplateLiteral) { - const { elements, expressions } = node; - return this.#create( { type: 'TemplateLiteral', - quasis: elements.map((element) => + quasis: node.elements.map((element) => this.transform(element, childTransformOptions), ), - expressions: expressions.map((expression) => + expressions: node.expressions.map((expression) => this.transform(expression, childTransformOptions), ), ...node.sourceSpan, @@ -621,7 +619,7 @@ class Transformer extends Source { return this.transform(node.expression, childTransformOptions); } - /* c8 ignore next */ + /* c8 ignore next @preserve */ throw new Error(`Unexpected node type '${node.constructor.name}'`); } } diff --git a/src/transform-template-binding.ts b/src/transform-template-binding.ts index 509a9174..7e1acfbc 100644 --- a/src/transform-template-binding.ts +++ b/src/transform-template-binding.ts @@ -55,9 +55,8 @@ class TemplateBindingTransformer extends NodeTransformer { #create( properties: Partial & { type: T['type'] } & RawNGSpan, - { stripSpaces = true } = {}, ) { - return this.createNode(properties, { stripSpaces }); + return this.createNode(properties); } #transform(node: angular.AST) { diff --git a/tests/__snapshots__/transform.test.ts.snap b/tests/__snapshots__/transform.test.ts.snap index 5e1b1f18..825674b4 100644 --- a/tests/__snapshots__/transform.test.ts.snap +++ b/tests/__snapshots__/transform.test.ts.snap @@ -38,29 +38,6 @@ Identifier { | ^ `; -exports[`('BindingPipe' -> 'NGPipeExpression') > parseBinding(" a | b ") 1`] = ` -NGPipeExpression { - "left": "Identifier", - "right": "Identifier", - "arguments": [], - "comments": [] -} -> 1 | a | b - | ^^^^^ --------------------------------------------------------------------------------- -Identifier { - "name": "a" -} -> 1 | a | b - | ^ --------------------------------------------------------------------------------- -Identifier { - "name": "b" -} -> 1 | a | b - | ^ -`; - exports[`('BindingPipe' -> 'NGPipeExpression') > parseBinding(" a | b : c ") 1`] = ` NGPipeExpression { "left": "Identifier", @@ -130,29 +107,6 @@ Identifier { | ^ `; -exports[`('BindingPipe' -> 'NGPipeExpression') > parseInterpolationExpression(" a | b ") 1`] = ` -NGPipeExpression { - "left": "Identifier", - "right": "Identifier", - "arguments": [], - "comments": [] -} -> 1 | a | b - | ^^^^^ --------------------------------------------------------------------------------- -Identifier { - "name": "a" -} -> 1 | a | b - | ^ --------------------------------------------------------------------------------- -Identifier { - "name": "b" -} -> 1 | a | b - | ^ -`; - exports[`('BindingPipe' -> 'NGPipeExpression') > parseInterpolationExpression(" a | b : c ") 1`] = ` NGPipeExpression { "left": "Identifier", From 01cbbe15124b6d7f054b3ec827a1c4ac9f7fff6e Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:38:22 +0800 Subject: [PATCH 25/34] Remove dead code --- src/__snapshots__/utils.test.ts.snap | 85 ---------------------------- src/source.ts | 25 +------- src/transform-template-binding.ts | 2 +- src/utils.test.ts | 37 +----------- src/utils.ts | 65 --------------------- 5 files changed, 4 insertions(+), 210 deletions(-) delete mode 100644 src/__snapshots__/utils.test.ts.snap diff --git a/src/__snapshots__/utils.test.ts.snap b/src/__snapshots__/utils.test.ts.snap deleted file mode 100644 index ebfc80ba..00000000 --- a/src/__snapshots__/utils.test.ts.snap +++ /dev/null @@ -1,85 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`fitSpans > start: 0, end: 15 1`] = ` -origin -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^^^ -inner -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -outer hasParens=false -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -`; - -exports[`fitSpans > start: 1, end: 14 1`] = ` -origin -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -inner -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -outer hasParens=false -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -`; - -exports[`fitSpans > start: 2, end: 13 1`] = ` -origin -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^ -inner -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^ -outer hasParens=true -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -`; - -exports[`fitSpans > start: 3, end: 12 1`] = ` -origin -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^ -inner -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^ -outer hasParens=true -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -`; - -exports[`fitSpans > start: 4, end: 11 1`] = ` -origin -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^ -inner -> 1 | ( ( ( 1 ) ) ) - | ^^^^^ -outer hasParens=true -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -`; - -exports[`fitSpans > start: 5, end: 10 1`] = ` -origin -> 1 | ( ( ( 1 ) ) ) - | ^^^^^ -inner -> 1 | ( ( ( 1 ) ) ) - | ^^^^^ -outer hasParens=true -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -`; - -exports[`fitSpans > start: 6, end: 9 1`] = ` -origin -> 1 | ( ( ( 1 ) ) ) - | ^^^ -inner -> 1 | ( ( ( 1 ) ) ) - | ^ -outer hasParens=true -> 1 | ( ( ( 1 ) ) ) - | ^^^^^^^^^^^^^ -`; diff --git a/src/source.ts b/src/source.ts index daf53a00..a1dd354d 100644 --- a/src/source.ts +++ b/src/source.ts @@ -23,29 +23,8 @@ export class Source { return getCharacterLastIndex(this.text, pattern, index); } - transformSpan( - span: RawNGSpan, - { stripSpaces = false, isInParentParens = false } = {}, - ): LocationInformation { - if (!stripSpaces) { - return sourceSpanToLocationInformation(span); - } - - const { outerSpan, innerSpan, hasParens } = fitSpans( - span, - this.text, - isInParentParens, - ); - const locationInformation = sourceSpanToLocationInformation(innerSpan); - if (hasParens) { - locationInformation.extra = { - parenthesized: true, - parenStart: outerSpan.start, - parenEnd: outerSpan.end, - }; - } - - return locationInformation; + transformSpan(span: RawNGSpan): LocationInformation { + return sourceSpanToLocationInformation(span); } createNode( diff --git a/src/transform-template-binding.ts b/src/transform-template-binding.ts index 7e1acfbc..19ce3200 100644 --- a/src/transform-template-binding.ts +++ b/src/transform-template-binding.ts @@ -174,7 +174,7 @@ class TemplateBindingTransformer extends NodeTransformer { const expression = updateExpressionAlias(lastNode.expression); body.push(updateSpanEnd({ ...lastNode, expression }, expression.end)); } else { - /* c8 ignore next 2 */ + /* c8 ignore next 2 @preserve */ throw new Error(`Unexpected type ${lastNode.type}`); } } else { diff --git a/src/utils.test.ts b/src/utils.test.ts index ca171a1f..abf4ce0f 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,39 +1,4 @@ -import { codeFrameColumns } from '@babel/code-frame'; -import { wrap } from 'jest-snapshot-serializer-raw'; - -import type { RawNGSpan } from './types.ts'; -import { fitSpans, getCharacterIndex, getCharacterLastIndex } from './utils.ts'; - -const text = ` ( ( ( 1 ) ) ) `; -const length = Math.floor(text.length / 2); - -describe('fitSpans', () => { - for (let i = 0; i < length; i++) { - const start = i; - const end = text.length - i; - test(`start: ${start}, end: ${end}`, () => { - const { innerSpan, outerSpan, hasParens } = fitSpans( - { start, end }, - text, - false, - ); - const show = ({ start: startColumn, end: endColumn }: RawNGSpan) => - codeFrameColumns(text, { - start: { line: 1, column: startColumn + 1 }, - end: { line: 1, column: endColumn + 1 }, - }); - const snapshot = [ - 'origin', - show({ start, end }), - 'inner', - show(innerSpan), - `outer hasParens=${hasParens}`, - show(outerSpan), - ].join('\n'); - expect(wrap(snapshot)).toMatchSnapshot(); - }); - } -}); +import { getCharacterIndex, getCharacterLastIndex } from './utils.ts'; test('getCharacterIndex', () => { expect(getCharacterIndex('foobar', /o/, 0)).toBe(1); diff --git a/src/utils.ts b/src/utils.ts index 9c327e0e..76e07f86 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,23 +1,5 @@ import type { LocationInformation, RawNGSpan } from './types.ts'; -function stripSurroundingSpaces( - { start: startIndex, end: endIndex }: RawNGSpan, - text: string, -) { - let start = startIndex; - let end = endIndex; - - while (end !== start && /\s/.test(text[end - 1])) { - end--; - } - - while (start !== end && /\s/.test(text[start])) { - start++; - } - - return { start, end }; -} - function expandSurroundingSpaces( { start: startIndex, end: endIndex }: RawNGSpan, text: string, @@ -36,53 +18,6 @@ function expandSurroundingSpaces( return { start, end }; } -function expandSurroundingParens(span: RawNGSpan, text: string) { - return text[span.start - 1] === '(' && text[span.end] === ')' - ? { start: span.start - 1, end: span.end + 1 } - : span; -} - -export function fitSpans( - span: RawNGSpan, - text: string, - hasParentParens: boolean, -): { outerSpan: RawNGSpan; innerSpan: RawNGSpan; hasParens: boolean } { - let parensCount = 0; - - const outerSpan = { start: span.start, end: span.end }; - - while (true) { - const spacesExpandedSpan = expandSurroundingSpaces(outerSpan, text); - const parensExpandedSpan = expandSurroundingParens( - spacesExpandedSpan, - text, - ); - - if ( - spacesExpandedSpan.start === parensExpandedSpan.start && - spacesExpandedSpan.end === parensExpandedSpan.end - ) { - break; - } - - outerSpan.start = parensExpandedSpan.start; - outerSpan.end = parensExpandedSpan.end; - - parensCount++; - } - - return { - hasParens: (hasParentParens ? parensCount - 1 : parensCount) !== 0, - outerSpan: stripSurroundingSpaces( - hasParentParens - ? { start: outerSpan.start + 1, end: outerSpan.end - 1 } - : outerSpan, - text, - ), - innerSpan: stripSurroundingSpaces(span, text), - }; -} - function getCharacterSearchTestFunction(pattern: RegExp | string) { if (typeof pattern === 'string') { return (character: string) => character === pattern; From d81d0c462a2f10e1b81be15e05074b48ffcab23d Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:40:59 +0800 Subject: [PATCH 26/34] Fix PIPE location --- src/transform-node.ts | 15 +++++---------- src/utils.ts | 18 ------------------ tests/__snapshots__/transform.test.ts.snap | 4 ++-- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index bbbbe975..f9252707 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -178,12 +178,11 @@ class Transformer extends Source { } if (node instanceof angular.BindingPipe) { - const { exp: expressionNode, name, args: originalArguments } = node; + const { name } = node; const left = this.transform( - expressionNode, + node.exp, childTransformOptions, ); - const start = getOuterStart(left); const leftEnd = getOuterEnd(left); const rightStart = this.getCharacterIndex( /\S/, @@ -198,7 +197,7 @@ class Transformer extends Source { }, ancestors, ); - const argumentNodes = originalArguments.map((node) => + const arguments_ = node.args.map((node) => this.transform(node, childTransformOptions), ); return this.#create( @@ -206,12 +205,8 @@ class Transformer extends Source { type: 'NGPipeExpression', left, right, - arguments: argumentNodes, - start, - end: getOuterEnd( - // TODO[@fisker]: End seems not correct, since there should be `()` - argumentNodes.length === 0 ? right : argumentNodes.at(-1)!, - ), + arguments: arguments_, + ...node.sourceSpan, }, ancestors, ); diff --git a/src/utils.ts b/src/utils.ts index 76e07f86..6cbd9ab8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,23 +1,5 @@ import type { LocationInformation, RawNGSpan } from './types.ts'; -function expandSurroundingSpaces( - { start: startIndex, end: endIndex }: RawNGSpan, - text: string, -) { - let start = startIndex; - let end = endIndex; - - while (end !== text.length && /\s/.test(text[end])) { - end++; - } - - while (start !== 0 && /\s/.test(text[start - 1])) { - start--; - } - - return { start, end }; -} - function getCharacterSearchTestFunction(pattern: RegExp | string) { if (typeof pattern === 'string') { return (character: string) => character === pattern; diff --git a/tests/__snapshots__/transform.test.ts.snap b/tests/__snapshots__/transform.test.ts.snap index 825674b4..70888c23 100644 --- a/tests/__snapshots__/transform.test.ts.snap +++ b/tests/__snapshots__/transform.test.ts.snap @@ -13,7 +13,7 @@ NGPipeExpression { "comments": [] } > 1 | ( ( ( ( a ) ) | b )) - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- Identifier { "name": "a", @@ -82,7 +82,7 @@ NGPipeExpression { "comments": [] } > 1 | ( ( ( ( a ) ) | b )) - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- Identifier { "name": "a", From df173024d26927f8ef9d00c12abd6b0596f04380 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:43:58 +0800 Subject: [PATCH 27/34] Simplify --- src/transform-node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index f9252707..58ed1c83 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -183,7 +183,7 @@ class Transformer extends Source { node.exp, childTransformOptions, ); - const leftEnd = getOuterEnd(left); + const leftEnd = node.exp.sourceSpan.end; const rightStart = this.getCharacterIndex( /\S/, this.getCharacterIndex('|', leftEnd) + 1, From 8b564a1eea8dfea9a02802f96726e8109dfc260b Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:50:06 +0800 Subject: [PATCH 28/34] Kill `getOuterEnd` --- src/transform-node.ts | 18 +++++++----------- tests/transform.test.ts | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 58ed1c83..30634a26 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -17,9 +17,6 @@ function isParenthesized(node: NGNode) { function getOuterStart(node: NGNode): number { return isParenthesized(node) ? node.extra.parenStart : node.start!; } -function getOuterEnd(node: NGNode): number { - return isParenthesized(node) ? node.extra.parenEnd : node.end!; -} function isOptionalObjectOrCallee(node: NGNode): boolean { if (node.type === 'TSNonNullExpression' && !isParenthesized(node)) { @@ -275,20 +272,15 @@ class Transformer extends Source { if (node instanceof angular.LiteralMap) { const { keys, values } = node; - const tValues = values.map((value) => - this.transform(value, childTransformOptions), - ); const tProperties = keys.map((property, index) => { const { key, quoted } = property; - const tValue = tValues[index]; - const valueStart = getOuterStart(tValue); - const valueEnd = getOuterEnd(tValue); + const { start: valueStart, end: valueEnd } = values[index].sourceSpan; const keyStart = this.getCharacterIndex( /\S/, index === 0 ? node.sourceSpan.start + 1 // { - : this.getCharacterIndex(',', getOuterEnd(tValues[index - 1])) + 1, + : this.getCharacterIndex(',', values[index - 1].sourceSpan.end) + 1, ); const keyEnd = valueStart === keyStart @@ -316,12 +308,16 @@ class Transformer extends Source { [], ); const shorthand = tKey.end < tKey.start || keyStart === valueStart; + const value = this.transform( + values[index], + childTransformOptions, + ); return this.#create( { type: 'ObjectProperty', key: tKey, - value: tValue, + value, shorthand, computed: false, start: getOuterStart(tKey), diff --git a/tests/transform.test.ts b/tests/transform.test.ts index 8f24735b..2073e85c 100644 --- a/tests/transform.test.ts +++ b/tests/transform.test.ts @@ -58,7 +58,7 @@ describe.each` ${'SafeKeyedRead'} | ${'OptionalMemberExpression'} | ${' a ?. b () ?. [ c ] '} | ${true} | ${true} | ${true} | ${true} ${'ImplicitReceiver'} | ${'ThisExpression'} | ${' this '} | ${true} | ${true} | ${true} | ${true} ${'LiteralArray'} | ${'ArrayExpression'} | ${' [ 1 ] '} | ${true} | ${true} | ${true} | ${true} - ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { "a" : 1 } )'} | ${true} | ${true} | ${true} | ${true} + ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( ( { "a" : ( ( 1 ) ) } ) )'} | ${true} | ${true} | ${true} | ${true} ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( { a : 1 } ) '} | ${true} | ${true} | ${true} | ${true} ${'Call'} | ${'CallExpression'} | ${' f ( { a : 1 } ) '} | ${true} | ${true} | ${true} | ${true} ${'LiteralMap'} | ${'ObjectExpression'} | ${' ( {a, b: 2} ) '} | ${true} | ${true} | ${true} | ${true} From 3ddd98f839e10dddfe18777c013b07b5210b9ab9 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:51:03 +0800 Subject: [PATCH 29/34] Kill `getOuterStart` --- src/transform-node.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 30634a26..689f13e9 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -14,9 +14,6 @@ import type { function isParenthesized(node: NGNode) { return Boolean(node.extra?.parenthesized); } -function getOuterStart(node: NGNode): number { - return isParenthesized(node) ? node.extra.parenStart : node.start!; -} function isOptionalObjectOrCallee(node: NGNode): boolean { if (node.type === 'TSNonNullExpression' && !isParenthesized(node)) { @@ -320,7 +317,7 @@ class Transformer extends Source { value, shorthand, computed: false, - start: getOuterStart(tKey), + start: tKey.start, end: valueEnd, }, [], From f63e57c27bffc5ffcc543c4ff96573b1110a2b0d Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 02:59:38 +0800 Subject: [PATCH 30/34] Remove `paren{Start,End}` --- src/transform-node.ts | 32 ++-------------------- tests/__snapshots__/transform.test.ts.snap | 24 ++++------------ tests/helpers.ts | 5 ++++ 3 files changed, 13 insertions(+), 48 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index 689f13e9..a149f553 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -32,33 +32,6 @@ function isImplicitThis(node: angular.AST, text: string): boolean { return start >= end || /^\s+$/.test(text.slice(start, end)); } -function getParenthesizedInformation(ancestors: angular.AST[]) { - const parenthesizedExpression = - getOutermostParenthesizedExpression(ancestors); - - if (!parenthesizedExpression) { - return; - } - - return { - parenthesized: true, - parenStart: parenthesizedExpression.sourceSpan.start, - parenEnd: parenthesizedExpression.sourceSpan.end, - }; -} - -function getOutermostParenthesizedExpression(ancestors: angular.AST[]) { - for (const [index, node] of ancestors.entries()) { - if (!(node instanceof angular.ParenthesizedExpression)) { - return; - } - - if (!(ancestors[index + 1] instanceof angular.ParenthesizedExpression)) { - return node; - } - } -} - type NodeTransformOptions = { ancestors: angular.AST[]; }; @@ -85,11 +58,10 @@ class Transformer extends Source { ) { const node = this.createNode(properties); - const parenthesizedInformation = getParenthesizedInformation(ancestors); - if (parenthesizedInformation) { + if (ancestors[0] instanceof angular.ParenthesizedExpression) { node.extra = { ...node.extra, - ...parenthesizedInformation, + parenthesized: true, }; } diff --git a/tests/__snapshots__/transform.test.ts.snap b/tests/__snapshots__/transform.test.ts.snap index 70888c23..9c95cf2b 100644 --- a/tests/__snapshots__/transform.test.ts.snap +++ b/tests/__snapshots__/transform.test.ts.snap @@ -6,9 +6,7 @@ NGPipeExpression { "right": "Identifier", "arguments": [], "extra": { - "parenthesized": true, - "parenStart": 1, - "parenEnd": 23 + "parenthesized": true }, "comments": [] } @@ -18,9 +16,7 @@ NGPipeExpression { Identifier { "name": "a", "extra": { - "parenthesized": true, - "parenStart": 5, - "parenEnd": 14 + "parenthesized": true } } > 1 | ( ( ( ( a ) ) | b )) @@ -29,9 +25,7 @@ Identifier { Identifier { "name": "b", "extra": { - "parenthesized": true, - "parenStart": 1, - "parenEnd": 23 + "parenthesized": true } } > 1 | ( ( ( ( a ) ) | b )) @@ -75,9 +69,7 @@ NGPipeExpression { "right": "Identifier", "arguments": [], "extra": { - "parenthesized": true, - "parenStart": 1, - "parenEnd": 23 + "parenthesized": true }, "comments": [] } @@ -87,9 +79,7 @@ NGPipeExpression { Identifier { "name": "a", "extra": { - "parenthesized": true, - "parenStart": 5, - "parenEnd": 14 + "parenthesized": true } } > 1 | ( ( ( ( a ) ) | b )) @@ -98,9 +88,7 @@ Identifier { Identifier { "name": "b", "extra": { - "parenthesized": true, - "parenStart": 1, - "parenEnd": 23 + "parenthesized": true } } > 1 | ( ( ( ( a ) ) | b )) diff --git a/tests/helpers.ts b/tests/helpers.ts index 2ff7e7b6..0017e6a2 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -46,6 +46,10 @@ export function massageAst(ast: any, parser: 'babel' | 'angular'): any { return ast.map((node) => massageAst(node, parser)); } + if (parser === 'babel' && typeof ast.extra?.parenStart === 'number') { + delete ast.extra.parenStart; + } + // Not exists in types, but exists in node. if (ast.type === 'ObjectProperty') { if (ast.method !== undefined && ast.method !== false) { @@ -54,6 +58,7 @@ export function massageAst(ast: any, parser: 'babel' | 'angular'): any { ); } delete ast.method; + if ( ast.shorthand && ast.extra && From 9a16ccf0e53658d51df4d931f2352a5c7e55fd19 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 03:02:10 +0800 Subject: [PATCH 31/34] Remove dead code --- src/source.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/source.ts b/src/source.ts index a1dd354d..c79701ba 100644 --- a/src/source.ts +++ b/src/source.ts @@ -2,7 +2,6 @@ import type * as babel from '@babel/types'; import type { LocationInformation, NGNode, RawNGSpan } from './types.ts'; import { - fitSpans, getCharacterIndex, getCharacterLastIndex, sourceSpanToLocationInformation, From 354ae1a4000d6ca6219012fc3f8c6be572c8a447 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 03:11:49 +0800 Subject: [PATCH 32/34] Remove `isImplicitThis` --- .gitignore | 2 +- src/transform-node.ts | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 1ab1310c..0e524cb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ coverage/ -dist/ +dist*/ node_modules/ *.log .yarn/* diff --git a/src/transform-node.ts b/src/transform-node.ts index a149f553..fc49c376 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -27,21 +27,13 @@ function isOptionalObjectOrCallee(node: NGNode): boolean { ); } -function isImplicitThis(node: angular.AST, text: string): boolean { - const { start, end } = node.sourceSpan; - return start >= end || /^\s+$/.test(text.slice(start, end)); -} - type NodeTransformOptions = { ancestors: angular.AST[]; }; class Transformer extends Source { - #text; - constructor(text: string) { super(text); - this.#text = text; } transform( @@ -457,24 +449,27 @@ class Transformer extends Source { const { receiver } = node; - const implicit = isImplicitThis(receiver, this.#text); + let isImplicitThis; let property; if (isComputed) { + isImplicitThis = node.sourceSpan.start === node.key.sourceSpan.start; property = this.transform(node.key); } else { - const { name } = node; + const { name, nameSpan } = node; + + isImplicitThis = node.sourceSpan.start === nameSpan.start; property = this.#create( { type: 'Identifier', name, ...node.nameSpan, }, - implicit ? ancestors : [], + isImplicitThis ? ancestors : [], ); } - if (implicit || receiver.sourceSpan.start === property.start) { + if (isImplicitThis) { return property; } From d8d15d081a36ec001797a68661094ecbe0596d41 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 03:13:39 +0800 Subject: [PATCH 33/34] Call super class methods with `super` --- src/transform-node.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/transform-node.ts b/src/transform-node.ts index fc49c376..26ad0493 100644 --- a/src/transform-node.ts +++ b/src/transform-node.ts @@ -48,7 +48,7 @@ class Transformer extends Source { properties: Partial & { type: T['type'] } & RawNGSpan, ancestors: angular.AST[], ) { - const node = this.createNode(properties); + const node = super.createNode(properties); if (ancestors[0] instanceof angular.ParenthesizedExpression) { node.extra = { @@ -142,9 +142,9 @@ class Transformer extends Source { childTransformOptions, ); const leftEnd = node.exp.sourceSpan.end; - const rightStart = this.getCharacterIndex( + const rightStart = super.getCharacterIndex( /\S/, - this.getCharacterIndex('|', leftEnd) + 1, + super.getCharacterIndex('|', leftEnd) + 1, ); const right = this.#create( { @@ -237,18 +237,19 @@ class Transformer extends Source { const { key, quoted } = property; const { start: valueStart, end: valueEnd } = values[index].sourceSpan; - const keyStart = this.getCharacterIndex( + const keyStart = super.getCharacterIndex( /\S/, index === 0 ? node.sourceSpan.start + 1 // { - : this.getCharacterIndex(',', values[index - 1].sourceSpan.end) + 1, + : super.getCharacterIndex(',', values[index - 1].sourceSpan.end) + + 1, ); const keyEnd = valueStart === keyStart ? valueEnd - : this.getCharacterLastIndex( + : super.getCharacterLastIndex( /\S/, - this.getCharacterLastIndex(':', valueStart - 1) - 1, + super.getCharacterLastIndex(':', valueStart - 1) - 1, ) + 1; const keySpan = { start: keyStart, end: keyEnd }; const tKey = quoted From bf0c480d4f2379db865eddb10a80add360d70fd6 Mon Sep 17 00:00:00 2001 From: fisker Date: Wed, 17 Dec 2025 03:18:05 +0800 Subject: [PATCH 34/34] Coverage --- src/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 6cbd9ab8..2d4f37dd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -23,7 +23,7 @@ export function getCharacterLastIndex( } } - /* c8 ignore next 4 */ + /* c8 ignore next 4 @preserve */ throw new Error( `Cannot find front char ${pattern} from index ${fromIndex} in ${JSON.stringify( text, @@ -46,6 +46,7 @@ export function getCharacterIndex( } } + /* c8 ignore next 4 @preserve */ throw new Error( `Cannot find character ${pattern} from index ${fromIndex} in ${JSON.stringify( text,