Skip to content

Commit 029fcde

Browse files
Brooooooklynclaude
andauthored
fix: directive compiler resolves namespace imports for hostDirectives references (#79)
When a @directive uses hostDirectives referencing imported directives, the compiled output now uses namespace-prefixed references (e.g., i1.BrnTooltipTrigger) instead of bare variable references that would be undefined after import elision. - Close #68 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8d5b53b commit 029fcde

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,43 @@ fn resolve_factory_dep_namespaces<'a>(
559559
}
560560
}
561561

562+
/// Resolves namespace imports for host directive references in `R3HostDirectiveMetadata`.
563+
///
564+
/// Replaces bare `ReadVar("X")` references with namespace-prefixed `ReadProp(ReadVar("i1"), "X")`
565+
/// for any host directive that has a corresponding import in the import map.
566+
/// This ensures the compiled output works correctly even after import elision.
567+
fn resolve_host_directive_namespaces<'a>(
568+
allocator: &'a Allocator,
569+
host_directives: &mut oxc_allocator::Vec<'a, crate::R3HostDirectiveMetadata<'a>>,
570+
import_map: &ImportMap<'a>,
571+
namespace_registry: &mut NamespaceRegistry<'a>,
572+
) {
573+
for hd in host_directives.iter_mut() {
574+
// Only process bare variable references (ReadVar)
575+
let OutputExpression::ReadVar(ref var) = hd.directive else { continue };
576+
let name = &var.name;
577+
// Look up this identifier in the import map
578+
let Some(import_info) = import_map.get(name) else { continue };
579+
// Replace with namespace-prefixed reference: i1.BrnTooltipTrigger instead of BrnTooltipTrigger
580+
let namespace = namespace_registry.get_or_assign(&import_info.source_module);
581+
hd.directive = OutputExpression::ReadProp(oxc_allocator::Box::new_in(
582+
ReadPropExpr {
583+
receiver: oxc_allocator::Box::new_in(
584+
OutputExpression::ReadVar(oxc_allocator::Box::new_in(
585+
ReadVarExpr { name: namespace, source_span: None },
586+
allocator,
587+
)),
588+
allocator,
589+
),
590+
name: name.clone(),
591+
optional: false,
592+
source_span: None,
593+
},
594+
allocator,
595+
));
596+
}
597+
}
598+
562599
/// This is used to determine where to insert namespace imports so they appear
563600
/// AFTER existing imports but BEFORE other code (like class declarations).
564601
///
@@ -968,6 +1005,17 @@ pub fn transform_angular_file(
9681005
);
9691006
}
9701007

1008+
// Resolve namespace imports for hostDirectives references.
1009+
// Host directive references (e.g., BrnTooltipTrigger from '@spartan-ng/brain/tooltip')
1010+
// must use namespace-prefixed references (e.g., i1.BrnTooltipTrigger) because the
1011+
// original named import may be elided and replaced by a namespace import.
1012+
resolve_host_directive_namespaces(
1013+
allocator,
1014+
&mut directive_metadata.host_directives,
1015+
&import_map,
1016+
&mut file_namespace_registry,
1017+
);
1018+
9711019
// Compile directive and generate definitions
9721020
// Pass shared_pool_index to ensure unique constant names across the file
9731021
let definitions = generate_directive_definitions(
@@ -5036,4 +5084,59 @@ export class TestNgModule {}
50365084
result.code
50375085
);
50385086
}
5087+
5088+
#[test]
5089+
fn test_directive_host_directives_get_namespace_resolution() {
5090+
// Regression test for https://github.com/voidzero-dev/oxc-angular-compiler/issues/68
5091+
// hostDirectives references must use namespace-prefixed references (e.g., i1.BrnTooltipTrigger)
5092+
// instead of bare variable references (e.g., BrnTooltipTrigger), because the original
5093+
// named import may be elided and replaced by a namespace import.
5094+
let allocator = Allocator::default();
5095+
let source = r#"
5096+
import { Directive } from '@angular/core';
5097+
import { BrnTooltipTrigger } from '@spartan-ng/brain/tooltip';
5098+
5099+
@Directive({
5100+
selector: '[uTooltip]',
5101+
hostDirectives: [{ directive: BrnTooltipTrigger }]
5102+
})
5103+
export class UnityTooltipTrigger {}
5104+
"#;
5105+
5106+
let result = transform_angular_file(
5107+
&allocator,
5108+
"tooltip.directive.ts",
5109+
source,
5110+
&TransformOptions::default(),
5111+
None,
5112+
);
5113+
5114+
assert!(!result.has_errors(), "Transform should not have errors: {:?}", result.diagnostics);
5115+
5116+
// Verify namespace import is generated for the external module
5117+
assert!(
5118+
result.code.contains("import * as i1 from '@spartan-ng/brain/tooltip'"),
5119+
"Should generate namespace import for @spartan-ng/brain/tooltip, but got:\n{}",
5120+
result.code
5121+
);
5122+
5123+
// Verify the host directive uses the namespace-prefixed reference
5124+
assert!(
5125+
result.code.contains("i1.BrnTooltipTrigger"),
5126+
"Host directive should reference BrnTooltipTrigger as i1.BrnTooltipTrigger, but got:\n{}",
5127+
result.code
5128+
);
5129+
5130+
// Verify there's no bare BrnTooltipTrigger reference in the features array
5131+
// (it should only appear in the import statement and as i1.BrnTooltipTrigger)
5132+
let features_section = result.code.split("features:").nth(1);
5133+
if let Some(features) = features_section {
5134+
assert!(
5135+
!features.contains("BrnTooltipTrigger")
5136+
|| features.contains("i1.BrnTooltipTrigger"),
5137+
"Features should NOT contain bare BrnTooltipTrigger reference, but got:\n{}",
5138+
result.code
5139+
);
5140+
}
5141+
}
50395142
}

0 commit comments

Comments
 (0)