mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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
This commit is contained in:
parent
3e50842593
commit
dbe612f2cd
9 changed files with 98 additions and 19 deletions
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* A constant defining the default value for the standalone attribute in Directive and Pipes decorators.
|
||||
* Extracted to a separate file to facilitate G3 patches.
|
||||
*/
|
||||
export const NG_STANDALONE_DEFAULT_VALUE = true;
|
||||
|
|
@ -258,6 +258,7 @@ export class ComponentDecoratorHandler
|
|||
private readonly i18nPreserveSignificantWhitespace: boolean,
|
||||
private readonly strictStandalone: boolean,
|
||||
private readonly enableHmr: boolean,
|
||||
private readonly implicitStandaloneValue: boolean,
|
||||
) {
|
||||
this.extractTemplateOptions = {
|
||||
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
||||
|
|
@ -464,6 +465,7 @@ export class ComponentDecoratorHandler
|
|||
this.compilationMode,
|
||||
this.elementSchemaRegistry.getDefaultComponentElementName(),
|
||||
this.strictStandalone,
|
||||
this.implicitStandaloneValue,
|
||||
);
|
||||
// `extractDirectiveMetadata` returns `jitForced = true` when the `@Component` has
|
||||
// set `jit: true`. In this case, compilation of the decorator is skipped. Returning
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ function setup(
|
|||
/* i18nPreserveSignificantWhitespace */ true,
|
||||
/* strictStandalone */ false,
|
||||
/* enableHmr */ false,
|
||||
/* implicitStandaloneValue */ true,
|
||||
);
|
||||
return {reflectionHost, handler, resourceLoader, metaRegistry};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ export class DirectiveDecoratorHandler
|
|||
private readonly compilationMode: CompilationMode,
|
||||
private readonly jitDeclarationRegistry: JitDeclarationRegistry,
|
||||
private readonly strictStandalone: boolean,
|
||||
private readonly implicitStandaloneValue: boolean,
|
||||
) {}
|
||||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
|
@ -192,6 +193,7 @@ export class DirectiveDecoratorHandler
|
|||
this.compilationMode,
|
||||
/* defaultSelector */ null,
|
||||
this.strictStandalone,
|
||||
this.implicitStandaloneValue,
|
||||
);
|
||||
// `extractDirectiveMetadata` returns `jitForced = true` when the `@Directive` has
|
||||
// set `jit: true`. In this case, compilation of the decorator is skipped. Returning
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ import {tryParseSignalInputMapping} from './input_function';
|
|||
import {tryParseSignalModelMapping} from './model_function';
|
||||
import {tryParseInitializerBasedOutput} from './output_function';
|
||||
import {tryParseSignalQueryFromInitializer} from './query_functions';
|
||||
import {NG_STANDALONE_DEFAULT_VALUE} from '../../common/src/standalone-default-value';
|
||||
|
||||
const EMPTY_OBJECT: {[key: string]: string} = {};
|
||||
|
||||
|
|
@ -117,6 +116,7 @@ export function extractDirectiveMetadata(
|
|||
compilationMode: CompilationMode,
|
||||
defaultSelector: string | null,
|
||||
strictStandalone: boolean,
|
||||
implicitStandaloneValue: boolean,
|
||||
):
|
||||
| {
|
||||
jitForced: false;
|
||||
|
|
@ -336,7 +336,7 @@ export function extractDirectiveMetadata(
|
|||
dep.token.value.name === 'TemplateRef',
|
||||
);
|
||||
|
||||
let isStandalone = NG_STANDALONE_DEFAULT_VALUE;
|
||||
let isStandalone = implicitStandaloneValue;
|
||||
if (directive.has('standalone')) {
|
||||
const expr = directive.get('standalone')!;
|
||||
const resolved = evaluator.evaluate(expr);
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ runInEachFileSystem(() => {
|
|||
/*compilationMode */ CompilationMode.FULL,
|
||||
jitDeclarationRegistry,
|
||||
/* strictStandalone */ false,
|
||||
/* implicitStandaloneValue */ true,
|
||||
);
|
||||
|
||||
const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ import {
|
|||
unwrapExpression,
|
||||
wrapTypeReference,
|
||||
} from '../common';
|
||||
import {NG_STANDALONE_DEFAULT_VALUE} from '../common/src/standalone-default-value';
|
||||
|
||||
export interface PipeHandlerData {
|
||||
meta: R3PipeMetadata;
|
||||
|
|
@ -97,6 +96,7 @@ export class PipeDecoratorHandler
|
|||
private readonly compilationMode: CompilationMode,
|
||||
private readonly generateExtraImportsInLocalMode: boolean,
|
||||
private readonly strictStandalone: boolean,
|
||||
private readonly implicitStandaloneValue: boolean,
|
||||
) {}
|
||||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
|
@ -177,7 +177,7 @@ export class PipeDecoratorHandler
|
|||
pure = pureValue;
|
||||
}
|
||||
|
||||
let isStandalone = NG_STANDALONE_DEFAULT_VALUE;
|
||||
let isStandalone = this.implicitStandaloneValue;
|
||||
if (pipe.has('standalone')) {
|
||||
const expr = pipe.get('standalone')!;
|
||||
const resolved = this.evaluator.evaluate(expr);
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ export class NgCompiler {
|
|||
private readonly enableLetSyntax: boolean;
|
||||
private readonly angularCoreVersion: string | null;
|
||||
private readonly enableHmr: boolean;
|
||||
private readonly implicitStandaloneValue: boolean;
|
||||
|
||||
/**
|
||||
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
|
||||
|
|
@ -455,6 +456,7 @@ export class NgCompiler {
|
|||
readonly usePoisonedData: boolean,
|
||||
private livePerfRecorder: ActivePerfRecorder,
|
||||
) {
|
||||
this.angularCoreVersion = options['_angularCoreVersion'] ?? null;
|
||||
this.delegatingPerfRecorder = new DelegatingPerfRecorder(this.perfRecorder);
|
||||
this.usePoisonedData = usePoisonedData || !!options._compilePoisonedComponents;
|
||||
this.enableTemplateTypeChecker =
|
||||
|
|
@ -462,7 +464,12 @@ export class NgCompiler {
|
|||
// TODO(crisbeto): remove this flag and base `enableBlockSyntax` on the `angularCoreVersion`.
|
||||
this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
|
||||
this.enableLetSyntax = options['_enableLetSyntax'] ?? true;
|
||||
this.angularCoreVersion = options['_angularCoreVersion'] ?? null;
|
||||
// Standalone by default is enabled since v19. We need to toggle it here,
|
||||
// because the language service extension may be running with the latest
|
||||
// version of the compiler against an older version of Angular.
|
||||
this.implicitStandaloneValue =
|
||||
this.angularCoreVersion === null ||
|
||||
coreVersionSupportsFeature(this.angularCoreVersion, '>= 19.0.0-0');
|
||||
this.enableHmr = !!options['_enableHmr'];
|
||||
this.constructionDiagnostics.push(
|
||||
...this.adapter.constructionDiagnostics,
|
||||
|
|
@ -1494,6 +1501,7 @@ export class NgCompiler {
|
|||
this.options.i18nPreserveWhitespaceForLegacyExtraction ?? true,
|
||||
!!this.options.strictStandalone,
|
||||
this.enableHmr,
|
||||
this.implicitStandaloneValue,
|
||||
),
|
||||
|
||||
// TODO(alxhub): understand why the cast here is necessary (something to do with `null`
|
||||
|
|
@ -1517,6 +1525,7 @@ export class NgCompiler {
|
|||
compilationMode,
|
||||
jitDeclarationRegistry,
|
||||
!!this.options.strictStandalone,
|
||||
this.implicitStandaloneValue,
|
||||
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null, unknown>>,
|
||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||
// before injectable factories (so injectable factories can delegate to them)
|
||||
|
|
@ -1532,6 +1541,7 @@ export class NgCompiler {
|
|||
compilationMode,
|
||||
!!this.options.generateExtraImportsInLocalMode,
|
||||
!!this.options.strictStandalone,
|
||||
this.implicitStandaloneValue,
|
||||
),
|
||||
new InjectableDecoratorHandler(
|
||||
reflector,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {platform} from 'os';
|
|||
import ts from 'typescript';
|
||||
|
||||
import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
|
||||
import {absoluteFrom, NgtscCompilerHost} from '../../src/ngtsc/file_system';
|
||||
import {absoluteFrom} from '../../src/ngtsc/file_system';
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
|
||||
import {
|
||||
|
|
@ -10928,6 +10928,82 @@ runInEachFileSystem((os: string) => {
|
|||
expect(env.getContents('/test.js')).toContain(`* @fileoverview Closure comment`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone by default opt-out', () => {
|
||||
it('should consider declarations as standalone by default', () => {
|
||||
env.write(
|
||||
'/test.ts',
|
||||
`
|
||||
import {Directive, Component, Pipe, NgModule} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {}
|
||||
|
||||
@Component({template: ''})
|
||||
export class TestComp {}
|
||||
|
||||
@Pipe({name: 'test'})
|
||||
export class TestPipe {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [TestDir, TestComp, TestPipe]
|
||||
})
|
||||
export class TestModule {}
|
||||
`,
|
||||
);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(3);
|
||||
expect(diags[0].messageText).toContain(
|
||||
'Directive TestDir is standalone, and cannot be declared in an NgModule.',
|
||||
);
|
||||
expect(diags[1].messageText).toContain(
|
||||
'Component TestComp is standalone, and cannot be declared in an NgModule.',
|
||||
);
|
||||
expect(diags[2].messageText).toContain(
|
||||
'Pipe TestPipe is standalone, and cannot be declared in an NgModule.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should disable standalone by default on versions older than 19', () => {
|
||||
env.tsconfig({
|
||||
_angularCoreVersion: '18.2.10',
|
||||
});
|
||||
|
||||
env.write(
|
||||
'/test.ts',
|
||||
`
|
||||
import {Directive, Component, Pipe, NgModule} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {}
|
||||
|
||||
@Component({template: ''})
|
||||
export class TestComp {}
|
||||
|
||||
@Pipe({name: 'test'})
|
||||
export class TestPipe {}
|
||||
|
||||
@NgModule({
|
||||
imports: [TestDir, TestComp, TestPipe]
|
||||
})
|
||||
export class TestModule {}
|
||||
`,
|
||||
);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(3);
|
||||
expect(diags[0].messageText).toContain(
|
||||
`The directive 'TestDir' appears in 'imports', but is not standalone`,
|
||||
);
|
||||
expect(diags[1].messageText).toContain(
|
||||
`The component 'TestComp' appears in 'imports', but is not standalone`,
|
||||
);
|
||||
expect(diags[2].messageText).toContain(
|
||||
`The pipe 'TestPipe' appears in 'imports', but is not standalone`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectTokenAtPosition<T extends ts.Node>(
|
||||
|
|
|
|||
Loading…
Reference in a new issue