feat(ivy): accept multiple values for exportAs in the compiler (#28001)

exportAs in @Directive metadata supports multiple values, separated by
commas. Previously it was treated as a single value string.

This commit modifies the compiler to understand that exportAs is a
string[]. It stops short of carrying the multiple values through to the
runtime. Instead, it only emits the first one. A future commit will modify
the runtime to accept all the values.

PR Close #28001
This commit is contained in:
Alex Rickabaugh 2019-01-08 16:30:57 -08:00 committed by Andrew Kushnir
parent 6003145422
commit 142553abc6
10 changed files with 27 additions and 13 deletions

View File

@ -177,7 +177,7 @@ export function extractDirectiveMetadata(
member.name === 'ngOnChanges'); member.name === 'ngOnChanges');
// Parse exportAs. // Parse exportAs.
let exportAs: string|null = null; let exportAs: string[]|null = null;
if (directive.has('exportAs')) { if (directive.has('exportAs')) {
const expr = directive.get('exportAs') !; const expr = directive.get('exportAs') !;
const resolved = evaluator.evaluate(expr); const resolved = evaluator.evaluate(expr);
@ -185,7 +185,7 @@ export function extractDirectiveMetadata(
throw new FatalDiagnosticError( throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `exportAs must be a string`); ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `exportAs must be a string`);
} }
exportAs = resolved; exportAs = resolved.split(',').map(part => part.trim());
} }
// Detect if the component inherits from another class // Detect if the component inherits from another class

View File

@ -366,7 +366,7 @@ export class SelectorScopeRegistry {
name: clazz.name !.text, name: clazz.name !.text,
directive: ref, directive: ref,
isComponent: def.name === 'ngComponentDef', selector, isComponent: def.name === 'ngComponentDef', selector,
exportAs: readStringType(def.type.typeArguments[2]), exportAs: readStringArrayType(def.type.typeArguments[2]),
inputs: readStringMapType(def.type.typeArguments[3]), inputs: readStringMapType(def.type.typeArguments[3]),
outputs: readStringMapType(def.type.typeArguments[4]), outputs: readStringMapType(def.type.typeArguments[4]),
queries: readStringArrayType(def.type.typeArguments[5]), queries: readStringArrayType(def.type.typeArguments[5]),

View File

@ -119,7 +119,7 @@ export interface R3DirectiveMetadataFacade {
inputs: string[]; inputs: string[];
outputs: string[]; outputs: string[];
usesInheritance: boolean; usesInheritance: boolean;
exportAs: string|null; exportAs: string[]|null;
providers: Provider[]|null; providers: Provider[]|null;
} }

View File

@ -104,7 +104,7 @@ export interface R3DirectiveMetadata {
* Reference name under which to export the directive's type in a template, * Reference name under which to export the directive's type in a template,
* if any. * if any.
*/ */
exportAs: string|null; exportAs: string[]|null;
/** /**
* The list of providers defined in the directive. * The list of providers defined in the directive.

View File

@ -117,7 +117,9 @@ function baseDirectiveFields(
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs)); definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
if (meta.exportAs !== null) { if (meta.exportAs !== null) {
definitionMap.set('exportAs', o.literal(meta.exportAs)); // TODO: handle multiple exportAs values (currently only the first is taken).
const [exportAs] = meta.exportAs;
definitionMap.set('exportAs', o.literal(exportAs));
} }
return {definitionMap, statements: result.statements}; return {definitionMap, statements: result.statements};
@ -605,7 +607,8 @@ function createTypeForDef(meta: R3DirectiveMetadata, typeBase: o.ExternalReferen
return o.expressionType(o.importExpr(typeBase, [ return o.expressionType(o.importExpr(typeBase, [
typeWithParameters(meta.type, meta.typeArgumentCount), typeWithParameters(meta.type, meta.typeArgumentCount),
stringAsType(selectorForType), stringAsType(selectorForType),
meta.exportAs !== null ? stringAsType(meta.exportAs) : o.NONE_TYPE, // TODO: handle multiple exportAs values (currently only the first is taken).
meta.exportAs !== null ? stringArrayAsType(meta.exportAs) : o.NONE_TYPE,
stringMapAsType(meta.inputs), stringMapAsType(meta.inputs),
stringMapAsType(meta.outputs), stringMapAsType(meta.outputs),
stringArrayAsType(meta.queries.map(q => q.propertyName)), stringArrayAsType(meta.queries.map(q => q.propertyName)),

View File

@ -58,7 +58,7 @@ export interface DirectiveMeta {
* *
* Null otherwise * Null otherwise
*/ */
exportAs: string|null; exportAs: string[]|null;
} }
/** /**

View File

@ -259,7 +259,10 @@ class DirectiveBinder<DirectiveT extends DirectiveMeta> implements Visitor {
dirTarget = directives.find(dir => dir.isComponent) || null; dirTarget = directives.find(dir => dir.isComponent) || null;
} else { } else {
// This is a reference to a directive exported via exportAs. One should exist. // This is a reference to a directive exported via exportAs. One should exist.
dirTarget = directives.find(dir => dir.exportAs === ref.value) || null; dirTarget =
directives.find(
dir => dir.exportAs !== null && dir.exportAs.some(value => value === ref.value)) ||
null;
// Check if a matching directive was found, and error if it wasn't. // Check if a matching directive was found, and error if it wasn't.
if (dirTarget === null) { if (dirTarget === null) {

View File

@ -60,7 +60,7 @@ export const enum DirectiveDefFlags {ContentQuery = 0b10}
export interface PipeType<T> extends Type<T> { ngPipeDef: never; } export interface PipeType<T> extends Type<T> { ngPipeDef: never; }
export type DirectiveDefWithMeta< export type DirectiveDefWithMeta<
T, Selector extends string, ExportAs extends string, InputMap extends{[key: string]: string}, T, Selector extends string, ExportAs extends string[], InputMap extends{[key: string]: string},
OutputMap extends{[key: string]: string}, QueryFields extends string[]> = DirectiveDef<T>; OutputMap extends{[key: string]: string}, QueryFields extends string[]> = DirectiveDef<T>;
/** /**
@ -163,7 +163,7 @@ export interface DirectiveDef<T> extends BaseDef<T> {
} }
export type ComponentDefWithMeta< export type ComponentDefWithMeta<
T, Selector extends String, ExportAs extends string, InputMap extends{[key: string]: string}, T, Selector extends String, ExportAs extends string[], InputMap extends{[key: string]: string},
OutputMap extends{[key: string]: string}, QueryFields extends string[]> = ComponentDef<T>; OutputMap extends{[key: string]: string}, QueryFields extends string[]> = ComponentDef<T>;
/** /**

View File

@ -119,7 +119,7 @@ export interface R3DirectiveMetadataFacade {
inputs: string[]; inputs: string[];
outputs: string[]; outputs: string[];
usesInheritance: boolean; usesInheritance: boolean;
exportAs: string|null; exportAs: string[]|null;
providers: Provider[]|null; providers: Provider[]|null;
} }

View File

@ -150,7 +150,7 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
}, },
typeSourceSpan: null !, typeSourceSpan: null !,
usesInheritance: !extendsDirectlyFromObject(type), usesInheritance: !extendsDirectlyFromObject(type),
exportAs: metadata.exportAs || null, exportAs: extractExportAs(metadata.exportAs),
providers: metadata.providers || null, providers: metadata.providers || null,
}; };
} }
@ -189,6 +189,14 @@ function extractQueriesMetadata(
return queriesMeta; return queriesMeta;
} }
function extractExportAs(exportAs: string | undefined): string[]|null {
if (exportAs === undefined) {
return null;
}
return exportAs.split(',').map(part => part.trim());
}
function isContentQuery(value: any): value is Query { function isContentQuery(value: any): value is Query {
const name = value.ngMetadataName; const name = value.ngMetadataName;
return name === 'ContentChild' || name === 'ContentChildren'; return name === 'ContentChild' || name === 'ContentChildren';