Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions crates/oxc_angular_compiler/src/component/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1981,6 +1981,30 @@ fn strip_typescript(allocator: &Allocator, path: &str, code: &str) -> String {
// In oxc 0.129.0, parameter properties generate field declarations which we need to remove.
let param_property_names = collect_parameter_property_names(&program);

// An import whose specifiers all carry inline `type` modifiers has no runtime
// bindings. Promote it to `import type` so `only_remove_type_imports` elides
// the whole statement instead of leaving a bare side-effect import behind.
// This intentionally diverges from tsc's verbatimModuleSyntax (which keeps
// `import {}` for side effects) to match Angular CLI's esbuild pipeline;
// side effects need an explicit `import 'mod'`, which is left untouched.
for stmt in &mut program.body {
if let Statement::ImportDeclaration(import_decl) = stmt
&& import_decl.import_kind == ImportOrExportKind::Value
&& import_decl.specifiers.as_ref().is_some_and(|specs| {
!specs.is_empty()
&& specs.iter().all(|spec| {
matches!(
spec,
ImportDeclarationSpecifier::ImportSpecifier(s)
if s.import_kind.is_type()
)
})
})
{
import_decl.import_kind = ImportOrExportKind::Type;
}
}

let semantic_ret =
oxc_semantic::SemanticBuilder::new().with_excess_capacity(2.0).build(&program);

Expand Down
68 changes: 68 additions & 0 deletions crates/oxc_angular_compiler/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7259,6 +7259,74 @@ export class AppComponent {
insta::assert_snapshot!("jit_constructor_deps", result.code);
}

#[test]
fn test_jit_all_inline_type_import_elided() {
// An import whose specifiers all carry inline `type` modifiers must be
// elided entirely, not left behind as a bare side-effect import.
let allocator = Allocator::default();
let source = r#"
import './side-effect';
import { Pipe, type PipeTransform } from '@angular/core';
import { type Get } from 'type-fest';
import type { Whole } from './whole';
import Def, { type Partial } from './default-mixed';

@Pipe({ name: 'demo' })
export class DemoPipe implements PipeTransform {
transform(v: string) { return Def(v as unknown as Whole as Partial as string); }
}

export let sample: Get<{ a: 1 }, 'a'> | undefined;
"#;

let options = ComponentTransformOptions { jit: true, ..Default::default() };
let result = transform_angular_file(&allocator, "demo.pipe.ts", source, Some(&options), None);
assert!(!result.has_errors(), "Should not have errors: {:?}", result.diagnostics);

assert!(
!result.code.contains("type-fest"),
"All-inline-type import should be elided, not kept as a side-effect import. Got:\n{}",
result.code
);
assert!(
!result.code.contains("./whole"),
"`import type {{ ... }}` statement should be elided. Got:\n{}",
result.code
);

// Mixed import keeps its value binding, drops the type-only one
assert!(
result.code.contains("import { Pipe } from"),
"Value binding of mixed import should be preserved. Got:\n{}",
result.code
);
assert!(
!result.code.contains("PipeTransform"),
"Type-only binding of mixed import should be dropped. Got:\n{}",
result.code
);

// Default import alongside an inline-type specifier keeps the default
// binding and drops the type-only sibling
assert!(
result.code.contains("import Def from"),
"Default import with inline-type sibling should be preserved. Got:\n{}",
result.code
);
assert!(
!result.code.contains("Partial"),
"Inline-type sibling of a default import should be dropped. Got:\n{}",
result.code
);

// Deliberate side-effect imports (no specifier list) must survive
assert!(
result.code.contains("import \"./side-effect\""),
"Bare side-effect import should be preserved. Got:\n{}",
result.code
);
}

#[test]
fn test_jit_component_class_restructuring() {
// JIT should restructure: export class X {} → let X = class X {}; X = __decorate([...], X); export { X };
Expand Down
Loading