Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
09f95f3
fix(host): report empty `[]` / `()` host keys like ngtsc (#704)
Brooooooklyn Nov 10, 2026
a969bd8
fix(host): emit host `aria-*` bindings as attributes per Angular vers…
Brooooooklyn Nov 10, 2026
31dc360
feat(vite): add augmentDeclarations for .d.ts from a tsc run (#706)
Brooooooklyn Nov 10, 2026
e63657e
fix: align template animate.* with ngtsc across versions (#701)
Brooooooklyn Nov 10, 2026
8088192
fix(linker): rebuild hostDirectives like Angular's linker (#702)
Brooooooklyn Nov 10, 2026
bdf5dcb
fix: align hostDirectives pairs and references with ngtsc (#702)
Brooooooklyn Nov 10, 2026
e169490
fix(ng-module): read forwardRef targets, namespace members and const …
Brooooooklyn Nov 10, 2026
f73f6ef
test(napi): run NgModule scopes read across files with ngJitMode off …
Brooooooklyn Nov 10, 2026
cc3fd92
fix(host): report empty `[]` / `()` host keys only from 21.2.8 / 22.0…
Brooooooklyn Nov 10, 2026
aeea8cf
fix(vite): embed the client page of the same round in `vite build --a…
Brooooooklyn Nov 10, 2026
0ffb107
fix(vite): keep line breaks and maps as tsc wrote them in augmentDecl…
Brooooooklyn Nov 10, 2026
d2001c7
fix: escape non-ASCII strings like ngtsc and match each linker's chan…
Brooooooklyn Nov 10, 2026
612de7e
test(napi): expect the escaped trigger name in syntheticHostListener …
Brooooooklyn Nov 10, 2026
acfe27b
fix(ng-module): wrap lists that name an import after the module, read…
Brooooooklyn Nov 10, 2026
1403a47
fix: print host directive names and JIT resource imports like Angular…
Brooooooklyn Nov 10, 2026
831f38a
Merge branch 'fix701/i704' into fix/issues-701-707
Brooooooklyn Nov 10, 2026
d38be9b
Merge branch 'fix701/i701' into fix/issues-701-707
Brooooooklyn Nov 10, 2026
0d9a254
Merge branch 'fix701/i703' into fix/issues-701-707
Brooooooklyn Nov 10, 2026
aa65d1e
Merge branch 'fix701/i705' into fix/issues-701-707
Brooooooklyn Nov 10, 2026
9db9550
Merge branch 'fix701/i706' into fix/issues-701-707
Brooooooklyn Nov 10, 2026
546c450
Merge branch 'fix701/i702' into fix/issues-701-707
Brooooooklyn Nov 10, 2026
180652a
Merge branch 'fix701/i707' into fix/issues-701-707
Brooooooklyn Nov 10, 2026
56e09c7
fix(linker): read quoted hostDirectives keys like Angular's linker (#…
Brooooooklyn Nov 10, 2026
2b25351
fix(vite): keep the #705 watch stamp in a private temp dir
Brooooooklyn Nov 10, 2026
3f145db
fix(vite): fail on a missing declarationDir, run the #705 tests on Wi…
Brooooooklyn Nov 10, 2026
196bb16
fix(ng-module): read string export names off the namespace in module …
Brooooooklyn Nov 10, 2026
c8a3431
test(vite): run the #706 tsc builds from the long temp path on Windows
Brooooooklyn Nov 10, 2026
9dac19e
test(vite): build the #706 test projects inside the package
Brooooooklyn Nov 10, 2026
9e0a5d9
fix(linker): keep quotes on keys that are not identifiers
Brooooooklyn Nov 10, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ enum Declared {
/// A `const` list of references, spread or named in a module's lists.
List(Vec<RawRef>),
/// A `declare const X: readonly [typeof A, typeof B]` list, by its type.
/// Only `ɵinj` reads it.
TypeList(Vec<RawRef>),
}

Expand Down Expand Up @@ -451,20 +450,24 @@ impl CrossFileAnalyzer {
if let Some(method) = question.method {
return ng_module(inner.mwp_method_module(&holder, method)?);
}
// A list, flattened. Its classes are named through the files that
// declare them, never through the binding, so only their files
// count for `unready` (ngtsc writes no wrap for them either).
let list = |items: &[RawRef]| {
let mut classes = Vec::new();
let mut forward_ref = false;
inner.flatten_list(&holder, items, &mut classes, &mut forward_ref, 0)?;
let unready = classes.iter().any(unready);
let classes = classes.iter().map(named).collect::<Option<_>>()?;
Some(Answer::Classes { classes, unready, forward_ref })
};
match inner.declared(&holder.decl).as_deref() {
// A spread must be of an array (`read_declared` also gives a
// list for `const X = A` or `const X = A.forRoot()`).
Some(Declared::List(items)) if inner.is_const_array(&holder) => {
let mut classes = Vec::new();
let mut forward_ref = false;
inner.flatten_list(&holder, items, &mut classes, &mut forward_ref, 0)?;
// Full compilation lists the binding as written, so the file
// it is imported from must not load this one back either.
let unready = classes.iter().any(unready)
|| inner.binding_loads_back(&file, question.namespace.unwrap_or(question.name));
let classes = classes.iter().map(named).collect::<Option<_>>()?;
Some(Answer::Classes { classes, unready, forward_ref })
}
// list for `const X = A` or `const X = A.forRoot()`). A
// declaration file's `declare const X: [typeof A, typeof B]` is
// read by its type, like ngtsc's evaluator does.
Some(Declared::List(items)) if inner.is_const_array(&holder) => list(items),
Some(Declared::TypeList(items)) => list(items),
_ if question.spread => None,
// A constant holding a `ModuleWithProviders`.
Some(Declared::List(_)) | None => ng_module(inner.mwp_const_module(&holder)?),
Expand Down Expand Up @@ -823,14 +826,15 @@ impl Inner {
let read = items.iter().try_for_each(|raw| {
let class = self.resolve_ref(raw, list.owner.as_deref(), 0)?;
match &*self.declared(&class.decl)? {
Declared::List(items) if raw.method.is_none() => {
// A declaration file writes a list as a tuple type, which
// ngtsc's evaluator reads too.
Declared::List(items) | Declared::TypeList(items) if raw.method.is_none() => {
self.flatten_list(&class, items, out, forward_ref, depth + 1)
}
Declared::NgModule { .. } => {
out.push(class);
Some(())
}
// A declaration file's list type is not read here.
Declared::TypeList(_) => None,
_ if raw.method.is_some() => None,
_ => {
Expand Down Expand Up @@ -1339,7 +1343,9 @@ fn read_declared<'a>(
ty = &operator.type_annotation;
}
let TSType::TSTupleType(_) = ty else { return None };
return Some(Declared::TypeList(type_list(ty, &decl.file)?));
let mut refs = Vec::new();
const_type_list(ty, &decl.file, &mut refs)?;
return Some(Declared::TypeList(refs));
};
let mut refs = Vec::new();
collect_refs(init, consts, &decl.file, &mut refs, 0, false, false)?;
Expand Down Expand Up @@ -1397,6 +1403,48 @@ fn read_declaration_type(class: &Class<'_>, file: &str) -> Option<Declared> {
})
}

/// The classes a declared `const`'s tuple type names, nested tuples
/// flattened, read like ngtsc's evaluator (`visitType`): `typeof A`, a
/// tuple, a `readonly` one, or a named member (`name: typeof A`).
fn const_type_list(ty: &TSType<'_>, file: &str, out: &mut Vec<RawRef>) -> Option<()> {
const MAX_NESTING: u8 = 8;
fn read(ty: &TSType<'_>, file: &str, out: &mut Vec<RawRef>, depth: u8) -> Option<()> {
if depth > MAX_NESTING {
return None;
}
match ty {
TSType::TSTypeQuery(query) => {
out.push(type_name_ref(file, query.expr_name.as_ts_type_name()?)?);
Some(())
}
TSType::TSTupleType(tuple) => tuple
.element_types
.iter()
.try_for_each(|element| read_element(element, file, out, depth)),
TSType::TSTypeOperatorType(operator)
if operator.operator == TSTypeOperatorOperator::Readonly =>
{
read(&operator.type_annotation, file, out, depth + 1)
}
_ => None,
}
}
fn read_element(
element: &TSTupleElement<'_>,
file: &str,
out: &mut Vec<RawRef>,
depth: u8,
) -> Option<()> {
match element {
TSTupleElement::TSNamedTupleMember(member) => {
read_element(&member.element_type, file, out, depth + 1)
}
_ => read(element.as_ts_type()?, file, out, depth + 1),
}
}
read(ty, file, out, 0)
}

/// The classes a `[typeof A, typeof i1.B]` tuple type names; `never` names
/// none.
fn type_list(ty: &TSType<'_>, file: &str) -> Option<Vec<RawRef>> {
Expand Down
33 changes: 30 additions & 3 deletions crates/oxc_angular_compiler/src/component/decorator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,9 +793,21 @@ fn extract_host_directives<'a>(
for entry in entries {
let has_mapping_keys = entry.has_mapping_keys();
let mut meta = HostDirectiveMetadata::new(allocator, entry.name);
// Look up the source module from the import map
if let Some(import_info) = import_map.get(&entry.name) {
if let Some(namespace) = entry.namespace {
// `ns.X`: the export `X` of the namespace import's module. The
// export name is never looked up in the import map, where it may
// name an unrelated import.
meta.namespace = Some(namespace);
meta.source_module = import_map
.get(&namespace)
.filter(|info| !info.is_named_import)
.map(|info| info.source_module);
} else if let Some(import_info) = import_map.get(&entry.name) {
// An imported class is referenced by its export name: `H` for
// `import { H as P }`, `default` for a default import, `s-dir`
// for `import { "s-dir" as SDir }`.
meta.source_module = Some(import_info.source_module);
meta.export_name = import_info.string_export_name.or(import_info.imported_name);
}
meta.is_forward_reference = entry.is_forward_reference;
meta.has_mapping_keys = has_mapping_keys;
Expand Down Expand Up @@ -925,6 +937,10 @@ fn extract_param_dependency<'a>(
let mut inject_token: Option<(Ident<'a>, &'a [Ident<'a>])> = None;
let mut inject_token_is_string = false;
let mut attribute_name: Option<Ident<'a>> = None;
// The string token or attribute name as written, when ngtsc keeps it
// (see `string_literal_as_written`).
let mut inject_literal_source: Option<Ident<'a>> = None;
let mut attribute_source: Option<Ident<'a>> = None;

for decorator in &param.decorators {
if let Some(name) = crate::directive::angular_param_decorator(decorator, Some(consts)) {
Expand All @@ -938,6 +954,12 @@ fn extract_param_dependency<'a>(
let string_token =
arg.as_expression().and_then(extract_string_inject_token);
inject_token_is_string = string_token.is_some();
inject_literal_source = arg.as_expression().and_then(|expr| {
crate::output::oxc_converter::literal_as_written(
allocator,
expr.get_inner_expression(),
)
});
inject_token = string_token
.map(|token| (token, &[][..]))
.or_else(|| extract_inject_token(allocator, arg));
Expand All @@ -955,6 +977,8 @@ fn extract_param_dependency<'a>(
&& let Some(name) = s.value.as_str()
{
attribute_name = Some(name.into());
attribute_source =
crate::output::oxc_converter::string_literal_as_written(s);
}
}
}
Expand All @@ -973,12 +997,15 @@ fn extract_param_dependency<'a>(

// Handle @Attribute decorator
if let Some(attr_name) = attribute_name {
return R3DependencyMetadata::attribute(attr_name);
let mut dep = R3DependencyMetadata::attribute(attr_name);
dep.literal_source = attribute_source;
return dep;
}

if inject_token_is_string && let Some(value) = token {
let mut dep = R3DependencyMetadata::new(value);
dep.token_is_string = true;
dep.literal_source = inject_literal_source;
dep.optional = optional;
dep.skip_self = skip_self;
dep.self_ = self_;
Expand Down
Loading
Loading