fix(angular-compiler): add type-only import elision to angular compiler#2249
Conversation
✅ Deploy Preview for analog-blog ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for analog-app ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for analog-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughA new OXC-based type-elision stage was added that detects and removes imports used only in TypeScript type positions. New exports: Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes BREAKING CHANGE Before:
After:
Reviewer checklist:
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/angular-compiler/src/lib/compile.ts (1)
845-862:⚠️ Potential issue | 🟠 MajorSource map misalignment when type-only imports are elided.
The map is generated from
msbefore elision, butcodeis returned post-elision. WhenelideTypeOnlyImportsremoves or rewrites import statements, source positions shift, causing the source map to reference positions that no longer exist in the output.While
component.spec.tsverifies the map is generated, the test input contains only value imports (Component,signal), soelideTypeOnlyImportsreturns the code unchanged. There is no integration test coverage for cases where type-only imports are actually elided.Fix: Apply elision via MagicString before
generateMap():const compiledCode = elideTypeOnlyImports(ms.toString()); ms = new MagicString(compiledCode); const map = ms.generateMap({...});Or rebuild the MagicString from the elided output to keep positions synchronized. Without this, source maps become invalid whenever type-only imports are present in the compiled output.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-compiler/src/lib/compile.ts` around lines 845 - 862, The source map is generated from the original MagicString `ms` before `elideTypeOnlyImports` runs, so when `elideTypeOnlyImports(ms.toString())` changes the code the map becomes misaligned; fix this by performing the elision first and rebuilding the MagicString used for `generateMap()` — i.e., call `elideTypeOnlyImports(ms.toString())`, assign the result to `compiledCode`, then create a new `MagicString(compiledCode)` (or otherwise replace `ms`) before calling `ms.generateMap({...})` so the `map` matches the returned `code` and preserves options like `includeContent` and `hires`.
🧹 Nitpick comments (1)
packages/angular-compiler/src/lib/type-elision.ts (1)
167-168: Minor: Only\nconsumed when trimming declaration end.If source has Windows line endings (
\r\n), the\rwould remain after removal. This is unlikely to cause issues since most build pipelines normalize line endings, but worth noting.let declEnd = node.end; - while (declEnd < code.length && code[declEnd] === '\n') declEnd++; + while (declEnd < code.length && (code[declEnd] === '\n' || code[declEnd] === '\r')) declEnd++;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-compiler/src/lib/type-elision.ts` around lines 167 - 168, The trimming loop for declaration end only checks for '\n' so a preceding '\r' in Windows CRLF endings remains; update the loop that advances declEnd (the variable declared as let declEnd = node.end) to consume both '\r' and '\n' characters (e.g., check code[declEnd] === '\n' || code[declEnd] === '\r' or use a small helper isLineBreak) so CRLF sequences are fully skipped when moving declEnd past trailing line breaks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/angular-compiler/COMPILER.md`:
- Around line 444-445: The COMPILER.md entry for conformance.spec.ts has an
incorrect test count: update the table row referencing `conformance.spec.ts` to
show the actual number of tests (replace 167 with 2) so the documentation
matches the file; edit the Markdown table line containing `conformance.spec.ts`
in COMPILER.md to change the numeric count to 2.
---
Outside diff comments:
In `@packages/angular-compiler/src/lib/compile.ts`:
- Around line 845-862: The source map is generated from the original MagicString
`ms` before `elideTypeOnlyImports` runs, so when
`elideTypeOnlyImports(ms.toString())` changes the code the map becomes
misaligned; fix this by performing the elision first and rebuilding the
MagicString used for `generateMap()` — i.e., call
`elideTypeOnlyImports(ms.toString())`, assign the result to `compiledCode`, then
create a new `MagicString(compiledCode)` (or otherwise replace `ms`) before
calling `ms.generateMap({...})` so the `map` matches the returned `code` and
preserves options like `includeContent` and `hires`.
---
Nitpick comments:
In `@packages/angular-compiler/src/lib/type-elision.ts`:
- Around line 167-168: The trimming loop for declaration end only checks for
'\n' so a preceding '\r' in Windows CRLF endings remains; update the loop that
advances declEnd (the variable declared as let declEnd = node.end) to consume
both '\r' and '\n' characters (e.g., check code[declEnd] === '\n' ||
code[declEnd] === '\r' or use a small helper isLineBreak) so CRLF sequences are
fully skipped when moving declEnd past trailing line breaks.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3ee34a35-60e9-4a72-9116-0ec7cec127c9
📒 Files selected for processing (4)
packages/angular-compiler/COMPILER.mdpackages/angular-compiler/src/lib/compile.tspackages/angular-compiler/src/lib/type-elision.spec.tspackages/angular-compiler/src/lib/type-elision.ts
| | `type-elision.spec.ts` | 27 | Type-only import detection (annotations, implements, generics, mixed, unused, default imports) and elision (whole-declaration removal, partial specifier removal, default import handling, mixed default+named, compiler output integration) | | ||
| | `conformance.spec.ts` | 167 | Angular compliance test suite (v17-v21, 87%+ Ivy instruction match) | |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Count actual tests in the spec files
echo "=== type-elision.spec.ts test count ==="
rg -c "^\s*it\(" packages/angular-compiler/src/lib/type-elision.spec.ts || echo "File not found"
echo ""
echo "=== conformance.spec.ts test count ==="
rg -c "^\s*it\(" packages/angular-compiler/src/lib/conformance.spec.ts || echo "File not found"Repository: analogjs/analog
Length of output: 143
Update conformance.spec.ts test count in documentation.
The type-elision.spec.ts count of 27 is correct, but conformance.spec.ts documentation claims 167 tests while the file contains only 2. Update COMPILER.md line 445 to reflect the actual test count.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/angular-compiler/COMPILER.md` around lines 444 - 445, The
COMPILER.md entry for conformance.spec.ts has an incorrect test count: update
the table row referencing `conformance.spec.ts` to show the actual number of
tests (replace 167 with 2) so the documentation matches the file; edit the
Markdown table line containing `conformance.spec.ts` in COMPILER.md to change
the numeric count to 2.
Imports used only in type positions (annotations, implements, generics) are now automatically elided from compiler output using OXC AST usage analysis. This prevents runtime errors when single-file transpilers (OXC/esbuild) can't distinguish type-only imports without the explicit `type` keyword. Handles named imports, default imports, and mixed default+named declarations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
aa07edb to
788b514
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/angular-compiler/src/lib/type-elision.ts (2)
53-66: Namespace imports (import * as ns) silently pass through.
ImportNamespaceSpecifieris not collected at line 63 since onlyspec.local.nameis grabbed without checking for this specifier type. If a namespace import is used only in type positions, it won't be detected or elided.The PR summary notes this is intentional, but consider adding a code comment here to document the limitation for future maintainers.
📝 Suggested documentation comment
// Step 1 – collect all value-imported names (skip `import type` / `{ type X }`) + // NOTE: Namespace imports (`import * as ns`) are intentionally skipped; we do + // not analyse namespace member access, so they are left untouched. const importedNames = new Set<string>(); for (const node of (ast as any).body) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-compiler/src/lib/type-elision.ts` around lines 53 - 66, The loop in detectTypeOnlyImportNames currently skips namespace imports because it assumes specifiers have a .local.name and doesn't handle ImportNamespaceSpecifier, so namespace imports used only as types won't be detected; update the import-collection loop (in detectTypeOnlyImportNames, referencing the importedNames set and the node.specifiers iteration) to either explicitly handle ImportNamespaceSpecifier by checking spec.type === 'ImportNamespaceSpecifier' and adding the namespace identifier, or—if you intentionally don't support namespace elision—add a clear inline comment above this loop stating that ImportNamespaceSpecifier (import * as ns) is intentionally ignored and why, so future maintainers understand the limitation.
131-136: Double-parsing the same source is inefficient.
elideTypeOnlyImportscallsdetectTypeOnlyImportNames(which parses once), then immediately parses again at line 135. For large files this doubles AST construction cost.A refactor to return the AST from detection—or combine both passes into one function with an internal helper—would cut parse overhead in half. Not blocking, but worth considering if profiling shows this is a hot path.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-compiler/src/lib/type-elision.ts` around lines 131 - 136, elideTypeOnlyImports currently reparses the source after detectTypeOnlyImportNames already parsed it, causing duplicate AST work; change detectTypeOnlyImportNames to either return the parsed AST (e.g., { names, ast }) or accept a pre-parsed AST parameter so elideTypeOnlyImports can call parseSync once (use parseSync in elideTypeOnlyImports and pass its AST into detectTypeOnlyImportNames), then reuse that AST for the rest of the logic instead of calling parseSync a second time; update call sites of detectTypeOnlyImportNames accordingly (or consolidate both passes into a single function that detects type-only names and performs elision) so parseSync is invoked only once per source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/angular-compiler/src/lib/type-elision.ts`:
- Around line 166-168: The loop that advances declEnd from node.end only checks
for '\n' and thus fails on Windows CRLF; update the logic around declEnd
(starting at node.end) to consume both '\r' and '\n' sequences (e.g., skip a
'\r' if present and then a '\n', or treat any '\r' or '\n' as newline
characters) so CRLF ("\r\n") is fully consumed from the code buffer variable;
ensure the check references code[declEnd] and advances declEnd appropriately
until no more CR or LF characters remain.
- Around line 131-135: elideTypeOnlyImports currently removes entire import
declarations by string-manipulation causing the final compiledCode to be shorter
than the MagicString (ms) used later for sourcemap generation; update
elideTypeOnlyImports (and its use of detectTypeOnlyImportNames) to perform
removals using MagicString APIs (e.g., ms.remove(rangeStart, rangeEnd) for each
import declaration) so ms reflects the same edits, or alternatively after
producing compiledCode regenerate the sourcemap from compiledCode (using
magic-string/source-map utilities) before returning from compile.ts; ensure you
reference and update the same ranges that elideTypeOnlyImports targets so no
line/column drift occurs between ms and compiledCode.
---
Nitpick comments:
In `@packages/angular-compiler/src/lib/type-elision.ts`:
- Around line 53-66: The loop in detectTypeOnlyImportNames currently skips
namespace imports because it assumes specifiers have a .local.name and doesn't
handle ImportNamespaceSpecifier, so namespace imports used only as types won't
be detected; update the import-collection loop (in detectTypeOnlyImportNames,
referencing the importedNames set and the node.specifiers iteration) to either
explicitly handle ImportNamespaceSpecifier by checking spec.type ===
'ImportNamespaceSpecifier' and adding the namespace identifier, or—if you
intentionally don't support namespace elision—add a clear inline comment above
this loop stating that ImportNamespaceSpecifier (import * as ns) is
intentionally ignored and why, so future maintainers understand the limitation.
- Around line 131-136: elideTypeOnlyImports currently reparses the source after
detectTypeOnlyImportNames already parsed it, causing duplicate AST work; change
detectTypeOnlyImportNames to either return the parsed AST (e.g., { names, ast })
or accept a pre-parsed AST parameter so elideTypeOnlyImports can call parseSync
once (use parseSync in elideTypeOnlyImports and pass its AST into
detectTypeOnlyImportNames), then reuse that AST for the rest of the logic
instead of calling parseSync a second time; update call sites of
detectTypeOnlyImportNames accordingly (or consolidate both passes into a single
function that detects type-only names and performs elision) so parseSync is
invoked only once per source.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c4fc3682-d8a6-4689-af2b-38efad63875f
📒 Files selected for processing (4)
packages/angular-compiler/COMPILER.mdpackages/angular-compiler/src/lib/compile.tspackages/angular-compiler/src/lib/type-elision.spec.tspackages/angular-compiler/src/lib/type-elision.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/angular-compiler/src/lib/compile.ts
- packages/angular-compiler/COMPILER.md
- packages/angular-compiler/src/lib/type-elision.spec.ts
…icstring Refactor type-only import elision to operate directly on MagicString using ms.remove()/ms.overwrite() instead of string splicing. This ensures ms.generateMap() produces a sourcemap that matches the final output after imports are elided. Also eliminates double-parse (shared analyzeTypeOnlyImports helper), fixes CRLF handling in trailing newline consumption, and adds a comment documenting intentional namespace import skip. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… counts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/angular-compiler/src/lib/type-elision.ts`:
- Around line 57-68: The collector currently adds all specifiers into
importedNames including ImportNamespaceSpecifier, which later causes the rewrite
path (rebuilding ImportDeclaration) to drop live namespace imports like `* as
ns`; update the analysis loop that populates importedNames (the for loop over
(ast as any).body and node.specifiers) to detect and skip any ImportDeclaration
that contains an ImportNamespaceSpecifier (i.e., if any spec.type ===
'ImportNamespaceSpecifier' then continue and do not add its specifiers to
importedNames), so namespace imports are excluded from type-elision rewriting
and preserved by the rewrite logic that only handles ImportDefaultSpecifier and
ImportSpecifier.
- Around line 30-31: Remove 'TSAsExpression' and 'TSSatisfiesExpression' from
the TYPE_NODE_TYPES array in packages/angular-compiler/src/lib/type-elision.ts
so the walk logic treats their expression subtrees as value-position (allowing
identifiers like imported names to be preserved); update any related comments
and ensure the walker implementation (the walk function referenced in this
module) remains unchanged so it will traverse the expression part normally. Also
add a unit test that asserts an imported identifier used only in a cast (e.g.,
const x = ImportedName as Type) is not elided to prevent regressions.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ff7fcac0-0150-4fd9-bb3b-0962d03f9e67
📒 Files selected for processing (5)
packages/angular-compiler/COMPILER.mdpackages/angular-compiler/src/lib/compile.tspackages/angular-compiler/src/lib/integration.spec.tspackages/angular-compiler/src/lib/type-elision.spec.tspackages/angular-compiler/src/lib/type-elision.ts
✅ Files skipped from review due to trivial changes (2)
- packages/angular-compiler/src/lib/integration.spec.ts
- packages/angular-compiler/src/lib/type-elision.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/angular-compiler/src/lib/compile.ts
- packages/angular-compiler/COMPILER.md
| 'TSAsExpression', | ||
| 'TSSatisfiesExpression', |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect the current classification and whether regression coverage exists for `as` / `satisfies`.
sed -n '23,35p' packages/angular-compiler/src/lib/type-elision.ts
sed -n '85,95p' packages/angular-compiler/src/lib/type-elision.ts
rg -n 'TSAsExpression|TSSatisfiesExpression|\bsatisfies\b|\bas\b' \
packages/angular-compiler/src/lib/type-elision.spec.ts \
packages/angular-compiler/src/lib/integration.spec.tsRepository: analogjs/analog
Length of output: 4322
🏁 Script executed:
#!/bin/bash
# Check for test cases involving imported names used in `as` or `satisfies` expressions
rg -A 3 -B 3 'import.*from|as Type|satisfies' packages/angular-compiler/src/lib/type-elision.spec.ts | head -100Repository: analogjs/analog
Length of output: 2801
🏁 Script executed:
#!/bin/bash
# Get the full walk() function to understand traversal order
sed -n '75,130p' packages/angular-compiler/src/lib/type-elision.tsRepository: analogjs/analog
Length of output: 1713
🏁 Script executed:
#!/bin/bash
# Check if there are any tests specifically for TSAsExpression or TSSatisfiesExpression classification
rg -n 'as.*import|import.*as' packages/angular-compiler/src/lib/type-elision.spec.ts -A 5Repository: analogjs/analog
Length of output: 2910
🏁 Script executed:
#!/bin/bash
# Search for any test with imported names in `as` or `satisfies` contexts
rg -B 5 -A 5 'import.*\bas\b|import.*satisfies' packages/angular-compiler/src/lib/type-elision.spec.tsRepository: analogjs/analog
Length of output: 2644
🏁 Script executed:
#!/bin/bash
# Check the full test case around line 201
sed -n '195,210p' packages/angular-compiler/src/lib/type-elision.spec.tsRepository: analogjs/analog
Length of output: 717
🏁 Script executed:
#!/bin/bash
# Look for any test involving a variable assignment with `as` that uses an imported name
rg -B 8 -A 2 'const.*=.*as\b' packages/angular-compiler/src/lib/type-elision.spec.tsRepository: analogjs/analog
Length of output: 641
🏁 Script executed:
#!/bin/bash
# Check the AST structure: does TSAsExpression have a separate "expression" subtree?
# Look at how walk() processes node keys
sed -n '110,130p' packages/angular-compiler/src/lib/type-elision.tsRepository: analogjs/analog
Length of output: 680
🏁 Script executed:
#!/bin/bash
# Find TYPE_POSITION_KEYS definition
rg -n 'TYPE_POSITION_KEYS' packages/angular-compiler/src/lib/type-elision.ts -A 15Repository: analogjs/analog
Length of output: 1068
🏁 Script executed:
#!/bin/bash
# Check if there's special handling for 'expression' key in walk logic
sed -n '50,130p' packages/angular-compiler/src/lib/type-elision.tsRepository: analogjs/analog
Length of output: 2686
Remove TSAsExpression and TSSatisfiesExpression from TYPE_NODE_TYPES.
Both expr as Type and expr satisfies Type evaluate expr at runtime. The current logic marks the entire subtree as type-position, incorrectly classifying imported names used in the expression part as type-only and eliding them. For example, import Foo from 'lib'; const x = Foo as any; would incorrectly elide Foo.
The walk function has no mechanism to distinguish between the value-expression and type-annotation subtrees within these node types. Removing them allows the expression to be walked normally in value-position, preserving the correct classification.
Missing test coverage: no test verifies an imported name used only within a cast expression (e.g., const x = ImportedName as Type;).
Suggested fix
- 'TSAsExpression',
- 'TSSatisfiesExpression',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'TSAsExpression', | |
| 'TSSatisfiesExpression', |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/angular-compiler/src/lib/type-elision.ts` around lines 30 - 31,
Remove 'TSAsExpression' and 'TSSatisfiesExpression' from the TYPE_NODE_TYPES
array in packages/angular-compiler/src/lib/type-elision.ts so the walk logic
treats their expression subtrees as value-position (allowing identifiers like
imported names to be preserved); update any related comments and ensure the
walker implementation (the walk function referenced in this module) remains
unchanged so it will traverse the expression part normally. Also add a unit test
that asserts an imported identifier used only in a cast (e.g., const x =
ImportedName as Type) is not elided to prevent regressions.
| // Step 1 – collect all value-imported names (skip `import type` / `{ type X }`) | ||
| // Namespace imports (`import * as ns`) are intentionally skipped — they are | ||
| // always value imports in TypeScript and cannot be type-only at the | ||
| // declaration level. | ||
| const importedNames = new Set<string>(); | ||
| for (const node of (ast as any).body) { | ||
| if (node.type !== 'ImportDeclaration') continue; | ||
| if (node.importKind === 'type') continue; // already type-only | ||
| for (const spec of node.specifiers ?? []) { | ||
| if (spec.importKind === 'type') continue; // already type-only | ||
| importedNames.add(spec.local.name); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify namespace specifiers are still included in analysis and not preserved by the rewrite path.
sed -n '57,68p' packages/angular-compiler/src/lib/type-elision.ts
sed -n '155,208p' packages/angular-compiler/src/lib/type-elision.ts
rg -n '\* as ' \
packages/angular-compiler/src/lib/type-elision.spec.ts \
packages/angular-compiler/src/lib/integration.spec.tsRepository: analogjs/analog
Length of output: 3173
🏁 Script executed:
#!/bin/bash
# Check complete type-elision.ts to understand the flow
wc -l packages/angular-compiler/src/lib/type-elision.ts
cat -n packages/angular-compiler/src/lib/type-elision.ts | head -250Repository: analogjs/analog
Length of output: 9685
🏁 Script executed:
#!/bin/bash
# Search for any tests involving mixed imports with namespace specifiers
rg -A5 -B5 'import.*\*\s+as.*from' packages/angular-compiler/src/lib/type-elision.spec.ts | head -100Repository: analogjs/analog
Length of output: 41
Exclude declarations with namespace imports from rewriting.
The analysis phase adds all specifiers (including ImportNamespaceSpecifier) to importedNames, but the rewrite logic only reconstructs ImportDefaultSpecifier and ImportSpecifier types. On import Foo, * as ns from './x' where Foo is type-only, the entire declaration is removed, silently dropping the live ns binding. Namespace imports cannot be type-only and should not reach the rewrite path.
Add a guard to skip any declaration containing a namespace specifier before attempting reconstruction:
Suggested fix
const allSpecs = node.specifiers ?? [];
+ if (allSpecs.some((s: any) => s.type === 'ImportNamespaceSpecifier')) {
+ continue;
+ }
+
// Separate default and named specifiers🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/angular-compiler/src/lib/type-elision.ts` around lines 57 - 68, The
collector currently adds all specifiers into importedNames including
ImportNamespaceSpecifier, which later causes the rewrite path (rebuilding
ImportDeclaration) to drop live namespace imports like `* as ns`; update the
analysis loop that populates importedNames (the for loop over (ast as any).body
and node.specifiers) to detect and skip any ImportDeclaration that contains an
ImportNamespaceSpecifier (i.e., if any spec.type === 'ImportNamespaceSpecifier'
then continue and do not add its specifiers to importedNames), so namespace
imports are excluded from type-elision rewriting and preserved by the rewrite
logic that only handles ImportDefaultSpecifier and ImportSpecifier.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR Checklist
Add type-only import elision to the angular compiler so that imports used only in type positions are automatically removed before reaching the browser.
Affected scope
Recommended merge strategy for maintainer [optional]
What is the new behavior?
implementsclauses, generic type arguments, return types) are now automatically elided from the compiler output.oxc-parser(already a dependency) for fast, Rust-based AST usage analysis — no TypeScript type-checker needed (~0.12ms per file).import { Foo }), default imports (import Foo), and mixed declarations (import Foo, { Bar }).Uncaught SyntaxError: The requested module does not provide an export named 'PotatoType'when types are imported without the explicittypekeyword.type-elision.tsmodule withdetectTypeOnlyImportNames()andelideTypeOnlyImports()functions.Test plan
npx vitest run packages/angular-compiler/src/lib/type-elision.spec.ts— 27 new tests covering detection and elision for all import formsnpx vitest run packages/angular-compiler/src/lib/— all 432 tests pass (17/17 test files), no regressionspnpm buildpnpm testDoes this PR introduce a breaking change?
Other information
This addresses the issue where
import { PotatoType } from "potato"(without thetypekeyword) leaks to the browser because single-file transpilers like OXC/esbuild cannot distinguish type-only imports without type information. Theimport typeandimport { type X }forms already worked; this fix covers the bareimport { X }case.Namespace imports (
import * as ns) are not handled as they require qualified-name tracking and are rare in Angular code. Re-exports (export { X } from) cannot be fixed with single-file analysis.