Skip to content

Commit dbe612f

Browse files
crisbetoalxhub
authored andcommitted
fix(compiler-cli): disable standalone by default on older versions of Angular (#58405)
Disables the standalone by default behavior in the compiler when running against and older version of Angular. This is necessary, because the language service may be using the latest version of the compiler against and older version of core in a particular workspace. PR Close #58405
1 parent 3e50842 commit dbe612f

File tree

9 files changed

+98
-19
lines changed

9 files changed

+98
-19
lines changed

packages/compiler-cli/src/ngtsc/annotations/common/src/standalone-default-value.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ export class ComponentDecoratorHandler
258258
private readonly i18nPreserveSignificantWhitespace: boolean,
259259
private readonly strictStandalone: boolean,
260260
private readonly enableHmr: boolean,
261+
private readonly implicitStandaloneValue: boolean,
261262
) {
262263
this.extractTemplateOptions = {
263264
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
@@ -464,6 +465,7 @@ export class ComponentDecoratorHandler
464465
this.compilationMode,
465466
this.elementSchemaRegistry.getDefaultComponentElementName(),
466467
this.strictStandalone,
468+
this.implicitStandaloneValue,
467469
);
468470
// `extractDirectiveMetadata` returns `jitForced = true` when the `@Component` has
469471
// set `jit: true`. In this case, compilation of the decorator is skipped. Returning

packages/compiler-cli/src/ngtsc/annotations/component/test/component_spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ function setup(
159159
/* i18nPreserveSignificantWhitespace */ true,
160160
/* strictStandalone */ false,
161161
/* enableHmr */ false,
162+
/* implicitStandaloneValue */ true,
162163
);
163164
return {reflectionHost, handler, resourceLoader, metaRegistry};
164165
}

packages/compiler-cli/src/ngtsc/annotations/directive/src/handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export class DirectiveDecoratorHandler
137137
private readonly compilationMode: CompilationMode,
138138
private readonly jitDeclarationRegistry: JitDeclarationRegistry,
139139
private readonly strictStandalone: boolean,
140+
private readonly implicitStandaloneValue: boolean,
140141
) {}
141142

142143
readonly precedence = HandlerPrecedence.PRIMARY;
@@ -192,6 +193,7 @@ export class DirectiveDecoratorHandler
192193
this.compilationMode,
193194
/* defaultSelector */ null,
194195
this.strictStandalone,
196+
this.implicitStandaloneValue,
195197
);
196198
// `extractDirectiveMetadata` returns `jitForced = true` when the `@Directive` has
197199
// set `jit: true`. In this case, compilation of the decorator is skipped. Returning

packages/compiler-cli/src/ngtsc/annotations/directive/src/shared.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ import {tryParseSignalInputMapping} from './input_function';
8484
import {tryParseSignalModelMapping} from './model_function';
8585
import {tryParseInitializerBasedOutput} from './output_function';
8686
import {tryParseSignalQueryFromInitializer} from './query_functions';
87-
import {NG_STANDALONE_DEFAULT_VALUE} from '../../common/src/standalone-default-value';
8887

8988
const EMPTY_OBJECT: {[key: string]: string} = {};
9089

@@ -117,6 +116,7 @@ export function extractDirectiveMetadata(
117116
compilationMode: CompilationMode,
118117
defaultSelector: string | null,
119118
strictStandalone: boolean,
119+
implicitStandaloneValue: boolean,
120120
):
121121
| {
122122
jitForced: false;
@@ -336,7 +336,7 @@ export function extractDirectiveMetadata(
336336
dep.token.value.name === 'TemplateRef',
337337
);
338338

339-
let isStandalone = NG_STANDALONE_DEFAULT_VALUE;
339+
let isStandalone = implicitStandaloneValue;
340340
if (directive.has('standalone')) {
341341
const expr = directive.get('standalone')!;
342342
const resolved = evaluator.evaluate(expr);

packages/compiler-cli/src/ngtsc/annotations/directive/test/directive_spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ runInEachFileSystem(() => {
215215
/*compilationMode */ CompilationMode.FULL,
216216
jitDeclarationRegistry,
217217
/* strictStandalone */ false,
218+
/* implicitStandaloneValue */ true,
218219
);
219220

220221
const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);

packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import {
4949
unwrapExpression,
5050
wrapTypeReference,
5151
} from '../common';
52-
import {NG_STANDALONE_DEFAULT_VALUE} from '../common/src/standalone-default-value';
5352

5453
export interface PipeHandlerData {
5554
meta: R3PipeMetadata;
@@ -97,6 +96,7 @@ export class PipeDecoratorHandler
9796
private readonly compilationMode: CompilationMode,
9897
private readonly generateExtraImportsInLocalMode: boolean,
9998
private readonly strictStandalone: boolean,
99+
private readonly implicitStandaloneValue: boolean,
100100
) {}
101101

102102
readonly precedence = HandlerPrecedence.PRIMARY;
@@ -177,7 +177,7 @@ export class PipeDecoratorHandler
177177
pure = pureValue;
178178
}
179179

180-
let isStandalone = NG_STANDALONE_DEFAULT_VALUE;
180+
let isStandalone = this.implicitStandaloneValue;
181181
if (pipe.has('standalone')) {
182182
const expr = pipe.get('standalone')!;
183183
const resolved = this.evaluator.evaluate(expr);

packages/compiler-cli/src/ngtsc/core/src/compiler.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ export class NgCompiler {
391391
private readonly enableLetSyntax: boolean;
392392
private readonly angularCoreVersion: string | null;
393393
private readonly enableHmr: boolean;
394+
private readonly implicitStandaloneValue: boolean;
394395

395396
/**
396397
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
@@ -455,14 +456,20 @@ export class NgCompiler {
455456
readonly usePoisonedData: boolean,
456457
private livePerfRecorder: ActivePerfRecorder,
457458
) {
459+
this.angularCoreVersion = options['_angularCoreVersion'] ?? null;
458460
this.delegatingPerfRecorder = new DelegatingPerfRecorder(this.perfRecorder);
459461
this.usePoisonedData = usePoisonedData || !!options._compilePoisonedComponents;
460462
this.enableTemplateTypeChecker =
461463
enableTemplateTypeChecker || !!options._enableTemplateTypeChecker;
462464
// TODO(crisbeto): remove this flag and base `enableBlockSyntax` on the `angularCoreVersion`.
463465
this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
464466
this.enableLetSyntax = options['_enableLetSyntax'] ?? true;
465-
this.angularCoreVersion = options['_angularCoreVersion'] ?? null;
467+
// Standalone by default is enabled since v19. We need to toggle it here,
468+
// because the language service extension may be running with the latest
469+
// version of the compiler against an older version of Angular.
470+
this.implicitStandaloneValue =
471+
this.angularCoreVersion === null ||
472+
coreVersionSupportsFeature(this.angularCoreVersion, '>= 19.0.0-0');
466473
this.enableHmr = !!options['_enableHmr'];
467474
this.constructionDiagnostics.push(
468475
...this.adapter.constructionDiagnostics,
@@ -1494,6 +1501,7 @@ export class NgCompiler {
14941501
this.options.i18nPreserveWhitespaceForLegacyExtraction ?? true,
14951502
!!this.options.strictStandalone,
14961503
this.enableHmr,
1504+
this.implicitStandaloneValue,
14971505
),
14981506

14991507
// TODO(alxhub): understand why the cast here is necessary (something to do with `null`
@@ -1517,6 +1525,7 @@ export class NgCompiler {
15171525
compilationMode,
15181526
jitDeclarationRegistry,
15191527
!!this.options.strictStandalone,
1528+
this.implicitStandaloneValue,
15201529
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null, unknown>>,
15211530
// Pipe handler must be before injectable handler in list so pipe factories are printed
15221531
// before injectable factories (so injectable factories can delegate to them)
@@ -1532,6 +1541,7 @@ export class NgCompiler {
15321541
compilationMode,
15331542
!!this.options.generateExtraImportsInLocalMode,
15341543
!!this.options.strictStandalone,
1544+
this.implicitStandaloneValue,
15351545
),
15361546
new InjectableDecoratorHandler(
15371547
reflector,

packages/compiler-cli/test/ngtsc/ngtsc_spec.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {platform} from 'os';
1313
import ts from 'typescript';
1414

1515
import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
16-
import {absoluteFrom, NgtscCompilerHost} from '../../src/ngtsc/file_system';
16+
import {absoluteFrom} from '../../src/ngtsc/file_system';
1717
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
1818
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
1919
import {
@@ -10928,6 +10928,82 @@ runInEachFileSystem((os: string) => {
1092810928
expect(env.getContents('/test.js')).toContain(`* @fileoverview Closure comment`);
1092910929
});
1093010930
});
10931+
10932+
describe('standalone by default opt-out', () => {
10933+
it('should consider declarations as standalone by default', () => {
10934+
env.write(
10935+
'/test.ts',
10936+
`
10937+
import {Directive, Component, Pipe, NgModule} from '@angular/core';
10938+
10939+
@Directive()
10940+
export class TestDir {}
10941+
10942+
@Component({template: ''})
10943+
export class TestComp {}
10944+
10945+
@Pipe({name: 'test'})
10946+
export class TestPipe {}
10947+
10948+
@NgModule({
10949+
declarations: [TestDir, TestComp, TestPipe]
10950+
})
10951+
export class TestModule {}
10952+
`,
10953+
);
10954+
10955+
const diags = env.driveDiagnostics();
10956+
expect(diags.length).toBe(3);
10957+
expect(diags[0].messageText).toContain(
10958+
'Directive TestDir is standalone, and cannot be declared in an NgModule.',
10959+
);
10960+
expect(diags[1].messageText).toContain(
10961+
'Component TestComp is standalone, and cannot be declared in an NgModule.',
10962+
);
10963+
expect(diags[2].messageText).toContain(
10964+
'Pipe TestPipe is standalone, and cannot be declared in an NgModule.',
10965+
);
10966+
});
10967+
10968+
it('should disable standalone by default on versions older than 19', () => {
10969+
env.tsconfig({
10970+
_angularCoreVersion: '18.2.10',
10971+
});
10972+
10973+
env.write(
10974+
'/test.ts',
10975+
`
10976+
import {Directive, Component, Pipe, NgModule} from '@angular/core';
10977+
10978+
@Directive()
10979+
export class TestDir {}
10980+
10981+
@Component({template: ''})
10982+
export class TestComp {}
10983+
10984+
@Pipe({name: 'test'})
10985+
export class TestPipe {}
10986+
10987+
@NgModule({
10988+
imports: [TestDir, TestComp, TestPipe]
10989+
})
10990+
export class TestModule {}
10991+
`,
10992+
);
10993+
10994+
const diags = env.driveDiagnostics();
10995+
expect(diags.length).toBe(3);
10996+
expect(diags[0].messageText).toContain(
10997+
`The directive 'TestDir' appears in 'imports', but is not standalone`,
10998+
);
10999+
expect(diags[1].messageText).toContain(
11000+
`The component 'TestComp' appears in 'imports', but is not standalone`,
11001+
);
11002+
expect(diags[2].messageText).toContain(
11003+
`The pipe 'TestPipe' appears in 'imports', but is not standalone`,
11004+
);
11005+
});
11006+
});
1093111007
});
1093211008

1093311009
function expectTokenAtPosition<T extends ts.Node>(

0 commit comments

Comments
 (0)