UNPKG

ph-dev-tools

Version:
309 lines (271 loc) 9.75 kB
/** * Created by Papa on 3/26/2016. */ import * as ts from "typescript"; import SyntaxKind = ts.SyntaxKind; import {EntityCandidate} from "./EntityCandidate"; import {EntityCandidateRegistry} from "./EntityCandidateRegistry"; import {getParentClassName, getParentClassImport, getClassPath, isDecoratedAsEntity} from "./utils"; import {DocEntry, Decorator} from "./DocEntry"; import NodeArray = ts.NodeArray; import PropertyAccessExpression = ts.PropertyAccessExpression; import Identifier = ts.Identifier; export var rootEntity = new EntityCandidate('Entity', null, null, 'PHibernate', true); export var globalCandidateRegistry = new EntityCandidateRegistry(rootEntity); /** Generate documention for all classes in a set of .ts files */ export function generateEntityDefinitions( fileNames:string[], options:ts.CompilerOptions ):EntityCandidate[] { // Build a program using the set of root file names in fileNames let program = ts.createProgram(fileNames, options); // Get the checker, we will use it to find more about classes let checker = program.getTypeChecker(); var processedCandidateRegistry = new EntityCandidateRegistry(rootEntity); let output:DocEntry[] = []; // Visit every sourceFile in the program for (const sourceFile of program.getSourceFiles()) { // Walk the tree to search for classes ts.forEachChild(sourceFile, visit); } // print out the doc // fs.writeFileSync("classes.json", JSON.stringify(output, undefined, 4)); return globalCandidateRegistry.matchVerifiedEntities(processedCandidateRegistry); /** visit nodes finding exported classes */ function visit( node:ts.Node ) { // Only consider exported nodes if (!isNodeExported(node)) { return; } if (node.kind === ts.SyntaxKind.ClassDeclaration) { // This is a top level class, get its symbol let symbol = checker.getSymbolAtLocation((<ts.ClassDeclaration>node).name); let serializedClass = serializeClass(symbol, node.decorators); if (serializedClass) { output.push(serializedClass); } // No need to walk any further, class expressions/inner declarations // cannot be exported } else if (node.kind === ts.SyntaxKind.ModuleDeclaration) { // This is a namespace, visit its children ts.forEachChild(node, visit); } } /** Serialize a symbol into a json object */ function serializeSymbol( symbol:ts.Symbol ):DocEntry { let docEntry = { name: symbol.getName(), decorators: [], documentation: ts.displayPartsToString(symbol.getDocumentationComment()), type: checker.typeToString(checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)) }; let declarations = symbol.declarations; if (declarations && declarations.length === 1) { let declaration = symbol.declarations[0]; let decorators = declaration.decorators; if (decorators) { decorators.forEach(( tsDecorator:ts.Decorator ) => { let decorator:Decorator = serializeDecorator(tsDecorator); docEntry.decorators.push(decorator); }); } } return docEntry; } function serializeDecorator( tsDecorator:ts.Decorator ):Decorator { let expression:ts.CallExpression = <ts.CallExpression>tsDecorator.expression; let decoratorName:string; if (expression.expression) { decoratorName = (<ts.Identifier>expression.expression).text; } else { decoratorName = (<ts.Identifier><any>expression).text; } let values = []; if (expression.arguments) { expression.arguments.forEach(( argument:ts.Node ) => { let value = parseObjectProperty(argument); values.push(value); }) } let decorator:Decorator = { name: decoratorName, values: values }; return decorator; } function parseObjectLiteralExpression( objLitExpr:ts.ObjectLiteralExpression ):any { let object = {}; if (objLitExpr.properties) { objLitExpr.properties.forEach(( property:ts.PropertyAssignment ) => { let propertyName = (<ts.Identifier>property.name).text; let initializer = property.initializer; let value = parseObjectProperty(initializer); object[propertyName] = value; }); } return object; } function parseObjectProperty( initializer:ts.Node ):any { let value; switch (initializer.kind) { case ts.SyntaxKind.Identifier: let identifier = <ts.Identifier>initializer; switch (identifier.text) { case 'undefined': value = undefined; break; case 'NaN': value = NaN; break; // Must be a variable declaration default: value = 'var ' + identifier.text; break; } break; case ts.SyntaxKind.NullKeyword: value = null; break; case ts.SyntaxKind.RegularExpressionLiteral: let regExp = <ts.Identifier>initializer; value = convertRegExpStringToObject(regExp.text); break; case ts.SyntaxKind.StringLiteral: case ts.SyntaxKind.NoSubstitutionTemplateLiteral: value = (<ts.StringLiteral>initializer).text; break; case ts.SyntaxKind.TrueKeyword: value = true; break; case ts.SyntaxKind.FalseKeyword: value = false; break; case ts.SyntaxKind.NumericLiteral: let numberText = (<any>initializer).text; if (numberText.indexOf('.') > 0) { value = parseFloat(numberText); } else { value = parseInt(numberText); } break; case ts.SyntaxKind.NewExpression: let newExpression = <ts.NewExpression>initializer; let type = (<ts.Identifier>newExpression.expression).text; value = 'new ' + type; break; case ts.SyntaxKind.ObjectLiteralExpression: value = parseObjectLiteralExpression(<ts.ObjectLiteralExpression>initializer); break; case ts.SyntaxKind.ArrayLiteralExpression: value = []; let arrayLiteral = <ts.ArrayLiteralExpression>initializer; arrayLiteral.elements.forEach(( element:ts.Node ) => { let arrayValue = parseObjectProperty(element); value.push(arrayValue); }); break; case ts.SyntaxKind.PropertyAccessExpression: value = convertPropertyAccessExpressionToString(<PropertyAccessExpression>initializer); break; case ts.SyntaxKind.CallExpression: throw `Function calls are not allowed as parameter values.`; case ts.SyntaxKind.BinaryExpression: throw `Expression are not allowed as parameter values.`; default: throw `Unsupported property initializer.kind: ${initializer.kind}`; } return value; } function convertPropertyAccessExpressionToString( propAccessExrp:PropertyAccessExpression ):string { let leftHandExrp = <Identifier>propAccessExrp.expression; return `${leftHandExrp.text}.${propAccessExrp.name.text}`; } function convertRegExpStringToObject( regExpString:string ):RegExp { let firstIndexOfSlash = regExpString.indexOf('/'); if (firstIndexOfSlash < 0) { return new RegExp(regExpString); } let lastIndexOfSlash = regExpString.lastIndexOf('/'); if (firstIndexOfSlash === lastIndexOfSlash) { return new RegExp(regExpString); } let regExpFragments = regExpString.split('/'); return new RegExp(regExpFragments[1], regExpFragments[2]); } /** Serialize a class symbol information */ function serializeClass( symbol:ts.Symbol, decorators:NodeArray<ts.Decorator> ) { let details = serializeSymbol(symbol); let properties:DocEntry[] = []; for (let memberName in symbol.members) { let member = symbol.members[memberName]; if (member.valueDeclaration) { if (member.valueDeclaration.kind === SyntaxKind.PropertyDeclaration) { console.log(`Property: ${memberName}`); let propertySymbolDescriptor = serializeSymbol(member); properties.push(propertySymbolDescriptor); } } else if (member.declarations) { console.log('member has declarations'); // FIXME: do we need to support this (constructor, methods)? } else { console.log(`member doesn't have valueDeclaration or declarations`); // FIXME: do we need to support other cases } } details.properties = properties; // Get the construct signatures let constructorType = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration); details.constructors = constructorType.getConstructSignatures().map(serializeSignature); let classPath = getClassPath(<ts.Node><any>symbol); // Only top level entities are supported if (!classPath) { return details; } let parentClassName = getParentClassName(symbol); let parentClassImport:string; if (parentClassName) { parentClassImport = getParentClassImport(<ts.Node><any>symbol, parentClassName); } if (isDecoratedAsEntity(decorators)) { let entityCandidate = EntityCandidate.create(details.name, classPath, parentClassName, parentClassImport); entityCandidate.docEntry = details; globalCandidateRegistry.addCandidate(entityCandidate); processedCandidateRegistry.addCandidate(entityCandidate); } return details; } /** Serialize a signature (call or construct) */ function serializeSignature( signature:ts.Signature ) { return { parameters: signature.parameters.map(serializeSymbol), returnType: checker.typeToString(signature.getReturnType()), documentation: ts.displayPartsToString(signature.getDocumentationComment()) }; } /** True if this is visible outside this file, false otherwise */ function isNodeExported( node:ts.Node ):boolean { return (node.flags & ts.NodeFlags.Export) !== 0 || (node.parent && node.parent.kind === ts.SyntaxKind.SourceFile); } }