From 24f91ba914e74d28b949164040b7fc42aab18d79 Mon Sep 17 00:00:00 2001 From: P4 <969645+P4@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:06:27 +0200 Subject: [PATCH 1/2] fix(compiler-cli): include toSignal in debugName transform the toSignal function received a debugName option in 0812ac3bec9b9f2fabba5a379e6626aa641a97c9, but was not covered by the signalMetadataTransform which sets the debugName in dev mode automatically. --- .../implicit_signal_debug_name_transform.ts | 1 + packages/compiler-cli/test/ngtsc/BUILD.bazel | 1 + .../test/ngtsc/debug_transform_spec.ts | 337 ++++++++++++++++++ 3 files changed, 339 insertions(+) diff --git a/packages/compiler-cli/src/ngtsc/transform/src/implicit_signal_debug_name_transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/implicit_signal_debug_name_transform.ts index 9fe48a2e082a..1de46e71599b 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/implicit_signal_debug_name_transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/implicit_signal_debug_name_transform.ts @@ -231,6 +231,7 @@ const signalFunctions: ReadonlyMap = new Map([ ['contentChild', 'core'], ['contentChildren', 'core'], ['effect', 'core'], + ['toSignal', 'core'], ['resource', 'core'], ['httpResource', 'common'], ]); diff --git a/packages/compiler-cli/test/ngtsc/BUILD.bazel b/packages/compiler-cli/test/ngtsc/BUILD.bazel index 779f0a94a70a..9f4ce7d42001 100644 --- a/packages/compiler-cli/test/ngtsc/BUILD.bazel +++ b/packages/compiler-cli/test/ngtsc/BUILD.bazel @@ -30,6 +30,7 @@ jasmine_test( timeout = "long", data = [ ":ngtsc_lib", + "//:node_modules/rxjs", "//:node_modules/yargs", "//packages/compiler-cli/src/ngtsc/testing/fake_common:npm_package", "//packages/core:npm_package", diff --git a/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts b/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts index 4fb520c9e37a..65295f923860 100644 --- a/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts +++ b/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts @@ -14,6 +14,7 @@ import {NgtscTestEnvironment} from './env'; const testFiles = loadStandardTestFiles({ fakeCommon: true, + rxjs: true, }); const minifiedDevBuildOptions = { @@ -2985,5 +2986,341 @@ runInEachFileSystem(() => { }); }); }); + + describe('toSignal', () => { + it('should not insert debug info into toSignal function if not imported from angular core', () => { + env.write( + 'test.ts', + ` + import {of} from 'rxjs'; + declare function toSignal(source: any): any; + + @Component({ + template: '' + }) class MyComponent { + testSignal = toSignal(of(0)); + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).not.toContain('debugName'); + }); + + it('should insert debug info into toSignal function if imported from angular core', () => { + env.write( + 'test.ts', + ` + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent { + testSignal = toSignal(of(0)); + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(cleanNewLines(jsContents)).toContain( + `toSignal(of(0), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testSignal" }] : /* istanbul ignore next */ []))`, + ); + }); + + describe('Property Declaration Case', () => { + it('should tree-shake away debug info if in prod mode', async () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal = toSignal(of(0)); + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; + expect(builtContent).not.toContain('debugName'); + expect(cleanNewLines(builtContent)).toContain('toSignal( of(0) )'); + }); + + it('should not tree-shake away debug info if in dev mode', async () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal = toSignal(of(0)); + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; + expect(cleanNewLines(builtContent)).toContain( + `toSignal( of(0), { debugName: "testSignal" } )`, + ); + }); + + it('should insert debug info into toSignal function that already has custom options', async () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal = toSignal(of(0), { initialValue: 0 }); + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(cleanNewLines(jsContents)).toContain( + `toSignal(of(0), { ...(ngDevMode ? { debugName: "testSignal" } : /* istanbul ignore next */ {}), initialValue: 0 })`, + ); + }); + + it('should tree-shake away debug info if in prod mode for toSignal function that has custom options', async () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal = toSignal(of(0), { initialValue: 0 }); + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; + expect(cleanNewLines(builtContent)).toContain(`toSignal(of(0), { initialValue: 0 })`); + expect(builtContent).not.toContain('ngDevMode'); + expect(builtContent).not.toContain('debugName'); + }); + + it('should not tree-shake away debug info if in dev mode and has custom options', async () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal = toSignal(of(0), { initialValue: 0 }); + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; + expect(cleanNewLines(builtContent)).toContain( + `toSignal(of(0), { debugName: "testSignal", initialValue: 0 })`, + ); + }); + }); + + describe('Property Assignment Case', () => { + it('should insert debug info into toSignal function', () => { + env.write( + 'test.ts', + ` + import {Component, Signal} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal: Signal; + constructor() { + this.testSignal = toSignal(of(0)); + } + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(cleanNewLines(jsContents)).toContain( + `toSignal(of(0), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testSignal" }] : /* istanbul ignore next */ [])`, + ); + }); + + it('should tree-shake away debug info if in prod mode', async () => { + env.write( + 'test.ts', + ` + import {Component, Signal} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal: Signal; + constructor() { + this.testSignal = toSignal(of(0)); + } + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; + expect(builtContent).not.toContain('debugName'); + expect(cleanNewLines(builtContent)).toContain('toSignal( of(0) )'); + }); + + it('should not tree-shake away debug info if in dev mode', async () => { + env.write( + 'test.ts', + ` + import {Component, Signal} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal: Signal; + constructor() { + this.testSignal = toSignal(of(0)); + } + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; + expect(cleanNewLines(builtContent)).toContain( + `toSignal( of(0), { debugName: "testSignal" } )`, + ); + }); + + it('should insert debug info into toSignal function that already has custom options', async () => { + env.write( + 'test.ts', + ` + import {Component, Signal} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal: Signal; + constructor() { + this.testSignal = toSignal(of(0), { initialValue: 0 }); + } + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(cleanNewLines(jsContents)).toContain( + `toSignal(of(0), { ...(ngDevMode ? { debugName: "testSignal" } : /* istanbul ignore next */ {}), initialValue: 0 })`, + ); + }); + + it('should tree-shake away debug info if in prod mode for toSignal function that has custom options', async () => { + env.write( + 'test.ts', + ` + import {Component, Signal} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal: Signal; + constructor() { + this.testSignal = toSignal(of(0), { initialValue: 0 }); + } + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; + expect(cleanNewLines(builtContent)).toContain(`toSignal(of(0), { initialValue: 0 })`); + expect(builtContent).not.toContain('ngDevMode'); + expect(builtContent).not.toContain('debugName'); + }); + + it('should not tree-shake away debug info if in dev mode and has custom options', async () => { + env.write( + 'test.ts', + ` + import {Component, Signal} from '@angular/core'; + import {toSignal} from '@angular/core/rxjs-interop'; + import {of} from 'rxjs'; + + @Component({ + template: '' + }) class MyComponent + { + testSignal: Signal; + constructor() { + this.testSignal = toSignal(of(0), { initialValue: 0 }); + } + } + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; + expect(cleanNewLines(builtContent)).toContain( + `toSignal(of(0), { debugName: "testSignal", initialValue: 0 })`, + ); + }); + }); + }); }); }); From 04af5e22f75e566d9e2a5172f5cc308f236fdf19 Mon Sep 17 00:00:00 2001 From: P4 <969645+P4@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:04:23 +0200 Subject: [PATCH 2/2] fixup! fix(compiler-cli): include toSignal in debugName transform --- packages/compiler-cli/test/ngtsc/debug_transform_spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts b/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts index 65295f923860..e6ba15044a46 100644 --- a/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts +++ b/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts @@ -2992,6 +2992,7 @@ runInEachFileSystem(() => { env.write( 'test.ts', ` + import {Component} from '@angular/core'; import {of} from 'rxjs'; declare function toSignal(source: any): any; @@ -3012,6 +3013,7 @@ runInEachFileSystem(() => { env.write( 'test.ts', ` + import {Component} from '@angular/core'; import {toSignal} from '@angular/core/rxjs-interop'; import {of} from 'rxjs';