TypeScript/scripts/processTypes.ts
2015-07-10 14:17:35 -07:00

1284 lines
43 KiB
TypeScript

import {
sys,
getDefaultCompilerOptions,
createProgram,
createCompilerHost,
forEachChild,
getLeadingCommentRanges,
computeLineStarts,
createTextWriter,
combinePaths,
getNodeId,
getSymbolId,
getProperty,
hasProperty,
createSourceFile,
map,
SyntaxKind,
CompilerOptions,
CompilerHost,
Program,
TypeChecker,
CommentRange,
EmitTextWriter,
Node,
SourceFile,
Declaration,
ModuleDeclaration,
ModuleBlock,
InterfaceDeclaration,
TypeAliasDeclaration,
EnumDeclaration,
EnumMember,
PropertyDeclaration,
TypeNode,
TypeReferenceNode,
UnionTypeNode,
ExpressionWithTypeArguments,
ExpressionStatement,
Expression,
CallExpression,
PropertyAccessExpression,
ObjectLiteralExpression,
ArrayLiteralExpression,
PropertyAssignment,
LiteralExpression,
Identifier,
SymbolFlags,
Symbol,
SymbolTable,
TypeFlags,
Type,
TypeReference,
InterfaceType,
Map,
} from "./typescript-internal";
interface SyntaxNode {
kind?: SyntaxKind;
kindName?: string;
typeName?: string;
members?: SyntaxMember[];
options?: KindOptions;
}
interface SyntaxMember {
paramName?: string;
propertyName?: string;
typeName?: string;
elementTypeName?: string;
isFactoryParam?: boolean;
isNode?: boolean;
isNodeArray?: boolean;
isModifiersArray?: boolean;
}
interface Annotation {
name: string;
arguments: any[];
}
interface FactoryHiddenAnnotation extends Annotation {
propertyName?: string;
hidden: boolean;
}
const enum FactoryHiddenState {
None,
Hidden,
Visible
}
interface FactoryOrderAnnotation extends Annotation {
propertyNames: string[];
}
interface FactoryParamAnnotation extends Annotation {
propertyName?: string;
}
interface KindOptions {
create: boolean;
update: boolean;
test: boolean;
}
interface KindAnnotation extends Annotation {
kind: SyntaxKind;
options: KindOptions;
}
const columnWrap = 150;
const emptyArray: any[] = [];
const kindPattern = /@kind\s*\(\s*SyntaxKind\.(\w+)\s*\)/g;
const annotationPattern = /@(\w+\s*[^\r\n]*)/g;
const annotationArgumentPattern = /[(,]([^,)]+)/g;
const whitespacePattern = /\s/;
const quotePattern = /["'`]/;
const numberPattern = /^[+-]?(\d+|\d*\.\d+|\d*e\d+|0x[\da-f]+)$/i;
let file: string;
let options: CompilerOptions;
let host: CompilerHost;
let program: Program;
let checker: TypeChecker;
let sourceFile: SourceFile;
let tsModuleSymbol: Symbol;
let nodeSymbol: Symbol;
let nodeArraySymbol: Symbol;
let modifiersArraySymbol: Symbol;
let syntaxKindSymbol: Symbol;
let syntaxKindValueToSymbol: Symbol[] = [];
let nodeAnnotations: Annotation[][] = [];
let syntax: SyntaxNode[] = [];
let syntaxKindTypeUsages: Map<Symbol[]> = {};
let memberTypeUsages: Map<boolean> = {};
let memberTypeUsageRedirects: Map<string> = {};
main();
function main() {
if (sys.args.length < 1) {
sys.write("Usage:" + sys.newLine)
sys.write("\tnode processTypes.js <types-ts-input-file>" + sys.newLine);
return;
}
options = getCompilerOptions();
host = createCompilerHost(options);
file = host.getCanonicalFileName(sys.resolvePath(sys.args[0]));
program = createProgram([file], options, host);
checker = program.getTypeChecker();
sourceFile = program.getSourceFile(file);
discover();
let inputDirectory = sys.resolvePath(combinePaths(file, ".."));
let factoryOutputFile = combinePaths(inputDirectory, "factory.generated.ts");
generateFactory(factoryOutputFile);
}
/**
* Discovers type information and symbols for various SyntaxNodes
*/
function discover() {
visit(sourceFile);
// Sort the syntax nodes by SyntaxKind
syntax.sort((a, b) => {
return a.kind - b.kind;
});
// Set up member type usage redirects for types with a single kind
for (let typeName in syntaxKindTypeUsages) {
if (syntaxKindTypeUsages[typeName].length === 1) {
memberTypeUsageRedirects[typeName] = syntaxKindTypeUsages[typeName][0].name;
}
}
function visit(node: Node) {
switch (node.kind) {
case SyntaxKind.SourceFile:
case SyntaxKind.ModuleBlock:
forEachChild(node, visit);
break;
case SyntaxKind.ModuleDeclaration:
visitModuleDeclaration(<ModuleDeclaration>node);
break;
case SyntaxKind.EnumDeclaration:
visitEnumDeclaration(<EnumDeclaration>node);
break;
case SyntaxKind.InterfaceDeclaration:
visitInterfaceDeclaration(<InterfaceDeclaration>node);
break;
case SyntaxKind.TypeAliasDeclaration:
visitTypeAliasDeclaration(<TypeAliasDeclaration>node);
break;
}
}
function visitModuleDeclaration(node: ModuleDeclaration) {
let name = node.name;
let symbol = checker.getSymbolAtLocation(name);
if (symbol.name === "ts") {
tsModuleSymbol = checker.getSymbolAtLocation(name);
forEachChild(node, visit);
}
}
function visitEnumDeclaration(node: EnumDeclaration) {
let name = node.name;
let symbol = checker.getSymbolAtLocation(name);
if (symbol.name === "SyntaxKind") {
syntaxKindSymbol = symbol;
for (let name in syntaxKindSymbol.exports) {
let exportedSymbol = getProperty(syntaxKindSymbol.exports, name);
if (!exportedSymbol || !(exportedSymbol.flags & SymbolFlags.EnumMember)) {
continue;
}
let value = checker.getConstantValue(<EnumMember>exportedSymbol.declarations[0]);
if (value !== undefined && !(value in syntaxKindValueToSymbol)) {
syntaxKindValueToSymbol[value] = exportedSymbol;
}
}
}
}
function visitInterfaceDeclaration(node: InterfaceDeclaration) {
let name = node.name;
let symbol = checker.getSymbolAtLocation(name);
if (symbol.name === "NodeArray") {
nodeArraySymbol = symbol;
return;
}
else if (symbol.name === "ModifiersArray") {
modifiersArraySymbol = symbol;
return;
}
else if (symbol.name === "Node") {
nodeSymbol = symbol;
}
let kinds = symbol ? getKindsForSymbol(symbol) : undefined;
if (kinds) {
createSyntaxNodes(node, symbol, kinds);
}
}
function visitTypeAliasDeclaration(node: TypeAliasDeclaration) {
let name = node.name;
let symbol = checker.getSymbolAtLocation(name);
let kinds = symbol ? getKindsForSymbol(symbol) : undefined;
if (kinds) {
createSyntaxNodes(node, symbol, kinds);
}
}
function createSyntaxNodes(decl: InterfaceDeclaration | TypeAliasDeclaration, symbol: Symbol, kinds: KindAnnotation[]) {
if (getFactoryHiddenStateForSymbol(symbol) === FactoryHiddenState.Hidden) {
return;
}
let symbolOrder = getFactoryOrder(symbol, /*inherited*/ true);
for (let kindAnnotation of kinds) {
let kind = kindAnnotation.kind;
let kindSymbol = syntaxKindValueToSymbol[kind];
recordTypeUsagesForKind(kindSymbol, symbol);
let kindOrder = getFactoryOrder(kindSymbol, /*inherited*/ false);
let type = checker.getDeclaredTypeOfSymbol(symbol);
var members: SyntaxMember[] = [];
for (let property of checker.getPropertiesOfType(type)) {
// Skip any hidden properties
if (getFactoryHiddenStateForProperty(symbol, property.name) === FactoryHiddenState.Hidden) {
continue;
}
// Collect information about the type
let typeNode = getTypeNodeForProperty(property);
let propertyIsFactoryParam = isFactoryParamProperty(symbol, property.name);
let propertyIsNode = typeNode && isSubtypeOf(typeNode, nodeSymbol);
let propertyIsNodeArray = typeNode && isNodeArray(typeNode);
let propertyIsModifiersArray = typeNode && isModifiersArray(typeNode);
if (propertyIsFactoryParam || propertyIsNodeArray || propertyIsModifiersArray || propertyIsNode) {
let typeName = typeNode ? normalizeTypeName(typeNode.getText()) : "any";
let elementTypeName = propertyIsNodeArray ? (<TypeReferenceNode>typeNode).typeArguments[0].getText() : undefined;
members.push(<SyntaxMember>{
propertyName: property.name,
paramName: property.name === "arguments" ? "_arguments" : property.name,
typeName: typeName,
elementTypeName: elementTypeName,
isFactoryParam: propertyIsFactoryParam,
isNodeArray: propertyIsNodeArray,
isModifiersArray: propertyIsModifiersArray,
isNode: propertyIsNode
});
if (!propertyIsFactoryParam && (propertyIsNodeArray || propertyIsNode)) {
recordTypeUsageForMember(propertyIsNode ? typeName : elementTypeName);
}
}
}
var overrides = kindOrder || symbolOrder;
if (overrides) {
let indices = members.map((_, i) => i);
indices.sort((a, b) => {
let aOverride = overrides.indexOf(members[a].propertyName);
let bOverride = overrides.indexOf(members[b].propertyName);
if (aOverride >= 0) {
if (bOverride >= 0) {
return aOverride - bOverride;
}
return -1;
}
else if (bOverride >= 0) {
return +1;
}
return a - b;
});
members = indices.map(i => members[i]);
}
syntax.push(<SyntaxNode>{
kind,
kindName: kindSymbol.name,
typeName: symbol.name,
members,
options: kindAnnotation.options
});
}
}
function recordTypeUsagesForKind(kindSymbol: Symbol, typeSymbol: Symbol) {
memberTypeUsages[kindSymbol.name] = false;
recordTypeUsagesForKindWorker(kindSymbol, typeSymbol);
}
function recordTypeUsagesForKindWorker(kindSymbol: Symbol, typeSymbol: Symbol) {
let usages = syntaxKindTypeUsages[typeSymbol.name];
if (!usages) {
syntaxKindTypeUsages[typeSymbol.name] = usages = [];
}
if (usages.indexOf(kindSymbol) === -1) {
usages.push(kindSymbol);
}
for (let superType of getSuperTypes(typeSymbol.declarations[0])) {
recordTypeUsagesForKindWorker(kindSymbol, superType);
}
}
function recordTypeUsageForMember(typeName: string) {
if (!hasProperty(memberTypeUsages, typeName)) {
memberTypeUsages[typeName] = true;
}
}
function normalizeTypeName(typeName: string) {
let parts = typeName.split(/\s*\|\s*/g);
if (parts.length === 0) {
return parts[0];
}
parts.sort();
return parts.join(" | ");
}
function getTypeNodeForProperty(property: Symbol) {
return (<PropertyDeclaration>property.declarations[0]).type;
}
function getFactoryHiddenStateForSymbol(symbol: Symbol): FactoryHiddenState {
let annotation: FactoryHiddenAnnotation;
if (annotation = findAnnotation<FactoryHiddenAnnotation>(symbol, annotation => isFactoryHiddenAnnotation(annotation) && annotation.propertyName === undefined)) {
return annotation.hidden ? FactoryHiddenState.Hidden : FactoryHiddenState.Visible;
}
return FactoryHiddenState.None;
}
function getFactoryHiddenStateForProperty(container: Symbol, propertyName: string): FactoryHiddenState {
let hiddenStates: FactoryHiddenStateResult[] = [];
let hiddenStateForSource: FactoryHiddenStateResult[] = [];
// find all the hidden states for this property
getFactoryHiddenStateForPropertyWorker(container, /*depth*/ 0);
if (hiddenStates.length === 0) {
return FactoryHiddenState.None;
}
else if (hiddenStates.length === 1) {
return hiddenStates[0].state;
}
let closestMatch: FactoryHiddenStateResult;
for (let hiddenState of hiddenStates) {
if (closestMatch === undefined || hiddenState.depth < closestMatch.depth) {
closestMatch = hiddenState;
}
}
return closestMatch.state;
interface FactoryHiddenStateResult {
state: FactoryHiddenState;
source?: Symbol;
depth?: number;
}
function getFactoryHiddenStateForPropertyWorker(source: Symbol, depth: number): void {
let state: FactoryHiddenState;
let result = hiddenStateForSource[getSymbolId(source)];
if (result) {
if (depth > result.depth) {
result.depth = depth;
}
return;
}
// First, check for a property-specific `@factoryhidden` annotation on the container (e.g. `@factoryhidden("x", true)`)
let annotation = findAnnotation<FactoryHiddenAnnotation>(source, annotation => isFactoryHiddenAnnotation(annotation) && annotation.propertyName === propertyName);
if (annotation) {
// encode the depth for later comparison
state = annotation.hidden ? FactoryHiddenState.Hidden : FactoryHiddenState.Visible;
}
let property: Symbol;
if (!state) {
property = source.members ? getProperty(source.members, propertyName) : undefined;
if (property) {
state = getFactoryHiddenStateForSymbol(property);
}
}
if (!state) {
state = getFactoryHiddenStateForSymbol(source);
}
if (!state && property) {
state = FactoryHiddenState.Visible;
}
if (state) {
let result = { state, source, depth };
hiddenStates.push(result);
hiddenStateForSource[getSymbolId(source)] = result;
if (depth === 0) {
return;
}
}
for (let superType of getSuperTypes(source.declarations[0])) {
getFactoryHiddenStateForPropertyWorker(superType, depth + 1);
}
}
}
function isFactoryParamProperty(container: Symbol, propertyName: string): boolean {
if (findAnnotation(container, annotation => isFactoryParamAnnotation(annotation) && annotation.propertyName === propertyName)) {
return true;
}
let property = container.members ? getProperty(container.members, propertyName) : undefined;
if (property) {
if (findAnnotation(property, annotation => isFactoryParamAnnotation(annotation) && annotation.propertyName === undefined)) {
return true;
}
}
else {
for (let superType of getSuperTypes(container.declarations[0])) {
if (isFactoryParamProperty(superType, propertyName)) {
return true;
}
}
}
return false;
}
function getFactoryOrder(symbol: Symbol, inherited?: boolean): string[] {
let annotation = findAnnotation<FactoryOrderAnnotation>(symbol, isFactoryOrderAnnotation);
if (annotation) {
return annotation.propertyNames;
}
let propertyNames: string[];
if (inherited) {
let propertyNameSet: Map<boolean>;
for (let superType of getSuperTypes(symbol.declarations[0])) {
let superTypeOrder = getFactoryOrder(superType, /*inherited*/ true);
if (superTypeOrder && superTypeOrder.length > 0) {
if (!propertyNames) {
propertyNames = [];
propertyNameSet = {};
}
for (let propertyName of superTypeOrder) {
if (propertyNameSet[propertyName]) {
continue;
}
propertyNameSet[propertyName] = true;
propertyNames.push(propertyName);
}
}
}
}
return propertyNames;
}
function getKindsForSymbol(symbol: Symbol): KindAnnotation[] {
return matchAnnotations<KindAnnotation>(symbol, isKindAnnotation);
}
}
function generateFactory(outputFile: string) {
let writer = createLineWrappingTextWriter(host.getNewLine(), columnWrap);
writer.write(`// <auto-generated />`);
writer.writeLine();
writer.write(`/// <reference path="parser.ts" />`);
writer.writeLine();
writer.write(`/// <reference path="factory.ts" />`);
writer.writeLine();
writer.write(`namespace ts {`);
writer.writeLine();
writer.increaseIndent();
writer.write(`export namespace factory {`);
writer.writeLine();
writer.increaseIndent();
writeCreateAndUpdateFunctions();
writeCloneFunction();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
writeIsNodeFunctions();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
sys.writeFile(outputFile, writer.getText());
function writeCreateAndUpdateFunctions() {
for (let syntaxNode of syntax) {
writeCreateFunction(syntaxNode);
writeUpdateFunction(syntaxNode);
}
}
function writeIsNodeFunctions() {
for (let syntaxNode of syntax) {
writeIsNodeFunction(syntaxNode);
}
for (let typeName in memberTypeUsages) {
if (getProperty(memberTypeUsages, typeName) && !hasProperty(memberTypeUsageRedirects, typeName)) {
writeIsAnyNodeFunction(typeName);
}
}
}
function writeCreateFunction(syntaxNode: SyntaxNode) {
if (!syntaxNode.options.create) {
return;
}
writer.write(`export function create${syntaxNode.kindName}(`);
let first = true;
for (let member of syntaxNode.members) {
if (!first) {
writer.write(`, `);
}
else {
first = false;
}
let type =
member.isNodeArray ? `Array<${member.elementTypeName}>` :
member.isModifiersArray ? `Array<Node>` :
member.typeName;
writer.write(`${member.paramName}?: ${type}`);
}
writer.write(`): ${syntaxNode.typeName} {`);
writer.writeLine();
writer.increaseIndent();
if (syntaxNode.members.length) {
writer.write(`let node = createNode<${syntaxNode.typeName}>(SyntaxKind.${syntaxNode.kindName});`);
writer.writeLine();
if (syntaxNode.members.length > 1) {
writer.write(`if (arguments.length) {`);
writer.writeLine();
writer.increaseIndent();
}
for (let member of syntaxNode.members) {
if (member.isModifiersArray) {
writer.write(`setModifiers(node, modifiers);`);
}
else if (member.isNodeArray) {
writer.write(`node.${member.propertyName} = ${member.paramName} && createNodeArray(${member.paramName})`);
}
else {
writer.write(`node.${member.propertyName} = ${member.paramName};`);
}
writer.writeLine();
}
if (syntaxNode.members.length > 1) {
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
}
writer.write(`return node;`);
writer.writeLine();
}
else {
writer.write(`return createNode<${syntaxNode.typeName}>(SyntaxKind.${syntaxNode.kindName});`);
writer.writeLine();
}
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
}
function writeIsNodeFunction(syntaxNode: SyntaxNode) {
if (!syntaxNode.options.test) {
return;
}
writer.write(`export function is${syntaxNode.kindName}(node: Node): node is ${syntaxNode.typeName} {`);
writer.writeLine();
writer.increaseIndent();
writer.write(`return node && node.kind === SyntaxKind.${syntaxNode.kindName};`);
writer.writeLine();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
}
function fillKindsForType(typeSymbol: Symbol, kinds: Symbol[]) {
let usages = getProperty(syntaxKindTypeUsages, typeSymbol.name);
if (usages) {
for (let usage of usages) {
if (kinds.indexOf(usage) === -1) {
kinds.push(usage);
}
}
}
else if (typeSymbol.declarations[0].kind === SyntaxKind.TypeAliasDeclaration) {
for (let superType of getSuperTypes(typeSymbol.declarations[0])) {
fillKindsForType(superType, kinds);
}
}
}
function writeIsAnyNodeFunction(typeName: string) {
let typeNames = typeName.split(/\s*\|\s*/g);
if (typeNames.length === 1) {
let typeSymbol = resolveName(tsModuleSymbol.declarations[0], typeName, SymbolFlags.Type);
if (typeSymbol && findAnnotation(typeSymbol, annotation => annotation.name === "nofactoryanynodetest")) {
return;
}
}
writer.write(`export function is${typeNames.join("Or")}(node: Node): node is ${typeNames.join(" | ")} {`);
writer.writeLine();
writer.increaseIndent();
writer.write(`if (node) {`);
writer.writeLine();
writer.increaseIndent();
writer.write(`switch (node.kind) {`);
writer.writeLine();
writer.increaseIndent();
let kinds: Symbol[] = [];
for (let typeName of typeNames) {
let typeSymbol = resolveName(tsModuleSymbol.declarations[0], typeName, SymbolFlags.Type);
if (typeSymbol) {
fillKindsForType(typeSymbol, kinds);
}
}
for (let kind of kinds) {
writer.write(`case SyntaxKind.${kind.name}:`);
writer.writeLine();
}
if (kinds.length > 0) {
writer.increaseIndent();
writer.write(`return true;`);
writer.writeLine();
writer.decreaseIndent();
}
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
writer.write(`return false; `);
writer.writeLine();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
}
function writeCloneFunction() {
writer.write(`export function cloneNode<TNode extends Node>(node: TNode): TNode;`);
writer.writeLine();
writer.write(`export function cloneNode(node: Node): Node {`);
writer.writeLine();
writer.increaseIndent();
writer.write(`if (!node) {`);
writer.writeLine();
writer.increaseIndent();
writer.write(`return node;`);
writer.writeLine();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
writer.write(`switch (node.kind) {`);
writer.writeLine();
writer.increaseIndent();
for (let syntaxNode of syntax) {
if (!syntaxNode.options.create) {
continue;
}
writer.write(`case SyntaxKind.${syntaxNode.kindName}:`);
writer.writeLine();
writer.increaseIndent();
writer.write(`return factory.create${syntaxNode.kindName}(`);
for (let i = 0; i < syntaxNode.members.length; ++i) {
if (i > 0) {
writer.write(`, `);
}
let member = syntaxNode.members[i];
writer.write(`(<${syntaxNode.typeName}>node).${member.propertyName}`);
}
writer.write(`);`);
writer.writeLine();
writer.decreaseIndent();
}
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
}
function writeUpdateFunction(syntaxNode: SyntaxNode) {
if (!syntaxNode.options.update || !hasChildNodes(syntaxNode)) {
return;
}
writer.write(`export function update${syntaxNode.kindName}(node: ${syntaxNode.typeName}`);
for (let i = 0; i < syntaxNode.members.length; ++i) {
let member = syntaxNode.members[i];
if (member.isFactoryParam) {
continue;
}
let type =
member.isNodeArray ? `Array<${member.elementTypeName}>` :
member.isModifiersArray ? `Array<Node>` :
member.typeName;
writer.write(`, ${member.paramName}: ${type}`);
}
writer.write(`): ${syntaxNode.typeName} {`);
writer.writeLine();
writer.increaseIndent();
writer.write(`if (`);
let first = true;
for (let member of syntaxNode.members) {
if (member.isFactoryParam) {
continue;
}
if (first) {
first = false;
}
else {
writer.write(` || `);
}
writer.write(`${member.paramName} !== node.${member.propertyName}`);
}
writer.write(`) {`);
writer.writeLine();
writer.increaseIndent();
writer.write(`let newNode = create${syntaxNode.kindName}(`);
for (let i = 0; i < syntaxNode.members.length; ++i) {
if (i > 0) {
writer.write(`, `);
}
let member = syntaxNode.members[i];
if (member.isFactoryParam) {
writer.write(`node.${member.propertyName}`);
}
else {
writer.write(member.paramName);
}
}
writer.write(`);`);
writer.writeLine();
writer.write(`return updateFrom(node, newNode);`);
writer.writeLine();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
writer.write(`return node;`);
writer.writeLine();
writer.decreaseIndent();
writer.write(`}`);
writer.writeLine();
}
}
function hasChildNodes(syntaxNode: SyntaxNode) {
for (let member of syntaxNode.members) {
if (member.isFactoryParam) {
continue;
}
if (member.isNode || member.isNodeArray || member.isModifiersArray) {
return true;
}
}
return false;
}
function isTypeReferenceNode(node: Node): node is TypeReferenceNode {
return node ? node.kind === SyntaxKind.TypeReference : false;
}
function isUnionTypeNode(node: Node): node is UnionTypeNode {
return node ? node.kind === SyntaxKind.UnionType : false;
}
function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration {
return node ? node.kind === SyntaxKind.InterfaceDeclaration : false;
}
function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration {
return node ? node.kind === SyntaxKind.TypeAliasDeclaration : false;
}
function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments {
return node ? node.kind === SyntaxKind.ExpressionWithTypeArguments : false;
}
function isNodeArray(typeNode: TypeNode): boolean {
return isTypeReferenceNode(typeNode) ? checker.getSymbolAtLocation(typeNode.typeName) === nodeArraySymbol : false;
}
function isModifiersArray(typeNode: TypeNode): boolean {
return isTypeReferenceNode(typeNode) ? checker.getSymbolAtLocation(typeNode.typeName) === modifiersArraySymbol : false;
}
function isSubtypeOf(node: TypeNode | Declaration, superTypeSymbol: Symbol): boolean {
if (isInterfaceDeclaration(node)) {
if (node.heritageClauses) {
for (let superType of node.heritageClauses[0].types) {
if (isSubtypeOf(superType, superTypeSymbol)) {
return true;
}
}
}
}
else if (isTypeAliasDeclaration(node)) {
return isSubtypeOf(node.type, superTypeSymbol);
}
else if (isUnionTypeNode(node)) {
for (let constituentType of node.types) {
if (isSubtypeOf(constituentType, superTypeSymbol)) {
return true;
}
}
}
else {
let typeSymbol = isTypeReferenceNode(node) ? checker.getSymbolAtLocation(node.typeName)
: isExpressionWithTypeArguments(node) ? checker.getSymbolAtLocation(node.expression)
: undefined;
if (!typeSymbol) {
return false;
}
else if (typeSymbol === superTypeSymbol) {
return true;
}
return isSubtypeOf(typeSymbol.declarations[0], superTypeSymbol);
}
return false;
}
function getSuperTypes(node: Declaration) {
let superTypes: Symbol[] = [];
let superTypeSymbolSet: boolean[] = [];
if (isTypeAliasDeclaration(node)) {
fillSuperTypes(node.type);
}
else if (isInterfaceDeclaration(node) && node.heritageClauses) {
for (let superType of node.heritageClauses[0].types) {
fillSuperTypes(superType);
}
}
return superTypes;
function fillSuperTypes(node: TypeNode) {
if (isUnionTypeNode(node)) {
// Flatten union types
for (let constituentType of node.types) {
fillSuperTypes(constituentType);
}
}
else {
// Add type references
let symbol = isTypeReferenceNode(node) ? checker.getSymbolAtLocation(node.typeName)
: isExpressionWithTypeArguments(node) ? checker.getSymbolAtLocation(node.expression)
: undefined;
if (symbol) {
if (superTypeSymbolSet[getSymbolId(symbol)]) {
return;
}
superTypeSymbolSet[getSymbolId(symbol)] = true;
superTypes.push(symbol);
}
}
}
}
function isFactoryHiddenAnnotation(annotation: Annotation): annotation is FactoryHiddenAnnotation {
return annotation.name === "factoryhidden";
}
function isFactoryOrderAnnotation(annotation: Annotation): annotation is FactoryOrderAnnotation {
return annotation.name === "factoryorder";
}
function isFactoryParamAnnotation(annotation: Annotation): annotation is FactoryParamAnnotation {
return annotation.name === "factoryparam";
}
function isKindAnnotation(annotation: Annotation): annotation is KindAnnotation {
return annotation.name === "kind";
}
function findAnnotation<TAnnotation extends Annotation>(symbol: Symbol, match: (annotation: Annotation) => boolean): TAnnotation {
for (let decl of symbol.declarations) {
for (let annotation of getAnnotationsForNode(decl)) {
if (match(annotation)) {
return <TAnnotation>annotation;
}
}
}
return undefined;
}
function matchAnnotations<TAnnotation extends Annotation>(symbol: Symbol, match: (annotation: Annotation) => boolean): TAnnotation[] {
let annotations: TAnnotation[];
for (let decl of symbol.declarations) {
for (let annotation of getAnnotationsForNode(decl)) {
if (match(annotation)) {
if (!annotations) {
annotations = [];
}
annotations.push(<TAnnotation>annotation);
}
}
}
return annotations || emptyArray;
}
function getAnnotations(symbol: Symbol): Annotation[] {
let annotations: Annotation[];
for (let decl of symbol.declarations) {
let declAnnotations = getAnnotationsForNode(decl);
if (declAnnotations !== emptyArray) {
if (!annotations) {
annotations = [];
}
for (let annotation of declAnnotations) {
annotations.push(annotation);
}
}
}
return annotations;
}
function getAnnotationsForNode(node: Node): Annotation[] {
let annotations = nodeAnnotations[getNodeId(node)];
if (annotations) {
return annotations;
}
let leadingCommentRanges = getLeadingCommentRanges(sourceFile.text, node.pos);
if (leadingCommentRanges) {
for (let range of leadingCommentRanges) {
parseAnnotations(range);
}
}
if (!annotations) {
annotations = emptyArray;
}
nodeAnnotations[getNodeId(node)] = annotations;
return annotations;
function getLiteralValue(expr: Expression): any {
switch (expr.kind) {
case SyntaxKind.TrueKeyword: return true;
case SyntaxKind.FalseKeyword: return false;
case SyntaxKind.NullKeyword: return null;
case SyntaxKind.VoidExpression: return undefined;
case SyntaxKind.StringLiteral: return (<LiteralExpression>expr).text;
case SyntaxKind.NoSubstitutionTemplateLiteral: return (<LiteralExpression>expr).text;
case SyntaxKind.NumericLiteral: return Number((<LiteralExpression>expr).text);
case SyntaxKind.PropertyAccessExpression:
return getEnumValue(node, expr.getText());
case SyntaxKind.ArrayLiteralExpression:
return (<ArrayLiteralExpression>expr).elements.map(getLiteralValue);
case SyntaxKind.ObjectLiteralExpression:
let obj: Map<any> = {};
for (let element of (<ObjectLiteralExpression>expr).properties) {
if (element.kind !== SyntaxKind.PropertyAssignment
|| (<PropertyAssignment>element).name.kind !== SyntaxKind.Identifier) {
continue;
}
obj[(<Identifier>(<PropertyAssignment>element).name).text] =
getLiteralValue((<PropertyAssignment>element).initializer);
}
return obj;
}
}
function parseAnnotation(annotationSource: string) {
let evalSourceFile = createSourceFile("eval.ts", annotationSource, options.target, true);
let statements = evalSourceFile.statements;
if (statements.length === 0) {
return undefined;
}
let stmt = statements[0];
if (stmt.kind !== SyntaxKind.ExpressionStatement) {
return undefined;
}
let expr = (<ExpressionStatement>stmt).expression;
if (expr.kind === SyntaxKind.Identifier) {
return createAnnotation((<Identifier>expr).text, emptyArray);
}
else if (expr.kind === SyntaxKind.CallExpression) {
let call = <CallExpression>expr;
if (call.expression.kind !== SyntaxKind.Identifier) {
return undefined;
}
let _arguments: any[] = [];
for (let argument of call.arguments) {
_arguments.push(getLiteralValue(argument));
}
return createAnnotation((<Identifier>call.expression).text, _arguments);
}
else {
return undefined;
}
}
function parseAnnotations(range: CommentRange) {
let text = sourceFile.text;
let comment = text.substring(range.pos, range.end);
let annotationMatch: RegExpExecArray;
while (annotationMatch = annotationPattern.exec(comment)) {
let annotation = parseAnnotation(annotationMatch[1]);
if (annotation) {
if (!annotations) {
annotations = [];
}
annotations.push(annotation);
}
}
}
}
function getEnumValue(location: Node, name: string) {
let qn = name.split(".");
if (qn.length === 1) {
return undefined;
}
let namespace: Symbol;
if (qn.length > 2) {
for (let i = 0; i < qn.length - 2; i++) {
namespace = i === 0
? resolveName(location, qn[i], SymbolFlags.Namespace)
: getSymbol(namespace.exports, qn[i], SymbolFlags.Namespace);
if (!namespace) {
return undefined;
}
}
}
let container = qn.length > 2
? getSymbol(namespace.exports, qn[qn.length - 2], SymbolFlags.Enum)
: resolveName(location, qn[qn.length - 2], SymbolFlags.Enum);
if (!container) {
return undefined;
}
let member = getSymbol(container.exports, qn[qn.length - 1], SymbolFlags.EnumMember);
if (!member) {
return undefined;
}
return checker.getConstantValue(<EnumMember>member.declarations[0]);
}
function isWhiteSpace(ch: string) {
return whitespacePattern.test(ch);
}
function isQuote(ch: string) {
return quotePattern.test(ch);
}
function createAnnotation(name: string, _arguments: any[]): Annotation {
switch (name) {
case "kind":
let options: KindOptions = { create: true, update: true, test: true };
for (var p in _arguments[1]) {
(<any>options)[p] = _arguments[1][p];
}
return <KindAnnotation>{
name,
arguments: _arguments,
kind: _arguments[0],
options
};
case "factoryhidden":
if (_arguments.length >= 2 && typeof _arguments[0] === "string" && typeof _arguments[1] === "boolean") {
return <FactoryHiddenAnnotation>{ name, arguments: _arguments, propertyName: _arguments[0], hidden: _arguments[1] };
}
else if (_arguments.length >= 1 && typeof _arguments[0] === "string") {
return <FactoryHiddenAnnotation>{ name, arguments: _arguments, propertyName: _arguments[0], hidden: true };
}
else if (_arguments.length >= 1 && typeof _arguments[0] === "boolean") {
return <FactoryHiddenAnnotation>{ name, arguments: _arguments, hidden: _arguments[0] };
}
else {
return <FactoryHiddenAnnotation>{ name, arguments: _arguments, hidden: true };
}
case "factoryorder":
return <FactoryOrderAnnotation>{ name, arguments: _arguments, propertyNames: _arguments };
case "factoryparam":
if (_arguments.length > 0) {
return <FactoryParamAnnotation>{ name, arguments: _arguments, propertyName: _arguments[0] };
}
else {
return <FactoryParamAnnotation>{ name, arguments: _arguments };
}
default:
return <Annotation>{ name, arguments: _arguments };
}
}
function getCompilerOptions() {
let options = getDefaultCompilerOptions();
options.noResolve = true;
options.noLib = true;
return options;
}
function createLineWrappingTextWriter(newLine: string, maxWidth: number): EmitTextWriter {
let writer = createTextWriter(newLine);
let noWrap = false;
let baseWrite = writer.write;
writer.write = writeWrap;
return writer;
function writeWrap(text: string) {
let textTrimRight = text.replace(/\s+$/, '');
if (writer.getColumn() + textTrimRight.length > maxWidth) {
writer.writeLine();
writer.increaseIndent();
baseWrite(text.replace(/^\s+/, ''));
writer.decreaseIndent();
}
else {
baseWrite(text);
}
}
}
function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags) {
if (symbols && meaning && hasProperty(symbols, name)) {
let symbol = symbols[name];
if (symbol.flags & meaning) {
return symbol;
}
}
return undefined;
}
function resolveName(location: Node, name: string, meaning: SymbolFlags) {
let symbols = checker.getSymbolsInScope(location, meaning);
for (let symbol of symbols) {
if (symbol.name === name) {
return symbol;
}
}
return undefined;
}