chore(build): Added tests for metadata extractor
Adds unit test to metadata extractor classes Fixes issues found while testing
This commit is contained in:
parent
ae876d1317
commit
3f57fa6e0e
|
@ -0,0 +1,128 @@
|
|||
var mockfs = require('mock-fs');
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import * as fs from 'fs';
|
||||
import {MockHost, expectNoDiagnostics, findVar} from './typescript.mock';
|
||||
import {Evaluator} from './evaluator';
|
||||
import {Symbols} from './symbols';
|
||||
|
||||
describe('Evaluator', () => {
|
||||
// Read the lib.d.ts before mocking fs.
|
||||
let libTs: string = fs.readFileSync(ts.getDefaultLibFilePath({}), 'utf8');
|
||||
|
||||
beforeEach(() => files['lib.d.ts'] = libTs);
|
||||
beforeEach(() => mockfs(files));
|
||||
afterEach(() => mockfs.restore());
|
||||
|
||||
let host: ts.LanguageServiceHost;
|
||||
let service: ts.LanguageService;
|
||||
let program: ts.Program;
|
||||
let typeChecker: ts.TypeChecker;
|
||||
let symbols: Symbols;
|
||||
let evaluator: Evaluator;
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockHost(['expressions.ts'], /*currentDirectory*/ undefined, 'lib.d.ts');
|
||||
service = ts.createLanguageService(host);
|
||||
program = service.getProgram();
|
||||
typeChecker = program.getTypeChecker();
|
||||
symbols = new Symbols();
|
||||
evaluator = new Evaluator(service, typeChecker, symbols, f => f);
|
||||
});
|
||||
|
||||
it('should not have typescript errors in test data', () => {
|
||||
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to fold literal expressions', () => {
|
||||
var consts = program.getSourceFile('consts.ts');
|
||||
expect(evaluator.isFoldable(findVar(consts, 'someName').initializer)).toBeTruthy();
|
||||
expect(evaluator.isFoldable(findVar(consts, 'someBool').initializer)).toBeTruthy();
|
||||
expect(evaluator.isFoldable(findVar(consts, 'one').initializer)).toBeTruthy();
|
||||
expect(evaluator.isFoldable(findVar(consts, 'two').initializer)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be able to fold expressions with foldable references', () => {
|
||||
var expressions = program.getSourceFile('expressions.ts');
|
||||
expect(evaluator.isFoldable(findVar(expressions, 'three').initializer)).toBeTruthy();
|
||||
expect(evaluator.isFoldable(findVar(expressions, 'four').initializer)).toBeTruthy();
|
||||
expect(evaluator.isFoldable(findVar(expressions, 'obj').initializer)).toBeTruthy();
|
||||
expect(evaluator.isFoldable(findVar(expressions, 'arr').initializer)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be able to evaluate literal expressions', () => {
|
||||
var consts = program.getSourceFile('consts.ts');
|
||||
expect(evaluator.evaluateNode(findVar(consts, 'someName').initializer)).toBe('some-name');
|
||||
expect(evaluator.evaluateNode(findVar(consts, 'someBool').initializer)).toBe(true);
|
||||
expect(evaluator.evaluateNode(findVar(consts, 'one').initializer)).toBe(1);
|
||||
expect(evaluator.evaluateNode(findVar(consts, 'two').initializer)).toBe(2);
|
||||
});
|
||||
|
||||
it('should be able to evaluate expressions', () => {
|
||||
var expressions = program.getSourceFile('expressions.ts');
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'three').initializer)).toBe(3);
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'four').initializer)).toBe(4);
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'obj').initializer))
|
||||
.toEqual({one: 1, two: 2, three: 3, four: 4});
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'arr').initializer)).toEqual([1, 2, 3, 4]);
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'bTrue').initializer)).toEqual(true);
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'bFalse').initializer)).toEqual(false);
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'bAnd').initializer)).toEqual(true);
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'bOr').initializer)).toEqual(true);
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'nDiv').initializer)).toEqual(2);
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'nMod').initializer)).toEqual(1);
|
||||
});
|
||||
|
||||
it('should report recursive references as symbolic', () => {
|
||||
var expressions = program.getSourceFile('expressions.ts');
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'recursiveA').initializer))
|
||||
.toEqual({__symbolic: "reference", name: "recursiveB", module: "expressions.ts"});
|
||||
expect(evaluator.evaluateNode(findVar(expressions, 'recursiveB').initializer))
|
||||
.toEqual({__symbolic: "reference", name: "recursiveA", module: "expressions.ts"});
|
||||
});
|
||||
});
|
||||
|
||||
const files = {
|
||||
'directives.ts': `
|
||||
export function Pipe(options: { name?: string, pure?: boolean}) {
|
||||
return function(fn: Function) { }
|
||||
}
|
||||
`,
|
||||
'consts.ts': `
|
||||
export var someName = 'some-name';
|
||||
export var someBool = true;
|
||||
export var one = 1;
|
||||
export var two = 2;
|
||||
`,
|
||||
'expressions.ts': `
|
||||
import {someName, someBool, one, two} from './consts';
|
||||
|
||||
export var three = one + two;
|
||||
export var four = two * two;
|
||||
export var obj = { one: one, two: two, three: three, four: four };
|
||||
export var arr = [one, two, three, four];
|
||||
export var bTrue = someBool;
|
||||
export var bFalse = !someBool;
|
||||
export var bAnd = someBool && someBool;
|
||||
export var bOr = someBool || someBool;
|
||||
export var nDiv = four / two;
|
||||
export var nMod = (four + one) % two;
|
||||
|
||||
export var recursiveA = recursiveB;
|
||||
export var recursiveB = recursiveA;
|
||||
`,
|
||||
'A.ts': `
|
||||
import {Pipe} from './directives';
|
||||
|
||||
@Pipe({name: 'A', pure: false})
|
||||
export class A {}`,
|
||||
'B.ts': `
|
||||
import {Pipe} from './directives';
|
||||
import {someName, someBool} from './consts';
|
||||
|
||||
@Pipe({name: someName, pure: someBool})
|
||||
export class B {}`
|
||||
}
|
|
@ -1,6 +1,18 @@
|
|||
import * as ts from 'typescript';
|
||||
import {Symbols} from './symbols';
|
||||
|
||||
// TOOD: Remove when tools directory is upgraded to support es6 target
|
||||
interface Map<K, V> {
|
||||
has(k: K): boolean;
|
||||
set(k: K, v: V): void;
|
||||
get(k: K): V;
|
||||
delete (k: K): void;
|
||||
}
|
||||
interface MapConstructor {
|
||||
new<K, V>(): Map<K, V>;
|
||||
}
|
||||
declare var Map: MapConstructor;
|
||||
|
||||
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
|
||||
const expression = callExpression.expression;
|
||||
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
|
||||
|
@ -105,25 +117,30 @@ export class Evaluator {
|
|||
* - An identifier is foldable if a value can be found for its symbol is in the evaluator symbol
|
||||
* table.
|
||||
*/
|
||||
public isFoldable(node: ts.Node) {
|
||||
public isFoldable(node: ts.Node): boolean {
|
||||
return this.isFoldableWorker(node, new Map<ts.Node, boolean>());
|
||||
}
|
||||
|
||||
private isFoldableWorker(node: ts.Node, folding: Map<ts.Node, boolean>): boolean {
|
||||
if (node) {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.ObjectLiteralExpression:
|
||||
return everyNodeChild(node, child => {
|
||||
if (child.kind === ts.SyntaxKind.PropertyAssignment) {
|
||||
const propertyAssignment = <ts.PropertyAssignment>child;
|
||||
return this.isFoldable(propertyAssignment.initializer)
|
||||
return this.isFoldableWorker(propertyAssignment.initializer, folding)
|
||||
}
|
||||
return false;
|
||||
});
|
||||
case ts.SyntaxKind.ArrayLiteralExpression:
|
||||
return everyNodeChild(node, child => this.isFoldable(child));
|
||||
return everyNodeChild(node, child => this.isFoldableWorker(child, folding));
|
||||
case ts.SyntaxKind.CallExpression:
|
||||
const callExpression = <ts.CallExpression>node;
|
||||
// We can fold a <array>.concat(<v>).
|
||||
if (isMethodCallOf(callExpression, "concat") && callExpression.arguments.length === 1) {
|
||||
const arrayNode = (<ts.PropertyAccessExpression>callExpression.expression).expression;
|
||||
if (this.isFoldable(arrayNode) && this.isFoldable(callExpression.arguments[0])) {
|
||||
if (this.isFoldableWorker(arrayNode, folding) &&
|
||||
this.isFoldableWorker(callExpression.arguments[0], folding)) {
|
||||
// It needs to be an array.
|
||||
const arrayValue = this.evaluateNode(arrayNode);
|
||||
if (arrayValue && Array.isArray(arrayValue)) {
|
||||
|
@ -133,7 +150,7 @@ export class Evaluator {
|
|||
}
|
||||
// We can fold a call to CONST_EXPR
|
||||
if (isCallOf(callExpression, "CONST_EXPR") && callExpression.arguments.length === 1)
|
||||
return this.isFoldable(callExpression.arguments[0]);
|
||||
return this.isFoldableWorker(callExpression.arguments[0], folding);
|
||||
return false;
|
||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
case ts.SyntaxKind.StringLiteral:
|
||||
|
@ -142,6 +159,9 @@ export class Evaluator {
|
|||
case ts.SyntaxKind.TrueKeyword:
|
||||
case ts.SyntaxKind.FalseKeyword:
|
||||
return true;
|
||||
case ts.SyntaxKind.ParenthesizedExpression:
|
||||
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
|
||||
return this.isFoldableWorker(parenthesizedExpression.expression, folding);
|
||||
case ts.SyntaxKind.BinaryExpression:
|
||||
const binaryExpression = <ts.BinaryExpression>node;
|
||||
switch (binaryExpression.operatorToken.kind) {
|
||||
|
@ -152,19 +172,37 @@ export class Evaluator {
|
|||
case ts.SyntaxKind.PercentToken:
|
||||
case ts.SyntaxKind.AmpersandAmpersandToken:
|
||||
case ts.SyntaxKind.BarBarToken:
|
||||
return this.isFoldable(binaryExpression.left) &&
|
||||
this.isFoldable(binaryExpression.right);
|
||||
return this.isFoldableWorker(binaryExpression.left, folding) &&
|
||||
this.isFoldableWorker(binaryExpression.right, folding);
|
||||
}
|
||||
case ts.SyntaxKind.PropertyAccessExpression:
|
||||
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
|
||||
return this.isFoldable(propertyAccessExpression.expression);
|
||||
return this.isFoldableWorker(propertyAccessExpression.expression, folding);
|
||||
case ts.SyntaxKind.ElementAccessExpression:
|
||||
const elementAccessExpression = <ts.ElementAccessExpression>node;
|
||||
return this.isFoldable(elementAccessExpression.expression) &&
|
||||
this.isFoldable(elementAccessExpression.argumentExpression);
|
||||
return this.isFoldableWorker(elementAccessExpression.expression, folding) &&
|
||||
this.isFoldableWorker(elementAccessExpression.argumentExpression, folding);
|
||||
case ts.SyntaxKind.Identifier:
|
||||
const symbol = this.typeChecker.getSymbolAtLocation(node);
|
||||
let symbol = this.typeChecker.getSymbolAtLocation(node);
|
||||
if (symbol.flags & ts.SymbolFlags.Alias) {
|
||||
symbol = this.typeChecker.getAliasedSymbol(symbol);
|
||||
}
|
||||
if (this.symbols.has(symbol)) return true;
|
||||
|
||||
// If this is a reference to a foldable variable then it is foldable too.
|
||||
const variableDeclaration = <ts.VariableDeclaration>(
|
||||
symbol.declarations && symbol.declarations.length && symbol.declarations[0]);
|
||||
if (variableDeclaration.kind === ts.SyntaxKind.VariableDeclaration) {
|
||||
const initializer = variableDeclaration.initializer;
|
||||
if (folding.has(initializer)) {
|
||||
// A recursive reference is not foldable.
|
||||
return false;
|
||||
}
|
||||
folding.set(initializer, true);
|
||||
const result = this.isFoldableWorker(initializer, folding);
|
||||
folding.delete(initializer);
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -252,8 +290,17 @@ export class Evaluator {
|
|||
break;
|
||||
}
|
||||
case ts.SyntaxKind.Identifier:
|
||||
const symbol = this.typeChecker.getSymbolAtLocation(node);
|
||||
let symbol = this.typeChecker.getSymbolAtLocation(node);
|
||||
if (symbol.flags & ts.SymbolFlags.Alias) {
|
||||
symbol = this.typeChecker.getAliasedSymbol(symbol);
|
||||
}
|
||||
if (this.symbols.has(symbol)) return this.symbols.get(symbol);
|
||||
if (this.isFoldable(node)) {
|
||||
// isFoldable implies, in this context, symbol declaration is a VariableDeclaration
|
||||
const variableDeclaration = <ts.VariableDeclaration>(
|
||||
symbol.declarations && symbol.declarations.length && symbol.declarations[0]);
|
||||
return this.evaluateNode(variableDeclaration.initializer);
|
||||
}
|
||||
return this.nodeSymbolReference(node);
|
||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
return (<ts.LiteralExpression>node).text;
|
||||
|
@ -267,7 +314,42 @@ export class Evaluator {
|
|||
return true;
|
||||
case ts.SyntaxKind.FalseKeyword:
|
||||
return false;
|
||||
|
||||
case ts.SyntaxKind.ParenthesizedExpression:
|
||||
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
|
||||
return this.evaluateNode(parenthesizedExpression.expression);
|
||||
case ts.SyntaxKind.PrefixUnaryExpression:
|
||||
const prefixUnaryExpression = <ts.PrefixUnaryExpression>node;
|
||||
const operand = this.evaluateNode(prefixUnaryExpression.operand);
|
||||
if (isDefined(operand) && isPrimitive(operand)) {
|
||||
switch (prefixUnaryExpression.operator) {
|
||||
case ts.SyntaxKind.PlusToken:
|
||||
return +operand;
|
||||
case ts.SyntaxKind.MinusToken:
|
||||
return -operand;
|
||||
case ts.SyntaxKind.TildeToken:
|
||||
return ~operand;
|
||||
case ts.SyntaxKind.ExclamationToken:
|
||||
return !operand;
|
||||
}
|
||||
}
|
||||
let operatorText: string;
|
||||
switch (prefixUnaryExpression.operator) {
|
||||
case ts.SyntaxKind.PlusToken:
|
||||
operatorText = '+';
|
||||
break;
|
||||
case ts.SyntaxKind.MinusToken:
|
||||
operatorText = '-';
|
||||
break;
|
||||
case ts.SyntaxKind.TildeToken:
|
||||
operatorText = '~';
|
||||
break;
|
||||
case ts.SyntaxKind.ExclamationToken:
|
||||
operatorText = '!';
|
||||
break;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
return {__symbolic: "pre", operator: operatorText, operand: operand };
|
||||
case ts.SyntaxKind.BinaryExpression:
|
||||
const binaryExpression = <ts.BinaryExpression>node;
|
||||
const left = this.evaluateNode(binaryExpression.left);
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
var mockfs = require('mock-fs');
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import * as fs from 'fs';
|
||||
import {MockHost, expectNoDiagnostics, findClass} from './typescript.mock';
|
||||
import {MetadataExtractor} from './extractor';
|
||||
|
||||
describe('MetadataExtractor', () => {
|
||||
// Read the lib.d.ts before mocking fs.
|
||||
let libTs: string = fs.readFileSync(ts.getDefaultLibFilePath({}), 'utf8');
|
||||
|
||||
beforeEach(() => files['lib.d.ts'] = libTs);
|
||||
beforeEach(() => mockfs(files));
|
||||
afterEach(() => mockfs.restore());
|
||||
|
||||
let host: ts.LanguageServiceHost;
|
||||
let service: ts.LanguageService;
|
||||
let program: ts.Program;
|
||||
let typeChecker: ts.TypeChecker;
|
||||
let extractor: MetadataExtractor;
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockHost(['A.ts', 'B.ts', 'C.ts'], /*currentDirectory*/ undefined, 'lib.d.ts');
|
||||
service = ts.createLanguageService(host);
|
||||
program = service.getProgram();
|
||||
typeChecker = program.getTypeChecker();
|
||||
extractor = new MetadataExtractor(service);
|
||||
});
|
||||
|
||||
it('should not have typescript errors in test data', () => {
|
||||
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to extract metadata when defined by literals', () => {
|
||||
const sourceFile = program.getSourceFile('A.ts');
|
||||
const metadata = extractor.getMetadata(sourceFile, typeChecker);
|
||||
expect(metadata).toEqual({
|
||||
__symbolic: 'module',
|
||||
module: './A',
|
||||
metadata: {
|
||||
A: {
|
||||
__symbolic: 'class',
|
||||
decorators: [
|
||||
{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', name: 'Pipe', module: './directives'},
|
||||
arguments: [{name: 'A', pure: false}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to extract metadata from metadata defined using vars', () => {
|
||||
const sourceFile = program.getSourceFile('B.ts');
|
||||
const metadata = extractor.getMetadata(sourceFile, typeChecker);
|
||||
expect(metadata).toEqual({
|
||||
__symbolic: 'module',
|
||||
module: './B',
|
||||
metadata: {
|
||||
B: {
|
||||
__symbolic: 'class',
|
||||
decorators: [
|
||||
{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', name: 'Pipe', module: './directives'},
|
||||
arguments: [{name: 'some-name', pure: true}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('souce be able to extract metadata that uses external references', () => {
|
||||
const sourceFile = program.getSourceFile('C.ts');
|
||||
const metadata = extractor.getMetadata(sourceFile, typeChecker);
|
||||
expect(metadata).toEqual({
|
||||
__symbolic: 'module',
|
||||
module: './C',
|
||||
metadata: {
|
||||
B: {
|
||||
__symbolic: 'class',
|
||||
decorators: [
|
||||
{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', name: 'Pipe', module: './directives'},
|
||||
arguments: [
|
||||
{
|
||||
name: {__symbolic: "reference", module: "./external", name: "externalName"},
|
||||
pure:
|
||||
{__symbolic: "reference", module: "./external", name: "externalBool"}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const files = {
|
||||
'directives.ts': `
|
||||
export function Pipe(options: { name?: string, pure?: boolean}) {
|
||||
return function(fn: Function) { }
|
||||
}
|
||||
`,
|
||||
'consts.ts': `
|
||||
export var someName = 'some-name';
|
||||
export var someBool = true;
|
||||
`,
|
||||
'external.d.ts': `
|
||||
export const externalName: string;
|
||||
export const externalBool: boolean;
|
||||
`,
|
||||
'A.ts': `
|
||||
import {Pipe} from './directives';
|
||||
|
||||
@Pipe({name: 'A', pure: false})
|
||||
export class A {}`,
|
||||
'B.ts': `
|
||||
import {Pipe} from './directives';
|
||||
import {someName, someBool} from './consts';
|
||||
|
||||
@Pipe({name: someName, pure: someBool})
|
||||
export class B {}`,
|
||||
'C.ts': `
|
||||
import {Pipe} from './directives';
|
||||
import {externalName, externalBool} from './external';
|
||||
|
||||
@Pipe({name: externalName, pure: externalBool})
|
||||
export class B {}`
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/// <reference path="../typings/node/node.d.ts" />
|
||||
/// <reference path="../typings/jasmine/jasmine.d.ts" />
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import {Symbols} from './symbols';
|
||||
import {MockSymbol, MockVariableDeclaration} from './typescript.mock';
|
||||
|
||||
describe('Symbols', () => {
|
||||
let symbols: Symbols;
|
||||
const someValue = 'some-value';
|
||||
const someSymbol = MockSymbol.of('some-symbol');
|
||||
const aliasSymbol = new MockSymbol('some-symbol', someSymbol.getDeclarations()[0]);
|
||||
const missingSymbol = MockSymbol.of('some-other-symbol');
|
||||
|
||||
beforeEach(() => symbols = new Symbols());
|
||||
|
||||
it('should be able to add a symbol', () => symbols.set(someSymbol, someValue));
|
||||
|
||||
beforeEach(() => symbols.set(someSymbol, someValue));
|
||||
|
||||
it('should be able to `has` a symbol', () => expect(symbols.has(someSymbol)).toBeTruthy());
|
||||
it('should be able to `get` a symbol value',
|
||||
() => expect(symbols.get(someSymbol)).toBe(someValue));
|
||||
it('should be able to `has` an alias symbol',
|
||||
() => expect(symbols.has(aliasSymbol)).toBeTruthy());
|
||||
it('should be able to `get` a symbol value',
|
||||
() => expect(symbols.get(aliasSymbol)).toBe(someValue));
|
||||
it('should be able to determine symbol is missing',
|
||||
() => expect(symbols.has(missingSymbol)).toBeFalsy());
|
||||
it('should return undefined from `get` for a missing symbol',
|
||||
() => expect(symbols.get(missingSymbol)).toBeUndefined());
|
||||
});
|
|
@ -24,11 +24,11 @@ var a: Array<number>;
|
|||
export class Symbols {
|
||||
private map = new Map<ts.Node, any>();
|
||||
|
||||
public has(symbol: ts.Symbol): boolean { return this.map.has(symbol.declarations[0]); }
|
||||
public has(symbol: ts.Symbol): boolean { return this.map.has(symbol.getDeclarations()[0]); }
|
||||
|
||||
public set(symbol: ts.Symbol, value): void { this.map.set(symbol.declarations[0], value); }
|
||||
public set(symbol: ts.Symbol, value): void { this.map.set(symbol.getDeclarations()[0], value); }
|
||||
|
||||
public get(symbol: ts.Symbol): any { return this.map.get(symbol.declarations[0]); }
|
||||
public get(symbol: ts.Symbol): any { return this.map.get(symbol.getDeclarations()[0]); }
|
||||
|
||||
static empty: Symbols = new Symbols();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
import * as ts from 'typescript';
|
||||
import * as fs from 'fs';
|
||||
|
||||
/**
|
||||
* A mock language service host that assumes mock-fs is used for the file system.
|
||||
*/
|
||||
export class MockHost implements ts.LanguageServiceHost {
|
||||
constructor(private fileNames: string[], private currentDirectory: string = process.cwd(),
|
||||
private libName?: string) {}
|
||||
|
||||
getCompilationSettings(): ts.CompilerOptions {
|
||||
return {
|
||||
experimentalDecorators: true,
|
||||
modules: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES5
|
||||
};
|
||||
}
|
||||
|
||||
getScriptFileNames(): string[] { return this.fileNames; }
|
||||
|
||||
getScriptVersion(fileName: string): string { return "1"; }
|
||||
|
||||
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
|
||||
if (fs.existsSync(fileName)) {
|
||||
return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName, 'utf8'))
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return this.currentDirectory; }
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
return this.libName || ts.getDefaultLibFilePath(options);
|
||||
}
|
||||
}
|
||||
|
||||
export class MockNode implements ts.Node {
|
||||
constructor(public kind: ts.SyntaxKind = ts.SyntaxKind.Identifier, public flags: ts.NodeFlags = 0,
|
||||
public pos: number = 0, public end: number = 0) {}
|
||||
getSourceFile(): ts.SourceFile { return null; }
|
||||
getChildCount(sourceFile?: ts.SourceFile): number { return 0 }
|
||||
getChildAt(index: number, sourceFile?: ts.SourceFile): ts.Node { return null; }
|
||||
getChildren(sourceFile?: ts.SourceFile): ts.Node[] { return []; }
|
||||
getStart(sourceFile?: ts.SourceFile): number { return 0; }
|
||||
getFullStart(): number { return 0; }
|
||||
getEnd(): number { return 0; }
|
||||
getWidth(sourceFile?: ts.SourceFile): number { return 0; }
|
||||
getFullWidth(): number { return 0; }
|
||||
getLeadingTriviaWidth(sourceFile?: ts.SourceFile): number { return 0; }
|
||||
getFullText(sourceFile?: ts.SourceFile): string { return ''; }
|
||||
getText(sourceFile?: ts.SourceFile): string { return ''; }
|
||||
getFirstToken(sourceFile?: ts.SourceFile): ts.Node { return null; }
|
||||
getLastToken(sourceFile?: ts.SourceFile): ts.Node { return null; }
|
||||
}
|
||||
|
||||
export class MockIdentifier extends MockNode implements ts.Identifier {
|
||||
public text: string;
|
||||
public _primaryExpressionBrand: any;
|
||||
public _memberExpressionBrand: any;
|
||||
public _leftHandSideExpressionBrand: any;
|
||||
public _incrementExpressionBrand: any;
|
||||
public _unaryExpressionBrand: any;
|
||||
public _expressionBrand: any;
|
||||
|
||||
constructor(public name: string, kind: ts.SyntaxKind = ts.SyntaxKind.Identifier,
|
||||
flags: ts.NodeFlags = 0, pos: number = 0, end: number = 0) {
|
||||
super(kind, flags, pos, end);
|
||||
this.text = name;
|
||||
}
|
||||
}
|
||||
|
||||
export class MockVariableDeclaration extends MockNode implements ts.VariableDeclaration {
|
||||
public _declarationBrand: any;
|
||||
|
||||
constructor(public name: ts.Identifier, kind: ts.SyntaxKind = ts.SyntaxKind.VariableDeclaration,
|
||||
flags: ts.NodeFlags = 0, pos: number = 0, end: number = 0) {
|
||||
super(kind, flags, pos, end);
|
||||
}
|
||||
|
||||
static of(name: string): MockVariableDeclaration {
|
||||
return new MockVariableDeclaration(new MockIdentifier(name));
|
||||
}
|
||||
}
|
||||
|
||||
export class MockSymbol implements ts.Symbol {
|
||||
constructor(public name: string, private node: ts.Declaration = MockVariableDeclaration.of(name),
|
||||
public flags: ts.SymbolFlags = 0) {}
|
||||
|
||||
getFlags(): ts.SymbolFlags { return this.flags; }
|
||||
getName(): string { return this.name; }
|
||||
getDeclarations(): ts.Declaration[] { return [this.node]; }
|
||||
getDocumentationComment(): ts.SymbolDisplayPart[] { return []; }
|
||||
|
||||
static of(name: string): MockSymbol { return new MockSymbol(name); }
|
||||
}
|
||||
|
||||
export function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||
for (const diagnostic of diagnostics) {
|
||||
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
||||
let {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||||
console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
|
||||
}
|
||||
expect(diagnostics.length).toBe(0);
|
||||
}
|
||||
|
||||
export function allChildren<T>(node: ts.Node, cb: (node: ts.Node) => T) {
|
||||
return ts.forEachChild(node, child => {
|
||||
const result = cb(node);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return allChildren(child, cb);
|
||||
})
|
||||
}
|
||||
|
||||
export function findVar(sourceFile: ts.SourceFile, name: string): ts.VariableDeclaration {
|
||||
return allChildren(sourceFile,
|
||||
node => isVar(node) && isNamed(node.name, name) ? node : undefined);
|
||||
}
|
||||
|
||||
export function findClass(sourceFile: ts.SourceFile, name: string): ts.ClassDeclaration {
|
||||
return ts.forEachChild(sourceFile,
|
||||
node => isClass(node) && isNamed(node.name, name) ? node : undefined);
|
||||
}
|
||||
|
||||
export function isVar(node: ts.Node): node is ts.VariableDeclaration {
|
||||
return node.kind === ts.SyntaxKind.VariableDeclaration;
|
||||
}
|
||||
|
||||
export function isClass(node: ts.Node): node is ts.ClassDeclaration {
|
||||
return node.kind === ts.SyntaxKind.ClassDeclaration;
|
||||
}
|
||||
|
||||
export function isNamed(node: ts.Node, name: string): node is ts.Identifier {
|
||||
return node.kind === ts.SyntaxKind.Identifier && (<ts.Identifier>node).text === name;
|
||||
}
|
Loading…
Reference in New Issue