feat(compiler-cli): implement partial directive declaration linking (#39518)
This commit implements the logic to compile a partial declaration of a directive into its full AOT compilation output. PR Close #39518
This commit is contained in:
		
							parent
							
								
									8c0a92bb45
								
							
						
					
					
						commit
						87e9cd643b
					
				| @ -18,7 +18,7 @@ export const NO_STATEMENTS: Readonly<any[]> = [] as const; | ||||
|  * This class is responsible for linking all the partial declarations found in a single file. | ||||
|  */ | ||||
| export class FileLinker<TConstantScope, TStatement, TExpression> { | ||||
|   private linkerSelector = new PartialLinkerSelector<TStatement, TExpression>(); | ||||
|   private linkerSelector = new PartialLinkerSelector<TExpression>(); | ||||
|   private emitScopes = new Map<TConstantScope, EmitScope<TStatement, TExpression>>(); | ||||
| 
 | ||||
|   constructor( | ||||
|  | ||||
| @ -15,8 +15,7 @@ import {PartialLinker} from './partial_linker'; | ||||
| /** | ||||
|  * A `PartialLinker` that is designed to process `$ngDeclareComponent()` call expressions. | ||||
|  */ | ||||
| export class PartialComponentLinkerVersion1<TStatement, TExpression> implements | ||||
|     PartialLinker<TStatement, TExpression> { | ||||
| export class PartialComponentLinkerVersion1<TExpression> implements PartialLinker<TExpression> { | ||||
|   linkPartialDeclaration( | ||||
|       sourceUrl: string, code: string, constantPool: ConstantPool, | ||||
|       metaObj: AstObject<TExpression>): o.Expression { | ||||
|  | ||||
| @ -5,21 +5,153 @@ | ||||
|  * 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
 | ||||
|  */ | ||||
| import {ConstantPool} from '@angular/compiler'; | ||||
| import {compileDirectiveFromMetadata, ConstantPool, makeBindingParser, ParseLocation, ParseSourceFile, ParseSourceSpan, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata, R3Reference} from '@angular/compiler'; | ||||
| import * as o from '@angular/compiler/src/output/output_ast'; | ||||
| 
 | ||||
| import {AstObject} from '../../ast/ast_value'; | ||||
| import {Range} from '../../ast/ast_host'; | ||||
| import {AstObject, AstValue} from '../../ast/ast_value'; | ||||
| import {FatalLinkerError} from '../../fatal_linker_error'; | ||||
| 
 | ||||
| import {PartialLinker} from './partial_linker'; | ||||
| 
 | ||||
| /** | ||||
|  * A `PartialLinker` that is designed to process `$ngDeclareDirective()` call expressions. | ||||
|  */ | ||||
| export class PartialDirectiveLinkerVersion1<TStatement, TExpression> implements | ||||
|     PartialLinker<TStatement, TExpression> { | ||||
| export class PartialDirectiveLinkerVersion1<TExpression> implements PartialLinker<TExpression> { | ||||
|   linkPartialDeclaration( | ||||
|       sourceUrl: string, code: string, constantPool: ConstantPool, | ||||
|       metaObj: AstObject<TExpression>): o.Expression { | ||||
|     throw new Error('Not implemented.'); | ||||
|     const meta = toR3DirectiveMeta(metaObj, code, sourceUrl); | ||||
|     const def = compileDirectiveFromMetadata(meta, constantPool, makeBindingParser()); | ||||
|     return def.expression; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Derives the `R3DirectiveMetadata` structure from the AST object. | ||||
|  */ | ||||
| export function toR3DirectiveMeta<TExpression>( | ||||
|     metaObj: AstObject<TExpression>, code: string, sourceUrl: string): R3DirectiveMetadata { | ||||
|   const typeExpr = metaObj.getValue('type'); | ||||
|   const typeName = typeExpr.getSymbolName(); | ||||
|   if (typeName === null) { | ||||
|     throw new FatalLinkerError( | ||||
|         typeExpr.expression, 'Unsupported type, its name could not be determined'); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     typeSourceSpan: createSourceSpan(typeExpr.getRange(), code, sourceUrl), | ||||
|     type: wrapReference(typeExpr.getOpaque()), | ||||
|     typeArgumentCount: 0, | ||||
|     internalType: metaObj.getOpaque('type'), | ||||
|     deps: null, | ||||
|     host: toHostMetadata(metaObj), | ||||
|     inputs: metaObj.has('inputs') ? metaObj.getObject('inputs').toLiteral(toInputMapping) : {}, | ||||
|     outputs: metaObj.has('outputs') ? | ||||
|         metaObj.getObject('outputs').toLiteral(value => value.getString()) : | ||||
|         {}, | ||||
|     queries: metaObj.has('queries') ? | ||||
|         metaObj.getArray('queries').map(entry => toQueryMetadata(entry.getObject())) : | ||||
|         [], | ||||
|     viewQueries: metaObj.has('viewQueries') ? | ||||
|         metaObj.getArray('viewQueries').map(entry => toQueryMetadata(entry.getObject())) : | ||||
|         [], | ||||
|     providers: metaObj.has('providers') ? metaObj.getOpaque('providers') : null, | ||||
|     fullInheritance: false, | ||||
|     selector: metaObj.has('selector') ? metaObj.getString('selector') : null, | ||||
|     exportAs: metaObj.has('exportAs') ? | ||||
|         metaObj.getArray('exportAs').map(entry => entry.getString()) : | ||||
|         null, | ||||
|     lifecycle: { | ||||
|       usesOnChanges: metaObj.has('usesOnChanges') ? metaObj.getBoolean('usesOnChanges') : false, | ||||
|     }, | ||||
|     name: typeName, | ||||
|     usesInheritance: metaObj.has('usesInheritance') ? metaObj.getBoolean('usesInheritance') : false, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Decodes the AST value for a single input to its representation as used in the metadata. | ||||
|  */ | ||||
| function toInputMapping<TExpression>(value: AstValue<TExpression>): string|[string, string] { | ||||
|   if (value.isString()) { | ||||
|     return value.getString(); | ||||
|   } | ||||
| 
 | ||||
|   const values = value.getArray().map(innerValue => innerValue.getString()); | ||||
|   if (values.length !== 2) { | ||||
|     throw new FatalLinkerError( | ||||
|         value.expression, | ||||
|         'Unsupported input, expected a string or an array containing exactly two strings'); | ||||
|   } | ||||
|   return values as [string, string]; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Extracts the host metadata configuration from the AST metadata object. | ||||
|  */ | ||||
| function toHostMetadata<TExpression>(metaObj: AstObject<TExpression>): R3HostMetadata { | ||||
|   if (!metaObj.has('host')) { | ||||
|     return { | ||||
|       attributes: {}, | ||||
|       listeners: {}, | ||||
|       properties: {}, | ||||
|       specialAttributes: {}, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   const host = metaObj.getObject('host'); | ||||
| 
 | ||||
|   const specialAttributes: R3HostMetadata['specialAttributes'] = {}; | ||||
|   if (host.has('styleAttribute')) { | ||||
|     specialAttributes.styleAttr = host.getString('styleAttribute'); | ||||
|   } | ||||
|   if (host.has('classAttribute')) { | ||||
|     specialAttributes.classAttr = host.getString('classAttribute'); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     attributes: host.has('attributes') ? | ||||
|         host.getObject('attributes').toLiteral(value => value.getOpaque()) : | ||||
|         {}, | ||||
|     listeners: host.has('listeners') ? | ||||
|         host.getObject('listeners').toLiteral(value => value.getString()) : | ||||
|         {}, | ||||
|     properties: host.has('properties') ? | ||||
|         host.getObject('properties').toLiteral(value => value.getString()) : | ||||
|         {}, | ||||
|     specialAttributes, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Extracts the metadata for a single query from an AST object. | ||||
|  */ | ||||
| function toQueryMetadata<TExpression>(obj: AstObject<TExpression>): R3QueryMetadata { | ||||
|   let predicate: R3QueryMetadata['predicate']; | ||||
|   const predicateExpr = obj.getValue('predicate'); | ||||
|   if (predicateExpr.isArray()) { | ||||
|     predicate = predicateExpr.getArray().map(entry => entry.getString()); | ||||
|   } else { | ||||
|     predicate = predicateExpr.getOpaque(); | ||||
|   } | ||||
|   return { | ||||
|     propertyName: obj.getString('propertyName'), | ||||
|     first: obj.getBoolean('first'), | ||||
|     predicate, | ||||
|     descendants: obj.getBoolean('descendants'), | ||||
|     read: obj.has('read') ? obj.getOpaque('read') : null, | ||||
|     static: obj.getBoolean('static'), | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference { | ||||
|   return {value: wrapped, type: wrapped}; | ||||
| } | ||||
| 
 | ||||
| export function createSourceSpan(range: Range, code: string, sourceUrl: string): ParseSourceSpan { | ||||
|   const sourceFile = new ParseSourceFile(code, sourceUrl); | ||||
|   const startLocation = | ||||
|       new ParseLocation(sourceFile, range.startPos, range.startLine, range.startCol); | ||||
|   return new ParseSourceSpan(startLocation, startLocation.moveBy(range.endPos - range.startPos)); | ||||
| } | ||||
|  | ||||
| @ -12,7 +12,7 @@ import {AstObject} from '../../ast/ast_value'; | ||||
| /** | ||||
|  * An interface for classes that can link partial declarations into full definitions. | ||||
|  */ | ||||
| export interface PartialLinker<TStatement, TExpression> { | ||||
| export interface PartialLinker<TExpression> { | ||||
|   /** | ||||
|    * Link the partial declaration `metaObj` information to generate a full definition expression. | ||||
|    */ | ||||
|  | ||||
| @ -9,8 +9,8 @@ import {PartialComponentLinkerVersion1} from './partial_component_linker_1'; | ||||
| import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1'; | ||||
| import {PartialLinker} from './partial_linker'; | ||||
| 
 | ||||
| export class PartialLinkerSelector<TStatement, TExpression> { | ||||
|   private linkers: Record<string, Record<number, PartialLinker<TStatement, TExpression>>> = { | ||||
| export class PartialLinkerSelector<TExpression> { | ||||
|   private linkers: Record<string, Record<number, PartialLinker<TExpression>>> = { | ||||
|     '$ngDeclareDirective': { | ||||
|       1: new PartialDirectiveLinkerVersion1(), | ||||
|     }, | ||||
| @ -30,7 +30,7 @@ export class PartialLinkerSelector<TStatement, TExpression> { | ||||
|    * Returns the `PartialLinker` that can handle functions with the given name and version. | ||||
|    * Throws an error if there is none. | ||||
|    */ | ||||
|   getLinker(functionName: string, version: number): PartialLinker<TStatement, TExpression> { | ||||
|   getLinker(functionName: string, version: number): PartialLinker<TExpression> { | ||||
|     const versions = this.linkers[functionName]; | ||||
|     if (versions === undefined) { | ||||
|       throw new Error(`Unknown partial declaration function ${functionName}.`); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user