Skip to content

Commit

Permalink
feat(compiler-cli): initial implementation of standalone components (#…
Browse files Browse the repository at this point in the history
…44812)

This commit implements the first phase of standalone components in the Angular
compiler. This mainly includes the scoping rules for standalone components
(`@Component({imports})`).

Significant functionality from the design is _not_ implemented by this PR,
including:

* imports of standalone components into NgModules.
* the provider aspect of standalone components

Future commits will address these issues, as we proceed with the design of
this feature.

PR Close #44812
  • Loading branch information
alxhub authored and dylhunn committed Feb 3, 2022
1 parent 3dcfc76 commit 0072eb4
Show file tree
Hide file tree
Showing 31 changed files with 701 additions and 47 deletions.
4 changes: 4 additions & 0 deletions goldens/public-api/compiler-cli/error_code.md
Expand Up @@ -6,10 +6,13 @@

// @public (undocumented)
export enum ErrorCode {
COMPONENT_IMPORT_NOT_STANDALONE = 2011,
COMPONENT_INVALID_SHADOW_DOM_SELECTOR = 2009,
// (undocumented)
COMPONENT_MISSING_TEMPLATE = 2001,
COMPONENT_NOT_STANDALONE = 2010,
COMPONENT_RESOURCE_NOT_FOUND = 2008,
COMPONENT_UNKNOWN_IMPORT = 2012,
// (undocumented)
CONFIG_EXTENDED_DIAGNOSTICS_IMPLIES_STRICT_TEMPLATES = 4003,
// (undocumented)
Expand Down Expand Up @@ -44,6 +47,7 @@ export enum ErrorCode {
INVALID_BANANA_IN_BOX = 8101,
MISSING_PIPE = 8004,
MISSING_REFERENCE_TARGET = 8003,
NGMODULE_DECLARATION_IS_STANDALONE = 6008,
NGMODULE_DECLARATION_NOT_UNIQUE = 6007,
NGMODULE_INVALID_DECLARATION = 6001,
NGMODULE_INVALID_EXPORT = 6003,
Expand Down
Expand Up @@ -71,6 +71,7 @@ export function toR3DirectiveMeta<TExpression>(
},
name: typeName,
usesInheritance: metaObj.has('usesInheritance') ? metaObj.getBoolean('usesInheritance') : false,
isStandalone: metaObj.has('isStandalone') ? metaObj.getBoolean('isStandalone') : false,
};
}

Expand Down
Expand Up @@ -41,6 +41,7 @@ export function toR3PipeMeta<TExpression>(metaObj: AstObject<R3DeclarePipeMetada
}

const pure = metaObj.has('pure') ? metaObj.getBoolean('pure') : true;
const isStandalone = metaObj.has('isStandalone') ? metaObj.getBoolean('isStandalone') : false;

return {
name: typeName,
Expand All @@ -50,5 +51,6 @@ export function toR3PipeMeta<TExpression>(metaObj: AstObject<R3DeclarePipeMetada
deps: null,
pipeName: metaObj.getString('name'),
pure,
isStandalone,
};
}
Expand Up @@ -101,9 +101,9 @@ export class DecorationAnalyzer {
handlers: DecoratorHandler<unknown, unknown, SemanticSymbol|null, unknown>[] = [
new ComponentDecoratorHandler(
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
this.scopeRegistry, this.scopeRegistry, this.typeCheckScopeRegistry, new ResourceRegistry(),
this.isCore, this.resourceManager, this.rootDirs,
!!this.compilerOptions.preserveWhitespaces,
this.scopeRegistry, this.dtsModuleScopeResolver, this.scopeRegistry,
this.typeCheckScopeRegistry, new ResourceRegistry(), this.isCore, this.resourceManager,
this.rootDirs, !!this.compilerOptions.preserveWhitespaces,
/* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat,
/* usePoisonedData */ false,
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
Expand Down
Expand Up @@ -9,8 +9,9 @@
import ts from 'typescript';

import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {Reference} from '../../../imports';
import {EnumValue, PartialEvaluator, ResolvedValue} from '../../../partial_evaluator';
import {Decorator} from '../../../reflection';
import {ClassDeclaration, Decorator, ValueUnavailableKind} from '../../../reflection';

import {createValueHasWrongTypeError} from './diagnostics';
import {isAngularCoreReference, unwrapExpression} from './util';
Expand Down Expand Up @@ -39,6 +40,16 @@ export function isStringArray(resolvedValue: ResolvedValue): resolvedValue is st
return Array.isArray(resolvedValue) && resolvedValue.every(elem => typeof elem === 'string');
}

export function isClassReferenceArray(resolvedValue: ResolvedValue):
resolvedValue is Reference<ClassDeclaration>[] {
return Array.isArray(resolvedValue) &&
resolvedValue.every(elem => elem instanceof Reference && ts.isClassDeclaration(elem.node));
}

export function isArray(value: ResolvedValue): value is Array<ResolvedValue> {
return Array.isArray(value);
}

export function resolveLiteral(
decorator: Decorator,
literalCache: Map<Decorator, ts.ObjectLiteralExpression>): ts.ObjectLiteralExpression {
Expand Down
Expand Up @@ -20,7 +20,7 @@ import {DirectiveMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, M
import {PartialEvaluator} from '../../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../../perf';
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../../scope';
import {ComponentScopeReader, DtsModuleScopeResolver, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../../transform';
import {TypeCheckContext} from '../../../typecheck/api';
import {ExtendedTemplateChecker} from '../../../typecheck/extended/api';
Expand All @@ -35,7 +35,7 @@ import {ComponentAnalysisData, ComponentResolutionData} from './metadata';
import {_extractTemplateStyleUrls, extractComponentStyleUrls, extractStyleResources, extractTemplate, makeResourceNotFoundError, ParsedTemplateWithSource, parseTemplateDeclaration, preloadAndParseTemplate, ResourceTypeForDiagnostics, StyleUrlMeta, transformDecoratorToInlineResources} from './resources';
import {scopeTemplate} from './scope';
import {ComponentSymbol} from './symbol';
import {collectAnimationNames} from './util';
import {collectAnimationNames, validateAndFlattenComponentImports} from './util';

const EMPTY_MAP = new Map<string, Expression>();
const EMPTY_ARRAY: any[] = [];
Expand All @@ -48,7 +48,8 @@ export class ComponentDecoratorHandler implements
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry, private metaReader: MetadataReader,
private scopeReader: ComponentScopeReader, private scopeRegistry: LocalModuleScopeRegistry,
private scopeReader: ComponentScopeReader, private dtsScopeReader: DtsModuleScopeResolver,
private scopeRegistry: LocalModuleScopeRegistry,
private typeCheckScopeRegistry: TypeCheckScopeRegistry,
private resourceRegistry: ResourceRegistry, private isCore: boolean,
private resourceLoader: ResourceLoader, private rootDirs: ReadonlyArray<string>,
Expand Down Expand Up @@ -248,6 +249,38 @@ export class ComponentDecoratorHandler implements
component.get('providers')!, this.reflector, this.evaluator);
}

let imports: {
resolved: Reference<ClassDeclaration>[],
raw: ts.Expression,
}|null = null;

if (component.has('imports') && !metadata.isStandalone) {
if (diagnostics === undefined) {
diagnostics = [];
}
diagnostics.push(makeDiagnostic(
ErrorCode.COMPONENT_NOT_STANDALONE, component.get('imports')!,
`'imports' is only valid on a component that is standalone.`));
} else if (component.has('imports')) {
const expr = component.get('imports')!;
const imported = this.evaluator.evaluate(expr);
const {imports: flattened, diagnostics: importDiagnostics} =
validateAndFlattenComponentImports(imported, expr);

imports = {
resolved: flattened,
raw: expr,
};

if (importDiagnostics.length > 0) {
isPoisoned = true;
if (diagnostics === undefined) {
diagnostics = [];
}
diagnostics.push(...importDiagnostics);
}
}

// Parse the template.
// If a preanalyze phase was executed, the template may already exist in parsed form, so check
// the preanalyzeTemplateCache.
Expand Down Expand Up @@ -388,6 +421,7 @@ export class ComponentDecoratorHandler implements
},
isPoisoned,
animationTriggerNames,
imports,
},
diagnostics,
};
Expand Down Expand Up @@ -423,6 +457,7 @@ export class ComponentDecoratorHandler implements
...analysis.typeCheckMeta,
isPoisoned: analysis.isPoisoned,
isStructural: false,
isStandalone: analysis.meta.isStandalone,
animationTriggerNames: analysis.animationTriggerNames,
});

Expand Down Expand Up @@ -511,9 +546,16 @@ export class ComponentDecoratorHandler implements
pipes: EMPTY_MAP,
declarationListEmitMode: DeclarationListEmitMode.Direct,
};
const scope = scopeTemplate(this.scopeReader, node, this.usePoisonedData);
const diagnostics: ts.Diagnostic[] = [];

const scope = scopeTemplate(
this.scopeReader, this.dtsScopeReader, this.scopeRegistry, this.metaReader, node, analysis,
this.usePoisonedData);


if (scope !== null) {
diagnostics.push(...scope.diagnostics);

// Replace the empty components and directives from the analyze() step with a fully expanded
// scope. This is possible now because during resolve() the whole compilation unit has been
// fully analyzed.
Expand Down Expand Up @@ -659,7 +701,7 @@ export class ComponentDecoratorHandler implements
// If a semantic graph is being tracked, record the fact that this component is remotely
// scoped with the declaring NgModule symbol as the NgModule's emit becomes dependent on
// the directive/pipe usages of this component.
if (this.semanticDepGraphUpdater !== null) {
if (this.semanticDepGraphUpdater !== null && scope.ngModule !== null) {
const moduleSymbol = this.semanticDepGraphUpdater.getSymbol(scope.ngModule);
if (!(moduleSymbol instanceof NgModuleSymbol)) {
throw new Error(
Expand Down Expand Up @@ -688,8 +730,6 @@ export class ComponentDecoratorHandler implements
}
}

const diagnostics: ts.Diagnostic[] = [];

if (analysis.providersRequiringFactory !== null &&
analysis.meta.providers instanceof WrappedNodeExpr) {
const providerDiagnostics = getProviderDiagnostics(
Expand Down
Expand Up @@ -65,6 +65,11 @@ export interface ComponentAnalysisData {

isPoisoned: boolean;
animationTriggerNames: AnimationTriggerNames|null;

imports: {
resolved: Reference<ClassDeclaration>[],
raw: ts.Expression,
}|null;
}

export type ComponentResolutionData = Pick<R3ComponentMetadata, ComponentMetadataResolvedFields>;

0 comments on commit 0072eb4

Please sign in to comment.