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.
|
* A set of collector options to use when collecting metadata.
|
||||||
*/
|
*/
|
||||||
export class CollectorOptions {
|
export interface CollectorOptions {
|
||||||
/**
|
/**
|
||||||
* Version of the metadata to collect.
|
* Version of the metadata to collect.
|
||||||
*/
|
*/
|
||||||
|
@ -42,6 +42,11 @@ export class CollectorOptions {
|
||||||
* Do not simplify invalid expressions.
|
* Do not simplify invalid expressions.
|
||||||
*/
|
*/
|
||||||
verboseInvalidExpression?: boolean;
|
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
|
* 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.
|
* 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 locals = new Symbols(sourceFile);
|
||||||
const nodeMap =
|
const nodeMap =
|
||||||
new Map<MetadataValue|ClassMetadata|InterfaceMetadata|FunctionMetadata, ts.Node>();
|
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;
|
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;
|
let exports: ModuleExportMetadata[]|undefined = undefined;
|
||||||
|
|
||||||
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
|
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
|
||||||
|
|
|
@ -9,9 +9,10 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {CollectorOptions} from './collector';
|
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';
|
import {Symbols} from './symbols';
|
||||||
|
|
||||||
|
|
||||||
// In TypeScript 2.1 the spread element kind was renamed.
|
// In TypeScript 2.1 the spread element kind was renamed.
|
||||||
const spreadElementSyntaxKind: ts.SyntaxKind =
|
const spreadElementSyntaxKind: ts.SyntaxKind =
|
||||||
(ts.SyntaxKind as any).SpreadElement || (ts.SyntaxKind as any).SpreadElementExpression;
|
(ts.SyntaxKind as any).SpreadElement || (ts.SyntaxKind as any).SpreadElementExpression;
|
||||||
|
@ -104,7 +105,8 @@ export function errorSymbol(
|
||||||
export class Evaluator {
|
export class Evaluator {
|
||||||
constructor(
|
constructor(
|
||||||
private symbols: Symbols, private nodeMap: Map<MetadataEntry, ts.Node>,
|
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 {
|
nameOf(node: ts.Node|undefined): string|MetadataError {
|
||||||
if (node && node.kind == ts.SyntaxKind.Identifier) {
|
if (node && node.kind == ts.SyntaxKind.Identifier) {
|
||||||
|
@ -232,7 +234,14 @@ export class Evaluator {
|
||||||
const t = this;
|
const t = this;
|
||||||
let error: MetadataError|undefined;
|
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);
|
t.nodeMap.set(entry, node);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -283,7 +292,7 @@ export class Evaluator {
|
||||||
if (this.options.quotedNames && quoted.length) {
|
if (this.options.quotedNames && quoted.length) {
|
||||||
obj['$quoted$'] = quoted;
|
obj['$quoted$'] = quoted;
|
||||||
}
|
}
|
||||||
return obj;
|
return recordEntry(obj, node);
|
||||||
case ts.SyntaxKind.ArrayLiteralExpression:
|
case ts.SyntaxKind.ArrayLiteralExpression:
|
||||||
let arr: MetadataValue[] = [];
|
let arr: MetadataValue[] = [];
|
||||||
ts.forEachChild(node, child => {
|
ts.forEachChild(node, child => {
|
||||||
|
@ -308,7 +317,7 @@ export class Evaluator {
|
||||||
arr.push(value);
|
arr.push(value);
|
||||||
});
|
});
|
||||||
if (error) return error;
|
if (error) return error;
|
||||||
return arr;
|
return recordEntry(arr, node);
|
||||||
case spreadElementSyntaxKind:
|
case spreadElementSyntaxKind:
|
||||||
let spreadExpression = this.evaluateNode((node as any).expression);
|
let spreadExpression = this.evaluateNode((node as any).expression);
|
||||||
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
|
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {MetadataCollector} from '../src/collector';
|
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';
|
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) {
|
function override(fileName: string, content: string) {
|
||||||
host.overrideFile(fileName, content);
|
host.overrideFile(fileName, content);
|
||||||
host.addFile(fileName);
|
host.addFile(fileName);
|
|
@ -223,6 +223,45 @@ describe('Evaluator', () => {
|
||||||
expect(evaluator.evaluateNode(expr.initializer !))
|
expect(evaluator.evaluateNode(expr.initializer !))
|
||||||
.toEqual({__symbolic: 'new', expression: {__symbolic: 'reference', name: 'f'}});
|
.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 {
|
function sourceFileOf(text: string): ts.SourceFile {
|
Loading…
Reference in New Issue