@@ -450,6 +450,21 @@ fn get_bool_property(obj: &ObjectExpression<'_>, name: &str) -> Option<bool> {
450450 None
451451}
452452
453+ /// Determine the default value for `standalone` based on the declaration's `version` field.
454+ /// Angular v19+ defaults to `true`; earlier versions default to `false`.
455+ /// The special placeholder version `"0.0.0-PLACEHOLDER"` (used in dev builds) defaults to `true`.
456+ fn get_default_standalone_value ( meta : & ObjectExpression < ' _ > ) -> bool {
457+ if let Some ( version_str) = get_string_property ( meta, "version" ) {
458+ if version_str == "0.0.0-PLACEHOLDER" {
459+ return true ;
460+ }
461+ if let Ok ( version) = semver:: Version :: parse ( version_str) {
462+ return version. major >= 19 ;
463+ }
464+ }
465+ true // If we can't determine the version, default to true (latest behavior)
466+ }
467+
453468/// Extract the `deps` array from a factory metadata object and generate inject calls.
454469fn extract_deps_source ( obj : & ObjectExpression < ' _ > , source : & str , ns : & str ) -> String {
455470 for prop in & obj. properties {
@@ -821,7 +836,8 @@ fn link_pipe(
821836) -> Option < String > {
822837 let pipe_name = get_string_property ( meta, "name" ) ?;
823838 let pure = get_property_source ( meta, "pure" , source) . unwrap_or ( "true" ) ;
824- let standalone = get_property_source ( meta, "isStandalone" , source) . unwrap_or ( "true" ) ;
839+ let standalone = get_property_source ( meta, "isStandalone" , source)
840+ . unwrap_or_else ( || if get_default_standalone_value ( meta) { "true" } else { "false" } ) ;
825841
826842 Some ( format ! (
827843 "{ns}.\u{0275} \u{0275} definePipe({{ name: \" {pipe_name}\" , type: {type_name}, pure: {pure}, standalone: {standalone} }})"
@@ -1011,7 +1027,8 @@ fn link_directive(
10111027 if let Some ( export_as) = get_property_source ( meta, "exportAs" , source) {
10121028 parts. push ( format ! ( "exportAs: {export_as}" ) ) ;
10131029 }
1014- let standalone = get_bool_property ( meta, "isStandalone" ) . unwrap_or ( true ) ;
1030+ let standalone = get_bool_property ( meta, "isStandalone" )
1031+ . unwrap_or_else ( || get_default_standalone_value ( meta) ) ;
10151032 parts. push ( format ! ( "standalone: {standalone}" ) ) ;
10161033
10171034 if get_bool_property ( meta, "isSignal" ) == Some ( true ) {
@@ -1430,7 +1447,8 @@ fn link_component(
14301447 }
14311448
14321449 // 11. standalone
1433- let standalone = get_bool_property ( meta, "isStandalone" ) . unwrap_or ( true ) ;
1450+ let standalone = get_bool_property ( meta, "isStandalone" )
1451+ . unwrap_or_else ( || get_default_standalone_value ( meta) ) ;
14341452 parts. push ( format ! ( "standalone: {standalone}" ) ) ;
14351453
14361454 // 11b. signals
@@ -2560,4 +2578,194 @@ MyComp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.
25602578 "InheritDefinitionFeature must come before NgOnChangesFeature"
25612579 ) ;
25622580 }
2581+
2582+ // === Issue #87: Version-aware standalone defaulting ===
2583+
2584+ #[ test]
2585+ fn test_link_component_v12_defaults_standalone_false ( ) {
2586+ let allocator = Allocator :: default ( ) ;
2587+ let code = r#"
2588+ import * as i0 from "@angular/core";
2589+ class MyComponent {}
2590+ MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.0.5", ngImport: i0, type: MyComponent, selector: "my-comp", template: "<div>Hello</div>" });
2591+ "# ;
2592+ let result = link ( & allocator, code, "test.mjs" ) ;
2593+ assert ! ( result. linked) ;
2594+ assert ! (
2595+ result. code. contains( "standalone: false" ) ,
2596+ "v12 component without isStandalone should default to false, got:\n {}" ,
2597+ result. code
2598+ ) ;
2599+ }
2600+
2601+ #[ test]
2602+ fn test_link_component_v18_defaults_standalone_false ( ) {
2603+ let allocator = Allocator :: default ( ) ;
2604+ let code = r#"
2605+ import * as i0 from "@angular/core";
2606+ class MyComponent {}
2607+ MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.0", ngImport: i0, type: MyComponent, selector: "my-comp", template: "<div>Hello</div>" });
2608+ "# ;
2609+ let result = link ( & allocator, code, "test.mjs" ) ;
2610+ assert ! ( result. linked) ;
2611+ assert ! (
2612+ result. code. contains( "standalone: false" ) ,
2613+ "v18 component without isStandalone should default to false, got:\n {}" ,
2614+ result. code
2615+ ) ;
2616+ }
2617+
2618+ #[ test]
2619+ fn test_link_component_v19_defaults_standalone_true ( ) {
2620+ let allocator = Allocator :: default ( ) ;
2621+ let code = r#"
2622+ import * as i0 from "@angular/core";
2623+ class MyComponent {}
2624+ MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.0", ngImport: i0, type: MyComponent, selector: "my-comp", template: "<div>Hello</div>" });
2625+ "# ;
2626+ let result = link ( & allocator, code, "test.mjs" ) ;
2627+ assert ! ( result. linked) ;
2628+ assert ! (
2629+ result. code. contains( "standalone: true" ) ,
2630+ "v19 component without isStandalone should default to true, got:\n {}" ,
2631+ result. code
2632+ ) ;
2633+ }
2634+
2635+ #[ test]
2636+ fn test_link_component_v20_defaults_standalone_true ( ) {
2637+ let allocator = Allocator :: default ( ) ;
2638+ let code = r#"
2639+ import * as i0 from "@angular/core";
2640+ class MyComponent {}
2641+ MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.0", ngImport: i0, type: MyComponent, selector: "my-comp", template: "<div>Hello</div>" });
2642+ "# ;
2643+ let result = link ( & allocator, code, "test.mjs" ) ;
2644+ assert ! ( result. linked) ;
2645+ assert ! (
2646+ result. code. contains( "standalone: true" ) ,
2647+ "v20 component without isStandalone should default to true, got:\n {}" ,
2648+ result. code
2649+ ) ;
2650+ }
2651+
2652+ #[ test]
2653+ fn test_link_component_placeholder_defaults_standalone_true ( ) {
2654+ let allocator = Allocator :: default ( ) ;
2655+ let code = r#"
2656+ import * as i0 from "@angular/core";
2657+ class MyComponent {}
2658+ MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, selector: "my-comp", template: "<div>Hello</div>" });
2659+ "# ;
2660+ let result = link ( & allocator, code, "test.mjs" ) ;
2661+ assert ! ( result. linked) ;
2662+ assert ! (
2663+ result. code. contains( "standalone: true" ) ,
2664+ "0.0.0-PLACEHOLDER component without isStandalone should default to true, got:\n {}" ,
2665+ result. code
2666+ ) ;
2667+ }
2668+
2669+ #[ test]
2670+ fn test_link_component_explicit_standalone_overrides_version ( ) {
2671+ let allocator = Allocator :: default ( ) ;
2672+ // v12 but explicitly standalone: true
2673+ let code = r#"
2674+ import * as i0 from "@angular/core";
2675+ class MyComponent {}
2676+ MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.0.5", ngImport: i0, type: MyComponent, selector: "my-comp", isStandalone: true, template: "<div>Hello</div>" });
2677+ "# ;
2678+ let result = link ( & allocator, code, "test.mjs" ) ;
2679+ assert ! ( result. linked) ;
2680+ assert ! (
2681+ result. code. contains( "standalone: true" ) ,
2682+ "Explicit isStandalone: true should override version default, got:\n {}" ,
2683+ result. code
2684+ ) ;
2685+ }
2686+
2687+ #[ test]
2688+ fn test_link_directive_v12_defaults_standalone_false ( ) {
2689+ let allocator = Allocator :: default ( ) ;
2690+ let code = r#"
2691+ import * as i0 from "@angular/core";
2692+ class NgIf {}
2693+ NgIf.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "12.0.5", ngImport: i0, type: NgIf, selector: "[ngIf]" });
2694+ "# ;
2695+ let result = link ( & allocator, code, "common.mjs" ) ;
2696+ assert ! ( result. linked) ;
2697+ assert ! (
2698+ result. code. contains( "standalone: false" ) ,
2699+ "v12 directive without isStandalone should default to false, got:\n {}" ,
2700+ result. code
2701+ ) ;
2702+ }
2703+
2704+ #[ test]
2705+ fn test_link_directive_v19_defaults_standalone_true ( ) {
2706+ let allocator = Allocator :: default ( ) ;
2707+ let code = r#"
2708+ import * as i0 from "@angular/core";
2709+ class MyDir {}
2710+ MyDir.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.0", ngImport: i0, type: MyDir, selector: "[myDir]" });
2711+ "# ;
2712+ let result = link ( & allocator, code, "test.mjs" ) ;
2713+ assert ! ( result. linked) ;
2714+ assert ! (
2715+ result. code. contains( "standalone: true" ) ,
2716+ "v19 directive without isStandalone should default to true, got:\n {}" ,
2717+ result. code
2718+ ) ;
2719+ }
2720+
2721+ #[ test]
2722+ fn test_link_pipe_v12_defaults_standalone_false ( ) {
2723+ let allocator = Allocator :: default ( ) ;
2724+ let code = r#"
2725+ import * as i0 from "@angular/core";
2726+ class AsyncPipe {}
2727+ AsyncPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "12.0.0", version: "12.0.5", ngImport: i0, type: AsyncPipe, name: "async" });
2728+ "# ;
2729+ let result = link ( & allocator, code, "common.mjs" ) ;
2730+ assert ! ( result. linked) ;
2731+ assert ! (
2732+ result. code. contains( "standalone: false" ) ,
2733+ "v12 pipe without isStandalone should default to false, got:\n {}" ,
2734+ result. code
2735+ ) ;
2736+ }
2737+
2738+ #[ test]
2739+ fn test_link_pipe_v19_defaults_standalone_true ( ) {
2740+ let allocator = Allocator :: default ( ) ;
2741+ let code = r#"
2742+ import * as i0 from "@angular/core";
2743+ class AsyncPipe {}
2744+ AsyncPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.0", ngImport: i0, type: AsyncPipe, name: "async" });
2745+ "# ;
2746+ let result = link ( & allocator, code, "common.mjs" ) ;
2747+ assert ! ( result. linked) ;
2748+ assert ! (
2749+ result. code. contains( "standalone: true" ) ,
2750+ "v19 pipe without isStandalone should default to true, got:\n {}" ,
2751+ result. code
2752+ ) ;
2753+ }
2754+
2755+ #[ test]
2756+ fn test_link_component_v19_prerelease_defaults_standalone_true ( ) {
2757+ let allocator = Allocator :: default ( ) ;
2758+ let code = r#"
2759+ import * as i0 from "@angular/core";
2760+ class MyComponent {}
2761+ MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.0-rc.1", ngImport: i0, type: MyComponent, selector: "my-comp", template: "<div>Hello</div>" });
2762+ "# ;
2763+ let result = link ( & allocator, code, "test.mjs" ) ;
2764+ assert ! ( result. linked) ;
2765+ assert ! (
2766+ result. code. contains( "standalone: true" ) ,
2767+ "v19.0.0-rc.1 component without isStandalone should default to true, got:\n {}" ,
2768+ result. code
2769+ ) ;
2770+ }
25632771}
0 commit comments