From 67dff7bd5d552d65b0e323c9da6a14b911ba958a Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Wed, 26 Jul 2017 14:58:07 -0700 Subject: [PATCH] feat(tsc-wrapped): allow values to be substituted by collector clients Also reenabled tests that were unintentionally disabled when they were moved from tools/@angular. --- packages/tsc-wrapped/src/collector.ts | 24 +++++++++-- packages/tsc-wrapped/src/evaluator.ts | 19 ++++++--- .../{collector.spec.ts => collector_spec.ts} | 40 ++++++++++++++++++- .../{evaluator.spec.ts => evaluator_spec.ts} | 39 ++++++++++++++++++ .../test/{main.spec.ts => main_spec.ts} | 0 .../test/{symbols.spec.ts => symbols_spec.ts} | 0 .../test/{tsc.spec.ts => tsc_spec.ts} | 0 7 files changed, 113 insertions(+), 9 deletions(-) rename packages/tsc-wrapped/test/{collector.spec.ts => collector_spec.ts} (97%) rename packages/tsc-wrapped/test/{evaluator.spec.ts => evaluator_spec.ts} (91%) rename packages/tsc-wrapped/test/{main.spec.ts => main_spec.ts} (100%) rename packages/tsc-wrapped/test/{symbols.spec.ts => symbols_spec.ts} (100%) rename packages/tsc-wrapped/test/{tsc.spec.ts => tsc_spec.ts} (100%) diff --git a/packages/tsc-wrapped/src/collector.ts b/packages/tsc-wrapped/src/collector.ts index f6d117d200..3ad18800fc 100644 --- a/packages/tsc-wrapped/src/collector.ts +++ b/packages/tsc-wrapped/src/collector.ts @@ -26,7 +26,7 @@ const isStatic = (ts as any).ModifierFlags ? /** * A set of collector options to use when collecting metadata. */ -export class CollectorOptions { +export interface CollectorOptions { /** * Version of the metadata to collect. */ @@ -42,6 +42,11 @@ export class CollectorOptions { * Do not simplify invalid expressions. */ verboseInvalidExpression?: boolean; + + /** + * An expression substitution callback. + */ + substituteExpression?: (value: MetadataValue, node: ts.Node) => MetadataValue; } /** @@ -54,12 +59,25 @@ export class MetadataCollector { * 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. */ - public getMetadata(sourceFile: ts.SourceFile, strict: boolean = false): ModuleMetadata|undefined { + public getMetadata( + sourceFile: ts.SourceFile, strict: boolean = false, + substituteExpression?: (value: MetadataValue, node: ts.Node) => MetadataValue): ModuleMetadata + |undefined { const locals = new Symbols(sourceFile); const nodeMap = new Map(); - const evaluator = new Evaluator(locals, nodeMap, this.options); + const composedSubstituter = substituteExpression && this.options.substituteExpression ? + (value: MetadataValue, node: ts.Node) => + this.options.substituteExpression !(substituteExpression(value, node), node) : + substituteExpression; + const evaluatorOptions = substituteExpression ? + {...this.options, substituteExpression: composedSubstituter} : + this.options; let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined; + const evaluator = new Evaluator(locals, nodeMap, evaluatorOptions, (name, value) => { + if (!metadata) metadata = {}; + metadata[name] = value; + }); let exports: ModuleExportMetadata[]|undefined = undefined; function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression { diff --git a/packages/tsc-wrapped/src/evaluator.ts b/packages/tsc-wrapped/src/evaluator.ts index b77f55c027..27e5afc9af 100644 --- a/packages/tsc-wrapped/src/evaluator.ts +++ b/packages/tsc-wrapped/src/evaluator.ts @@ -9,9 +9,10 @@ import * as ts from 'typescript'; import {CollectorOptions} from './collector'; -import {MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema'; +import {MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema'; import {Symbols} from './symbols'; + // In TypeScript 2.1 the spread element kind was renamed. const spreadElementSyntaxKind: ts.SyntaxKind = (ts.SyntaxKind as any).SpreadElement || (ts.SyntaxKind as any).SpreadElementExpression; @@ -104,7 +105,8 @@ export function errorSymbol( export class Evaluator { constructor( private symbols: Symbols, private nodeMap: Map, - private options: CollectorOptions = {}) {} + private options: CollectorOptions = {}, + private recordExport?: (name: string, value: MetadataValue) => void) {} nameOf(node: ts.Node|undefined): string|MetadataError { if (node && node.kind == ts.SyntaxKind.Identifier) { @@ -232,7 +234,14 @@ export class Evaluator { const t = this; let error: MetadataError|undefined; - function recordEntry(entry: T, node: ts.Node): T { + function recordEntry(entry: MetadataValue, node: ts.Node): MetadataValue { + if (t.options.substituteExpression) { + const newEntry = t.options.substituteExpression(entry, node); + if (t.recordExport && newEntry != entry && isMetadataGlobalReferenceExpression(newEntry)) { + t.recordExport(newEntry.name, entry); + } + entry = newEntry; + } t.nodeMap.set(entry, node); return entry; } @@ -283,7 +292,7 @@ export class Evaluator { if (this.options.quotedNames && quoted.length) { obj['$quoted$'] = quoted; } - return obj; + return recordEntry(obj, node); case ts.SyntaxKind.ArrayLiteralExpression: let arr: MetadataValue[] = []; ts.forEachChild(node, child => { @@ -308,7 +317,7 @@ export class Evaluator { arr.push(value); }); if (error) return error; - return arr; + return recordEntry(arr, node); case spreadElementSyntaxKind: let spreadExpression = this.evaluateNode((node as any).expression); return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node); diff --git a/packages/tsc-wrapped/test/collector.spec.ts b/packages/tsc-wrapped/test/collector_spec.ts similarity index 97% rename from packages/tsc-wrapped/test/collector.spec.ts rename to packages/tsc-wrapped/test/collector_spec.ts index b22f3d1f5b..7f95df423c 100644 --- a/packages/tsc-wrapped/test/collector.spec.ts +++ b/packages/tsc-wrapped/test/collector_spec.ts @@ -9,7 +9,7 @@ import * as ts from 'typescript'; import {MetadataCollector} from '../src/collector'; -import {ClassMetadata, ConstructorMetadata, MetadataEntry, ModuleMetadata, isClassMetadata} from '../src/schema'; +import {ClassMetadata, ConstructorMetadata, MetadataEntry, ModuleMetadata, isClassMetadata, isMetadataGlobalReferenceExpression} from '../src/schema'; import {Directory, Host, expectValidSources} from './typescript.mocks'; @@ -939,6 +939,44 @@ describe('Collector', () => { }); }); + describe('substitutions', () => { + const lambdaTemp = 'lambdaTemp'; + + it('should be able to substitute a lambda', () => { + const source = createSource(` + const b = 1; + export const a = () => b; + `); + const metadata = collector.getMetadata(source, /* strict */ false, (value, node) => { + if (node.kind === ts.SyntaxKind.ArrowFunction) { + return {__symbolic: 'reference', name: lambdaTemp}; + } + return value; + }); + expect(metadata !.metadata['a']).toEqual({__symbolic: 'reference', name: lambdaTemp}); + }); + + it('should compose substitution functions', () => { + const collector = new MetadataCollector({ + substituteExpression: (value, node) => isMetadataGlobalReferenceExpression(value) && + value.name == lambdaTemp ? + {__symbolic: 'reference', name: value.name + '2'} : + value + }); + const source = createSource(` + const b = 1; + export const a = () => b; + `); + const metadata = collector.getMetadata(source, /* strict */ false, (value, node) => { + if (node.kind === ts.SyntaxKind.ArrowFunction) { + return {__symbolic: 'reference', name: lambdaTemp}; + } + return value; + }); + expect(metadata !.metadata['a']).toEqual({__symbolic: 'reference', name: lambdaTemp + '2'}); + }); + }); + function override(fileName: string, content: string) { host.overrideFile(fileName, content); host.addFile(fileName); diff --git a/packages/tsc-wrapped/test/evaluator.spec.ts b/packages/tsc-wrapped/test/evaluator_spec.ts similarity index 91% rename from packages/tsc-wrapped/test/evaluator.spec.ts rename to packages/tsc-wrapped/test/evaluator_spec.ts index 2205a27120..bc6a75d471 100644 --- a/packages/tsc-wrapped/test/evaluator.spec.ts +++ b/packages/tsc-wrapped/test/evaluator_spec.ts @@ -223,6 +223,45 @@ describe('Evaluator', () => { expect(evaluator.evaluateNode(expr.initializer !)) .toEqual({__symbolic: 'new', expression: {__symbolic: 'reference', name: 'f'}}); }); + + describe('with substitution', () => { + let evaluator: Evaluator; + const lambdaTemp = 'lambdaTemp'; + + beforeEach(() => { + evaluator = new Evaluator(symbols, new Map(), { + substituteExpression: (value, node) => { + if (node.kind == ts.SyntaxKind.ArrowFunction) { + return {__symbolic: 'reference', name: lambdaTemp}; + } + return value; + } + }); + }); + + it('should be able to substitute a lambda with a reference', () => { + const source = sourceFileOf(` + var b = 1; + export var a = () => b; + `); + const expr = findVar(source, 'a'); + expect(evaluator.evaluateNode(expr !.initializer !)) + .toEqual({__symbolic: 'reference', name: lambdaTemp}); + }); + + it('should be able to substitute a lambda in an expression', () => { + const source = sourceFileOf(` + var b = 1; + export var a = [ + { provide: 'someValue': useFactory: () => b } + ]; + `); + const expr = findVar(source, 'a'); + expect(evaluator.evaluateNode(expr !.initializer !)).toEqual([ + {provide: 'someValue', useFactory: {__symbolic: 'reference', name: lambdaTemp}} + ]) + }); + }) }); function sourceFileOf(text: string): ts.SourceFile { diff --git a/packages/tsc-wrapped/test/main.spec.ts b/packages/tsc-wrapped/test/main_spec.ts similarity index 100% rename from packages/tsc-wrapped/test/main.spec.ts rename to packages/tsc-wrapped/test/main_spec.ts diff --git a/packages/tsc-wrapped/test/symbols.spec.ts b/packages/tsc-wrapped/test/symbols_spec.ts similarity index 100% rename from packages/tsc-wrapped/test/symbols.spec.ts rename to packages/tsc-wrapped/test/symbols_spec.ts diff --git a/packages/tsc-wrapped/test/tsc.spec.ts b/packages/tsc-wrapped/test/tsc_spec.ts similarity index 100% rename from packages/tsc-wrapped/test/tsc.spec.ts rename to packages/tsc-wrapped/test/tsc_spec.ts