import { SyntaxKind, Symbol, SymbolFlags, Map, } from "./typescript-internal"; import { hasProperty, getProperty } from "./utilities"; import { getType, getTypes, makeArrayType, resolveQualifiedName, annotation, EnumValue, Annotation, AnnotationTargets, TypeInfo, PropertyInfo } from "./types"; export interface DiscoveryResult { createableNodes: SyntaxNode[]; updateableNodes: SyntaxNode[]; testableNodes: SyntaxNode[]; testableTypes: SyntaxType[]; } export interface SyntaxType { type: TypeInfo; typeName: string; syntaxNodes?: SyntaxNode[]; testFunctionName?: string; } export interface SyntaxNode { kind: SyntaxKind; kindName: string; typeName: string; createFunctionName: string; createParameters: SyntaxMember[]; updateFunctionName: string; updateParameters: SyntaxMember[]; testFunctionName: string; } export interface SyntaxMember { propertyName?: string; parameterName?: string; parameterTypeName: string; isFactoryParameter?: boolean; isNodeArray?: boolean; isModifiersArray?: boolean; testFunctionName?: string; visitorFunctionName?: string; } /** * Discovers type information and symbols for various SyntaxNodes */ export function discover(): DiscoveryResult { const typesReferencedByProperties: boolean[] = []; const syntaxTypeForTypeInfo: SyntaxType[] = []; const syntaxNodesForType: SyntaxNode[][] = []; const syntaxNodesForTypeFlat: SyntaxNode[][] = []; const createableNodes: SyntaxNode[] = []; const updateableNodes: SyntaxNode[] = []; const testableNodes: SyntaxNode[] = []; const syntaxTypes: SyntaxType[] = []; const testableTypes: SyntaxType[] = []; const localNames: Map = {}; let types = getTypes("ts"); let nodeType = getType("ts.Node"); let modifierType = getType("ts.Modifier"); let statementType = getType("ts.Statement"); let conciseBodyType = getType("ts.ConciseBody"); let functionBodyType = getType("ts.FunctionBody"); let moduleBodyType = getType("ts.ModuleBody"); let sourceFileType = getType("ts.SourceFile"); let nodeArrayType = getType("ts.NodeArray"); let modifiersArrayType = getType("ts.ModifiersArray"); for (let type of types) { discoverType(type); } for (let type of syntaxTypes) { discoverTestableType(type); } createableNodes.sort((a, b) => a.kind - b.kind); updateableNodes.sort((a, b) => a.kind - b.kind); testableNodes.sort((a, b) => a.kind - b.kind); testableTypes.sort((a, b) => a.toString().localeCompare(b.toString())); return { createableNodes, updateableNodes, testableNodes, testableTypes }; function discoverType(type: TypeInfo) { let syntaxType = syntaxTypeForTypeInfo[type.id]; if (!syntaxType) { if (nodeType.isAssignableFrom(type)) { let typeName = type.toString(); syntaxType = { type, typeName, }; syntaxTypes.push(syntaxType); syntaxTypeForTypeInfo[type.id] = syntaxType; for (let superType of type.getSuperTypes()) { discoverType(superType); } for (let kind of type.findAllAnnotations(/*inherited*/ false, KindAnnotation.match)) { discoverKind(type, kind); } } } return syntaxType; } function discoverKind(type: TypeInfo, kind: KindAnnotation) { let kindName = kind.kindSymbol.name; let typeName = type.toString(); let createFunctionName = discoverFunctionName(kindName, "create", kind.create); let createParameters: SyntaxMember[] = createFunctionName ? [] : undefined; let updateFunctionName = discoverFunctionName(kindName, "update", kind.update); let updateParameters: SyntaxMember[] = updateFunctionName ? [] : undefined; let testFunctionName = discoverFunctionName(kindName, "is", kind.test); if (type.name === "StringLiteral") { debugger; } for (let property of type.getProperties(/*inherited*/ true)) { if (FactoryHiddenAnnotation.getState(type, property.name) === FactoryHiddenState.Hidden) { continue; } let isFactoryParameter = property.findFirstAnnotation(/*inherited*/ true, FactoryParamAnnotation.match) !== undefined; let isNode = nodeType.isAssignableFrom(property.propertyType); let isModifiersArray = modifiersArrayType.isAssignableFrom(property.propertyType); let isNodeArray = !isModifiersArray && nodeArrayType.isAssignableFrom(property.propertyType); if (isFactoryParameter || isNode || isNodeArray || isModifiersArray) { let propertyName = property.name; let parameterName = property.name === "arguments" ? "_arguments" : property.name; let parameterType: TypeInfo; let nodeType: TypeInfo; if (isModifiersArray) { nodeType = modifierType; parameterType = makeArrayType(nodeType); } else if (isNodeArray) { nodeType = property.propertyType.getGenericTypeArguments()[0]; parameterType = makeArrayType(nodeType); } else if (isNode) { nodeType = property.propertyType; parameterType = nodeType; } else { parameterType = property.propertyType; } if (nodeType) { typesReferencedByProperties[nodeType.id] = true; discoverType(nodeType); } let parameterTypeName = parameterType.toString(); let testFunctionName: string; let visitorFunctionName: string; if (!isFactoryParameter) { switch (property.propertyType) { case nodeType: break; case statementType: visitorFunctionName = "transformer.visitStatement"; break; case conciseBodyType: visitorFunctionName = "transformer.visitConciseBody"; break; case functionBodyType: visitorFunctionName = "transformer.visitFunctionBody"; break; case moduleBodyType: visitorFunctionName = "transformer.visitModuleBody"; break; case sourceFileType: visitorFunctionName = "transformer.visitSourceFile"; break; default: if (isModifiersArray || isNodeArray) { visitorFunctionName = "transformer.visitNodes"; visitorFunctionName = "transformer.visitNodes"; } else if (isNode) { visitorFunctionName = "transformer.visitNode"; } if (isModifiersArray) { testFunctionName = "isModifier"; } else if (isNodeArray || isNode) { testFunctionName = getIsAnyNodeFunctionName(nodeType); } break; } } let syntaxMember = { propertyName, parameterName, parameterTypeName, isFactoryParameter, isNodeArray, isModifiersArray, testFunctionName, visitorFunctionName }; if (createFunctionName) { createParameters.push(syntaxMember); } if (updateFunctionName && !isFactoryParameter) { updateParameters.push(syntaxMember); } } } let factoryOrder = type.findFirstAnnotation(/*inherited*/ true, FactoryOrderAnnotation.match); if (factoryOrder) { if (createFunctionName) { createParameters = sortMembers(createParameters, factoryOrder.propertyNames); } if (updateFunctionName) { updateParameters = sortMembers(updateParameters, factoryOrder.propertyNames); } } let syntaxNode = { kind: kind.kind, kindName, typeName, createFunctionName, createParameters, updateFunctionName, updateParameters, testFunctionName }; if (createFunctionName) { createableNodes.push(syntaxNode); localNames[createFunctionName] = createFunctionName; } if (updateFunctionName && updateParameters.length > 0) { updateableNodes.push(syntaxNode); localNames[updateFunctionName] = updateFunctionName; } if (testFunctionName) { testableNodes.push(syntaxNode); localNames[testFunctionName] = testFunctionName; } let typeNodes = syntaxNodesForType[type.id] || (syntaxNodesForType[type.id] = []); typeNodes.push(syntaxNode); return syntaxNode; } function sortMembers(members: SyntaxMember[], order: string[]) { let indices = members.map((_, i) => i); indices.sort((a, b) => { let aOverride = order.indexOf(members[a].propertyName); let bOverride = order.indexOf(members[b].propertyName); if (aOverride >= 0) { if (bOverride >= 0) { return aOverride - bOverride; } return -1; } else if (bOverride >= 0) { return +1; } return a - b; }); return indices.map(i => members[i]); } function discoverFunctionName(kindName: string, prefix: string, option: boolean | string) { if (typeof option === "string") { return option; } else if (option) { let functionName = prefix + kindName; if (!hasProperty(localNames, functionName)) { let symbol = resolveQualifiedName("ts." + functionName, SymbolFlags.Function); if (!symbol) { return functionName; } } } return undefined; } function getIsAnyNodeFunctionName(type: TypeInfo) { if (type !== nodeType) { let functionName: string; let nodeTest = type.findFirstAnnotation(/*inherited*/ false, NodeTestAnnotation.match); if (nodeTest) { return nodeTest.functionName; } else if (type.isTypeAlias || type.isInterface) { return "is" + getKindOrTypeNameForType(type); } else if (type.isUnionType) { return "is" + type.getConstituentTypes().map(getKindOrTypeNameForType).join("Or"); } } return undefined; } function discoverTestableType(syntaxType: SyntaxType) { if (syntaxType.type !== nodeType && !syntaxType.syntaxNodes) { syntaxType.syntaxNodes = discoverSyntaxNodes(syntaxType.type); if (syntaxType.syntaxNodes.length > 0) { let testFunctionName = discoverIsAnyNodeFunctionName(syntaxType.type); if (testFunctionName) { syntaxType.testFunctionName = testFunctionName; testableTypes.push(syntaxType); } } } } /* // EntityName is a union type of Identifier | QualifiedName. // We want anything that can be an identifier and anything that can be a qualified name. isEntityName(node: Node): node is EntityName { } */ function discoverSyntaxNodes(type: TypeInfo) { let syntaxNodesFlat = syntaxNodesForTypeFlat[type.id]; if (!syntaxNodesFlat) { syntaxNodesFlat = syntaxNodesForTypeFlat[type.id] = []; let seen: boolean[] = []; copySyntaxNodes(syntaxNodesForType[type.id], syntaxNodesFlat, seen); if (type.isTypeAlias) { let aliasedType = type.getAliasedType(); copySyntaxNodes(discoverSyntaxNodes(aliasedType), syntaxNodesFlat, seen); } else if (type.isInterface) { for (let subType of type.getSubTypes()) { copySyntaxNodes(discoverSyntaxNodes(subType), syntaxNodesFlat, seen); } } else if (type.isUnionType) { for (let constituentType of type.getConstituentTypes()) { copySyntaxNodes(discoverSyntaxNodes(constituentType), syntaxNodesFlat, seen); } } } return syntaxNodesFlat; } function copySyntaxNodes(source: SyntaxNode[], dest: SyntaxNode[], seen: boolean[]) { if (source) { for (let syntaxNode of source) { if (!seen[syntaxNode.kind]) { seen[syntaxNode.kind] = true; dest.push(syntaxNode); } } } } function discoverIsAnyNodeFunctionName(type: TypeInfo) { if (typesReferencedByProperties[type.id]) { let functionName = getIsAnyNodeFunctionName(type); if (functionName && !hasProperty(localNames, functionName)) { let symbol = resolveQualifiedName("ts." + functionName, SymbolFlags.Function); if (!symbol) { localNames[functionName] = functionName; return functionName; } } } return undefined; } function getKindOrTypeNameForType(type: TypeInfo) { let kinds = type.findAllAnnotations(/*inherited*/ false, KindAnnotation.match); if (kinds.length === 1) { return kinds[0].kindSymbol.name; } return type.name; } } export interface KindOptions { create: boolean | string; update: boolean | string; test: boolean | string; } @annotation("kind", { inherited: false, targets: AnnotationTargets.Interface }) export class KindAnnotation extends Annotation { public kind: SyntaxKind; public kindSymbol: Symbol; public create: boolean | string; public update: boolean | string; public test: boolean | string; constructor([{ value, symbol }, { create = true, update = true, test = true } = {}, ..._arguments]: [EnumValue, KindOptions, any]) { super(_arguments); this.kind = value; this.kindSymbol = symbol; this.create = create; this.update = update; this.test = test; } public static match(annotation: Annotation): annotation is KindAnnotation { return annotation instanceof KindAnnotation; } } export const enum FactoryHiddenState { None, Hidden, Visible } @annotation("factoryhidden", { inherited: true, allowMultiple: true, targets: AnnotationTargets.Interface | AnnotationTargets.Property }) export class FactoryHiddenAnnotation extends Annotation { 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 getState(type: TypeInfo, propertyName?: string) { if (propertyName) { return this.getStateForProperty(type, propertyName); } else { return this.getStateForType(type); } } private static getStateForProperty(type: TypeInfo, propertyName: string) { let factoryHiddenState = FactoryHiddenState.None; let inheritedTypes: TypeInfo[] = [type]; while (factoryHiddenState === FactoryHiddenState.None && inheritedTypes.length) { [factoryHiddenState, inheritedTypes] = this.getStateForPropertyBreadthFirst(inheritedTypes, propertyName); } return factoryHiddenState; } private static getStateForPropertyBreadthFirst(types: TypeInfo[], propertyName: string): [FactoryHiddenState, TypeInfo[]] { let inheritedTypes: TypeInfo[] = []; let factoryHidden: FactoryHiddenAnnotation; let factoryHiddenState: FactoryHiddenState = FactoryHiddenState.None; for (let type of types) { let property = type.getProperty(propertyName, /*inherited*/ false); if (property) { if (factoryHidden = property.findFirstAnnotation(/*inherited*/ false, FactoryHiddenAnnotation.match)) { factoryHiddenState = factoryHidden.hidden ? FactoryHiddenState.Hidden : FactoryHiddenState.Visible; break; } } if (factoryHidden = type.findFirstAnnotation(/*inherited*/ false, FactoryHiddenAnnotation.matchProperty(propertyName))) { factoryHiddenState = factoryHidden.hidden ? FactoryHiddenState.Hidden : FactoryHiddenState.Visible; break; } if (property) { factoryHiddenState = FactoryHiddenState.Visible; break; } if (type.isInterface) { inheritedTypes = inheritedTypes.concat(type.getSuperTypes()); } else if (type.isTypeAlias) { inheritedTypes.push(type.getAliasedType()); } else if (type.isUnionType) { inheritedTypes = inheritedTypes.concat(type.getConstituentTypes()); } } return [factoryHiddenState, inheritedTypes]; } private static getStateForType(type: TypeInfo) { let annotation = type.findFirstAnnotation(/*inherited*/ false, this.matchProperty(undefined)); return annotation ? annotation.hidden ? FactoryHiddenState.Hidden : FactoryHiddenState.Visible : FactoryHiddenState.None; } public static matchProperty(propertyName: string): (annotation: Annotation) => annotation is FactoryHiddenAnnotation { return (annotation): annotation is FactoryHiddenAnnotation => this.match(annotation) && annotation.propertyName === propertyName; } public static match(annotation: Annotation): annotation is FactoryHiddenAnnotation { return annotation instanceof FactoryHiddenAnnotation; } } @annotation("factoryorder", { inherited: true, targets: AnnotationTargets.Interface }) export class FactoryOrderAnnotation extends Annotation { public propertyNames: string[]; constructor(propertyNames: string[]) { super([]); this.propertyNames = propertyNames; } public static match(annotation: Annotation): annotation is FactoryOrderAnnotation { return annotation instanceof FactoryOrderAnnotation; } } @annotation("factoryparam", { inherited: true, allowMultiple: true, targets: AnnotationTargets.Interface | AnnotationTargets.Property }) export class FactoryParamAnnotation extends Annotation { public propertyName: string; constructor([propertyName, ..._arguments]: [string, any]) { super(_arguments); this.propertyName = propertyName; } public static matchProperty(propertyName: string): (annotation: Annotation) => annotation is FactoryParamAnnotation { return (annotation): annotation is FactoryParamAnnotation => this.match(annotation) && annotation.propertyName === propertyName; } public static match(annotation: Annotation): annotation is FactoryParamAnnotation { return annotation instanceof FactoryParamAnnotation; } } @annotation("nodetest", { inherited: false, allowMultiple: false, targets: AnnotationTargets.Interface | AnnotationTargets.TypeAlias }) export class NodeTestAnnotation extends Annotation { public functionName: string; constructor([functionName, ..._arguments]: [string, any]) { super(_arguments); this.functionName = functionName } public static match(annotation: Annotation): annotation is NodeTestAnnotation { return annotation instanceof NodeTestAnnotation; } }