@@ -52,6 +52,7 @@ pub fn generate_component_dts(
5252 type_argument_count : u32 ,
5353 content_query_names : & [ String ] ,
5454 has_injectable : bool ,
55+ ng_content_selectors : & [ String ] ,
5556) -> DtsDeclaration {
5657 let class_name = metadata. class_name . as_str ( ) ;
5758 let type_with_params = type_with_parameters ( class_name, type_argument_count) ;
@@ -103,8 +104,19 @@ pub fn generate_component_dts(
103104 )
104105 } ;
105106
106- // NgContentSelectors: would require template analysis; use never for now
107- let ng_content_selectors = "never" . to_string ( ) ;
107+ // NgContentSelectors: format as tuple type from template ng-content selectors
108+ let ng_content_selectors = if ng_content_selectors. is_empty ( ) {
109+ "never" . to_string ( )
110+ } else {
111+ format ! (
112+ "[{}]" ,
113+ ng_content_selectors
114+ . iter( )
115+ . map( |s| format!( "\" {}\" " , escape_dts_string( s) ) )
116+ . collect:: <Vec <_>>( )
117+ . join( ", " )
118+ )
119+ } ;
108120
109121 let is_standalone = if metadata. standalone { "true" } else { "false" } ;
110122
@@ -140,6 +152,9 @@ pub fn generate_component_dts(
140152 . push_str ( & format ! ( "\n static ɵprov: i0.ɵɵInjectableDeclaration<{type_with_params}>;" ) ) ;
141153 }
142154
155+ // Add ngAcceptInputType_* fields for non-signal inputs with transform functions
156+ generate_input_transform_fields ( & metadata. inputs , & mut members) ;
157+
143158 DtsDeclaration { class_name : class_name. to_string ( ) , members }
144159}
145160
@@ -241,6 +256,9 @@ pub fn generate_directive_dts(
241256 . push_str ( & format ! ( "\n static ɵprov: i0.ɵɵInjectableDeclaration<{type_with_params}>;" ) ) ;
242257 }
243258
259+ // Add ngAcceptInputType_* fields for non-signal inputs with transform functions
260+ generate_input_transform_fields ( & metadata. inputs , & mut members) ;
261+
244262 DtsDeclaration { class_name : class_name. to_string ( ) , members }
245263}
246264
@@ -253,10 +271,13 @@ pub fn generate_directive_dts(
253271/// Produces:
254272/// - `static ɵfac: i0.ɵɵFactoryDeclaration<T, CtorDeps>;`
255273/// - `static ɵpipe: i0.ɵɵPipeDeclaration<T, Name, IsStandalone>;`
256- pub fn generate_pipe_dts ( metadata : & PipeMetadata , has_injectable : bool ) -> DtsDeclaration {
274+ pub fn generate_pipe_dts (
275+ metadata : & PipeMetadata ,
276+ type_argument_count : u32 ,
277+ has_injectable : bool ,
278+ ) -> DtsDeclaration {
257279 let class_name = metadata. class_name . as_str ( ) ;
258- // Pipes don't have type parameters in practice
259- let type_with_params = class_name. to_string ( ) ;
280+ let type_with_params = type_with_parameters ( class_name, type_argument_count) ;
260281
261282 // ɵfac declaration
262283 let ctor_deps_type =
@@ -267,7 +288,7 @@ pub fn generate_pipe_dts(metadata: &PipeMetadata, has_injectable: bool) -> DtsDe
267288 // ɵpipe declaration
268289 let pipe_name = match & metadata. pipe_name {
269290 Some ( name) => format ! ( "\" {}\" " , escape_dts_string( name. as_str( ) ) ) ,
270- None => "\" \" " . to_string ( ) ,
291+ None => "null " . to_string ( ) ,
271292 } ;
272293
273294 let is_standalone = if metadata. standalone { "true" } else { "false" } ;
@@ -296,9 +317,13 @@ pub fn generate_pipe_dts(metadata: &PipeMetadata, has_injectable: bool) -> DtsDe
296317/// - `static ɵfac: i0.ɵɵFactoryDeclaration<T, CtorDeps>;`
297318/// - `static ɵmod: i0.ɵɵNgModuleDeclaration<T, Declarations, Imports, Exports>;`
298319/// - `static ɵinj: i0.ɵɵInjectorDeclaration<T>;`
299- pub fn generate_ng_module_dts ( metadata : & NgModuleMetadata , has_injectable : bool ) -> DtsDeclaration {
320+ pub fn generate_ng_module_dts (
321+ metadata : & NgModuleMetadata ,
322+ type_argument_count : u32 ,
323+ has_injectable : bool ,
324+ ) -> DtsDeclaration {
300325 let class_name = metadata. class_name . as_str ( ) ;
301- let type_with_params = class_name . to_string ( ) ;
326+ let type_with_params = type_with_parameters ( class_name , type_argument_count ) ;
302327
303328 // ɵfac declaration
304329 let ctor_deps_type =
@@ -375,9 +400,12 @@ pub fn generate_ng_module_dts(metadata: &NgModuleMetadata, has_injectable: bool)
375400/// Produces:
376401/// - `static ɵfac: i0.ɵɵFactoryDeclaration<T, CtorDeps>;`
377402/// - `static ɵprov: i0.ɵɵInjectableDeclaration<T>;`
378- pub fn generate_injectable_dts ( metadata : & InjectableMetadata ) -> DtsDeclaration {
403+ pub fn generate_injectable_dts (
404+ metadata : & InjectableMetadata ,
405+ type_argument_count : u32 ,
406+ ) -> DtsDeclaration {
379407 let class_name = metadata. class_name . as_str ( ) ;
380- let type_with_params = class_name . to_string ( ) ;
408+ let type_with_params = type_with_parameters ( class_name , type_argument_count ) ;
381409
382410 // ɵfac declaration
383411 let ctor_deps_type =
@@ -412,22 +440,47 @@ fn type_with_parameters(class_name: &str, count: u32) -> String {
412440
413441/// Generate the constructor deps type parameter for `ɵɵFactoryDeclaration`.
414442///
415- /// Returns `never` if there are no `@Attribute()` dependencies .
416- /// Returns a tuple type like `[null, "attrName ", null]` if there are attribute deps .
443+ /// Returns `never` if no dependency has any special flags (attribute, optional, host, self, skipSelf) .
444+ /// Otherwise returns a tuple type like `[null, {attribute: "title ", optional: true}, null]`.
417445fn generate_ctor_deps_type_from_component_deps ( deps : Option < & [ R3DependencyMetadata ] > ) -> String {
418446 match deps {
419447 None => "never" . to_string ( ) ,
420448 Some ( deps) => {
421- let has_attributes = deps. iter ( ) . any ( |d| d. attribute_name . is_some ( ) ) ;
422- if !has_attributes {
449+ let dep_types: Vec < Option < String > > = deps
450+ . iter ( )
451+ . map ( |d| {
452+ let mut entries: Vec < String > = Vec :: new ( ) ;
453+ if let Some ( name) = & d. attribute_name {
454+ entries
455+ . push ( format ! ( "attribute: \" {}\" " , escape_dts_string( name. as_str( ) ) ) ) ;
456+ }
457+ if d. optional {
458+ entries. push ( "optional: true" . to_string ( ) ) ;
459+ }
460+ if d. host {
461+ entries. push ( "host: true" . to_string ( ) ) ;
462+ }
463+ if d. self_ {
464+ entries. push ( "self: true" . to_string ( ) ) ;
465+ }
466+ if d. skip_self {
467+ entries. push ( "skipSelf: true" . to_string ( ) ) ;
468+ }
469+ if entries. is_empty ( ) {
470+ None
471+ } else {
472+ Some ( format ! ( "{{ {} }}" , entries. join( ", " ) ) )
473+ }
474+ } )
475+ . collect ( ) ;
476+
477+ let has_types = dep_types. iter ( ) . any ( |t| t. is_some ( ) ) ;
478+ if !has_types {
423479 "never" . to_string ( )
424480 } else {
425- let entries: Vec < String > = deps
426- . iter ( )
427- . map ( |d| match & d. attribute_name {
428- Some ( name) => format ! ( "\" {}\" " , escape_dts_string( name. as_str( ) ) ) ,
429- None => "null" . to_string ( ) ,
430- } )
481+ let entries: Vec < String > = dep_types
482+ . into_iter ( )
483+ . map ( |t| t. unwrap_or_else ( || "null" . to_string ( ) ) )
431484 . collect ( ) ;
432485 format ! ( "[{}]" , entries. join( ", " ) )
433486 }
@@ -439,35 +492,80 @@ fn generate_ctor_deps_type_from_component_deps(deps: Option<&[R3DependencyMetada
439492///
440493/// Uses the factory module's `R3DependencyMetadata` which is used by directives,
441494/// pipes, NgModules, and injectables.
495+ ///
496+ /// Returns `never` if no dependency has any special flags (attribute, optional, host, self, skipSelf).
497+ /// Otherwise returns a tuple type like `[null, {attribute: string, optional: true}, null]`.
442498fn generate_ctor_deps_type_from_factory_deps (
443499 deps : Option < & [ crate :: factory:: R3DependencyMetadata ] > ,
444500) -> String {
445501 match deps {
446502 None => "never" . to_string ( ) ,
447503 Some ( deps) => {
448- // The factory R3DependencyMetadata uses `attribute_name_type` (an OutputExpression)
449- // for @Attribute() dependencies. If any dep has it set, we emit a tuple type.
450- let has_attributes = deps. iter ( ) . any ( |d| d. attribute_name_type . is_some ( ) ) ;
451- if !has_attributes {
504+ let dep_types: Vec < Option < String > > = deps
505+ . iter ( )
506+ . map ( |d| {
507+ let mut entries: Vec < String > = Vec :: new ( ) ;
508+ if d. attribute_name_type . is_some ( ) {
509+ entries. push ( "attribute: string" . to_string ( ) ) ;
510+ }
511+ if d. optional {
512+ entries. push ( "optional: true" . to_string ( ) ) ;
513+ }
514+ if d. host {
515+ entries. push ( "host: true" . to_string ( ) ) ;
516+ }
517+ if d. self_ {
518+ entries. push ( "self: true" . to_string ( ) ) ;
519+ }
520+ if d. skip_self {
521+ entries. push ( "skipSelf: true" . to_string ( ) ) ;
522+ }
523+ if entries. is_empty ( ) {
524+ None
525+ } else {
526+ Some ( format ! ( "{{ {} }}" , entries. join( ", " ) ) )
527+ }
528+ } )
529+ . collect ( ) ;
530+
531+ let has_types = dep_types. iter ( ) . any ( |t| t. is_some ( ) ) ;
532+ if !has_types {
452533 "never" . to_string ( )
453534 } else {
454- let entries: Vec < String > = deps
455- . iter ( )
456- . map ( |d| {
457- if d. attribute_name_type . is_some ( ) {
458- // @Attribute deps get a string type in the tuple
459- "string" . to_string ( )
460- } else {
461- "null" . to_string ( )
462- }
463- } )
535+ let entries: Vec < String > = dep_types
536+ . into_iter ( )
537+ . map ( |t| t. unwrap_or_else ( || "null" . to_string ( ) ) )
464538 . collect ( ) ;
465539 format ! ( "[{}]" , entries. join( ", " ) )
466540 }
467541 }
468542 }
469543}
470544
545+ /// Generate `ngAcceptInputType_*` static fields for non-signal inputs with transform functions.
546+ ///
547+ /// When an input has a `transform` function (e.g., `@Input({transform: booleanAttribute})`),
548+ /// Angular generates a static field like:
549+ /// ```text
550+ /// static ngAcceptInputType_disabled: unknown;
551+ /// ```
552+ /// This enables template type-checking to know that transformed inputs accept wider types.
553+ ///
554+ /// Signal inputs do NOT generate these fields (they capture WriteT within the InputSignal type).
555+ ///
556+ /// Note: We use `unknown` as the type because we don't have access to the TypeScript type checker
557+ /// to determine the actual write type of the transform function.
558+ fn generate_input_transform_fields ( inputs : & [ R3InputMetadata ] , members : & mut String ) {
559+ for input in inputs {
560+ if !input. is_signal && input. transform_function . is_some ( ) {
561+ members. push_str ( & format ! (
562+ "\n static ngAcceptInputType_{}: unknown;" ,
563+ input. class_property_name. as_str( )
564+ ) ) ;
565+ }
566+ }
567+ }
568+
471569/// Generate the input map type for `ɵɵComponentDeclaration` / `ɵɵDirectiveDeclaration`.
472570///
473571/// Produces a TypeScript object literal type like:
@@ -628,7 +726,11 @@ fn extract_directive_name_from_expr(expr: &crate::output::ast::OutputExpression)
628726
629727/// Escape a string for use in a TypeScript `.d.ts` string literal type.
630728fn escape_dts_string ( s : & str ) -> String {
631- s. replace ( '\\' , "\\ \\ " ) . replace ( '"' , "\\ \" " )
729+ s. replace ( '\\' , "\\ \\ " )
730+ . replace ( '"' , "\\ \" " )
731+ . replace ( '\n' , "\\ n" )
732+ . replace ( '\r' , "\\ r" )
733+ . replace ( '\t' , "\\ t" )
632734}
633735
634736#[ cfg( test) ]
@@ -650,6 +752,9 @@ mod tests {
650752 assert_eq ! ( escape_dts_string( "hello" ) , "hello" ) ;
651753 assert_eq ! ( escape_dts_string( r#"he"llo"# ) , r#"he\"llo"# ) ;
652754 assert_eq ! ( escape_dts_string( r"he\llo" ) , r"he\\llo" ) ;
755+ assert_eq ! ( escape_dts_string( "line1\n line2" ) , "line1\\ nline2" ) ;
756+ assert_eq ! ( escape_dts_string( "col1\t col2" ) , "col1\\ tcol2" ) ;
757+ assert_eq ! ( escape_dts_string( "a\r \n b" ) , "a\\ r\\ nb" ) ;
653758 }
654759
655760 #[ test]
0 commit comments