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
2016-06-15 17:56:56 -04:00
import { Evaluator , errorSymbol , isPrimitive } from './evaluator' ;
2016-08-22 20:37:48 -04:00
import { ClassMetadata , ConstructorMetadata , FunctionMetadata , MemberMetadata , MetadataEntry , MetadataError , MetadataMap , MetadataObject , 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
2016-06-08 14:14:43 -04:00
2016-03-22 20:11:42 -04:00
/ * *
* Collect decorator metadata from a TypeScript module .
* /
export class MetadataCollector {
2016-04-26 00:29:06 -04:00
constructor ( ) { }
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 .
* /
2016-08-22 20:37:48 -04:00
public getMetadata ( sourceFile : ts.SourceFile , strict : boolean = false ) : ModuleMetadata {
2016-05-31 14:00:39 -04:00
const locals = new Symbols ( sourceFile ) ;
2016-08-22 20:37:48 -04:00
const nodeMap = new Map < MetadataValue | ClassMetadata | FunctionMetadata , ts.Node > ( ) ;
const evaluator = new Evaluator ( locals , nodeMap ) ;
2016-07-29 12:10:45 -04:00
let metadata : { [ name : string ] : MetadataValue | ClassMetadata | FunctionMetadata } | undefined ;
2016-08-02 14:45:14 -04:00
let exports : ModuleExportMetadata [ ] ;
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 {
2016-07-26 13:18:35 -04:00
if ( functionDeclaration . name . kind == ts . SyntaxKind . Identifier ) {
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 ) ) {
const defaults : MetadataValue [ ] = [ ] ;
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-05-26 13:45:37 -04:00
let result : ClassMetadata = { __symbolic : 'class' } ;
2016-04-19 22:37:57 -04:00
function getDecorators ( decorators : ts.Decorator [ ] ) : MetadataSymbolicExpression [ ] {
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-03-22 20:11:42 -04:00
// Add class decorators
if ( classDeclaration . decorators ) {
result . decorators = getDecorators ( classDeclaration . decorators ) ;
}
// member decorators
let members : MetadataMap = null ;
function recordMember ( name : string , metadata : MemberMetadata ) {
if ( ! members ) members = { } ;
let data = members . hasOwnProperty ( name ) ? members [ name ] : [ ] ;
data . push ( metadata ) ;
members [ name ] = data ;
}
2016-07-26 13:18:35 -04:00
// static member
2016-07-29 12:10:45 -04:00
let statics : { [ name : string ] : MetadataValue | FunctionMetadata } = null ;
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 ;
2016-07-26 13:18:35 -04:00
if ( method . flags & ts . NodeFlags . Static ) {
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 ;
2016-05-31 14:00:39 -04:00
const parameterDecoratorData : ( MetadataSymbolicExpression | MetadataError ) [ ] [ ] = [ ] ;
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 ;
2016-07-27 22:26:59 -04:00
if ( property . flags & ts . NodeFlags . Static ) {
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-08-22 20:37:48 -04:00
return result . decorators || members || statics ? recordEntry ( result , classDeclaration ) :
undefined ;
2016-03-22 20:11:42 -04: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 ;
if ( node . flags & ts . NodeFlags . Export ) {
locals . define ( className , { __symbolic : 'reference' , name : className } ) ;
} else {
locals . define (
className , errorSym ( 'Reference to non-exported class' , node , { className } ) ) ;
}
2016-05-31 14:00:39 -04:00
}
break ;
2016-08-22 20:37:48 -04:00
case ts . SyntaxKind . FunctionDeclaration :
if ( ! ( node . flags & ts . NodeFlags . Export ) ) {
// Report references to this function as an error.
const functionDeclaration = < ts.FunctionDeclaration > node ;
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
}
} ) ;
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 ;
const module Specifier = exportDeclaration . module Specifier ;
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 } ;
if ( exportDeclaration . exportClause ) {
module Export.export = exportDeclaration . exportClause . elements . map (
element = > element . propertyName ?
{ name : element.propertyName.text , as : element . name . text } :
2016-10-23 16:52:57 -04:00
element . 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 ) {
const className = classDeclaration . name . text ;
if ( node . flags & ts . NodeFlags . Export ) {
if ( classDeclaration . decorators ) {
if ( ! metadata ) metadata = { } ;
metadata [ className ] = 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-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 ;
2016-06-13 18:56:51 -04:00
if ( node . flags & ts . NodeFlags . Export ) {
2016-07-26 13:18:35 -04:00
const maybeFunc = maybeGetSimpleFunction ( functionDeclaration ) ;
if ( maybeFunc ) {
if ( ! metadata ) metadata = { } ;
2016-08-22 20:37:48 -04:00
metadata [ maybeFunc . name ] = recordEntry ( maybeFunc . func , node ) ;
2016-06-13 18:56:51 -04:00
}
}
break ;
2016-07-11 14:50:33 -04:00
case ts . SyntaxKind . EnumDeclaration :
2016-08-22 13:26:22 -04:00
if ( node . flags & ts . NodeFlags . Export ) {
const enumDeclaration = < ts.EnumDeclaration > node ;
let enumValueHolder : { [ name : string ] : MetadataValue } = { } ;
const enumName = enumDeclaration . name . text ;
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 ) ;
}
let name : string = undefined ;
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-08-22 13:26:22 -04:00
} ;
2016-07-11 14:50:33 -04:00
}
2016-08-22 13:26:22 -04:00
if ( writtenMembers ) {
if ( ! metadata ) metadata = { } ;
2016-08-22 20:37:48 -04:00
metadata [ enumName ] = recordEntry ( enumValueHolder , node ) ;
2016-07-11 14:50:33 -04:00
}
}
break ;
2016-05-31 14:00:39 -04:00
case ts . SyntaxKind . VariableStatement :
const variableStatement = < ts.VariableStatement > node ;
for ( let variableDeclaration of variableStatement . declarationList . declarations ) {
if ( variableDeclaration . name . kind == ts . SyntaxKind . Identifier ) {
let nameNode = < ts.Identifier > variableDeclaration . name ;
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 ;
2016-05-31 14:00:39 -04:00
if ( variableStatement . flags & ts . NodeFlags . Export ||
variableDeclaration . flags & ts . NodeFlags . Export ) {
2016-03-22 20:11:42 -04:00
if ( ! metadata ) metadata = { } ;
2016-08-22 20:37:48 -04:00
metadata [ nameNode . text ] = recordEntry ( varValue , node ) ;
2016-08-02 17:38:31 -04:00
exported = true ;
2016-05-31 14:00:39 -04:00
}
if ( isPrimitive ( varValue ) ) {
locals . define ( nameNode . text , varValue ) ;
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,
// var {<identifier>[, <identifer>]+} = <expression>;
// or
// var [<identifier>[, <identifier}+] = <expression>;
// are not supported.
const report = ( nameNode : ts.Node ) = > {
switch ( nameNode . kind ) {
case ts . SyntaxKind . Identifier :
const name = < ts.Identifier > nameNode ;
2016-06-03 18:43:09 -04:00
const varValue = errorSym ( 'Destructuring not supported' , nameNode ) ;
2016-05-31 14:00:39 -04:00
locals . define ( name . text , varValue ) ;
if ( node . flags & ts . NodeFlags . Export ) {
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 ;
bindings . elements . forEach ( report ) ;
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-08-02 14:45:14 -04:00
const result : ModuleMetadata = { __symbolic : 'module' , version : VERSION , metadata } ;
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 ;
}
}
}
function validateMember ( member : MemberMetadata ) {
if ( member . decorators ) {
member . decorators . forEach ( validateExpression ) ;
}
if ( isMethodMetadata ( member ) && member . parameterDecorators ) {
member . parameterDecorators . forEach ( validateExpression ) ;
}
if ( isConstructorMetadata ( member ) && member . parameters ) {
member . parameters . forEach ( validateExpression ) ;
}
}
function validateClass ( classData : ClassMetadata ) {
if ( classData . decorators ) {
classData . decorators . forEach ( validateExpression ) ;
}
if ( classData . members ) {
Object . getOwnPropertyNames ( classData . members )
. forEach ( name = > classData . members [ name ] . forEach ( validateMember ) ) ;
}
}
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 ;
}
}
function shouldReportNode ( node : ts.Node ) {
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 ) {
let { line , character } = sourceFile . getLineAndCharacterOfPosition ( node . getStart ( ) ) ;
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 [ ] {
let result : string [ ] = [ ] ;
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 ;
for ( let element of bindingPattern . elements ) {
addNamesOf ( element . name ) ;
}
}
}
for ( let parameter of parameters ) {
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 ;
}