mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-07 14:34:35 -06:00
552 lines
21 KiB
TypeScript
552 lines
21 KiB
TypeScript
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<string> = {};
|
|
|
|
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 elementType: TypeInfo;
|
|
if (isModifiersArray) {
|
|
elementType = modifierType;
|
|
parameterType = makeArrayType(elementType);
|
|
}
|
|
else if (isNodeArray) {
|
|
elementType = property.propertyType.getGenericTypeArguments()[0];
|
|
parameterType = makeArrayType(elementType);
|
|
}
|
|
else if (isNode) {
|
|
elementType = property.propertyType;
|
|
parameterType = elementType;
|
|
}
|
|
else {
|
|
parameterType = property.propertyType;
|
|
}
|
|
|
|
if (elementType) {
|
|
typesReferencedByProperties[elementType.id] = true;
|
|
discoverType(elementType);
|
|
}
|
|
|
|
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(elementType);
|
|
}
|
|
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 } = <KindOptions>{}, ..._arguments]: [EnumValue<SyntaxKind>, 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;
|
|
}
|
|
} |