Skip to content

fix(compiler): lower all @angular/core member decorators into JIT propDecorators - #537

Merged
Brooooooklyn merged 1 commit into
mainfrom
fix/issue-522-jit-prop-decorators
Oct 6, 2026
Merged

Brooooooklyn merged 1 commit into
mainfrom
fix/issue-522-jit-prop-decorators

Conversation

@Brooooooklyn

Copy link
Copy Markdown
Member

Fixes #522.

Problem

In JIT mode (jit: true), OXC dropped @angular/core member decorators that are not field decorators — @Inject, @Optional, @Attribute, @Self, @Host, and even @Component on a member — instead of lowering them into static propDecorators like ngtsc's downlevel_decorators_transform does.

Root cause

extract_all_jit_member_decorators bucketed member decorators into "field decorator → propDecorators", "in ANGULAR_DECORATOR_NAMES but not a field decorator → silently dropped", and "otherwise → __decorate". Upstream's gate is isAngularDecorator, which checks only decorator.import.from === '@angular/core' — the import decides, not the name — so every core decorator on a property/method/accessor (static or instance) lands in propDecorators as {type: <written callee>, args?} in source order.

Change

  • directive/property_decorators.rs: new is_angular_core_decorator — the nameless counterpart of angular_core_decorator. Named imports under any alias, default imports, and ns.X() through import * as ns from '@angular/core' all qualify; foreign/local/undeclared decorators do not.
  • component/transform.rs: every @angular/core member decorator now goes into propDecorators; the ANGULAR_DECORATOR_NAMES allowlist is deleted. Non-core decorators still go to __decorate unchanged.
  • String-literal member keys ('x-y') are now quoted in the emitted propDecorators object literal — previously spliced unquoted, producing invalid JS that made strip_typescript bail.

Example (issue repro)

@Component({selector: 'c', template: ''})
export class C {
  @Inject(TOKEN) @Optional() x: unknown;
  @Attribute('a') y: unknown;
}

Now emits (matching ngtsc 22.x):

static propDecorators = {
  x: [{type: Inject, args: [TOKEN]}, {type: Optional}],
  y: [{type: Attribute, args: ["a"]}]
};

Known remaining divergences (follow-ups, out of scope)

  • JIT ctorParameters still name-checks against PARAM_DECORATORS; upstream applies isAngularDecorator to ctor params too.
  • AOT setClassMetadata.propDecorators stays field-decorator-only; upstream decoratedClassMemberToMetadata uses the import check there as well.
  • Upstream prepends synthesized signal-API decorators ahead of explicit ones; OXC appends (cosmetic ordering).

Testing

  • cargo test -p oxc_angular_compiler: all green (~2900 tests), including a renamed/updated test_jit_angular_param_decorators_go_to_prop_decorators, an updated aliased-import test, and a new member_decorators_from_angular_core_all_go_to_prop_decorators covering @Inject(T) @Optional(), @Attribute('a'), mixed @Input() @Inject(T), namespaced @core.Host()/@core.Component(), and string-literal keys.
  • Conformance: 1264/1264 PASS.
  • Verified end-to-end through the napi binding (transformAngularFileSync(..., {jit: true})): the issue repro, static/method/accessor members, and aliased/namespace imports all emit propDecorators matching ngtsc.

…pDecorators

In JIT mode, OXC dropped @angular/core member decorators that are not
field decorators (@Inject, @optional, @Attribute, @self, @host, and even
@component on a member) instead of lowering them into `static
propDecorators` like ngtsc's `downlevel_decorators_transform` does.

ngtsc's `isAngularDecorator` checks only `decorator.import.from ===
'@angular/core'` — the import decides, not the decorator name — and every
matching member decorator (properties, methods, get/set accessors,
static or instance) lands in `propDecorators` as `{type, args?}` in
source order.

Add `is_angular_core_decorator` (directive/property_decorators.rs), the
nameless counterpart of `angular_core_decorator`: any named or default
import from `@angular/core` (under any alias) or `ns.X()` through a
namespace import qualifies. `extract_all_jit_member_decorators` now
routes every core decorator into `propDecorators`; decorators from
other modules keep going to `__decorate` as before.

Also quote string-literal member keys ('x-y') in the emitted
propDecorators object literal — previously they were spliced unquoted,
producing invalid JS that made `strip_typescript` bail.

Fixes #522
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

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.

fix(jit): @Inject / @Optional / @Attribute on a class member are dropped instead of lowered to propDecorators

1 participant