Skip to content

feat(transform): add ngtsc's implicit signal debugName - #658

Merged
Brooooooklyn merged 2 commits into
mainfrom
feat/signal-debug-name
Oct 29, 2026
Merged

Brooooooklyn merged 2 commits into
mainfrom
feat/signal-debug-name

Conversation

@Brooooooklyn

Copy link
Copy Markdown
Member

Summary

Part of #623 (the debugName row, listed as known in known-differences.ts).

This ports ngtsc's signalMetadataTransform (implicit_signal_debug_name_transform.ts). Calls to signal, computed, linkedSignal, input, model, the signal queries, effect, afterRenderEffect, resource and httpResource now get an ngDevMode debugName named after their binding. This works in full and partial mode, and in files without an Angular decorator too.

Upstream changes the AST nodes themselves. So any text that gets printed a second time also gets the name:

source text  --(edits, each tied to its declaration's span)-->  file text
     |                                                     (edits inside removed text are dropped)
     +-- slice printed again elsewhere ---------------->  takes the edits of the declarations inside the slice
         - decorator args in ɵcmp / ɵprov / ɵinj / setClassMetadata (make_raw_source)
         - jit: true class: __decorate, propDecorators, __param texts
         - @Service factory
         - hoisted statements

Design note

make_raw_source and the other places that print decorator text again get the edits through a thread-local (FileDebugNames in signal_debug_name.rs). It is keyed by the source text's address and cleared on drop. The other way was to pass the edits through about 120 converter call sites. If you'd rather have them passed in explicitly, I can change it.

Before / after

Input:

@Component({ selector: 'x', template: '', providers: [{ provide: 'k', useFactory: () => { const fromProv = signal(0); return fromProv; } }] })
export class X {}

Before (broken JS, the edit landed in deleted decorator text):

import * as i0 from '@angular/core';
Mode ? [{ debugName: "fromProv" }] : /* istanbul ignore next */ [])); return fromProv; } }] })
export class X {

After (same as ngtsc, in ɵcmp and in setClassMetadata):

useFactory:() => { const fromProv = signal(0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "fromProv" }] : /* istanbul ignore next */ [])); return fromProv; }

Other shapes, matching ngtsc:

export const a = signal?.(0, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "a" }] : ...));  // optional call is named
(this.b) = signal(1);                                // parenthesized target: skipped
debugName: "[/* c */ k]"                             // computed key keeps the comments inside its brackets
@Component({ jit: true }) class X { name = input('a'); }   // input() in a jit class: not named

ngtsc parity cases checked (compiler-cli 22.2.3)

  • @Component providers with a useFactory block, @Injectable useFactory, @NgModule providers, @Input({ transform }), a class expression inside providers, and @Service factory. Checked in full and partial mode.
  • jit: true component with providers, input(), input.required(), @Input() x = input(), model(), viewChild(.., { read }), signal(), and a nameless export default jit class.
  • signal?.(0), (this.b) = signal(1), (this).c = signal(2), and computed keys with block and line comments.

Checks

  • cargo test --all-features: 3610 passed, 0 failed.
  • New Rust tests (decorator_arguments_are_named_in_every_copy, jit_forced_class, service_factory_is_named, call_and_target_shapes) fail on the previous source and pass now.
  • Conformance: 1264/1264, snapshot unchanged.
  • clippy: no warnings on changed lines.
  • napi build-test: OK. vitest: 21 files, 400 tests passed. tsc --noEmit: OK. oxfmt and oxlint on the changed TS files: clean.
  • e2e compare --fixtures: 51 matched, 0 mismatched, 0 errors.

🤖 Generated with Claude Code

Port ngtsc's `signalMetadataTransform`
(compiler-cli transform/src/implicit_signal_debug_name_transform.ts,
chunk-G5LDCSK3.js:1970-2179). NgCompiler always adds it to the `before`
emit transforms (chunk-HIDZX47I.js:4547), in full and partial mode, for
every emitted file.

`const x = signal(0)`, `x = input()` fields and `this.x = computed(...)`
statements now get an ngDevMode `debugName` named after the binding, for
signal, computed, linkedSignal, input, model, the signal queries,
effect, afterRenderEffect, resource and httpResource. The callee must be
a named import from its `@angular/*` package, under the same local name.
Options objects get `...(ngDevMode ? { debugName } : {})` as their first
property; a missing options argument becomes a spread argument.

Upstream edits the nodes, so text printed again elsewhere is named in
every copy: decorator arguments in the definitions and
`ɵsetClassMetadata` (`make_raw_source`), the JIT `__decorate` and
`propDecorators` texts of a `jit: true` class, a `@Service` factory and a
hoisted statement. Each edit keeps the span of the declaration it names;
a slice takes the edits of the declarations inside it, and the file
drops the edits in text another edit removes.

Like upstream: `signal?.(0)` is a call; `(this.x) = signal()` has a
parenthesized target and is skipped; a computed key's name keeps the
comments inside its brackets; and `input()` fields of a jit-forced
class keep no name, because the JIT `signalInputsTransform`
(initializer_api_transforms/input_function.ts) rebuilds them with a
string name first, so `node.name.getText()` throws.

The pass runs in transform_angular_file for files without an Angular
decorator too. The Vite plugin now lets a decorator-less file through
when it calls one of these APIs from Angular (not in JIT mode, which has
no such transform).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T08:10:04.260499Z f4b6a74 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e6dc08237

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…e pass

The fast path for files without an Angular decorator matched only
`signal(`-like calls, so `signal?.(0)` and `signal /* c */ (0)` skipped
the transform the Rust pass handles. Match the API name alone: the file
must still import from `@angular/core` or `@angular/common`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: f4b6a74f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn
Brooooooklyn merged commit cf04f7c into main Oct 29, 2026
10 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/signal-debug-name branch October 29, 2026 02:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant