Skip to content

fix(vite-plugin-angular): @Service decorator support and conformance coverage#2338

Merged
brandonroberts merged 12 commits into
betafrom
fix/fast-compiler-conformance-coverage
May 15, 2026
Merged

fix(vite-plugin-angular): @Service decorator support and conformance coverage#2338
brandonroberts merged 12 commits into
betafrom
fix/fast-compiler-conformance-coverage

Conversation

@brandonroberts

@brandonroberts brandonroberts commented May 15, 2026

Copy link
Copy Markdown
Member

PR Checklist

Adds Angular 22 @Service decorator support to the vite-plugin-angular fast compiler and expands its Angular conformance-suite coverage.

Closes #

Affected scope

  • Primary scope: vite-plugin-angular
  • Secondary scopes: —

Recommended merge strategy for maintainer [optional]

  • Squash merge
  • Rebase merge
  • Other

Commit preservation note [optional]

The 10 commits are deliberately focused — one fix for the @Service compiler change, one commit per conformance category wired in, the drift-detector test, and a docs commit. Squashing collapses independently-reviewable, independently-bisectable units (each category can be reverted on its own) into one opaque change. Rebase preserves those boundaries.

What is the new behavior?

  • @Service decorator — the fast compiler now compiles Angular 22's @Service decorator; previously a @Service class fell through uncompiled, emitting no ɵprov/ɵfac. Emits ɵprov via compileService, parses autoProvided/factory, gated on Angular 22+.
  • Drift detector — conformance now fails on any untriaged Angular compliance category, so new Angular compiler surface can no longer be silently ignored.
  • Conformance coverage — wired in the arrow_functions, providers, signal_queries, i18n, and service_decorator categories.
  • Nested fixtures — the conformance runner now descends into subdirectories, sweeping 38 nested TEST_CASES.json files across 7 categories that the flat top-level read was missing.
  • MatcherexpectEmit gained a cosmetic-insensitive per-call fallback (tolerates object-literal whitespace differences).
  • DocsCOMPILER.md updated for @Service and category drift detection.

Net conformance: +133 passing checks, pass rate ~91%, no hard failures.

Test plan

  • Manual verification

Ran pnpm exec vitest run packages/vite-plugin-angular/src/lib/compiler/ against both the workspace-pinned Angular 21 and Angular 22 (@angular/compiler@next via pnpm.overrides):

  • Angular 21 — 23 test files pass; @Service paths skip as version-gated.

  • Angular 22 — 24 test files pass; all @Service unit and conformance cases green.

  • prettier --check on all changed files.

  • nx format:check

  • pnpm build

  • pnpm test

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

source_mapping remains the sole UNSUPPORTED_CATEGORIES entry — template source-map emission is deferred (the JSEmitter would need to become source-map-aware, and the conformance harness would need a map-decoding check).

brandonroberts and others added 10 commits May 15, 2026 10:49
…compiler

Angular 22 introduces the `@Service` decorator as a lighter-weight
alternative to `@Injectable`. The fast compiler recognised only the five
classic decorators, so a `@Service` class fell through uncompiled — no
`ɵprov`/`ɵfac` emitted.

Wire it through the AOT path:
  - add `Service` to `ANGULAR_DECORATORS` (it self-registers via `ɵprov`,
    so it stays out of `COMPILABLE_DECORATORS`, same as `@Injectable`)
  - emit `ɵprov` via `compileService` / `compileDeclareServiceFromMetadata`,
    gated on `angularVersionAtLeast(22)` since the compiler API and the
    decorator only exist on Angular 22+
  - parse the `autoProvided` and `factory` decorator options in metadata
  - keep `@Service` on the class in the JIT transform, like `@Injectable` —
    its runtime decorator self-registers `ɵprov`/`ɵfac` lazily

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover the four @service shapes the fast compiler now handles: a bare
service, `autoProvided: false`, an explicit `factory`, and constructor
dependency resolution. The suite is gated on `angularVersionAtLeast(22)`
so it skips cleanly on the workspace-pinned Angular 21 and runs on the
compiler-compat `next` matrix slot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gory

Wire Angular's `service_decorator` compliance fixtures into the
conformance sweep. The category is gated through `CATEGORY_MIN_MAJOR`
so it runs only when the installed `@angular/compiler` is 22+, where
`@Service` exists; on older peers it is skipped rather than failed.

Also extend `expectEmit` with a cosmetic-insensitive per-call fallback
reusing the existing `aggressiveNorm`. Previously that normalization
ran only on the ellipsis path, so the ellipsis-free `service_decorator`
fixtures failed purely on object-literal spacing (`{token: …}` vs
`{ token: … }`) despite semantically identical output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gories

Add a drift detector to the conformance suite: it enumerates every
directory under Angular's compliance `test_cases` and asserts each is
consciously triaged into either `CATEGORIES` (run) or the new
`UNSUPPORTED_CATEGORIES` allowlist (skipped, with a reason).

When a future Angular release ships compiler fixtures for a new feature
— a new decorator, a new template construct — the directory lands in
neither list and this test fails, forcing a maintainer to either
implement support or record a deliberate skip. Without it, new Angular
surface is silently ignored: the suite only ever tests what it already
knows about. This runs across the conformance matrix, including the
`next` (Angular prerelease) slot, so the warning lands early.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ft detection

Add `@Service` to the supported-decorators table and describe the
conformance category drift detector under Conformance Testing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The fast compiler delegates template expression compilation to
`@angular/compiler`, so template arrow functions (`@let`, `$event`,
host bindings, pipes, nested contexts) already compile correctly. Wire
the `r3_view_compiler_arrow_functions` category into the conformance
sweep — all 20 fixtures pass — and drop it from UNSUPPORTED_CATEGORIES.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`providers` and `viewProviders` on `@Component`/`@Directive` are parsed
in metadata and forwarded into the component/directive definition, so
`@angular/compiler` already emits the `ɵɵProvidersFeature`. Wire the
`r3_view_compiler_providers` category into the conformance sweep — all
4 fixtures pass — and drop it from UNSUPPORTED_CATEGORIES.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signal-based queries (`viewChild`/`contentChild` and their plural and
required variants) are extracted by `detectSignals` and emitted as
`R3QueryReference`s, so the directive/component definitions already
match. Wire the `signal_queries` category into the conformance sweep —
all 3 fixtures pass — and drop it from UNSUPPORTED_CATEGORIES.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire `r3_view_compiler_i18n` into the conformance sweep. Unlike the
flat categories, Angular splits its i18n fixtures across 11
subdirectories (ICU logic, nested nodes, namespaces, blocks, …), each
with its own TEST_CASES.json — a flat read of the category's top-level
file would miss all 87 nested cases.

Teach the runner to descend into subdirectories, opt-in per category
via `NESTED_CATEGORIES` so the existing flat categories are unaffected.
Test cases now carry the directory they were discovered in, and nested
cases are labelled with their subpath to keep descriptions unique.
Also harden the runner against fixtures that omit `expectations` or
`inputFiles` (e.g. the `icu_and_i18n` case), which the i18n fixtures
are the first to exercise.

i18n template compilation is handled by `@angular/compiler`, so the
fast compiler already emits correct `$localize`/`ɵɵi18n` output: +30
conformance checks pass with zero new failures. Four external-template
fixtures in `line_ending_normalization` record tolerated compile
errors — the runner compiles `.ts` directly without the resource
inliner, so `templateUrl` stays unresolved (a pre-existing harness
limitation, not i18n-specific).

Other wired categories also have nested fixture folders that remain
unswept; sweeping those is tracked as separate follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…egories

The subdirectory-recursion machinery added for i18n is now applied to
the other six wired categories that also nest fixtures into subfolders:
`r3_view_compiler_bindings`, `_styling`, `_di`, `_directives`,
`r3_view_compiler`, and `r3_compiler_compliance` — 38 nested
TEST_CASES.json files that the flat top-level read was silently
missing.

This surfaces +103 passing conformance checks. It also surfaces 11 new
soft-failures (`value_composition`, `di`, `directives/matching` +
`host_directives`, `template_variables`) — codegen-formatting
differences of the same class as the pre-existing soft-failures, now
tracked rather than invisible. Overall pass rate 91.3%, comfortably
above the 0.75 gate; no hard failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented May 15, 2026

Copy link
Copy Markdown

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit b7462c6
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/6a0785431ee68800084d97a8
😎 Deploy Preview https://deploy-preview-2338--analog-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented May 15, 2026

Copy link
Copy Markdown

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit b7462c6
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/6a0785438c1ab70008657c73
😎 Deploy Preview https://deploy-preview-2338--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented May 15, 2026

Copy link
Copy Markdown

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit b7462c6
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/6a07854314f1fa00096d0b3e
😎 Deploy Preview https://deploy-preview-2338--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular label May 15, 2026
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f645e4b0-e191-4448-8b26-ceaa35776ec6

📥 Commits

Reviewing files that changed from the base of the PR and between 328d800 and b7462c6.

📒 Files selected for processing (1)
  • packages/vite-plugin-angular/src/lib/compiler/compile.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/vite-plugin-angular/src/lib/compiler/compile.ts

📝 Walkthrough

Walkthrough

This PR adds Angular 22+ @Service decorator support to the Vite plugin compiler. The compiler now recognizes @Service, extracts its metadata properties (autoProvided, factory), and emits Ivy code. Conformance testing infrastructure is extended with nested fixture discovery, version-gated categories, and a drift detector to ensure uncategorized fixtures are not silently ignored.

Changes

Service Decorator Implementation and Testing

Layer / File(s) Summary
Service decorator recognition and metadata foundation
packages/vite-plugin-angular/src/lib/compiler/constants.ts, packages/vite-plugin-angular/src/lib/compiler/metadata.ts, packages/vite-plugin-angular/src/lib/compiler/jit-transform.ts
ANGULAR_DECORATORS is extended to include 'Service'. Metadata extraction now parses @Service arguments for autoProvided and factory properties. JIT transformation excludes @Service from decorator removal, preserving it like @Injectable.
Service compilation with version gating and Ivy emission
packages/vite-plugin-angular/src/lib/compiler/compile.ts
A new Service case in the class compilation switch version-gates to Angular 22+, sets FactoryTarget.Service, constructs metadata with autoProvided and factory fields, and emits static ɵprov via Angular compiler service APIs (compileDeclareServiceFromMetadata or compileService).
Service compiler unit tests
packages/vite-plugin-angular/src/lib/compiler/service.spec.ts
Conditional Vitest suite (Angular 22+ only) validates bare @Service(), autoProvided: false configuration, explicit factory forwarding, and constructor dependency resolution, ensuring correct Ivy output.
Conformance test discovery and drift detection
packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts
Introduces TestCaseGroup and nested fixture discovery via loadTestCaseGroups(). Extends expectEmit with aggressive whitespace-stripped normalization for fuzzy call matching. Adds CATEGORY_MIN_MAJOR and UNSUPPORTED_CATEGORIES configuration, and implements a drift detector that fails when on-disk fixtures are neither explicitly categorized nor marked unsupported.
Feature and test infrastructure documentation
packages/vite-plugin-angular/src/lib/compiler/COMPILER.md
Documents @Service in the decorator support table with Angular 22+ requirement and emitted Ivy fields. Adds Category Drift Detection subsection describing the conformance test triage enforcement.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows Conventional Commit style with correct scope and clearly summarizes the main changes: @Service decorator support and conformance coverage.
Description check ✅ Passed The PR description comprehensively details the changes, test plan, affected scopes, and implementation rationale, directly relating to the changeset modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/vite-plugin-angular/src/lib/compiler/metadata.ts (1)

320-328: 💤 Low value

Consider storing only explicit false for autoProvided.

The comment states "only an explicit false is meaningful" since autoProvided defaults to true in Angular. The current implementation stores both true and false, but compile.ts only checks meta.autoProvided === false before forwarding it. Storing true is redundant.

For clarity, consider:

 case 'autoProvided':
-  meta.autoProvided = valText === 'true';
+  if (valText === 'false') {
+    meta.autoProvided = false;
+  }
   break;

This makes the intent explicit: only preserve the non-default value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vite-plugin-angular/src/lib/compiler/metadata.ts` around lines 320 -
328, The code currently sets meta.autoProvided = valText === 'true' in the
'autoProvided' case and thus stores both true and false; change it to only
record the explicit non-default value by setting meta.autoProvided = false when
valText === 'false' (leave it undefined for true/default) so that
meta.autoProvided only exists when explicitly disabling autoProvided; update the
'autoProvided' switch case handling in metadata.ts (and ensure
compileService/compile.ts continues to check meta.autoProvided === false) to
reflect this intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/vite-plugin-angular/src/lib/compiler/compile.ts`:
- Around line 975-1001: When encountering a 'Service' decorator on Angular <22
the code falls through leaving targetType as FactoryTarget.Injectable and later
unconditionally emits an Injectable factory; change the handling so Service only
sets targetType to FactoryTarget.Service when angularVersionAtLeast(22) is true
and otherwise prevents factory emission for that class (either by
continuing/returning early or marking a flag to skip factory generation).
Concretely, update the 'Service' case around
FactoryTarget/compileService/compileDeclareServiceFromMetadata to not fall
through on older Angular peers (do not leave targetType set to Injectable), and
update the factory-generation logic (the block that emits the factory into
ivyCode) to check the resolved targetType or the new skip flag before calling
factory emit so a Service on <22 will not produce an incorrect Injectable
factory. Ensure references to FactoryTarget, targetType, compileService,
compileDeclareServiceFromMetadata, and ivyCode are used to locate and adjust the
logic.

In `@packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts`:
- Around line 423-448: The drift detector test "has no untriaged Angular
compliance categories" is failing because the on-disk category "isolated" is not
listed in either CATEGORIES or UNSUPPORTED_CATEGORIES; fix by adding "isolated"
to the CATEGORIES array if the compiler should run those fixtures, or add an
entry in UNSUPPORTED_CATEGORIES with a clear reason string (so
Object.keys(UNSUPPORTED_CATEGORIES) will include it), then re-run the test to
ensure the untriaged set is empty.

---

Nitpick comments:
In `@packages/vite-plugin-angular/src/lib/compiler/metadata.ts`:
- Around line 320-328: The code currently sets meta.autoProvided = valText ===
'true' in the 'autoProvided' case and thus stores both true and false; change it
to only record the explicit non-default value by setting meta.autoProvided =
false when valText === 'false' (leave it undefined for true/default) so that
meta.autoProvided only exists when explicitly disabling autoProvided; update the
'autoProvided' switch case handling in metadata.ts (and ensure
compileService/compile.ts continues to check meta.autoProvided === false) to
reflect this intent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f58a5597-6c00-44f8-b366-475da6ac8f40

📥 Commits

Reviewing files that changed from the base of the PR and between 152fe32 and b2f9a1f.

📒 Files selected for processing (7)
  • packages/vite-plugin-angular/src/lib/compiler/COMPILER.md
  • packages/vite-plugin-angular/src/lib/compiler/compile.ts
  • packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts
  • packages/vite-plugin-angular/src/lib/compiler/constants.ts
  • packages/vite-plugin-angular/src/lib/compiler/jit-transform.ts
  • packages/vite-plugin-angular/src/lib/compiler/metadata.ts
  • packages/vite-plugin-angular/src/lib/compiler/service.spec.ts

Comment thread packages/vite-plugin-angular/src/lib/compiler/compile.ts
Comment thread packages/vite-plugin-angular/src/lib/compiler/conformance.spec.ts
brandonroberts and others added 2 commits May 15, 2026 14:38
…tegory

The drift detector flagged `isolated` on the Angular 21 / latest
conformance jobs: it exists in Angular 21.x's compliance fixtures but
not in 22/main, so it was never triaged when the allowlist was built
against `main`. Working as designed — a category in neither list
fails the suite.

`isolated` tests Angular's source-to-source "isolated" transform mode;
its expected outputs are transformed `.ts` and `.ngtypecheck.ts` shims,
not Ivy JS. That is out of scope for the fast compiler (which emits Ivy
JS only), so it joins `source_mapping` in UNSUPPORTED_CATEGORIES.

Verified by running the conformance suite against downloaded Angular
21.2.13 fixtures — drift detector green, 538 passed / 19 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses CodeRabbit review feedback. When a `@Service` class was
compiled against Angular <22, the version gate did a bare `break`,
leaving `targetType` at its `FactoryTarget.Injectable` default — the
unconditional factory block then emitted an `Injectable`-target `ɵfac`
with no `ɵprov`, a silently wrong half-compiled class.

`@Service` does not exist before Angular 22, so this is a broken
configuration. Set `classCompileError` instead, surfacing a clear
`@Service requires Angular 22 or later` error before factory emission
rather than degrading silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@brandonroberts brandonroberts merged commit bc63520 into beta May 15, 2026
37 checks passed
@brandonroberts brandonroberts deleted the fix/fast-compiler-conformance-coverage branch May 15, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant