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.
This commit is contained in:
parent
381471d338
commit
67dff7bd5d
|
@ -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<MetadataValue|ClassMetadata|InterfaceMetadata|FunctionMetadata, ts.Node>();
|
||||
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 {
|
||||
|
|
|
@ -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<MetadataEntry, ts.Node>,
|
||||
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<T extends MetadataEntry>(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);
|
||||
|
|
|
@ -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);
|
|
@ -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 {
|
Loading…
Reference in New Issue