2016-10-23 16:37:15 -04:00
/ * *
* @license
* Copyright Google Inc . 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
* /
2016-03-22 20:11:42 -04:00
import * as ts from 'typescript' ;
2016-05-26 13:45:37 -04:00
2017-03-02 12:37:01 -05:00
import { Evaluator , errorSymbol } from './evaluator' ;
2017-03-15 12:24:56 -04:00
import { ClassMetadata , ConstructorMetadata , FunctionMetadata , InterfaceMetadata , MemberMetadata , MetadataEntry , MetadataError , MetadataMap , MetadataSymbolicBinaryExpression , MetadataSymbolicCallExpression , MetadataSymbolicExpression , MetadataSymbolicIfExpression , MetadataSymbolicIndexExpression , MetadataSymbolicPrefixExpression , MetadataSymbolicReferenceExpression , MetadataSymbolicSelectExpression , MetadataSymbolicSpreadExpression , MetadataValue , MethodMetadata , ModuleExportMetadata , ModuleMetadata , VERSION , isClassMetadata , isConstructorMetadata , isFunctionMetadata , isMetadataError , isMetadataGlobalReferenceExpression , isMetadataSymbolicExpression , isMetadataSymbolicReferenceExpression , isMetadataSymbolicSelectExpression , isMethodMetadata } from './schema' ;
2016-03-22 20:11:42 -04:00
import { Symbols } from './symbols' ;
2016-05-26 13:45:37 -04:00
2017-01-05 14:34:42 -05:00
// In TypeScript 2.1 these flags moved
// These helpers work for both 2.0 and 2.1.
const isExport = ( ts as any ) . ModifierFlags ?
( ( node : ts.Node ) = >
! ! ( ( ts as any ) . getCombinedModifierFlags ( node ) & ( ts as any ) . ModifierFlags . Export ) ) :
( ( node : ts.Node ) = > ! ! ( ( node . flags & ( ts as any ) . NodeFlags . Export ) ) ) ;
const isStatic = ( ts as any ) . ModifierFlags ?
( ( node : ts.Node ) = >
! ! ( ( ts as any ) . getCombinedModifierFlags ( node ) & ( ts as any ) . ModifierFlags . Static ) ) :
( ( node : ts.Node ) = > ! ! ( ( node . flags & ( ts as any ) . NodeFlags . Static ) ) ) ;
2016-06-08 14:14:43 -04:00
2016-12-12 13:49:17 -05:00
/ * *
* A set of collector options to use when collecting metadata .
* /
export class CollectorOptions {
2016-12-14 18:28:51 -05:00
/ * *
* Version of the metadata to collect .
* /
version? : number ;
2016-12-12 13:49:17 -05:00
/ * *
* Collect a hidden field "$quoted$" in objects literals that record when the key was quoted in
* the source .
* /
quotedNames? : boolean ;
2017-03-01 16:23:34 -05:00
/ * *
* Do not simplify invalid expressions .
* /
verboseInvalidExpression? : boolean ;
2016-12-12 13:49:17 -05:00
}
2016-03-22 20:11:42 -04:00
/ * *
* Collect decorator metadata from a TypeScript module .
* /
export class MetadataCollector {
2016-12-12 13:49:17 -05:00
constructor ( private options : CollectorOptions = { } ) { }
2016-04-26 00:29:06 -04:00
2016-03-22 20:11:42 -04:00
/ * *
* Returns a JSON . stringify friendly form describing the decorators of the exported classes from
* the source file that is expected to correspond to a module .
* /
2017-07-18 15:52:48 -04:00
public getMetadata ( sourceFile : ts.SourceFile , strict : boolean = false ) : ModuleMetadata | undefined {
2016-05-31 14:00:39 -04:00
const locals = new Symbols ( sourceFile ) ;
2017-03-15 12:24:56 -04:00
const nodeMap =
new Map < MetadataValue | ClassMetadata | InterfaceMetadata | FunctionMetadata , ts.Node > ( ) ;
2016-12-12 13:49:17 -05:00
const evaluator = new Evaluator ( locals , nodeMap , this . options ) ;
2016-07-29 12:10:45 -04:00
let metadata : { [ name : string ] : MetadataValue | ClassMetadata | FunctionMetadata } | undefined ;
2017-07-18 15:52:48 -04:00
let exports : ModuleExportMetadata [ ] | undefined = undefined ;
2016-03-22 20:11:42 -04:00
function objFromDecorator ( decoratorNode : ts.Decorator ) : MetadataSymbolicExpression {
return < MetadataSymbolicExpression > evaluator . evaluateNode ( decoratorNode . expression ) ;
}
2016-08-22 20:37:48 -04:00
function recordEntry < T extends MetadataEntry > ( entry : T , node : ts.Node ) : T {
nodeMap . set ( entry , node ) ;
return entry ;
}
2016-06-03 18:43:09 -04:00
function errorSym (
message : string , node? : ts.Node , context ? : { [ name : string ] : string } ) : MetadataError {
return errorSymbol ( message , node , context , sourceFile ) ;
}
2016-07-26 13:18:35 -04:00
function maybeGetSimpleFunction (
functionDeclaration : ts.FunctionDeclaration |
2016-07-29 12:10:45 -04:00
ts . MethodDeclaration ) : { func : FunctionMetadata , name : string } | undefined {
2017-07-18 15:52:48 -04:00
if ( functionDeclaration . name && functionDeclaration . name . kind == ts . SyntaxKind . Identifier ) {
2016-07-26 13:18:35 -04:00
const nameNode = < ts.Identifier > functionDeclaration . name ;
const functionName = nameNode . text ;
const functionBody = functionDeclaration . body ;
if ( functionBody && functionBody . statements . length == 1 ) {
const statement = functionBody . statements [ 0 ] ;
if ( statement . kind === ts . SyntaxKind . ReturnStatement ) {
const returnStatement = < ts.ReturnStatement > statement ;
if ( returnStatement . expression ) {
2016-07-29 12:10:45 -04:00
const func : FunctionMetadata = {
__symbolic : 'function' ,
parameters : namesOf ( functionDeclaration . parameters ) ,
value : evaluator.evaluateNode ( returnStatement . expression )
} ;
if ( functionDeclaration . parameters . some ( p = > p . initializer != null ) ) {
func . defaults = functionDeclaration . parameters . map (
p = > p . initializer && evaluator . evaluateNode ( p . initializer ) ) ;
2016-07-26 13:18:35 -04:00
}
2016-08-22 20:37:48 -04:00
return recordEntry ( { func , name : functionName } , functionDeclaration ) ;
2016-07-26 13:18:35 -04:00
}
}
}
}
}
2016-03-22 20:11:42 -04:00
function classMetadataOf ( classDeclaration : ts.ClassDeclaration ) : ClassMetadata {
2016-11-12 08:08:58 -05:00
const result : ClassMetadata = { __symbolic : 'class' } ;
2016-04-19 22:37:57 -04:00
2017-07-18 15:52:48 -04:00
function getDecorators ( decorators : ts.Decorator [ ] | undefined ) : MetadataSymbolicExpression [ ] |
undefined {
2016-04-19 22:37:57 -04:00
if ( decorators && decorators . length )
return decorators . map ( decorator = > objFromDecorator ( decorator ) ) ;
return undefined ;
}
2016-03-22 20:11:42 -04:00
2016-07-11 20:26:35 -04:00
function referenceFrom ( node : ts.Node ) : MetadataSymbolicReferenceExpression | MetadataError |
MetadataSymbolicSelectExpression {
2016-05-31 14:00:39 -04:00
const result = evaluator . evaluateNode ( node ) ;
2016-07-11 20:26:35 -04:00
if ( isMetadataError ( result ) || isMetadataSymbolicReferenceExpression ( result ) ||
isMetadataSymbolicSelectExpression ( result ) ) {
2016-05-31 14:00:39 -04:00
return result ;
} else {
2016-06-03 18:43:09 -04:00
return errorSym ( 'Symbol reference expected' , node ) ;
2016-05-31 14:00:39 -04:00
}
}
2016-11-18 18:17:44 -05:00
// Add class parents
if ( classDeclaration . heritageClauses ) {
classDeclaration . heritageClauses . forEach ( ( hc ) = > {
if ( hc . token === ts . SyntaxKind . ExtendsKeyword && hc . types ) {
hc . types . forEach ( type = > result . extends = referenceFrom ( type . expression ) ) ;
}
} ) ;
}
2017-01-25 16:40:55 -05:00
// Add arity if the type is generic
const typeParameters = classDeclaration . typeParameters ;
if ( typeParameters && typeParameters . length ) {
result . arity = typeParameters . length ;
}
2016-03-22 20:11:42 -04:00
// Add class decorators
if ( classDeclaration . decorators ) {
result . decorators = getDecorators ( classDeclaration . decorators ) ;
}
// member decorators
2017-07-18 15:52:48 -04:00
let members : MetadataMap | null = null ;
2016-03-22 20:11:42 -04:00
function recordMember ( name : string , metadata : MemberMetadata ) {
if ( ! members ) members = { } ;
2016-11-12 08:08:58 -05:00
const data = members . hasOwnProperty ( name ) ? members [ name ] : [ ] ;
2016-03-22 20:11:42 -04:00
data . push ( metadata ) ;
members [ name ] = data ;
}
2016-07-26 13:18:35 -04:00
// static member
2017-07-18 15:52:48 -04:00
let statics : { [ name : string ] : MetadataValue | FunctionMetadata } | null = null ;
2016-07-29 12:10:45 -04:00
function recordStaticMember ( name : string , value : MetadataValue | FunctionMetadata ) {
2016-07-26 13:18:35 -04:00
if ( ! statics ) statics = { } ;
statics [ name ] = value ;
}
2016-03-22 20:11:42 -04:00
for ( const member of classDeclaration . members ) {
let isConstructor = false ;
switch ( member . kind ) {
case ts . SyntaxKind . Constructor :
case ts . SyntaxKind . MethodDeclaration :
2016-06-22 18:57:24 -04:00
isConstructor = member . kind === ts . SyntaxKind . Constructor ;
2016-05-26 13:45:37 -04:00
const method = < ts.MethodDeclaration | ts.ConstructorDeclaration > member ;
2017-01-05 14:34:42 -05:00
if ( isStatic ( method ) ) {
2016-07-26 13:18:35 -04:00
const maybeFunc = maybeGetSimpleFunction ( < ts.MethodDeclaration > method ) ;
if ( maybeFunc ) {
recordStaticMember ( maybeFunc . name , maybeFunc . func ) ;
}
continue ;
}
2016-03-22 20:11:42 -04:00
const methodDecorators = getDecorators ( method . decorators ) ;
const parameters = method . parameters ;
2017-07-18 15:52:48 -04:00
const parameterDecoratorData :
( ( MetadataSymbolicExpression | MetadataError ) [ ] | undefined ) [ ] = [ ] ;
2016-07-11 20:26:35 -04:00
const parametersData :
( MetadataSymbolicReferenceExpression | MetadataError |
MetadataSymbolicSelectExpression | null ) [ ] = [ ] ;
2016-03-22 20:11:42 -04:00
let hasDecoratorData : boolean = false ;
let hasParameterData : boolean = false ;
for ( const parameter of parameters ) {
const parameterData = getDecorators ( parameter . decorators ) ;
parameterDecoratorData . push ( parameterData ) ;
hasDecoratorData = hasDecoratorData || ! ! parameterData ;
if ( isConstructor ) {
2016-05-31 14:00:39 -04:00
if ( parameter . type ) {
parametersData . push ( referenceFrom ( parameter . type ) ) ;
} else {
parametersData . push ( null ) ;
}
2016-03-22 20:11:42 -04:00
hasParameterData = true ;
}
}
2016-05-26 13:45:37 -04:00
const data : MethodMetadata = { __symbolic : isConstructor ? 'constructor' : 'method' } ;
const name = isConstructor ? '__ctor__' : evaluator . nameOf ( member . name ) ;
2016-05-04 12:11:04 -04:00
if ( methodDecorators ) {
data . decorators = methodDecorators ;
}
if ( hasDecoratorData ) {
data . parameterDecorators = parameterDecoratorData ;
}
if ( hasParameterData ) {
( < ConstructorMetadata > data ) . parameters = parametersData ;
2016-03-22 20:11:42 -04:00
}
2016-05-31 14:00:39 -04:00
if ( ! isMetadataError ( name ) ) {
recordMember ( name , data ) ;
}
2016-03-22 20:11:42 -04:00
break ;
case ts . SyntaxKind . PropertyDeclaration :
2016-03-25 13:16:06 -04:00
case ts . SyntaxKind . GetAccessor :
case ts . SyntaxKind . SetAccessor :
2016-03-22 20:11:42 -04:00
const property = < ts.PropertyDeclaration > member ;
2017-01-05 14:34:42 -05:00
if ( isStatic ( property ) ) {
2016-07-27 22:26:59 -04:00
const name = evaluator . nameOf ( property . name ) ;
if ( ! isMetadataError ( name ) ) {
if ( property . initializer ) {
const value = evaluator . evaluateNode ( property . initializer ) ;
recordStaticMember ( name , value ) ;
} else {
recordStaticMember ( name , errorSym ( 'Variable not initialized' , property . name ) ) ;
}
}
}
2016-03-22 20:11:42 -04:00
const propertyDecorators = getDecorators ( property . decorators ) ;
if ( propertyDecorators ) {
2016-07-27 22:26:59 -04:00
const name = evaluator . nameOf ( property . name ) ;
2016-05-31 14:00:39 -04:00
if ( ! isMetadataError ( name ) ) {
recordMember ( name , { __symbolic : 'property' , decorators : propertyDecorators } ) ;
}
2016-03-22 20:11:42 -04:00
}
break ;
}
}
if ( members ) {
result . members = members ;
}
2016-07-26 13:18:35 -04:00
if ( statics ) {
result . statics = statics ;
}
2016-03-22 20:11:42 -04:00
2016-11-18 18:17:44 -05:00
return recordEntry ( result , classDeclaration ) ;
2016-03-22 20:11:42 -04:00
}
2016-12-16 18:33:47 -05:00
// Collect all exported symbols from an exports clause.
const exportMap = new Map < string , string > ( ) ;
ts . forEachChild ( sourceFile , node = > {
switch ( node . kind ) {
case ts . SyntaxKind . ExportDeclaration :
const exportDeclaration = < ts.ExportDeclaration > node ;
const { module Specifier , exportClause } = exportDeclaration ;
if ( ! module Specifier ) {
2017-07-18 15:52:48 -04:00
// If there is a module specifier there is also an exportClause
exportClause ! . elements . forEach ( spec = > {
2016-12-16 18:33:47 -05:00
const exportedAs = spec . name . text ;
const name = ( spec . propertyName || spec . name ) . text ;
exportMap . set ( name , exportedAs ) ;
} ) ;
}
}
} ) ;
2017-07-18 15:52:48 -04:00
const isExportedIdentifier = ( identifier? : ts.Identifier ) = >
identifier && exportMap . has ( identifier . text ) ;
2017-03-15 12:24:56 -04:00
const isExported =
( node : ts.FunctionDeclaration | ts . ClassDeclaration | ts . InterfaceDeclaration |
ts . EnumDeclaration ) = > isExport ( node ) || isExportedIdentifier ( node . name ) ;
2017-07-18 15:52:48 -04:00
const exportedIdentifierName = ( identifier? : ts.Identifier ) = >
identifier && ( exportMap . get ( identifier . text ) || identifier . text ) ;
2016-12-16 18:33:47 -05:00
const exportedName =
2017-03-15 12:24:56 -04:00
( node : ts.FunctionDeclaration | ts . ClassDeclaration | ts . InterfaceDeclaration |
ts . EnumDeclaration ) = > exportedIdentifierName ( node . name ) ;
2016-12-16 18:33:47 -05:00
2016-08-22 20:37:48 -04:00
// Predeclare classes and functions
2016-05-31 14:00:39 -04:00
ts . forEachChild ( sourceFile , node = > {
switch ( node . kind ) {
case ts . SyntaxKind . ClassDeclaration :
const classDeclaration = < ts.ClassDeclaration > node ;
2016-11-10 14:58:55 -05:00
if ( classDeclaration . name ) {
const className = classDeclaration . name . text ;
2016-12-16 18:33:47 -05:00
if ( isExported ( classDeclaration ) ) {
locals . define (
className , { __symbolic : 'reference' , name : exportedName ( classDeclaration ) } ) ;
2016-11-10 14:58:55 -05:00
} else {
locals . define (
className , errorSym ( 'Reference to non-exported class' , node , { className } ) ) ;
}
2016-05-31 14:00:39 -04:00
}
break ;
2016-12-14 14:37:01 -05:00
2017-03-15 12:24:56 -04:00
case ts . SyntaxKind . InterfaceDeclaration :
const interfaceDeclaration = < ts.InterfaceDeclaration > node ;
if ( interfaceDeclaration . name ) {
const interfaceName = interfaceDeclaration . name . text ;
// All references to interfaces should be converted to references to `any`.
locals . define ( interfaceName , { __symbolic : 'reference' , name : 'any' } ) ;
}
break ;
2016-08-22 20:37:48 -04:00
case ts . SyntaxKind . FunctionDeclaration :
2016-12-16 18:33:47 -05:00
const functionDeclaration = < ts.FunctionDeclaration > node ;
if ( ! isExported ( functionDeclaration ) ) {
2016-08-22 20:37:48 -04:00
// Report references to this function as an error.
const nameNode = functionDeclaration . name ;
2016-11-10 14:58:55 -05:00
if ( nameNode && nameNode . text ) {
locals . define (
nameNode . text ,
errorSym (
'Reference to a non-exported function' , nameNode , { name : nameNode.text } ) ) ;
}
2016-08-22 20:37:48 -04:00
}
break ;
2016-05-31 14:00:39 -04:00
}
} ) ;
2016-12-14 14:37:01 -05:00
2016-05-31 14:00:39 -04:00
ts . forEachChild ( sourceFile , node = > {
switch ( node . kind ) {
2016-08-02 14:45:14 -04:00
case ts . SyntaxKind . ExportDeclaration :
// Record export declarations
const exportDeclaration = < ts.ExportDeclaration > node ;
2016-12-14 14:37:01 -05:00
const { module Specifier , exportClause } = exportDeclaration ;
if ( ! module Specifier ) {
// no module specifier -> export {propName as name};
if ( exportClause ) {
exportClause . elements . forEach ( spec = > {
const name = spec . name . text ;
2016-12-16 18:33:47 -05:00
// If the symbol was not already exported, export a reference since it is a
// reference to an import
if ( ! metadata || ! metadata [ name ] ) {
const propNode = spec . propertyName || spec . name ;
const value : MetadataValue = evaluator . evaluateNode ( propNode ) ;
if ( ! metadata ) metadata = { } ;
metadata [ name ] = recordEntry ( value , node ) ;
}
2016-12-14 14:37:01 -05:00
} ) ;
}
}
2016-08-02 14:45:14 -04:00
if ( module Specifier && module Specifier.kind == ts . SyntaxKind . StringLiteral ) {
// Ignore exports that don't have string literals as exports.
// This is allowed by the syntax but will be flagged as an error by the type checker.
const from = ( < ts.StringLiteral > module Specifier ) . text ;
const module Export : ModuleExportMetadata = { from } ;
2016-12-14 14:37:01 -05:00
if ( exportClause ) {
module Export.export = exportClause . elements . map (
spec = > spec . propertyName ? { name : spec.propertyName.text , as : spec . name . text } :
spec . name . text ) ;
2016-08-02 14:45:14 -04:00
}
if ( ! exports ) exports = [ ] ;
exports . push ( module Export ) ;
}
break ;
2016-05-31 14:00:39 -04:00
case ts . SyntaxKind . ClassDeclaration :
const classDeclaration = < ts.ClassDeclaration > node ;
2016-11-10 14:58:55 -05:00
if ( classDeclaration . name ) {
2016-12-16 18:33:47 -05:00
if ( isExported ( classDeclaration ) ) {
2017-07-18 15:52:48 -04:00
const name = exportedName ( classDeclaration ) ;
if ( name ) {
if ( ! metadata ) metadata = { } ;
metadata [ name ] = classMetadataOf ( classDeclaration ) ;
}
2016-03-22 20:11:42 -04:00
}
2016-05-31 14:00:39 -04:00
}
// Otherwise don't record metadata for the class.
break ;
2016-12-14 14:37:01 -05:00
2017-03-15 12:24:56 -04:00
case ts . SyntaxKind . InterfaceDeclaration :
const interfaceDeclaration = < ts.InterfaceDeclaration > node ;
if ( interfaceDeclaration . name && isExported ( interfaceDeclaration ) ) {
2017-07-18 15:52:48 -04:00
const name = exportedName ( interfaceDeclaration ) ;
if ( name ) {
if ( ! metadata ) metadata = { } ;
metadata [ name ] = { __symbolic : 'interface' } ;
}
2017-03-15 12:24:56 -04:00
}
break ;
2016-06-13 18:56:51 -04:00
case ts . SyntaxKind . FunctionDeclaration :
// Record functions that return a single value. Record the parameter
// names substitution will be performed by the StaticReflector.
2016-08-22 20:37:48 -04:00
const functionDeclaration = < ts.FunctionDeclaration > node ;
2017-06-20 13:36:04 -04:00
if ( isExported ( functionDeclaration ) && functionDeclaration . name ) {
2016-12-16 18:33:47 -05:00
const name = exportedName ( functionDeclaration ) ;
2016-07-26 13:18:35 -04:00
const maybeFunc = maybeGetSimpleFunction ( functionDeclaration ) ;
2017-07-18 15:52:48 -04:00
if ( name ) {
if ( ! metadata ) metadata = { } ;
metadata [ name ] =
maybeFunc ? recordEntry ( maybeFunc . func , node ) : { __symbolic : 'function' } ;
}
2016-06-13 18:56:51 -04:00
}
break ;
2016-12-14 14:37:01 -05:00
2016-07-11 14:50:33 -04:00
case ts . SyntaxKind . EnumDeclaration :
2016-12-16 18:33:47 -05:00
const enumDeclaration = < ts.EnumDeclaration > node ;
if ( isExported ( enumDeclaration ) ) {
2016-11-12 08:08:58 -05:00
const enumValueHolder : { [ name : string ] : MetadataValue } = { } ;
2016-12-16 18:33:47 -05:00
const enumName = exportedName ( enumDeclaration ) ;
2016-08-22 13:26:22 -04:00
let nextDefaultValue : MetadataValue = 0 ;
let writtenMembers = 0 ;
for ( const member of enumDeclaration . members ) {
let enumValue : MetadataValue ;
if ( ! member . initializer ) {
enumValue = nextDefaultValue ;
} else {
enumValue = evaluator . evaluateNode ( member . initializer ) ;
}
2017-07-18 15:52:48 -04:00
let name : string | undefined = undefined ;
2016-08-22 13:26:22 -04:00
if ( member . name . kind == ts . SyntaxKind . Identifier ) {
const identifier = < ts.Identifier > member . name ;
name = identifier . text ;
enumValueHolder [ name ] = enumValue ;
writtenMembers ++ ;
}
if ( typeof enumValue === 'number' ) {
nextDefaultValue = enumValue + 1 ;
} else if ( name ) {
nextDefaultValue = {
__symbolic : 'binary' ,
operator : '+' ,
left : {
__symbolic : 'select' ,
2016-08-22 20:37:48 -04:00
expression : recordEntry ( { __symbolic : 'reference' , name : enumName } , node ) , name
2016-08-22 13:26:22 -04:00
}
2016-10-23 16:52:57 -04:00
} ;
2016-08-22 13:26:22 -04:00
} else {
2016-08-22 20:37:48 -04:00
nextDefaultValue =
recordEntry ( errorSym ( 'Unsuppported enum member name' , member . name ) , node ) ;
2016-12-14 14:37:01 -05:00
}
2016-07-11 14:50:33 -04:00
}
2016-08-22 13:26:22 -04:00
if ( writtenMembers ) {
2017-07-18 15:52:48 -04:00
if ( enumName ) {
if ( ! metadata ) metadata = { } ;
metadata [ enumName ] = recordEntry ( enumValueHolder , node ) ;
}
2016-07-11 14:50:33 -04:00
}
}
break ;
2016-12-14 14:37:01 -05:00
2016-05-31 14:00:39 -04:00
case ts . SyntaxKind . VariableStatement :
const variableStatement = < ts.VariableStatement > node ;
2016-11-12 08:08:58 -05:00
for ( const variableDeclaration of variableStatement . declarationList . declarations ) {
2016-05-31 14:00:39 -04:00
if ( variableDeclaration . name . kind == ts . SyntaxKind . Identifier ) {
2016-11-12 08:08:58 -05:00
const nameNode = < ts.Identifier > variableDeclaration . name ;
2016-05-31 14:00:39 -04:00
let varValue : MetadataValue ;
if ( variableDeclaration . initializer ) {
varValue = evaluator . evaluateNode ( variableDeclaration . initializer ) ;
} else {
2016-08-22 20:37:48 -04:00
varValue = recordEntry ( errorSym ( 'Variable not initialized' , nameNode ) , nameNode ) ;
2016-05-31 14:00:39 -04:00
}
2016-08-02 17:38:31 -04:00
let exported = false ;
2017-01-05 14:34:42 -05:00
if ( isExport ( variableStatement ) || isExport ( variableDeclaration ) ||
2016-12-16 18:33:47 -05:00
isExportedIdentifier ( nameNode ) ) {
2017-07-18 15:52:48 -04:00
const name = exportedIdentifierName ( nameNode ) ;
if ( name ) {
if ( ! metadata ) metadata = { } ;
metadata [ name ] = recordEntry ( varValue , node ) ;
}
2016-08-02 17:38:31 -04:00
exported = true ;
2016-05-31 14:00:39 -04:00
}
2017-03-02 12:37:01 -05:00
if ( typeof varValue == 'string' || typeof varValue == 'number' ||
typeof varValue == 'boolean' ) {
2016-05-31 14:00:39 -04:00
locals . define ( nameNode . text , varValue ) ;
2017-07-13 19:16:56 -04:00
if ( exported ) {
locals . defineReference (
nameNode . text , { __symbolic : 'reference' , name : nameNode.text } ) ;
}
2016-08-02 17:38:31 -04:00
} else if ( ! exported ) {
2016-08-22 20:37:48 -04:00
if ( varValue && ! isMetadataError ( varValue ) ) {
locals . define ( nameNode . text , recordEntry ( varValue , node ) ) ;
} else {
locals . define (
nameNode . text ,
recordEntry (
errorSym ( 'Reference to a local symbol' , nameNode , { name : nameNode.text } ) ,
node ) ) ;
}
2016-03-22 20:11:42 -04:00
}
2016-05-31 14:00:39 -04:00
} else {
// Destructuring (or binding) declarations are not supported,
2016-12-14 14:37:01 -05:00
// var {<identifier>[, <identifier>]+} = <expression>;
2016-05-31 14:00:39 -04:00
// or
// var [<identifier>[, <identifier}+] = <expression>;
// are not supported.
2017-01-05 14:34:42 -05:00
const report : ( nameNode : ts.Node ) = > void = ( nameNode : ts.Node ) = > {
2016-05-31 14:00:39 -04:00
switch ( nameNode . kind ) {
case ts . SyntaxKind . Identifier :
const name = < ts.Identifier > nameNode ;
2017-01-05 14:34:42 -05:00
const varValue = errorSym ( 'Destructuring not supported' , name ) ;
2016-05-31 14:00:39 -04:00
locals . define ( name . text , varValue ) ;
2017-01-05 14:34:42 -05:00
if ( isExport ( node ) ) {
2016-05-31 14:00:39 -04:00
if ( ! metadata ) metadata = { } ;
metadata [ name . text ] = varValue ;
}
break ;
case ts . SyntaxKind . BindingElement :
const bindingElement = < ts.BindingElement > nameNode ;
report ( bindingElement . name ) ;
break ;
case ts . SyntaxKind . ObjectBindingPattern :
case ts . SyntaxKind . ArrayBindingPattern :
const bindings = < ts.BindingPattern > nameNode ;
2017-01-05 14:34:42 -05:00
( bindings as any ) . elements . forEach ( report ) ;
2016-05-31 14:00:39 -04:00
break ;
}
} ;
report ( variableDeclaration . name ) ;
2016-03-22 20:11:42 -04:00
}
2016-05-31 14:00:39 -04:00
}
break ;
2016-03-22 20:11:42 -04:00
}
2016-05-31 14:00:39 -04:00
} ) ;
2016-04-26 00:29:06 -04:00
2016-08-02 14:45:14 -04:00
if ( metadata || exports ) {
2016-08-22 20:37:48 -04:00
if ( ! metadata )
metadata = { } ;
else if ( strict ) {
validateMetadata ( sourceFile , nodeMap , metadata ) ;
}
2016-12-14 18:28:51 -05:00
const result : ModuleMetadata = {
__symbolic : 'module' ,
version : this.options.version || VERSION , metadata
} ;
2016-08-02 14:45:14 -04:00
if ( exports ) result . exports = exports ;
return result ;
}
2016-03-22 20:11:42 -04:00
}
}
2016-06-13 18:56:51 -04:00
2016-08-22 20:37:48 -04:00
// This will throw if the metadata entry given contains an error node.
function validateMetadata (
sourceFile : ts.SourceFile , nodeMap : Map < MetadataEntry , ts.Node > ,
metadata : { [ name : string ] : MetadataEntry } ) {
let locals : Set < string > = new Set ( [ 'Array' , 'Object' , 'Set' , 'Map' , 'string' , 'number' , 'any' ] ) ;
function validateExpression (
expression : MetadataValue | MetadataSymbolicExpression | MetadataError ) {
if ( ! expression ) {
return ;
} else if ( Array . isArray ( expression ) ) {
expression . forEach ( validateExpression ) ;
} else if ( typeof expression === 'object' && ! expression . hasOwnProperty ( '__symbolic' ) ) {
Object . getOwnPropertyNames ( expression ) . forEach ( v = > validateExpression ( ( < any > expression ) [ v ] ) ) ;
} else if ( isMetadataError ( expression ) ) {
reportError ( expression ) ;
} else if ( isMetadataGlobalReferenceExpression ( expression ) ) {
if ( ! locals . has ( expression . name ) ) {
const reference = < MetadataValue > metadata [ expression . name ] ;
if ( reference ) {
validateExpression ( reference ) ;
}
}
} else if ( isFunctionMetadata ( expression ) ) {
validateFunction ( < any > expression ) ;
} else if ( isMetadataSymbolicExpression ( expression ) ) {
switch ( expression . __symbolic ) {
case 'binary' :
const binaryExpression = < MetadataSymbolicBinaryExpression > expression ;
validateExpression ( binaryExpression . left ) ;
validateExpression ( binaryExpression . right ) ;
break ;
case 'call' :
case 'new' :
const callExpression = < MetadataSymbolicCallExpression > expression ;
validateExpression ( callExpression . expression ) ;
if ( callExpression . arguments ) callExpression . arguments . forEach ( validateExpression ) ;
break ;
case 'index' :
const indexExpression = < MetadataSymbolicIndexExpression > expression ;
validateExpression ( indexExpression . expression ) ;
validateExpression ( indexExpression . index ) ;
break ;
case 'pre' :
const prefixExpression = < MetadataSymbolicPrefixExpression > expression ;
validateExpression ( prefixExpression . operand ) ;
break ;
case 'select' :
const selectExpression = < MetadataSymbolicSelectExpression > expression ;
validateExpression ( selectExpression . expression ) ;
break ;
case 'spread' :
const spreadExpression = < MetadataSymbolicSpreadExpression > expression ;
validateExpression ( spreadExpression . expression ) ;
break ;
case 'if' :
const ifExpression = < MetadataSymbolicIfExpression > expression ;
validateExpression ( ifExpression . condition ) ;
validateExpression ( ifExpression . elseExpression ) ;
validateExpression ( ifExpression . thenExpression ) ;
break ;
}
}
}
2016-11-18 18:17:44 -05:00
function validateMember ( classData : ClassMetadata , member : MemberMetadata ) {
2016-08-22 20:37:48 -04:00
if ( member . decorators ) {
member . decorators . forEach ( validateExpression ) ;
}
if ( isMethodMetadata ( member ) && member . parameterDecorators ) {
member . parameterDecorators . forEach ( validateExpression ) ;
}
2016-11-18 18:17:44 -05:00
// Only validate parameters of classes for which we know that are used with our DI
if ( classData . decorators && isConstructorMetadata ( member ) && member . parameters ) {
2016-08-22 20:37:48 -04:00
member . parameters . forEach ( validateExpression ) ;
}
}
function validateClass ( classData : ClassMetadata ) {
if ( classData . decorators ) {
classData . decorators . forEach ( validateExpression ) ;
}
if ( classData . members ) {
Object . getOwnPropertyNames ( classData . members )
2017-07-18 15:52:48 -04:00
. forEach ( name = > classData . members ! [ name ] . forEach ( ( m ) = > validateMember ( classData , m ) ) ) ;
2016-08-22 20:37:48 -04:00
}
2017-03-01 17:45:00 -05:00
if ( classData . statics ) {
Object . getOwnPropertyNames ( classData . statics ) . forEach ( name = > {
2017-07-18 15:52:48 -04:00
const staticMember = classData . statics ! [ name ] ;
2017-03-01 17:45:00 -05:00
if ( isFunctionMetadata ( staticMember ) ) {
validateExpression ( staticMember . value ) ;
} else {
validateExpression ( staticMember ) ;
}
} ) ;
}
2016-08-22 20:37:48 -04:00
}
function validateFunction ( functionDeclaration : FunctionMetadata ) {
if ( functionDeclaration . value ) {
const oldLocals = locals ;
if ( functionDeclaration . parameters ) {
locals = new Set ( oldLocals . values ( ) ) ;
if ( functionDeclaration . parameters )
functionDeclaration . parameters . forEach ( n = > locals . add ( n ) ) ;
}
validateExpression ( functionDeclaration . value ) ;
locals = oldLocals ;
}
}
2017-07-18 15:52:48 -04:00
function shouldReportNode ( node : ts.Node | undefined ) {
2016-08-22 20:37:48 -04:00
if ( node ) {
const nodeStart = node . getStart ( ) ;
return ! (
node . pos != nodeStart &&
sourceFile . text . substring ( node . pos , nodeStart ) . indexOf ( '@dynamic' ) >= 0 ) ;
}
return true ;
}
function reportError ( error : MetadataError ) {
const node = nodeMap . get ( error ) ;
if ( shouldReportNode ( node ) ) {
const lineInfo = error . line != undefined ?
error . character != undefined ? ` : ${ error . line + 1 } : ${ error . character + 1 } ` :
` : ${ error . line + 1 } ` :
'' ;
throw new Error (
` ${ sourceFile . fileName } ${ lineInfo } : Metadata collected contains an error that will be reported at runtime: ${ expandedMessage ( error ) } . \ n ${ JSON . stringify ( error ) } ` ) ;
}
}
Object . getOwnPropertyNames ( metadata ) . forEach ( name = > {
const entry = metadata [ name ] ;
try {
if ( isClassMetadata ( entry ) ) {
2016-10-23 16:52:57 -04:00
validateClass ( entry ) ;
2016-08-22 20:37:48 -04:00
}
} catch ( e ) {
const node = nodeMap . get ( entry ) ;
if ( shouldReportNode ( node ) ) {
if ( node ) {
2016-11-12 08:08:58 -05:00
const { line , character } = sourceFile . getLineAndCharacterOfPosition ( node . getStart ( ) ) ;
2016-08-22 20:37:48 -04:00
throw new Error (
` ${ sourceFile . fileName } : ${ line + 1 } : ${ character + 1 } : Error encountered in metadata generated for exported symbol ' ${ name } ': \ n ${ e . message } ` ) ;
}
throw new Error (
` Error encountered in metadata generated for exported symbol ${ name } : \ n ${ e . message } ` ) ;
}
}
} ) ;
}
2016-06-13 18:56:51 -04:00
// Collect parameter names from a function.
function namesOf ( parameters : ts.NodeArray < ts.ParameterDeclaration > ) : string [ ] {
2016-11-12 08:08:58 -05:00
const result : string [ ] = [ ] ;
2016-06-13 18:56:51 -04:00
function addNamesOf ( name : ts.Identifier | ts . BindingPattern ) {
if ( name . kind == ts . SyntaxKind . Identifier ) {
const identifier = < ts.Identifier > name ;
result . push ( identifier . text ) ;
} else {
const bindingPattern = < ts.BindingPattern > name ;
2016-11-12 08:08:58 -05:00
for ( const element of bindingPattern . elements ) {
2017-01-05 14:34:42 -05:00
const name = ( element as any ) . name ;
if ( name ) {
addNamesOf ( name ) ;
}
2016-06-13 18:56:51 -04:00
}
}
}
2016-11-12 08:08:58 -05:00
for ( const parameter of parameters ) {
2016-06-13 18:56:51 -04:00
addNamesOf ( parameter . name ) ;
}
return result ;
2016-08-22 20:37:48 -04:00
}
function expandedMessage ( error : any ) : string {
switch ( error . message ) {
case 'Reference to non-exported class' :
if ( error . context && error . context . className ) {
return ` Reference to a non-exported class ${ error . context . className } . Consider exporting the class ` ;
}
break ;
case 'Variable not initialized' :
return 'Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler' ;
case 'Destructuring not supported' :
return 'Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring' ;
case 'Could not resolve type' :
if ( error . context && error . context . typeName ) {
return ` Could not resolve type ${ error . context . typeName } ` ;
}
break ;
case 'Function call not supported' :
let prefix =
error . context && error . context . name ? ` Calling function ' ${ error . context . name } ', f ` : 'F' ;
return prefix +
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function' ;
case 'Reference to a local symbol' :
if ( error . context && error . context . name ) {
return ` Reference to a local (non-exported) symbol ' ${ error . context . name } '. Consider exporting the symbol ` ;
}
}
return error . message ;
}