UNPKG

alm

Version:

The best IDE for TypeScript

263 lines (221 loc) 9.15 kB
/** * This is the backend for the uml diagram view */ /** Imports */ import * as utils from "../../../../common/utils"; import * as types from "../../../../common/types"; import {getDocumentedTypeLocation} from "../modules/astUtils"; /** We just use the *active* project if any */ import * as activeProject from "../activeProject"; let getProject = activeProject.GetProject.getCurrentIfAny; /** * Get a uml diagram structure for a file */ export function getUmlDiagramForFile(query: { filePath: string }) : Promise<{classes: types.UMLClass[]}> { let project = getProject(); const sourceFile = project.getSourceFile(query.filePath); const program = project.languageService.getProgram(); // const modules = tsAnalyzer.collectInformation(program, sourceFile); // console.log(modules); const classes = getClasses({ sourceFile, program }); // TODO: sort by base classes (i.e. the class that has the lowest length of extend) return utils.resolve({ classes }); } export function getClasses({sourceFile, program}: { sourceFile: ts.SourceFile, program: ts.Program }): types.UMLClass[] { const result: types.UMLClass[] = []; const typeChecker = program.getTypeChecker(); const collect = (cls: types.UMLClass) => result.push(cls); collectClasses({ node: sourceFile, collect, sourceFile, program, }); return result; } function collectClasses(config: { node: ts.SourceFile | ts.ModuleBlock | ts.ModuleDeclaration, sourceFile: ts.SourceFile, program: ts.Program, collect: (cls: types.UMLClass) => void }) { const {sourceFile, program, collect} = config; ts.forEachChild(config.node, node => { if (node.kind == ts.SyntaxKind.ClassDeclaration) { collect(transformClass(node as ts.ClassDeclaration, sourceFile, program)); } // Support recursively looking into `a.b.c` style namespaces as well if (node.kind === ts.SyntaxKind.ModuleDeclaration) { collectClasses({ node: node as ts.ModuleDeclaration, collect, program, sourceFile }); } if (node.kind === ts.SyntaxKind.ModuleBlock) { collectClasses({ node: node as ts.ModuleBlock, collect, program, sourceFile }); } }); } /** * Various Transformers */ function transformClass(node: ts.ClassDeclaration, sourceFile: ts.SourceFile, program: ts.Program): types.UMLClass { const result: types.UMLClass = { name: node.name.text, icon: types.IconType.Class, location: getDocumentedTypeLocation(sourceFile, node.name.pos), members: [], extends: null, } if (node.typeParameters) { result.icon = types.IconType.ClassGeneric; } /** Collect members */ ts.forEachChild(node, (node) => { if (node.kind == ts.SyntaxKind.Constructor) { result.members.push(transformClassConstructor(node as ts.ConstructorDeclaration, sourceFile)); } if (node.kind == ts.SyntaxKind.PropertyDeclaration) { result.members.push(transformClassProperty(node as ts.PropertyDeclaration, sourceFile)); } if (node.kind == ts.SyntaxKind.MethodDeclaration) { result.members.push(transformClassMethod(node as ts.MethodDeclaration, sourceFile)); } if (node.kind == ts.SyntaxKind.IndexSignature) { result.members.push(transformClassIndexSignature(node as ts.IndexSignatureDeclaration, sourceFile)); } }); /** Collect parent classes */ const classDeclaration = node; if (classDeclaration.heritageClauses) { let extendsClause = classDeclaration.heritageClauses.find(c => c.token === ts.SyntaxKind.ExtendsKeyword); if (extendsClause && extendsClause.types.length > 0) { const expression = extendsClause.types[0]; const typeChecker = program.getTypeChecker(); const symbol = typeChecker.getTypeAtLocation(expression.expression).symbol; if (symbol) { const valueDeclaration = symbol.valueDeclaration; if (valueDeclaration && valueDeclaration.kind === ts.SyntaxKind.ClassDeclaration) { const node = valueDeclaration as ts.ClassDeclaration; const nodeSourceFile = node.getSourceFile(); result.extends = transformClass(node, nodeSourceFile, program); } } } } /** Figure out any override */ if (result.extends) { /** Collect all parents */ const parents: types.UMLClass[] = []; let parent = result.extends; while (parent) { parents.push(parent); parent = parent.extends; } /** For each member check if a parent has a member with the same name */ result.members.forEach(m => { if (m.name === "constructor") return; // (except for constructor) parents.forEach(p => { const matchedParentMember = p.members.find(pm => pm.lifetime === types.UMLClassMemberLifetime.Instance && pm.name === m.name) if (matchedParentMember) { m.override = matchedParentMember; } }); }); } return result; } /** Class Constructor */ function transformClassConstructor(node: ts.ConstructorDeclaration, sourceFile: ts.SourceFile): types.UMLClassMember { const name = "constructor"; let icon = types.IconType.ClassConstructor; const location = getDocumentedTypeLocation(sourceFile, node.pos); const result: types.UMLClassMember = { name, icon, location, visibility: types.UMLClassMemberVisibility.Public, lifetime: types.UMLClassMemberLifetime.Instance, } return result; } /** Class Property */ function transformClassProperty(node: ts.PropertyDeclaration, sourceFile: ts.SourceFile): types.UMLClassMember { const name = ts.unescapeLeadingUnderscores(ts.getPropertyNameForPropertyNameNode(node.name)); let icon = types.IconType.ClassProperty; const location = getDocumentedTypeLocation(sourceFile, node.name.getEnd() - 1); const visibility = getVisibility(node); const lifetime = getLifetime(node); const result: types.UMLClassMember = { name, icon, location, visibility, lifetime, } return result; } /** Class Method */ function transformClassMethod(node: ts.MethodDeclaration, sourceFile: ts.SourceFile): types.UMLClassMember { const name = ts.unescapeLeadingUnderscores(ts.getPropertyNameForPropertyNameNode(node.name)); let icon = types.IconType.ClassMethod; if (node.typeParameters) { icon = types.IconType.ClassMethodGeneric; } const location = getDocumentedTypeLocation(sourceFile, node.name.getEnd() - 1); const visibility = getVisibility(node); const lifetime = getLifetime(node); const result: types.UMLClassMember = { name, icon, location, visibility, lifetime, } return result; } /** Class Index Signature */ function transformClassIndexSignature(node: ts.IndexSignatureDeclaration, sourceFile: ts.SourceFile): types.UMLClassMember { const name = "Index Signature"; let icon = types.IconType.ClassIndexSignature; let location = getDocumentedTypeLocation(sourceFile, node.pos); const result: types.UMLClassMember = { name, icon, location, visibility: types.UMLClassMemberVisibility.Public, lifetime: types.UMLClassMemberLifetime.Instance, } return result; } /** * * General Utilities * */ /** Visibility */ function getVisibility(node: ts.Node): types.UMLClassMemberVisibility { if (node.modifiers) { if (hasModifierSet(node.modifiers, ts.ModifierFlags.Protected)) { return types.UMLClassMemberVisibility.Protected; } else if (hasModifierSet(node.modifiers, ts.ModifierFlags.Private)) { return types.UMLClassMemberVisibility.Private; } else if (hasModifierSet(node.modifiers, ts.ModifierFlags.Public)) { return types.UMLClassMemberVisibility.Public; } else if (hasModifierSet(node.modifiers, ts.ModifierFlags.Export)) { return types.UMLClassMemberVisibility.Public; } } switch (node.parent.kind) { case ts.SyntaxKind.ClassDeclaration: return types.UMLClassMemberVisibility.Public; case ts.SyntaxKind.ModuleDeclaration: return types.UMLClassMemberVisibility.Private; } return types.UMLClassMemberVisibility.Private; } /** Lifetime */ function getLifetime(node: ts.Node): types.UMLClassMemberLifetime { if (node.modifiers) { if (hasModifierSet(node.modifiers, ts.ModifierFlags.Static)) { return types.UMLClassMemberLifetime.Static; } } return types.UMLClassMemberLifetime.Instance; } /** Just checks if a flag is set */ function hasModifierSet(modifiers: ts.NodeArray<ts.Modifier>, modifier: number) { return modifiers.some(value => (value.flags & modifier) === modifier); }