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; visitorFunction?: string; isFactoryParam?: boolean; isNode?: boolean; isNodeArray?: boolean; isModifiersArray?: boolean; startsNewLexicalEnvironment?: boolean; } interface EnumValue { symbol: Symbol; value: T; } const enum FactoryHiddenState { None, Hidden, Visible } interface KindOptions { create: boolean; update: boolean; test: boolean; } const columnWrap = 150; const emptyArray: any[] = []; const annotationPattern = /@(\w+\s*[^\r\n]*)/g; let options: CompilerOptions; let host: CompilerHost; let program: Program; let checker: TypeChecker; let typesSourceFile: SourceFile; let factorySourceFile: SourceFile; let transformSourceFile: SourceFile; let utilitiesSourceFile: SourceFile; let tsModuleSymbol: Symbol; let nodeSymbol: Symbol; let nodeArraySymbol: Symbol; let modifiersArraySymbol: Symbol; let syntaxKindSymbol: Symbol; let syntaxNodes: SyntaxNode[] = []; let annotationConstructors: Map = {}; /** * A one-to-many map of one Symbol to the many Annotations that are attached to its declarations. */ let symbolToAnnotations: Annotation[][] = []; /** * A one-to-many map of one node type Symbol to the many SyntaxKind Symbols it directly describes. */ let typeToSyntaxKinds: Symbol[][] = []; /** * A one-to-one map of one SyntaxKind Symbol to the one node type Symbol that directly describes it. */ let syntaxKindToType: Symbol[] = []; let syntaxKindToSyntaxNode: SyntaxNode[] = []; let nodeArrayTypeUsages: Map = {}; let superTypeSymbolsForTypeSymbol: Symbol[][] = []; let ancestorTypeSymbolsForTypeSymbol: Symbol[][] = []; let descendantTypeSymbolsForTypeSymbol: Symbol[][] = []; let descendantSyntaxKindSymbolsForTypeSymbol: Symbol[][] = []; let typeSymbolsById: Symbol[] = []; let syntaxKindTypeUsages: Map = {}; let memberTypeUsages: Map = {}; let memberTypeUsageRedirects: Map = {}; function main() { if (sys.args.length < 3) { sys.write("Usage:" + sys.newLine) sys.write("\tnode processTypes.js " + sys.newLine); return; } options = getCompilerOptions(); host = createCompilerHost(options); let typesTsFileName = host.getCanonicalFileName(sys.resolvePath(sys.args[0])); let factoryTsFileName = host.getCanonicalFileName(sys.resolvePath(sys.args[1])); let transformTsFileName = host.getCanonicalFileName(sys.resolvePath(sys.args[2])); let utilitiesTsFileName = host.getCanonicalFileName(sys.resolvePath(sys.args[3])); program = createProgram([typesTsFileName, factoryTsFileName, transformTsFileName, utilitiesTsFileName], options, host); checker = program.getTypeChecker(); typesSourceFile = program.getSourceFile(typesTsFileName); factorySourceFile = program.getSourceFile(factoryTsFileName); transformSourceFile = program.getSourceFile(transformTsFileName); utilitiesSourceFile = program.getSourceFile(utilitiesTsFileName); // Resolve common symbols tsModuleSymbol = resolveQualifiedName(typesSourceFile, "ts", SymbolFlags.Module); nodeSymbol = resolveQualifiedName(typesSourceFile, "ts.Node", SymbolFlags.Type); nodeArraySymbol = resolveQualifiedName(typesSourceFile, "ts.NodeArray", SymbolFlags.Type); modifiersArraySymbol = resolveQualifiedName(typesSourceFile, "ts.ModifiersArray", SymbolFlags.Type); syntaxKindSymbol = resolveQualifiedName(typesSourceFile, "ts.SyntaxKind", SymbolFlags.Enum); discover(); let factoryDirectory = sys.resolvePath(combinePaths(factoryTsFileName, "..")); let factoryOutputFile = combinePaths(factoryDirectory, "factory.generated.ts"); generateFactory(factoryOutputFile); let transformDirectory = sys.resolvePath(combinePaths(transformTsFileName, "..")); let transformOutputFile = combinePaths(transformDirectory, "transform.generated.ts"); generateTransform(transformOutputFile); } /** * Discovers type information and symbols for various SyntaxNodes */ function discover() { // Discover each interface and type alias let typeSymbols = getSymbols(tsModuleSymbol.exports, SymbolFlags.Interface | SymbolFlags.TypeAlias); for (let typeSymbol of typeSymbols) { let kindAnnotations = findAllAnnotations(typeSymbol, KindAnnotation.match); if (kindAnnotations.length) { discoverSyntaxNodes(typeSymbol, kindAnnotations); } } // Sort the syntax nodes by SyntaxKind syntaxNodes.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 discoverTypeHierarchy(typeSymbol: Symbol) { discoverTypeHierarchyWorker(typeSymbol, /*ancestorOrSelfTypeSymbol*/ typeSymbol); } function discoverTypeHierarchyWorker(typeSymbol: Symbol, ancestorOrSelfTypeSymbol: Symbol) { let superTypeSymbols = getSuperTypeSymbols(ancestorOrSelfTypeSymbol); for (let ancestorTypeSymbol of superTypeSymbols) { let descendantTypeSymbols = descendantTypeSymbolsForTypeSymbol[getSymbolId(ancestorTypeSymbol)]; if (!descendantTypeSymbols) { descendantTypeSymbols = descendantTypeSymbolsForTypeSymbol[getSymbolId(ancestorTypeSymbol)] = []; discoverTypeHierarchyWorker(ancestorTypeSymbol, /*ancestorOrSelfTypeSymbol*/ ancestorTypeSymbol); } if (descendantTypeSymbols.indexOf(typeSymbol) === -1) { descendantTypeSymbols.push(typeSymbol); } } } /** * Discovers syntax nodes associated with a type and one or more syntax kinds. */ function discoverSyntaxNodes(typeSymbol: Symbol, kindAnnotations: KindAnnotation[]) { // Skip this node if it is marked with @factoryhidden(true) if (getFactoryHiddenStateForSymbol(typeSymbol) === FactoryHiddenState.Hidden) { return; } getSuperTypeSymbols(typeSymbol, /*flattenHierarchy*/ true); let symbolOrder: string[]; for (let kindAnnotation of kindAnnotations) { let kind = kindAnnotation.kind; let kindSymbol = kindAnnotation.kindSymbol; let type = checker.getDeclaredTypeOfSymbol(typeSymbol); recordTypeToSyntaxKindRelationships(typeSymbol, kindSymbol); // Discover the members of the node type var members: SyntaxMember[] = []; for (let property of checker.getPropertiesOfType(type)) { // Skip any hidden properties if (getFactoryHiddenStateForProperty(typeSymbol, property.name) === FactoryHiddenState.Hidden) { continue; } let visitorAnnotation = findFirstAnnotation(property, MemberVisitorAnnotation.match); let visitorFunction = visitorAnnotation ? visitorAnnotation.functionName : undefined; // Collect information about the type let typeNode = getTypeNodeForProperty(property); let propertyIsFactoryParam = isFactoryParamProperty(typeSymbol, 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 ? normalizeTypeName((typeNode).typeArguments[0].getText()) : propertyIsModifiersArray ? "Modifier" : undefined; let propertyStartsNewLexicalEnvironment = propertyIsNode && startsNewLexicalEnvironment(property); members.push({ propertyName: property.name, paramName: property.name === "arguments" ? "_arguments" : property.name, typeName: typeName, elementTypeName: elementTypeName, visitorFunction: visitorFunction, isFactoryParam: propertyIsFactoryParam, isNodeArray: propertyIsNodeArray, isModifiersArray: propertyIsModifiersArray, isNode: propertyIsNode, startsNewLexicalEnvironment: propertyStartsNewLexicalEnvironment }); if (!propertyIsFactoryParam) { let typeNames = splitTypeName(propertyIsNode ? typeName : elementTypeName); for (let typeName of typeNames) { let typeSymbol = getSymbol(tsModuleSymbol.exports, typeName, SymbolFlags.Type); if (typeSymbol) { getSuperTypeSymbols(typeSymbol, /*flattenHierarchy*/ true); } } recordTypeUsageForMember(propertyIsNode ? typeName : elementTypeName); } if (elementTypeName) { nodeArrayTypeUsages[elementTypeName] = true; } } } // Sort the members of the type in the correct order for create and update methods let kindOrder = getFactoryOrder(kindSymbol, /*inherited*/ false); var overrides = kindOrder || symbolOrder || (symbolOrder = getFactoryOrder(typeSymbol, /*inherited*/ true)); 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]); } // Add the syntax node let syntaxNode: SyntaxNode = { kind, kindName: kindSymbol.name, typeName: typeSymbol.name, members, options: kindAnnotation.options }; syntaxNodes.push(syntaxNode); syntaxKindToSyntaxNode[getSymbolId(kindSymbol)] = syntaxNode; } } /** * Records the various relationships between a node type Symbol and a SyntaxKind symbol. */ function recordTypeToSyntaxKindRelationships(typeSymbol: Symbol, kindSymbol: Symbol): void { // Add the SyntaxKind Symbol to the set of SyntaxKind Symbols for the node type Symbol. // NOTE: This could also be retrieved by reading its @kind annotations. let syntaxKindsForType = getSyntaxKindSymbolsForTypeSymbol(typeSymbol, /*ensureExists*/ true); if (syntaxKindsForType.indexOf(kindSymbol) === -1) { syntaxKindsForType.push(kindSymbol); } // Set the node type Symbol for this SyntaxKind Symbol syntaxKindToType[getSymbolId(kindSymbol)] = typeSymbol; // Record the supertypes associated with this SyntaxKind Symbol recordTypeUsagesForKind(kindSymbol, typeSymbol); } 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 getSuperTypeSymbols(typeSymbol)) { 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 (property.declarations[0]).type; } } function generateFactory(outputFile: string) { let writer = createLineWrappingTextWriter(host.getNewLine(), columnWrap); writer.write(`// `); writer.writeLine(); writer.write(`/// `); 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 syntaxNodes) { writeCreateFunction(syntaxNode); writeUpdateFunction(syntaxNode); } } function writeIsNodeFunctions() { for (let syntaxNode of syntaxNodes) { 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; } // Skip the create function for this node if it is already defined in factory.ts if (resolveQualifiedName(factorySourceFile, `ts.factory.create${syntaxNode.kindName}`, SymbolFlags.Function)) { return; } writer.write(`export function create${syntaxNode.kindName}(`); for (let member of syntaxNode.members) { let type = member.isNodeArray ? `Array<${member.elementTypeName}>` : member.isModifiersArray ? `Array` : member.typeName; writer.write(`${member.paramName}?: ${type}, `); } writer.write(`location?: TextRange, flags?: NodeFlags): ${syntaxNode.typeName} {`); writer.writeLine(); writer.increaseIndent(); if (syntaxNode.members.length) { writer.write(`let node = createNode<${syntaxNode.typeName}>(SyntaxKind.${syntaxNode.kindName}, location, flags);`); writer.writeLine(); if (syntaxNode.members.length > 1) { writer.write(`if (`); let first = true; for (let member of syntaxNode.members) { if (first) { first = false; } else { writer.write(` || `); } writer.write(member.paramName); } writer.write(`) {`); 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}, location, flags);`); writer.writeLine(); } writer.decreaseIndent(); writer.write(`}`); writer.writeLine(); } function writeIsNodeFunction(syntaxNode: SyntaxNode) { if (syntaxNode.typeName === "Node") { return; } if (!syntaxNode.options.test) { return; } // Skip the is function for this node if it is already defined if (resolveQualifiedName(factorySourceFile, `ts.is${syntaxNode.kindName}`, SymbolFlags.Function)) { 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 writeIsAnyNodeFunction(typeName: string) { if (typeName === "Node") { return; } // Skip the is function for this type if it is already defined in factory.ts if (resolveQualifiedName(factorySourceFile, `ts.is${typeNameToMethodNameSuffix(typeName)}`, SymbolFlags.Function)) { return; } // Skip the is function for this type if has the same name as a SyntaxKind if (getSymbol(syntaxKindSymbol.exports, typeName, SymbolFlags.EnumMember)) { return; } writer.write(`export function is${typeNameToMethodNameSuffix(typeName)}(node: Node): node is ${typeName} {`); writer.writeLine(); writer.increaseIndent(); writer.write(`if (node) {`); writer.writeLine(); writer.increaseIndent(); writer.write(`switch (node.kind) {`); writer.writeLine(); writer.increaseIndent(); let kinds = getDescendantSyntaxKindSymbolsForTypeName(typeName); 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(node: TNode, location?: TextRange, flags?: NodeFlags): TNode;`); writer.writeLine(); writer.write(`export function cloneNode(node: Node, location?: TextRange, flags: NodeFlags = node.flags): 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 syntaxNodes) { if (!syntaxNode.options.create) { continue; } writer.write(`case SyntaxKind.${syntaxNode.kindName}:`); writer.writeLine(); writer.increaseIndent(); writer.write(`return factory.create${syntaxNode.kindName}(`); for (let member of syntaxNode.members) { writer.write(`(<${syntaxNode.typeName}>node).${member.propertyName}, `); } writer.write(`location, flags);`); 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; } // Skip the update function for this node if it is defined in factory.ts if (resolveQualifiedName(factorySourceFile, `ts.factory.update${syntaxNode.kindName}`, SymbolFlags.Function)) { 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` : 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 generateTransform(outputFile: string) { let writer = createTextWriter(host.getNewLine()); writer.write(`// `); writer.writeLine(); writer.write(`/// `); writer.writeLine(); writer.write(`/// `); writer.writeLine(); writer.write(`/* @internal */`); writer.writeLine(); writer.write(`namespace ts.transform {`); writer.writeLine(); writer.increaseIndent(); writeAcceptFunction(); writer.decreaseIndent(); writer.write(`}`); writer.writeLine(); sys.writeFile(outputFile, writer.getText()); function writeAcceptFunction() { writer.write(`export function accept(node: Node, visitor: (input: Node, write: (node: Node) => void) => void, write: (node: Node) => void): void {`); writer.writeLine(); writer.increaseIndent(); writer.write(`if (!node) {`); writer.writeLine(); writer.increaseIndent(); writer.write(`return;`); writer.writeLine(); writer.decreaseIndent(); writer.write(`}`); writer.writeLine(); writer.write(`switch (node.kind) {`); writer.writeLine(); writer.increaseIndent(); for (let syntaxNode of syntaxNodes) { if (!hasChildNodes(syntaxNode)) { continue; } writer.write(`case SyntaxKind.${syntaxNode.kindName}:`); writer.writeLine(); writer.increaseIndent(); writer.write(`return write(factory.update${syntaxNode.kindName}(`); writer.writeLine(); writer.increaseIndent(); writer.write(`<${syntaxNode.typeName}>node`); for (let member of syntaxNode.members) { if (member.isFactoryParam) { continue; } writer.write(`, `); writer.writeLine(); if (member.typeName === "Node") { writer.write(`(<${syntaxNode.typeName}>node).${member.propertyName}`); } else { let visitorFunction = member.visitorFunction ? member.visitorFunction : member.isNodeArray || member.isModifiersArray ? `>visitNodes` : member.startsNewLexicalEnvironment ? `<${member.typeName}>visitNewLexicalEnvironment` : `<${member.typeName}>visitNode`; writer.write(`${visitorFunction}((<${syntaxNode.typeName}>node).${member.propertyName}, visitor)`); } } writer.write(`));`); writer.writeLine(); writer.decreaseIndent(); writer.decreaseIndent(); } writer.write(`default:`); writer.writeLine(); writer.increaseIndent(); writer.write(`return write(node);`); writer.writeLine(); writer.decreaseIndent(); writer.decreaseIndent(); writer.write(`}`); writer.writeLine(); writer.decreaseIndent(); writer.write('}'); writer.writeLine(); } } // // Utilities // function splitTypeName(typeName: string) { return typeName.split(/\s*\|\s*/g); } function typeNameToMethodNameSuffix(typeName: string) { let typeNames = splitTypeName(typeName); typeNames = typeNames.map(typeNameToSyntaxKindOrTypeName); return typeNames.join("Or"); } function typeNameToSyntaxKindOrTypeName(typeName: string) { let typeSymbol = getSymbol(tsModuleSymbol.exports, typeName, SymbolFlags.Type); let syntaxKindSymbols = getDescendantSyntaxKindSymbolsForTypeSymbol(typeSymbol); if (syntaxKindSymbols && syntaxKindSymbols.length === 1) { return syntaxKindSymbols[0].name; } return typeName; } 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 getSuperTypeSymbols(typeSymbol)) { fillKindsForType(superType, kinds); } } } /** * Resolves a symbol with the specified name and meaning starting at the provided location. * @param location The node at which to resolve the name. * @param name The name of a symbol to resolve. * @param meaning The meaning of the symbol. */ function resolveName(location: Node, name: string, meaning: SymbolFlags): Symbol { let symbols = checker.getSymbolsInScope(location, meaning); for (let symbol of symbols) { if (symbol.name === name) { return symbol; } } return undefined; } /** * Gets a symbol with the specified name and meaning from the provided symbol table. * @param symbols The symbol table from which to retrieve a named symbol. * @param name The name of the symbol to retrieve. * @param meaning The meaning of the symbol. */ function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol { if (symbols && meaning && hasProperty(symbols, name)) { let symbol = symbols[name]; if (symbol.flags & meaning) { return symbol; } } return undefined; } /** * Gets al symbols with the specified meaning from the provided symbol table. * @param symbols The symbol table from which to retrieve symbols. * @param meaning The meaning of each symbol. */ function getSymbols(symbols: SymbolTable, meaning: SymbolFlags): Symbol[] { let results: Symbol[]; if (symbols && meaning) { for (let name in symbols) { let symbol = getProperty(symbols, name); if (symbol && symbol.flags & meaning) { if (!results) { results = []; } results.push(symbol); } } } return results || emptyArray; } /** * Resolves a symbol with the specified fully qualified name and meaning starting at the provided location. * @param location The node at which to resolve the name. * @param name A dot-seperated name for the symbol to retrieve. * @param meaning The meaning of the symbol. */ function resolveQualifiedName(location: Node, name: string, meaning: SymbolFlags): Symbol { return resolveQualifiedNameParts(location, name.split("."), meaning); } function resolveExportedQualifiedName(symbol: Symbol, name: string, meaning: SymbolFlags): Symbol { return resolveExportedQualifiedNameParts(symbol, name.split("."), meaning); } function resolveExportedQualifiedNameParts(symbol: Symbol, qn: string[], meaning: SymbolFlags): Symbol { if (qn.length === 1) { return getSymbol(symbol.exports, qn[0], meaning); } let namespace = getSymbol(symbol.exports, qn[0], SymbolFlags.Namespace); if (namespace === undefined) { return undefined; } for (let i = 1; i < qn.length - 1; i++) { namespace = getSymbol(namespace.exports, qn[i], SymbolFlags.Namespace); if (namespace === undefined) { return undefined; } } return getSymbol(namespace.exports, qn[qn.length - 1], meaning); } /** * Resolves a symbol with the specified fully qualified name and meaning starting at the provided location. * @param location The node at which to resolve the name. * @param qn An array of qualified name segments. * @param meaning The meaning of the symbol. */ function resolveQualifiedNameParts(location: Node, qn: string[], meaning: SymbolFlags): Symbol { if (qn.length === 1) { return resolveName(location, qn[0], meaning); } let namespace = resolveName(location, qn[0], SymbolFlags.Namespace); if (namespace === undefined) { return undefined; } for (let i = 1; i < qn.length - 1; i++) { namespace = getSymbol(namespace.exports, qn[i], SymbolFlags.Namespace); if (namespace === undefined) { return undefined; } } return getSymbol(namespace.exports, qn[qn.length - 1], meaning); } /** * Gets the value of the specified enum. * @param location The node at which to resolve the name. * @param name The name of the enum member. */ function getEnumLiteralValue(location: Node, name: string): EnumValue { let qn = name.split("."); if (qn.length === 1) { return undefined; } let container = resolveQualifiedNameParts(location, qn.slice(0, qn.length - 1), SymbolFlags.Enum); if (!container) { return undefined; } let symbol = getSymbol(container.exports, qn[qn.length - 1], SymbolFlags.EnumMember); if (!symbol) { return undefined; } let value = checker.getConstantValue(symbol.declarations[0]); return { symbol, value }; } function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string { return symbol ? checker.symbolToString(symbol, enclosingDeclaration, meaning) : undefined; } 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 isPropertyAssignment(node: Node): node is PropertyAssignment { return node && node.kind === SyntaxKind.PropertyAssignment; } function isIdentifier(node: Node): node is Identifier { return node && node.kind === SyntaxKind.Identifier; } 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 getSuperTypeSymbols(typeSymbol: Symbol, flattenHierarchy?: boolean) { if (flattenHierarchy) { let superTypeSymbolsFlat = ancestorTypeSymbolsForTypeSymbol[getSymbolId(typeSymbol)]; if (superTypeSymbolsFlat) { return superTypeSymbolsFlat; } superTypeSymbolsFlat = []; let superTypeSymbols = getSuperTypeSymbols(typeSymbol, /*flattenHierarchy*/ false).slice(0); for (let superTypeSymbol of superTypeSymbols) { if (superTypeSymbolsFlat.indexOf(superTypeSymbol) !== -1) { continue; } superTypeSymbolsFlat.push(superTypeSymbol); if (superTypeSymbol === nodeSymbol) { continue; } let ancestorTypeSymbols = getSuperTypeSymbols(superTypeSymbol, /*flattenHierarch*/ true); for (let ancestorTypeSymbol of ancestorTypeSymbols) { if (superTypeSymbolsFlat.indexOf(ancestorTypeSymbol) === -1) { superTypeSymbolsFlat.push(ancestorTypeSymbol); } } } return ancestorTypeSymbolsForTypeSymbol[getSymbolId(typeSymbol)] = superTypeSymbolsFlat; } else { let superTypeSymbols = superTypeSymbolsForTypeSymbol[getSymbolId(typeSymbol)]; if (superTypeSymbols) { return superTypeSymbols; } superTypeSymbols = []; typeSymbolsById[getSymbolId(typeSymbol)] = typeSymbol; let decl = typeSymbol.declarations[0]; if (isTypeAliasDeclaration(decl)) { fillSuperTypes(decl.type, superTypeSymbols); } else if (isInterfaceDeclaration(decl) && decl.heritageClauses) { for (let superType of decl.heritageClauses[0].types) { fillSuperTypes(superType, superTypeSymbols); } } return superTypeSymbolsForTypeSymbol[getSymbolId(typeSymbol)] = superTypeSymbols; } } function fillSuperTypes(node: TypeNode, superTypeSymbols: Symbol[]) { if (isUnionTypeNode(node)) { // Flatten union types for (let constituentType of node.types) { fillSuperTypes(constituentType, superTypeSymbols); } } else { // Add type references let symbol = isTypeReferenceNode(node) ? checker.getSymbolAtLocation(node.typeName) : isExpressionWithTypeArguments(node) ? checker.getSymbolAtLocation(node.expression) : undefined; if (symbol && superTypeSymbols.indexOf(symbol) === -1) { superTypeSymbols.push(symbol); } } } function getDescendantTypeSymbols(typeSymbol: Symbol) { let descendantTypeSymbols = descendantTypeSymbolsForTypeSymbol[getSymbolId(typeSymbol)]; if (descendantTypeSymbols) { return descendantTypeSymbols; } descendantTypeSymbols = []; for (let symbolId in ancestorTypeSymbolsForTypeSymbol) { let ancestorTypeSymbols = ancestorTypeSymbolsForTypeSymbol[symbolId]; if (!Array.isArray(ancestorTypeSymbols) || ancestorTypeSymbols.indexOf(typeSymbol) === -1) { continue; } let ancestorTypeSymbol = typeSymbolsById[symbolId]; if (descendantTypeSymbols.indexOf(ancestorTypeSymbol) === -1) { descendantTypeSymbols.push(ancestorTypeSymbol); } } return descendantTypeSymbolsForTypeSymbol[getSymbolId(typeSymbol)] = descendantTypeSymbols; } /** * Get the SyntaxKind Symbols associated with a node type Symbol. */ function getSyntaxKindSymbolsForTypeSymbol(typeSymbol: Symbol, ensureExists?: boolean): Symbol[] { let syntaxKindsForType = typeToSyntaxKinds[getSymbolId(typeSymbol)]; if (!syntaxKindsForType && ensureExists) { syntaxKindsForType = []; typeToSyntaxKinds[getSymbolId(typeSymbol)] = syntaxKindsForType; } return syntaxKindsForType; } function getDescendantSyntaxKindSymbolsForTypeName(typeName: string): Symbol[] { let symbols: Symbol[] = []; let typeNames = splitTypeName(typeName); for (let typeName of typeNames) { let typeSymbol = resolveExportedQualifiedName(tsModuleSymbol, typeName, SymbolFlags.Type); if (typeSymbol) { let kindSymbols = getDescendantSyntaxKindSymbolsForTypeSymbol(typeSymbol); for (let kindSymbol of kindSymbols) { if (symbols.indexOf(kindSymbol) === -1) { symbols.push(kindSymbol); } } } } return symbols; } function getDescendantSyntaxKindSymbolsForTypeSymbol(typeSymbol: Symbol): Symbol[] { let descendantSyntaxKindSymbols = descendantSyntaxKindSymbolsForTypeSymbol[getSymbolId(typeSymbol)]; if (descendantSyntaxKindSymbols) { return descendantSyntaxKindSymbols; } descendantSyntaxKindSymbols = []; for (let descendantOrSelfTypeSymbol of [typeSymbol, ...getDescendantTypeSymbols(typeSymbol)]) { let kindSymbols = getSyntaxKindSymbolsForTypeSymbol(descendantOrSelfTypeSymbol) || emptyArray; for (let kindSymbol of kindSymbols) { if (descendantSyntaxKindSymbols.indexOf(kindSymbol) === -1) { descendantSyntaxKindSymbols.push(kindSymbol); } } } let decl = typeSymbol.declarations[0]; if (isTypeAliasDeclaration(decl)) { let superTypes = getSuperTypeSymbols(typeSymbol); for (let typeSymbol of superTypes) { let kindSymbols = getDescendantSyntaxKindSymbolsForTypeSymbol(typeSymbol); for (let kindSymbol of kindSymbols) { if (descendantSyntaxKindSymbols.indexOf(kindSymbol) === -1) { descendantSyntaxKindSymbols.push(kindSymbol); } } } } return descendantSyntaxKindSymbolsForTypeSymbol[getSymbolId(typeSymbol)] = descendantSyntaxKindSymbols; } 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); } } } // // Annotations // function findFirstAnnotation(symbol: Symbol, match: (annotation: Annotation) => boolean): TAnnotation { for (let annotation of getAnnotations(symbol)) { if (match(annotation)) { return annotation; } } return undefined; } function findAllAnnotations(symbol: Symbol, match: (annotation: Annotation) => boolean): TAnnotation[] { let annotations: TAnnotation[]; for (let annotation of getAnnotations(symbol)) { if (match(annotation)) { if (!annotations) { annotations = []; } annotations.push(annotation); } } return annotations || emptyArray; } function getAnnotations(symbol: Symbol): Annotation[] { let annotations = symbolToAnnotations[getSymbolId(symbol)]; if (annotations) { return annotations; } annotations = []; for (let decl of symbol.declarations) { let leadingCommentRanges = getLeadingCommentRanges(typesSourceFile.text, decl.pos); if (leadingCommentRanges) { for (let range of leadingCommentRanges) { parseAnnotations(decl, range, annotations); } } } return symbolToAnnotations[getSymbolId(symbol)] = annotations; } function getLiteralValue(location: Node, 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 (expr).text; case SyntaxKind.NoSubstitutionTemplateLiteral: return (expr).text; case SyntaxKind.NumericLiteral: return Number((expr).text); case SyntaxKind.PropertyAccessExpression: return getEnumLiteralValue(location, expr.getText()); case SyntaxKind.ArrayLiteralExpression: return getArrayLiteralValue(location, expr); case SyntaxKind.ObjectLiteralExpression: return getObjectLiteralValue(location, expr); } } function getArrayLiteralValue(location: Node, expr: ArrayLiteralExpression) { let values: any[] = []; for (let element of expr.elements) { let value = getLiteralValue(location, element); values.push(value); } return values; } function getObjectLiteralValue(location: Node, expr: ObjectLiteralExpression) { let obj: Map = {}; for (let element of expr.properties) { if (isPropertyAssignment(element)) { let name = element.name; if (isIdentifier(name)) { obj[name.text] = getLiteralValue(location, element.initializer); } } } return obj; } function parseAnnotations(location: Node, range: CommentRange, annotations: Annotation[]) { let text = typesSourceFile.text; let comment = text.substring(range.pos, range.end); let annotationMatch: RegExpExecArray; while (annotationMatch = annotationPattern.exec(comment)) { let annotation = parseAnnotation(location, annotationMatch[1]); if (annotation) { annotations.push(annotation); } } } function parseAnnotation(location: Node, 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 = (stmt).expression; if (expr.kind === SyntaxKind.Identifier) { return createAnnotation((expr).text, emptyArray); } else if (expr.kind === SyntaxKind.CallExpression) { let call = expr; if (call.expression.kind !== SyntaxKind.Identifier) { return undefined; } let _arguments: any[] = []; for (let argument of call.arguments) { _arguments.push(getLiteralValue(location, argument)); } return createAnnotation((call.expression).text, _arguments); } else { return undefined; } } function annotation(name: string) { return function(target: T) { annotationConstructors[name] = target; return target; } } class Annotation { public name: string; public arguments: any[]; constructor(_arguments: any[]) { this.arguments = _arguments; } public static match(annotation: Annotation): boolean { return annotation instanceof this; } } @annotation("kind") class KindAnnotation extends Annotation { public name = "kind"; public kind: SyntaxKind; public kindSymbol: Symbol; public options: KindOptions; constructor([{ value, symbol }, { create = true, update = true, test = true } = {}, ..._arguments]: [EnumValue, KindOptions, any]) { super(_arguments); this.kind = value; this.kindSymbol = symbol; this.options = { create, update, test }; } public static match(annotation: Annotation): annotation is KindAnnotation { return annotation instanceof KindAnnotation; } } @annotation("factoryhidden") class FactoryHiddenAnnotation extends Annotation { public name = "factoryhidden"; public propertyName: string; public hidden: boolean; constructor([ propertyName, hidden, ..._arguments]: [string | boolean, boolean, any]) { super(_arguments); if (typeof propertyName === "boolean") { this.hidden = propertyName; } else if (typeof propertyName === "string") { this.propertyName = propertyName; if (typeof hidden === "boolean") { this.hidden = hidden; } else { this.hidden = true; } } else { this.hidden = true; } } public static match(annotation: Annotation): annotation is FactoryHiddenAnnotation { return annotation instanceof FactoryHiddenAnnotation; } } @annotation("factoryorder") class FactoryOrderAnnotation extends Annotation { public name = "factoryorder"; public propertyNames: string[]; constructor(propertyNames: string[]) { super([]); this.propertyNames = propertyNames; } public static match(annotation: Annotation): annotation is FactoryOrderAnnotation { return annotation instanceof FactoryOrderAnnotation; } } @annotation("factoryparam") class FactoryParamAnnotation extends Annotation { public name = "factoryparam"; public propertyName: string; constructor([propertyName, ..._arguments]: [string, any]) { super(_arguments); this.propertyName = propertyName; } public static match(annotation: Annotation): annotation is FactoryParamAnnotation { return annotation instanceof FactoryParamAnnotation; } } @annotation("visitor") class MemberVisitorAnnotation extends Annotation { public name = "visitor"; public functionName: string; constructor([functionName, ..._arguments]: [string, any]) { super(_arguments); this.functionName = functionName; } public static match(annotation: Annotation): annotation is MemberVisitorAnnotation { return annotation instanceof MemberVisitorAnnotation; } } @annotation("newlexicalenvironment") class NewLexicalEnvironmentAnnotation extends Annotation { public name = "newlexicalenvironment"; public static match(annotation: Annotation): annotation is NewLexicalEnvironmentAnnotation { return annotation instanceof NewLexicalEnvironmentAnnotation; } } function createAnnotation(name: string, _arguments: any[]): Annotation { let ctor = getProperty(annotationConstructors, name); if (ctor) { return new ctor(_arguments); } else { let annotation = new Annotation(_arguments); annotation.name = name; return annotation; } } function getFactoryHiddenStateForSymbol(symbol: Symbol): FactoryHiddenState { let annotation: FactoryHiddenAnnotation; if (annotation = findFirstAnnotation(symbol, annotation => FactoryHiddenAnnotation.match(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 = findFirstAnnotation(source, annotation => FactoryHiddenAnnotation.match(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 getSuperTypeSymbols(source)) { getFactoryHiddenStateForPropertyWorker(superType, depth + 1); } } } function isFactoryParamProperty(container: Symbol, propertyName: string): boolean { if (findFirstAnnotation(container, annotation => FactoryParamAnnotation.match(annotation) && annotation.propertyName === propertyName)) { return true; } let property = container.members ? getProperty(container.members, propertyName) : undefined; if (property) { if (findFirstAnnotation(property, annotation => FactoryParamAnnotation.match(annotation) && annotation.propertyName === undefined)) { return true; } } else { for (let superType of getSuperTypeSymbols(container)) { if (isFactoryParamProperty(superType, propertyName)) { return true; } } } return false; } function getFactoryOrder(symbol: Symbol, inherited?: boolean): string[] { let annotation = findFirstAnnotation(symbol, FactoryOrderAnnotation.match); if (annotation) { return annotation.propertyNames; } let propertyNames: string[]; if (inherited) { for (let superType of getSuperTypeSymbols(symbol)) { let superTypeOrder = getFactoryOrder(superType, /*inherited*/ true); if (superTypeOrder && superTypeOrder.length > 0) { if (!propertyNames) { propertyNames = []; } for (let propertyName of superTypeOrder) { if (propertyNames.indexOf(propertyName) === -1) { propertyNames.push(propertyName); } } } } } return propertyNames; } function startsNewLexicalEnvironment(symbol: Symbol): boolean { return !!findFirstAnnotation(symbol, NewLexicalEnvironmentAnnotation.match); } // Main entry point main();