mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
feat(compiler-cli): add experimental support for fast type declaration emission (#61334)
In declaration-only emission mode, the compiler extracts the type declarations (.d.ts) files without full type-checking, which is possible with sufficient type annotations on exports that can be ensured by the `isolatedDeclarations` TS compiler option. This allows us to decouple type declaration emission from the actual full compilation doing the type-checking, thereby removing the edge between dependent TS files in the build action graph. In other words, the compilation of a TS file no longer indirectly depends on the compilation of all the TS files it imports through its dependency on their type declarations, because the type declarations themselves no longer depend on the compilation of their associated TS file. Without the coupling between type declaration emission and compilation, compilation time of a TS project is no longer bound dependent on the depth of the TS dependency tree as we can now build the entire project with just two entirely parallel phases: 1) emit the type declarations of all TS files in parallel and 2) compile all TS files in parallel. Since the Angular compiler adds static metadata fields to components, directives, modules, pipes and services based on their respective class annotations, it needs to actively partake in the type declaration emission in order to provide the types for these static fields in the declaration. In this change, we add experimental support for a declaration-only emission mode based on the local compilation mode, which already operates without type-checking and access to external type information, i.e. the same environment as is required for declaration-only emisssion. Apart from the same restrictions applied in local compilation mode, there are a few more restrictions imposed on code being compatible with this initial and experimental implementation: * No support for `@NgModule`s using external references. * No support for `hostDirectives` in `@Component`s and `@Directive`s using external references * No support for `@Input` annotations with `transform`. PR Close #61334
This commit is contained in:
parent
68d774f49d
commit
e62fb359d6
37 changed files with 754 additions and 101 deletions
|
|
@ -7,6 +7,7 @@
|
|||
// @public
|
||||
export interface BazelAndG3Options {
|
||||
annotateForClosureCompiler?: boolean;
|
||||
_geminiAllowEmitDeclarationOnly?: boolean;
|
||||
generateDeepReexports?: boolean;
|
||||
generateExtraImportsInLocalMode?: boolean;
|
||||
onlyExplicitDeferDependencyImports?: boolean;
|
||||
|
|
|
|||
|
|
@ -279,6 +279,7 @@ export class ComponentDecoratorHandler
|
|||
private readonly implicitStandaloneValue: boolean,
|
||||
private readonly typeCheckHostBindings: boolean,
|
||||
private readonly enableSelectorless: boolean,
|
||||
private readonly emitDeclarationOnly: boolean,
|
||||
) {
|
||||
this.extractTemplateOptions = {
|
||||
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
||||
|
|
@ -488,6 +489,7 @@ export class ComponentDecoratorHandler
|
|||
this.elementSchemaRegistry.getDefaultComponentElementName(),
|
||||
this.strictStandalone,
|
||||
this.implicitStandaloneValue,
|
||||
this.emitDeclarationOnly,
|
||||
);
|
||||
// `extractDirectiveMetadata` returns `jitForced = true` when the `@Component` has
|
||||
// set `jit: true`. In this case, compilation of the decorator is skipped. Returning
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ function setup(
|
|||
/* implicitStandaloneValue */ true,
|
||||
/* typeCheckHostBindings */ true,
|
||||
/* enableSelectorless */ false,
|
||||
/* emitDeclarationOnly */ false,
|
||||
);
|
||||
return {reflectionHost, handler, resourceLoader, metaRegistry};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ export class DirectiveDecoratorHandler
|
|||
private readonly implicitStandaloneValue: boolean,
|
||||
private readonly usePoisonedData: boolean,
|
||||
private readonly typeCheckHostBindings: boolean,
|
||||
private readonly emitDeclarationOnly: boolean,
|
||||
) {}
|
||||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
|
@ -209,6 +210,7 @@ export class DirectiveDecoratorHandler
|
|||
/* defaultSelector */ null,
|
||||
this.strictStandalone,
|
||||
this.implicitStandaloneValue,
|
||||
this.emitDeclarationOnly,
|
||||
);
|
||||
// `extractDirectiveMetadata` returns `jitForced = true` when the `@Directive` has
|
||||
// set `jit: true`. In this case, compilation of the decorator is skipped. Returning
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ export function extractDirectiveMetadata(
|
|||
defaultSelector: string | null,
|
||||
strictStandalone: boolean,
|
||||
implicitStandaloneValue: boolean,
|
||||
emitDeclarationOnly: boolean,
|
||||
):
|
||||
| {
|
||||
jitForced: false;
|
||||
|
|
@ -185,6 +186,7 @@ export function extractDirectiveMetadata(
|
|||
reflector,
|
||||
refEmitter,
|
||||
compilationMode,
|
||||
emitDeclarationOnly,
|
||||
);
|
||||
const inputsFromFields = parseInputFields(
|
||||
clazz,
|
||||
|
|
@ -197,6 +199,7 @@ export function extractDirectiveMetadata(
|
|||
compilationMode,
|
||||
inputsFromMeta,
|
||||
decorator,
|
||||
emitDeclarationOnly,
|
||||
);
|
||||
const inputs = ClassPropertyMapping.fromMappedObject({...inputsFromMeta, ...inputsFromFields});
|
||||
|
||||
|
|
@ -394,6 +397,7 @@ export function extractDirectiveMetadata(
|
|||
evaluator,
|
||||
compilationMode,
|
||||
createForwardRefResolver(isCore),
|
||||
emitDeclarationOnly,
|
||||
);
|
||||
|
||||
if (compilationMode !== CompilationMode.LOCAL && hostDirectives !== null) {
|
||||
|
|
@ -965,6 +969,7 @@ function parseInputsArray(
|
|||
reflector: ReflectionHost,
|
||||
refEmitter: ReferenceEmitter,
|
||||
compilationMode: CompilationMode,
|
||||
emitDeclarationOnly: boolean,
|
||||
): Record<string, InputMapping> {
|
||||
const inputsField = decoratorMetadata.get('inputs');
|
||||
|
||||
|
|
@ -1030,6 +1035,7 @@ function parseInputsArray(
|
|||
reflector,
|
||||
refEmitter,
|
||||
compilationMode,
|
||||
emitDeclarationOnly,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1080,6 +1086,7 @@ function tryParseInputFieldMapping(
|
|||
isCore: boolean,
|
||||
refEmitter: ReferenceEmitter,
|
||||
compilationMode: CompilationMode,
|
||||
emitDeclarationOnly: boolean,
|
||||
): InputMapping | null {
|
||||
const classPropertyName = member.name;
|
||||
|
||||
|
|
@ -1156,6 +1163,7 @@ function tryParseInputFieldMapping(
|
|||
reflector,
|
||||
refEmitter,
|
||||
compilationMode,
|
||||
emitDeclarationOnly,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1192,6 +1200,7 @@ function parseInputFields(
|
|||
compilationMode: CompilationMode,
|
||||
inputsFromClassDecorator: Record<string, InputMapping>,
|
||||
classDecorator: Decorator,
|
||||
emitDeclarationOnly: boolean,
|
||||
): Record<string, InputMapping> {
|
||||
const inputs = {} as Record<string, InputMapping>;
|
||||
|
||||
|
|
@ -1206,6 +1215,7 @@ function parseInputFields(
|
|||
isCore,
|
||||
refEmitter,
|
||||
compilationMode,
|
||||
emitDeclarationOnly,
|
||||
);
|
||||
if (inputMapping === null) {
|
||||
continue;
|
||||
|
|
@ -1252,7 +1262,24 @@ export function parseDecoratorInputTransformFunction(
|
|||
reflector: ReflectionHost,
|
||||
refEmitter: ReferenceEmitter,
|
||||
compilationMode: CompilationMode,
|
||||
emitDeclarationOnly: boolean,
|
||||
): DecoratorInputTransform {
|
||||
if (emitDeclarationOnly) {
|
||||
const chain: ts.DiagnosticMessageChain = {
|
||||
messageText:
|
||||
'@Input decorators with a transform function are not supported in experimental declaration-only emission mode',
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
code: 0,
|
||||
next: [
|
||||
{
|
||||
messageText: `Consider converting '${clazz.name.text}.${classPropertyName}' to an input signal`,
|
||||
category: ts.DiagnosticCategory.Message,
|
||||
code: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
throw new FatalDiagnosticError(ErrorCode.DECORATOR_UNEXPECTED, value.node, chain);
|
||||
}
|
||||
// In local compilation mode we can skip type checking the function args. This is because usually
|
||||
// the type check is done in a separate build which runs in full compilation mode. So here we skip
|
||||
// all the diagnostics.
|
||||
|
|
@ -1708,6 +1735,7 @@ function extractHostDirectives(
|
|||
evaluator: PartialEvaluator,
|
||||
compilationMode: CompilationMode,
|
||||
forwardRefResolver: ForeignFunctionResolver,
|
||||
emitDeclarationOnly: boolean,
|
||||
): HostDirectiveMeta[] {
|
||||
const resolved = evaluator.evaluate(rawHostDirectives, forwardRefResolver);
|
||||
if (!Array.isArray(resolved)) {
|
||||
|
|
@ -1759,6 +1787,14 @@ function extractHostDirectives(
|
|||
);
|
||||
}
|
||||
|
||||
if (emitDeclarationOnly) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION,
|
||||
hostReference.node,
|
||||
'External references in host directives are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
}
|
||||
|
||||
directive = new WrappedNodeExpr(hostReference.node);
|
||||
} else if (hostReference instanceof Reference) {
|
||||
directive = hostReference as Reference<ClassDeclaration>;
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ runInEachFileSystem(() => {
|
|||
/* implicitStandaloneValue */ true,
|
||||
/* usePoisonedData */ false,
|
||||
/* typeCheckHostBindings */ true,
|
||||
/* emitDeclarationOnly */ false,
|
||||
);
|
||||
|
||||
const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);
|
||||
|
|
|
|||
|
|
@ -287,6 +287,7 @@ export class NgModuleDecoratorHandler
|
|||
private readonly compilationMode: CompilationMode,
|
||||
private readonly localCompilationExtraImportsTracker: LocalCompilationExtraImportsTracker | null,
|
||||
private readonly jitDeclarationRegistry: JitDeclarationRegistry,
|
||||
private readonly emitDeclarationOnly: boolean,
|
||||
) {}
|
||||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
|
@ -354,6 +355,8 @@ export class NgModuleDecoratorHandler
|
|||
forwardRefResolver,
|
||||
]);
|
||||
|
||||
const allowUnresolvedReferences =
|
||||
this.compilationMode === CompilationMode.LOCAL && !this.emitDeclarationOnly;
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
// Resolving declarations
|
||||
|
|
@ -367,7 +370,7 @@ export class NgModuleDecoratorHandler
|
|||
name,
|
||||
'declarations',
|
||||
0,
|
||||
this.compilationMode === CompilationMode.LOCAL,
|
||||
allowUnresolvedReferences,
|
||||
).references;
|
||||
|
||||
// Look through the declarations to make sure they're all a part of the current compilation.
|
||||
|
|
@ -403,7 +406,7 @@ export class NgModuleDecoratorHandler
|
|||
name,
|
||||
'imports',
|
||||
0,
|
||||
this.compilationMode === CompilationMode.LOCAL,
|
||||
allowUnresolvedReferences,
|
||||
);
|
||||
|
||||
if (
|
||||
|
|
@ -438,7 +441,7 @@ export class NgModuleDecoratorHandler
|
|||
name,
|
||||
'exports',
|
||||
0,
|
||||
this.compilationMode === CompilationMode.LOCAL,
|
||||
allowUnresolvedReferences,
|
||||
).references;
|
||||
this.referencesRegistry.add(node, ...exportRefs);
|
||||
}
|
||||
|
|
@ -446,7 +449,7 @@ export class NgModuleDecoratorHandler
|
|||
// Resolving bootstrap
|
||||
let bootstrapRefs: Reference<ClassDeclaration>[] = [];
|
||||
const rawBootstrap: ts.Expression | null = ngModule.get('bootstrap') ?? null;
|
||||
if (this.compilationMode !== CompilationMode.LOCAL && rawBootstrap !== null) {
|
||||
if (!allowUnresolvedReferences && rawBootstrap !== null) {
|
||||
const bootstrapMeta = this.evaluator.evaluate(rawBootstrap, forwardRefResolver);
|
||||
bootstrapRefs = this.resolveTypeList(
|
||||
rawBootstrap,
|
||||
|
|
@ -546,7 +549,7 @@ export class NgModuleDecoratorHandler
|
|||
const type = wrapTypeReference(this.reflector, node);
|
||||
|
||||
let ngModuleMetadata: R3NgModuleMetadata;
|
||||
if (this.compilationMode === CompilationMode.LOCAL) {
|
||||
if (allowUnresolvedReferences) {
|
||||
ngModuleMetadata = {
|
||||
kind: R3NgModuleMetadataKind.Local,
|
||||
type,
|
||||
|
|
@ -602,7 +605,7 @@ export class NgModuleDecoratorHandler
|
|||
}
|
||||
|
||||
const topLevelImports: TopLevelImportedExpression[] = [];
|
||||
if (this.compilationMode !== CompilationMode.LOCAL && ngModule.has('imports')) {
|
||||
if (!allowUnresolvedReferences && ngModule.has('imports')) {
|
||||
const rawImports = unwrapExpression(ngModule.get('imports')!);
|
||||
|
||||
let topLevelExpressions: ts.Expression[] = [];
|
||||
|
|
@ -650,7 +653,7 @@ export class NgModuleDecoratorHandler
|
|||
imports: [],
|
||||
};
|
||||
|
||||
if (this.compilationMode === CompilationMode.LOCAL) {
|
||||
if (allowUnresolvedReferences) {
|
||||
// Adding NgModule's raw imports/exports to the injector's imports field in local compilation
|
||||
// mode.
|
||||
for (const exp of [rawImports, rawExports]) {
|
||||
|
|
@ -1170,6 +1173,17 @@ export class NgModuleDecoratorHandler
|
|||
} else if (entry instanceof DynamicValue && allowUnresolvedReferences) {
|
||||
dynamicValueSet.add(entry);
|
||||
continue;
|
||||
} else if (
|
||||
this.emitDeclarationOnly &&
|
||||
entry instanceof DynamicValue &&
|
||||
entry.isFromUnknownIdentifier()
|
||||
) {
|
||||
throw createValueHasWrongTypeError(
|
||||
entry.node,
|
||||
entry,
|
||||
`Value at position ${absoluteIndex} in the NgModule.${arrayName} of ${className} is an external reference. ` +
|
||||
'External references in @NgModule declarations are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
} else {
|
||||
// TODO(alxhub): Produce a better diagnostic here - the array index may be an inner array.
|
||||
throw createValueHasWrongTypeError(
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ function setup(program: ts.Program, compilationMode = CompilationMode.FULL) {
|
|||
compilationMode,
|
||||
/* localCompilationExtraImportsTracker */ null,
|
||||
jitDeclarationRegistry,
|
||||
/* emitDeclarationOnly */ false,
|
||||
);
|
||||
|
||||
return {handler, reflectionHost};
|
||||
|
|
|
|||
|
|
@ -342,6 +342,20 @@ export interface BazelAndG3Options {
|
|||
* extra imports are needed for bundling purposes in g3.
|
||||
*/
|
||||
generateExtraImportsInLocalMode?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to allow the experimental declaration-only emission mode when the `emitDeclarationOnly`
|
||||
* TS compiler option is enabled.
|
||||
*
|
||||
* The declaration-only emission mode relies on the local compilation mode for fast type
|
||||
* declaration emission, i.e. emitting `.d.ts` files without type-checking. Certain restrictions
|
||||
* on supported code constructs apply due to the absence of type information for external
|
||||
* references.
|
||||
*
|
||||
* The mode is experimental and specifically tailored to support fast type declaration emission
|
||||
* for the Gemini app in g3.
|
||||
*/
|
||||
_geminiAllowEmitDeclarationOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@ export class NgCompiler {
|
|||
private readonly enableHmr: boolean;
|
||||
private readonly implicitStandaloneValue: boolean;
|
||||
private readonly enableSelectorless: boolean;
|
||||
private readonly emitDeclarationOnly: boolean;
|
||||
|
||||
/**
|
||||
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
|
||||
|
|
@ -466,6 +467,8 @@ export class NgCompiler {
|
|||
this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
|
||||
this.enableLetSyntax = options['_enableLetSyntax'] ?? true;
|
||||
this.enableSelectorless = options['_enableSelectorless'] ?? false;
|
||||
this.emitDeclarationOnly =
|
||||
!!options.emitDeclarationOnly && !!options._geminiAllowEmitDeclarationOnly;
|
||||
// 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.
|
||||
|
|
@ -826,6 +829,7 @@ export class NgCompiler {
|
|||
this.delegatingPerfRecorder,
|
||||
compilation.isCore,
|
||||
this.closureCompilerEnabled,
|
||||
this.emitDeclarationOnly,
|
||||
),
|
||||
aliasTransformFactory(compilation.traitCompiler.exportStatements),
|
||||
defaultImportTracker.importPreservingTransformer(),
|
||||
|
|
@ -869,9 +873,15 @@ export class NgCompiler {
|
|||
// In local compilation mode we don't make use of .d.ts files for Angular compilation, so their
|
||||
// transformation can be ditched.
|
||||
if (
|
||||
this.options.compilationMode !== 'experimental-local' &&
|
||||
(this.options.compilationMode !== 'experimental-local' || this.emitDeclarationOnly) &&
|
||||
compilation.dtsTransforms !== null
|
||||
) {
|
||||
// If we are emitting declarations only, the script transformations are skipped by the TS
|
||||
// compiler, so we have to add them to the afterDeclarations transforms to run their analysis
|
||||
// because the declaration transform depends on their metadata output.
|
||||
if (this.emitDeclarationOnly) {
|
||||
afterDeclarations.push(...before);
|
||||
}
|
||||
afterDeclarations.push(
|
||||
declarationTransformFactory(
|
||||
compilation.dtsTransforms,
|
||||
|
|
@ -1286,6 +1296,9 @@ export class NgCompiler {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (this.emitDeclarationOnly) {
|
||||
compilationMode = CompilationMode.LOCAL;
|
||||
}
|
||||
|
||||
const checker = this.inputProgram.getTypeChecker();
|
||||
|
||||
|
|
@ -1513,6 +1526,7 @@ export class NgCompiler {
|
|||
this.implicitStandaloneValue,
|
||||
typeCheckHostBindings,
|
||||
this.enableSelectorless,
|
||||
this.emitDeclarationOnly,
|
||||
),
|
||||
|
||||
// TODO(alxhub): understand why the cast here is necessary (something to do with `null`
|
||||
|
|
@ -1541,6 +1555,7 @@ export class NgCompiler {
|
|||
this.implicitStandaloneValue,
|
||||
this.usePoisonedData,
|
||||
typeCheckHostBindings,
|
||||
this.emitDeclarationOnly,
|
||||
) 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)
|
||||
|
|
@ -1588,6 +1603,7 @@ export class NgCompiler {
|
|||
compilationMode,
|
||||
localCompilationExtraImportsTracker,
|
||||
jitDeclarationRegistry,
|
||||
this.emitDeclarationOnly,
|
||||
),
|
||||
];
|
||||
|
||||
|
|
@ -1601,6 +1617,7 @@ export class NgCompiler {
|
|||
dtsTransforms,
|
||||
semanticDepGraphUpdater,
|
||||
this.adapter,
|
||||
this.emitDeclarationOnly,
|
||||
);
|
||||
|
||||
// Template type-checking may use the `ProgramDriver` to produce new `ts.Program`(s). If this
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||
private dtsTransforms: DtsTransformRegistry,
|
||||
private semanticDepGraphUpdater: SemanticDepGraphUpdater | null,
|
||||
private sourceFileTypeIdentifier: SourceFileTypeIdentifier,
|
||||
private emitDeclarationOnly: boolean,
|
||||
) {
|
||||
for (const handler of handlers) {
|
||||
this.handlersByName.set(handler.name, handler);
|
||||
|
|
@ -374,14 +375,16 @@ export class TraitCompiler implements ProgramTypeCheckAdapter {
|
|||
) {
|
||||
// Custom decorators found in local compilation mode! In this mode we don't support custom
|
||||
// decorators yet. But will eventually do (b/320536434). For now a temporary error is thrown.
|
||||
const compilationModeName = this.emitDeclarationOnly
|
||||
? 'experimental declaration-only emission'
|
||||
: 'local compilation';
|
||||
record.metaDiagnostics = [...nonNgDecoratorsInLocalMode].map((decorator) => ({
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
code: Number('-99' + ErrorCode.DECORATOR_UNEXPECTED),
|
||||
file: getSourceFile(clazz),
|
||||
start: decorator.node.getStart(),
|
||||
length: decorator.node.getWidth(),
|
||||
messageText:
|
||||
'In local compilation mode, Angular does not support custom decorators. Ensure all class decorators are from Angular.',
|
||||
messageText: `In ${compilationModeName} mode, Angular does not support custom decorators. Ensure all class decorators are from Angular.`,
|
||||
}));
|
||||
record.traits = foundTraits = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export function ivyTransformFactory(
|
|||
perf: PerfRecorder,
|
||||
isCore: boolean,
|
||||
isClosureCompilerEnabled: boolean,
|
||||
emitDeclarationOnly: boolean,
|
||||
): ts.TransformerFactory<ts.SourceFile> {
|
||||
const recordWrappedNode = createRecorderFn(defaultImportTracker);
|
||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||
|
|
@ -66,6 +67,7 @@ export function ivyTransformFactory(
|
|||
file,
|
||||
isCore,
|
||||
isClosureCompilerEnabled,
|
||||
emitDeclarationOnly,
|
||||
recordWrappedNode,
|
||||
),
|
||||
);
|
||||
|
|
@ -368,6 +370,7 @@ function transformIvySourceFile(
|
|||
file: ts.SourceFile,
|
||||
isCore: boolean,
|
||||
isClosureCompilerEnabled: boolean,
|
||||
emitDeclarationOnly: boolean,
|
||||
recordWrappedNode: RecordWrappedNodeFn<ts.Expression>,
|
||||
): ts.SourceFile {
|
||||
const constantPool = new ConstantPool(isClosureCompilerEnabled);
|
||||
|
|
@ -390,6 +393,11 @@ function transformIvySourceFile(
|
|||
const compilationVisitor = new IvyCompilationVisitor(compilation, constantPool);
|
||||
visit(file, compilationVisitor, context);
|
||||
|
||||
// If we are emitting declarations only, we can skip the script transforms.
|
||||
if (emitDeclarationOnly) {
|
||||
return file;
|
||||
}
|
||||
|
||||
// Step 2. Scan through the AST again and perform transformations based on Ivy compilation
|
||||
// results obtained at Step 1.
|
||||
const transformationVisitor = new IvyTransformationVisitor(
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ runInEachFileSystem(() => {
|
|||
new DtsTransformRegistry(),
|
||||
null,
|
||||
fakeSfTypeIdentifier,
|
||||
/* emitDeclarationOnly */ false,
|
||||
);
|
||||
const sourceFile = program.getSourceFile(filename)!;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@
|
|||
|
||||
This directory contains rules, helpers and test-cases for the Angular compiler compliance tests.
|
||||
|
||||
There are three different types of tests that are run based on file-based "test-cases".
|
||||
There are five different types of tests that are run based on file-based "test-cases".
|
||||
|
||||
* **Full compile** - in this test the source files defined by the test-case are fully compiled by Angular.
|
||||
The generated files are compared to "expected files" via a matching algorithm that is tolerant to
|
||||
whitespace and variable name changes.
|
||||
* **Local compile** - in this test the source files defined by the test-case are compiled in local mode by Angular.
|
||||
The generated files are compared to "expected files" via a matching algorithm similar to the case of "full compile"
|
||||
* **Declaration-only emit** - in this test the source files defined by the test-case are compiled in full mode by
|
||||
Angular. The same source files are then compiled again in declaration-only emission mode and the resulting type
|
||||
declarations (.d.ts files) are compared to the type declarations produced by the full compilation checking for exact
|
||||
matches.
|
||||
* **Partial compile** - in this test the source files defined by the test-case are "partially" compiled by
|
||||
Angular to produce files that can be published. These partially compiled files are compared directly
|
||||
against "golden files" to ensure that we do not inadvertently break the public API of partial
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = True,
|
||||
srcs = ["declaration_only_emit_spec.ts"],
|
||||
deps = [
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/test/compliance/test_helpers",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "declaration-only",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/test/compliance/test_cases",
|
||||
"//packages/core:npm_package",
|
||||
],
|
||||
shard_count = 2,
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @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.dev/license
|
||||
*/
|
||||
import {FileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {getReferenceFileForTypeDeclaration} from '../test_helpers/check_type_declarations';
|
||||
import {CompileResult, compileTest} from '../test_helpers/compile_test';
|
||||
import {ComplianceTest} from '../test_helpers/get_compliance_tests';
|
||||
import {runTests} from '../test_helpers/test_runner';
|
||||
|
||||
runTests('declaration-only emit', emitDeclarationOnlyTest, {emitDeclarationOnly: true});
|
||||
|
||||
/**
|
||||
* Compile all the input files in the given `test` in full compilation mode and compare the emitted
|
||||
* type declarations with the declarations produced by declaration-only emission.
|
||||
*
|
||||
* @param fs The mock file-system where the input files can be found.
|
||||
* @param test The compliance test whose input files should be compiled.
|
||||
*/
|
||||
function emitDeclarationOnlyTest(fs: FileSystem, test: ComplianceTest): CompileResult {
|
||||
const {emittedFiles} = compileTest(
|
||||
fs,
|
||||
test.inputFiles,
|
||||
test.compilerOptions,
|
||||
test.angularCompilerOptions,
|
||||
);
|
||||
const emittedTypeDeclarations = emittedFiles.filter((file) => file.endsWith('.d.ts'));
|
||||
for (const emittedTypeDeclaration of emittedTypeDeclarations) {
|
||||
fs.moveFile(
|
||||
emittedTypeDeclaration,
|
||||
getReferenceFileForTypeDeclaration(fs, emittedTypeDeclaration),
|
||||
);
|
||||
}
|
||||
return compileTest(
|
||||
fs,
|
||||
test.inputFiles,
|
||||
{
|
||||
...test.compilerOptions,
|
||||
emitDeclarationOnly: true,
|
||||
},
|
||||
{
|
||||
...test.angularCompilerOptions,
|
||||
_geminiAllowEmitDeclarationOnly: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -13,6 +13,10 @@
|
|||
"class_decorators.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -213,7 +213,8 @@
|
|||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -60,7 +60,11 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"compilationModeFilter": ["full compile", "local compile"]
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support complex selectors",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -33,7 +34,8 @@
|
|||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -64,7 +66,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"linkerJitMode": true
|
||||
|
|
@ -86,7 +89,8 @@
|
|||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -104,7 +108,8 @@
|
|||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -121,7 +126,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"linkerJitMode": true
|
||||
|
|
@ -143,7 +149,8 @@
|
|||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -162,7 +169,8 @@
|
|||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -179,7 +187,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"linkerJitMode": true
|
||||
|
|
@ -201,7 +210,8 @@
|
|||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -220,7 +230,8 @@
|
|||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile",
|
||||
"local compile"
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"target": "ES5"
|
||||
},
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@
|
|||
"enableI18nLegacyMessageIdFormat": true
|
||||
},
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
|
|
@ -134,7 +135,8 @@
|
|||
"enableI18nLegacyMessageIdFormat": true
|
||||
},
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
|
|
@ -161,7 +163,8 @@
|
|||
"enableI18nLegacyMessageIdFormat": true
|
||||
},
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
|
|
@ -182,7 +185,8 @@
|
|||
"enableI18nLegacyMessageIdFormat": true
|
||||
},
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
"enableI18nLegacyMessageIdFormat": true
|
||||
},
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -115,6 +115,10 @@
|
|||
"verifyUniqueConsts"
|
||||
]
|
||||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@
|
|||
],
|
||||
"failureMessage": "Incorrect directive definition"
|
||||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@
|
|||
"externalRuntimeStyles": true
|
||||
},
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"compilationModeFilter": ["full compile"]
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@
|
|||
],
|
||||
"failureMessage": "Incorrect definition"
|
||||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile",
|
||||
"linked compile"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"external_template.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
|
|
@ -30,7 +31,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
|
|
@ -43,7 +45,8 @@
|
|||
"extra_root_dir.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
|
|
@ -60,7 +63,8 @@
|
|||
"extra_root_dir.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
|
|
@ -87,7 +91,8 @@
|
|||
"escaped_chars.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
|
|
@ -110,7 +115,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"simple_element.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -29,7 +30,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -41,7 +43,8 @@
|
|||
"void_element.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -63,7 +66,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -75,7 +79,8 @@
|
|||
"interpolation_basic.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -97,7 +102,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -109,7 +115,8 @@
|
|||
"interpolation_complex.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -131,7 +138,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -143,7 +151,8 @@
|
|||
"interpolation_properties.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -165,7 +174,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -177,7 +187,8 @@
|
|||
"interpolation_with_pipe.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -199,7 +210,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -211,7 +223,8 @@
|
|||
"input_binding_simple.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -233,7 +246,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -245,7 +259,8 @@
|
|||
"input_binding_complex.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -267,7 +282,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -279,7 +295,8 @@
|
|||
"input_binding_longhand.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -301,7 +318,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -313,7 +331,8 @@
|
|||
"output_binding_simple.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -335,7 +354,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -347,7 +367,8 @@
|
|||
"output_binding_complex.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -369,7 +390,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -381,7 +403,8 @@
|
|||
"output_binding_longhand.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -403,7 +426,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -415,7 +439,8 @@
|
|||
"two_way_binding_simple.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -437,7 +462,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -449,7 +475,8 @@
|
|||
"two_way_binding_longhand.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -471,7 +498,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -483,7 +511,8 @@
|
|||
"input_binding_class.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -505,7 +534,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -517,7 +547,8 @@
|
|||
"ng_if_simple.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -539,7 +570,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -551,7 +583,8 @@
|
|||
"ng_if_templated.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -573,7 +606,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -585,7 +619,8 @@
|
|||
"ng_for_simple.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -608,7 +643,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -620,7 +656,8 @@
|
|||
"ng_for_templated.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -642,7 +679,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -654,7 +692,8 @@
|
|||
"projection.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -676,7 +715,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -688,7 +728,8 @@
|
|||
"i18n_message_simple.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -710,7 +751,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -722,7 +764,8 @@
|
|||
"i18n_message_placeholder.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -744,7 +787,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -756,7 +800,8 @@
|
|||
"i18n_message_placeholder_entities.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -778,7 +823,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -800,7 +846,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -823,7 +870,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -835,7 +883,8 @@
|
|||
"i18n_message_element_whitespace.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -857,7 +906,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -869,7 +919,8 @@
|
|||
"i18n_message_container_tag.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -891,7 +942,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -903,7 +955,8 @@
|
|||
"update_mode.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -925,7 +978,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -937,7 +991,8 @@
|
|||
"escape_sequences.ts"
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
"full compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
@ -959,7 +1014,8 @@
|
|||
}
|
||||
],
|
||||
"compilationModeFilter": [
|
||||
"linked compile"
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
|
|
|
|||
|
|
@ -23,9 +23,18 @@
|
|||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["full compile", "linked compile", "local compile"]
|
||||
"enum": [
|
||||
"full compile",
|
||||
"linked compile",
|
||||
"local compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
"default": ["full compile", "linked compile"]
|
||||
"default": [
|
||||
"full compile",
|
||||
"linked compile",
|
||||
"declaration-only emit"
|
||||
]
|
||||
},
|
||||
"inputFiles": {
|
||||
"title": "A collection of source files to compile",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* @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.dev/license
|
||||
*/
|
||||
|
||||
import {AbsoluteFsPath, FileSystem} from '../../../src/ngtsc/file_system';
|
||||
|
||||
/**
|
||||
* Check that the content of each emitted type declaration file matches the content of their
|
||||
* associated reference file. The content of the files is in the filesystem. The reference file is
|
||||
* defined by the {@link getReferenceFileForTypeDeclaration} function.
|
||||
*
|
||||
* @param fs The mock file-system where the files can be found.
|
||||
* @param emittedFiles The list of type declaration files to check.
|
||||
*/
|
||||
export function checkTypeDeclarations(fs: FileSystem, emittedFiles: AbsoluteFsPath[]) {
|
||||
const emittedDeclarationFiles = emittedFiles.filter((file) => file.endsWith('.d.ts'));
|
||||
if (!emittedDeclarationFiles.length) {
|
||||
throw new Error('No type declarations emitted.');
|
||||
}
|
||||
const diff = emittedDeclarationFiles
|
||||
.map((file) => ({
|
||||
expectedFilename: getReferenceFileForTypeDeclaration(fs, file),
|
||||
generatedFilename: file,
|
||||
}))
|
||||
.map(({expectedFilename, generatedFilename}) => ({
|
||||
expectedFile: {name: expectedFilename, content: fs.readFile(expectedFilename)},
|
||||
generatedFile: {name: generatedFilename, content: fs.readFile(generatedFilename)},
|
||||
}))
|
||||
.find(({expectedFile, generatedFile}) => expectedFile.content !== generatedFile.content);
|
||||
|
||||
if (diff) {
|
||||
throw new Error(
|
||||
[
|
||||
`Type declarations don't match between full compilation and declaration-only emission\n\n`,
|
||||
`/** FULL COMPILATION: ${diff.expectedFile.name} **/\n`,
|
||||
`${diff.expectedFile.content}\n\n`,
|
||||
`/** DECLARATION-ONLY EMISSION: ${diff.generatedFile.name} */\n`,
|
||||
`${diff.generatedFile.content}\n`,
|
||||
].join(''),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a '.ref' pre-extension to a type declaration file representing its reference file.
|
||||
*
|
||||
* Example: my_declarations.d.ts -> my_declarations.ref.d.ts
|
||||
*
|
||||
* @param fs The file-system used for file name and path manipulation.
|
||||
* @param file The path of the original type declaration file.
|
||||
* @returns The path of the reference type declaration file.
|
||||
*/
|
||||
export function getReferenceFileForTypeDeclaration(fs: FileSystem, file: AbsoluteFsPath) {
|
||||
return fs.join(fs.dirname(file), fs.basename(file, 'd.ts') + '.ref.d.ts');
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ export function* getComplianceTests(absTestConfigPath: AbsoluteFsPath): Generato
|
|||
test,
|
||||
'compilationModeFilter',
|
||||
realTestPath,
|
||||
['linked compile', 'full compile'],
|
||||
['linked compile', 'full compile', 'declaration-only emit'],
|
||||
) as CompilationMode[];
|
||||
|
||||
yield {
|
||||
|
|
@ -292,7 +292,11 @@ export interface ComplianceTest {
|
|||
excludeTest?: boolean;
|
||||
}
|
||||
|
||||
export type CompilationMode = 'linked compile' | 'full compile' | 'local compile';
|
||||
export type CompilationMode =
|
||||
| 'linked compile'
|
||||
| 'full compile'
|
||||
| 'local compile'
|
||||
| 'declaration-only emit';
|
||||
|
||||
export interface Expectation {
|
||||
/** The message to display if this expectation fails. */
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {FileSystem} from '../../../src/ngtsc/file_system';
|
|||
|
||||
import {checkErrors, checkNoUnexpectedErrors} from './check_errors';
|
||||
import {checkExpectations} from './check_expectations';
|
||||
import {checkTypeDeclarations} from './check_type_declarations';
|
||||
import {CompileResult, initMockTestFileSystem} from './compile_test';
|
||||
import {
|
||||
CompilationMode,
|
||||
|
|
@ -47,7 +48,11 @@ function getFilenameForLocalCompilation(fileName: string): string {
|
|||
export function runTests(
|
||||
type: CompilationMode,
|
||||
compileFn: (fs: FileSystem, test: ComplianceTest) => CompileResult,
|
||||
options: {isLocalCompilation?: boolean; skipMappingChecks?: boolean} = {},
|
||||
options: {
|
||||
isLocalCompilation?: boolean;
|
||||
emitDeclarationOnly?: boolean;
|
||||
skipMappingChecks?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
describe(`compliance tests (${type})`, () => {
|
||||
for (const test of getAllComplianceTests()) {
|
||||
|
|
@ -69,7 +74,7 @@ export function runTests(
|
|||
}
|
||||
|
||||
const fs = initMockTestFileSystem(test.realTestPath);
|
||||
const {errors} = compileFn(fs, test);
|
||||
const {errors, emittedFiles} = compileFn(fs, test);
|
||||
for (const expectation of test.expectations) {
|
||||
transformExpectation(expectation, !!options.isLocalCompilation);
|
||||
if (expectation.expectedErrors.length > 0) {
|
||||
|
|
@ -79,6 +84,9 @@ export function runTests(
|
|||
expectation.expectedErrors,
|
||||
errors,
|
||||
);
|
||||
} else if (!!options.emitDeclarationOnly) {
|
||||
checkNoUnexpectedErrors(test.relativePath, errors);
|
||||
checkTypeDeclarations(fs, emittedFiles);
|
||||
} else {
|
||||
checkNoUnexpectedErrors(test.relativePath, errors);
|
||||
checkExpectations(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,289 @@
|
|||
/**
|
||||
* @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.dev/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
|
||||
import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
|
||||
const testFiles = loadStandardTestFiles();
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('declaration-only emission', () => {
|
||||
let env!: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
const tsconfig: {[key: string]: any} = {
|
||||
extends: '../tsconfig-base.json',
|
||||
compilerOptions: {
|
||||
baseUrl: '.',
|
||||
rootDirs: ['/app'],
|
||||
emitDeclarationOnly: true,
|
||||
noCheck: true,
|
||||
},
|
||||
angularCompilerOptions: {
|
||||
_geminiAllowEmitDeclarationOnly: true,
|
||||
},
|
||||
};
|
||||
env.write('tsconfig.json', JSON.stringify(tsconfig, null, 2));
|
||||
});
|
||||
|
||||
it('should show correct error message when using an @NgModule with an external reference in declarations', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {NgModule} from '@angular/core';
|
||||
import {Comp} from './comp';
|
||||
|
||||
@NgModule({
|
||||
declarations: [Comp],
|
||||
})
|
||||
export class CompModule {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.VALUE_HAS_WRONG_TYPE));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toContain(
|
||||
'Value at position 0 in the NgModule.declarations of CompModule is an external reference. ' +
|
||||
'External references in @NgModule declarations are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using an @NgModule with an external reference in imports', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {NgModule} from '@angular/core';
|
||||
import {Comp} from './comp';
|
||||
|
||||
@NgModule({
|
||||
imports: [Comp],
|
||||
})
|
||||
export class CompModule {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.VALUE_HAS_WRONG_TYPE));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toContain(
|
||||
'Value at position 0 in the NgModule.imports of CompModule is an external reference. ' +
|
||||
'External references in @NgModule declarations are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using an @NgModule with an external reference in exports', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {NgModule} from '@angular/core';
|
||||
import {Comp} from './comp';
|
||||
|
||||
@NgModule({
|
||||
exports: [Comp],
|
||||
})
|
||||
export class CompModule {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.VALUE_HAS_WRONG_TYPE));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toContain(
|
||||
'Value at position 0 in the NgModule.exports of CompModule is an external reference. ' +
|
||||
'External references in @NgModule declarations are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using an @NgModule with an external reference in bootstrap', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {NgModule} from '@angular/core';
|
||||
import {Comp} from './comp';
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [Comp],
|
||||
})
|
||||
export class CompModule {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.VALUE_HAS_WRONG_TYPE));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toContain(
|
||||
'Value at position 0 in the NgModule.bootstrap of CompModule is an external reference. ' +
|
||||
'External references in @NgModule declarations are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using an external reference in simple host directive on a component', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {Component} from '@angular/core';
|
||||
import {Dir} from './dir';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
selector: 'host-comp',
|
||||
hostDirectives: [Dir],
|
||||
})
|
||||
export class HostComp {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe(
|
||||
'External references in host directives are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using an external reference in host directive object on a component', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {Component} from '@angular/core';
|
||||
import {Dir} from './dir';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
selector: 'host-comp',
|
||||
hostDirectives: [{
|
||||
directive: Dir,
|
||||
}],
|
||||
})
|
||||
export class HostComp {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe(
|
||||
'External references in host directives are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using an external reference in simple host directive on a directive', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {Directive} from '@angular/core';
|
||||
import {Dir} from './dir';
|
||||
|
||||
@Directive({
|
||||
selector: '[host-dir]',
|
||||
hostDirectives: [Dir],
|
||||
})
|
||||
export class HostDir {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe(
|
||||
'External references in host directives are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using an external reference in host directive object on a directive', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {Directive} from '@angular/core';
|
||||
import {Dir} from './dir';
|
||||
|
||||
@Directive({
|
||||
selector: '[host-dir]',
|
||||
hostDirectives: [{
|
||||
directive: Dir,
|
||||
}],
|
||||
})
|
||||
export class HostDir {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe(
|
||||
'External references in host directives are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using an @Input decorator with a transform function', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {booleanAttribute, Component, Input} from '@angular/core';
|
||||
|
||||
@Component({template: '', selector: 'comp'})
|
||||
export class Comp {
|
||||
@Input({ transform: booleanAttribute }) decoratedInput!: boolean;
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.DECORATOR_UNEXPECTED));
|
||||
const errorMessage = ts.flattenDiagnosticMessageText(errors[0].messageText, '\n');
|
||||
expect(errorMessage).toContain(
|
||||
'@Input decorators with a transform function are not supported in experimental declaration-only emission mode',
|
||||
);
|
||||
expect(errorMessage).toContain(
|
||||
`Consider converting 'Comp.decoratedInput' to an input signal`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct error message when using custom decorators', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
export function Custom() {
|
||||
return function(target: any) {};
|
||||
}
|
||||
|
||||
@Custom()
|
||||
@Component({template: '', selector: 'comp'})
|
||||
export class Comp {}
|
||||
`,
|
||||
);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].code).toBe(ngErrorCode(ErrorCode.DECORATOR_UNEXPECTED));
|
||||
expect(ts.flattenDiagnosticMessageText(errors[0].messageText, '\n')).toBe(
|
||||
'In experimental declaration-only emission mode, Angular does not support custom decorators. Ensure all class decorators are from Angular.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -210,6 +210,7 @@ function parseTransformOfInput(
|
|||
reflector,
|
||||
noopRefEmitter,
|
||||
CompilationMode.FULL,
|
||||
/* emitDeclarationOnly */ false,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (!(e instanceof FatalDiagnosticError)) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue