UNPKG

compodoc

Version:

The missing documentation tool for your Angular application

1,381 lines (1,228 loc) 54 kB
import * as _ from 'lodash'; import * as path from 'path'; import * as util from 'util'; import * as ts from 'typescript'; import marked from 'marked'; import { compilerHost, detectIndent } from '../../utilities'; import { logger } from '../../logger'; import { RouterParser } from '../../utils/router.parser'; import { LinkParser } from '../../utils/link-parser'; import { JSDocTagsParser } from '../../utils/jsdoc.parser'; import { generate } from './codegen'; import { Configuration, IConfiguration } from '../configuration'; interface NodeObject { kind: Number; pos: Number; end: Number; text: string; initializer: NodeObject, name?: { text: string }; expression?: NodeObject; elements?: NodeObject[]; arguments?: NodeObject[]; properties?: any[]; parserContextFlags?: Number; equalsGreaterThanToken?: NodeObject[]; parameters?: NodeObject[]; Component?: String; body?: { pos: Number; end: Number; statements: NodeObject[]; } } interface Deps { name: string; type: string; label?: string; file?: string; sourceCode?: string; description?: string; //Component animations?: string[]; // TODO changeDetection?: string; encapsulation?: string; entryComponents?: string; // TODO exportAs?: string; host?: string; inputs?: string[]; interpolation?: string; // TODO moduleId?: string; outputs?: string[]; queries?: Deps[]; // TODO selector?: string; styleUrls?: string[]; styles?: string[]; template?: string; templateUrl?: string[]; viewProviders?: string[]; inputsClass?: Object[]; //common providers?: Deps[]; //module declarations?: Deps[]; bootstrap?: Deps[]; imports?: Deps[]; exports?: Deps[]; } interface SymbolDeps { full: string; alias: string; } export class Dependencies { private files: string[]; private program: ts.Program; private programComponent: ts.Program; private typeChecker: ts.TypeChecker; private typeCheckerComponent: ts.TypeChecker; private engine: any; private __cache: any = {}; private __nsModule: any = {}; private unknown = '???'; private configuration = Configuration.getInstance(); constructor(files: string[], options: any) { this.files = files; const transpileOptions = { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, tsconfigDirectory: options.tsconfigDirectory }; this.program = ts.createProgram(this.files, transpileOptions, compilerHost(transpileOptions)); } getDependencies() { let deps: Object = { 'modules': [], 'components': [], 'injectables': [], 'pipes': [], 'directives': [], 'routes': [], 'classes': [], 'interfaces': [], 'miscellaneous': { variables: [], functions: [], typealiases: [], enumerations: [] } }; let sourceFiles = this.program.getSourceFiles() || []; sourceFiles.map((file: ts.SourceFile) => { let filePath = file.fileName; if (path.extname(filePath) === '.ts') { if (filePath.lastIndexOf('.d.ts') === -1 && filePath.lastIndexOf('spec.ts') === -1) { logger.info('parsing', filePath); try { this.getSourceFileDecorators(file, deps); } catch (e) { logger.error(e, file.fileName); } } } return deps; }); /*RouterParser.printModulesRoutes(); RouterParser.printRoutes(); RouterParser.linkModulesAndRoutes(); RouterParser.constructModulesTree(); RouterParser.constructRoutesTree();*/ return deps; } private getSourceFileDecorators(srcFile: ts.SourceFile, outputSymbols: Object): void { let cleaner = (process.cwd() + path.sep).replace(/\\/g, '/'); let file = srcFile.fileName.replace(cleaner, ''); this.programComponent = ts.createProgram([file], {}); let sourceFile = this.programComponent.getSourceFile(file); this.typeCheckerComponent = this.programComponent.getTypeChecker(true); ts.forEachChild(srcFile, (node: ts.Node) => { let deps: Deps = <Deps>{}; if (node.decorators) { let visitNode = (visitedNode, index) => { let metadata = node.decorators.pop(); let name = this.getSymboleName(node); let props = this.findProps(visitedNode); let IO = this.getComponentIO(file, sourceFile); if (this.isModule(metadata)) { deps = { name, file: file, providers: this.getModuleProviders(props), declarations: this.getModuleDeclations(props), imports: this.getModuleImports(props), exports: this.getModuleExports(props), bootstrap: this.getModuleBootstrap(props), type: 'module', description: IO.description, sourceCode: sourceFile.getText() }; if (RouterParser.hasRouterModuleInImports(deps.imports)) { RouterParser.addModuleWithRoutes(name, this.getModuleImportsRaw(props)); } RouterParser.addModule(name, deps.imports); outputSymbols['modules'].push(deps); } else if (this.isComponent(metadata)) { if(props.length === 0) return; //console.log(util.inspect(props, { showHidden: true, depth: 10 })); deps = { name, file: file, //animations?: string[]; // TODO changeDetection: this.getComponentChangeDetection(props), encapsulation: this.getComponentEncapsulation(props), //entryComponents?: string; // TODO waiting doc infos exportAs: this.getComponentExportAs(props), host: this.getComponentHost(props), inputs: this.getComponentInputsMetadata(props), //interpolation?: string; // TODO waiting doc infos moduleId: this.getComponentModuleId(props), outputs: this.getComponentOutputs(props), providers: this.getComponentProviders(props), //queries?: Deps[]; // TODO selector: this.getComponentSelector(props), styleUrls: this.getComponentStyleUrls(props), styles: this.getComponentStyles(props), // TODO fix args template: this.getComponentTemplate(props), templateUrl: this.getComponentTemplateUrl(props), viewProviders: this.getComponentViewProviders(props), inputsClass: IO.inputs, outputsClass: IO.outputs, propertiesClass: IO.properties, methodsClass: IO.methods, description: IO.description, type: 'component', sourceCode: sourceFile.getText() }; if (IO.jsdoctags && IO.jsdoctags.length > 0) { deps.jsdoctags = IO.jsdoctags[0].tags } if(IO.constructor) { deps.constructorObj = IO.constructor; } outputSymbols['components'].push(deps); } else if (this.isInjectable(metadata)) { deps = { name, file: file, type: 'injectable', properties: IO.properties, methods: IO.methods, description: IO.description, sourceCode: sourceFile.getText() }; if(IO.constructor) { deps.constructorObj = IO.constructor; } outputSymbols['injectables'].push(deps); } else if (this.isPipe(metadata)) { deps = { name, file: file, type: 'pipe', description: IO.description, sourceCode: sourceFile.getText() }; if (IO.jsdoctags && IO.jsdoctags.length > 0) { deps.jsdoctags = IO.jsdoctags[0].tags } outputSymbols['pipes'].push(deps); } else if (this.isDirective(metadata)) { if(props.length === 0) return; deps = { name, file: file, type: 'directive', description: IO.description, sourceCode: sourceFile.getText(), selector: this.getComponentSelector(props), providers: this.getComponentProviders(props), inputsClass: IO.inputs, outputsClass: IO.outputs, propertiesClass: IO.properties, methodsClass: IO.methods }; if (IO.jsdoctags && IO.jsdoctags.length > 0) { deps.jsdoctags = IO.jsdoctags[0].tags } if(IO.constructor) { deps.constructorObj = IO.constructor; } outputSymbols['directives'].push(deps); } this.debug(deps); this.__cache[name] = deps; } let filterByDecorators = (node) => { if (node.expression && node.expression.expression) { return /(NgModule|Component|Injectable|Pipe|Directive)/.test(node.expression.expression.text) } return false; }; node.decorators .filter(filterByDecorators) .forEach(visitNode); } else if (node.symbol) { if(node.symbol.flags === ts.SymbolFlags.Class) { let name = this.getSymboleName(node); let IO = this.getComponentIO(file, sourceFile); deps = { name, file: file, type: 'class', sourceCode: sourceFile.getText() }; if(IO.constructor) { deps.constructorObj = IO.constructor; } if(IO.properties) { deps.properties = IO.properties; } if(IO.description) { deps.description = IO.description; } if(IO.methods) { deps.methods = IO.methods; } this.debug(deps); outputSymbols['classes'].push(deps); } else if(node.symbol.flags === ts.SymbolFlags.Interface) { let name = this.getSymboleName(node); let IO = this.getInterfaceIO(file, sourceFile, node); deps = { name, file: file, type: 'interface', sourceCode: sourceFile.getText() }; if(IO.properties) { deps.properties = IO.properties; } if(IO.indexSignatures) { deps.indexSignatures = IO.indexSignatures; } if(IO.kind) { deps.kind = IO.kind; } if(IO.description) { deps.description = IO.description; } if(IO.methods) { deps.methods = IO.methods; } this.debug(deps); outputSymbols['interfaces'].push(deps); } } else { let IO = this.getRouteIO(file, sourceFile); if(IO.routes) { let newRoutes; try { newRoutes = JSON.parse(IO.routes.replace(/ /gm, '')); } catch (e) { logger.error('Routes parsing error, maybe a trailing comma or an external variable ?'); return true; } outputSymbols['routes'] = [...outputSymbols['routes'], ...newRoutes]; } if (node.kind === ts.SyntaxKind.ClassDeclaration) { let name = this.getSymboleName(node); let IO = this.getComponentIO(file, sourceFile); deps = { name, file: file, type: 'class', sourceCode: sourceFile.getText() }; if(IO.constructor) { deps.constructorObj = IO.constructor; } if(IO.properties) { deps.properties = IO.properties; } if(IO.indexSignatures) { deps.indexSignatures = IO.indexSignatures; } if(IO.description) { deps.description = IO.description; } if(IO.methods) { deps.methods = IO.methods; } this.debug(deps); outputSymbols['classes'].push(deps); } if (node.kind === ts.SyntaxKind.ExpressionStatement) { //Find the root module with bootstrapModule call //Find recusively in expression nodes one with name 'bootstrapModule' let rootModule, resultNode = this.findExpressionByName(node, 'bootstrapModule'); if(resultNode) { if(resultNode.arguments.length > 0) { _.forEach(resultNode.arguments, function(argument) { if(argument.text) { rootModule = argument.text; } }); } if (rootModule) { RouterParser.setRootModule(rootModule); } } } if (node.kind === ts.SyntaxKind.VariableStatement && !this.isVariableRoutes(node)) { let infos = this.visitVariableDeclaration(node), name = infos.name; deps = { name, file: file } deps.type = (infos.type) ? infos.type : ''; if (infos.defaultValue) { deps.defaultValue = infos.defaultValue; } if (node.jsDoc && node.jsDoc.length > 0 && node.jsDoc[0].comment) { deps.description = marked(node.jsDoc[0].comment); } outputSymbols['miscellaneous'].variables.push(deps); } if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) { //console.log('TypeAliasDeclaration'); } if (node.kind === ts.SyntaxKind.FunctionDeclaration) { let deps = this.visitFunctionDeclaration(node) outputSymbols['miscellaneous'].functions.push(deps); } if (node.kind === ts.SyntaxKind.EnumDeclaration) { //console.log('EnumDeclaration'); } } }); } private debug(deps: Deps) { logger.debug('debug', `${deps.name}:`); [ 'imports', 'exports', 'declarations', 'providers', 'bootstrap' ].forEach(symbols => { if (deps[symbols] && deps[symbols].length > 0) { logger.debug('', `- ${symbols}:`); deps[symbols].map(i => i.name).forEach(d => { logger.debug('', `\t- ${d}`); }); } }); } private isVariableRoutes(node) { var result = false; if( node.declarationList.declarations ) { let i = 0, len = node.declarationList.declarations.length; for(i; i<len; i++) { if(node.declarationList.declarations[i].type) { if(node.declarationList.declarations[i].type.typeName && node.declarationList.declarations[i].type.typeName.text === 'Routes') { result = true; } } } } return result; } private findExpressionByName(entryNode, name) { let result, loop = function(node, name) { if(node.expression && !node.expression.name) { loop(node.expression, name); } if(node.expression && node.expression.name) { if(node.expression.name.text === name) { result = node; } } } loop(entryNode, name); return result; } private isComponent(metadata) { return metadata.expression.expression.text === 'Component'; } private isPipe(metadata) { return metadata.expression.expression.text === 'Pipe'; } private isDirective(metadata) { return metadata.expression.expression.text === 'Directive'; } private isInjectable(metadata) { return metadata.expression.expression.text === 'Injectable'; } private isModule(metadata) { return metadata.expression.expression.text === 'NgModule'; } private getType(name) { let type; if( name.toLowerCase().indexOf('component') !== -1 ) { type = 'component'; } else if( name.toLowerCase().indexOf('pipe') !== -1 ) { type = 'pipe'; } else if( name.toLowerCase().indexOf('module') !== -1 ) { type = 'module'; } else if( name.toLowerCase().indexOf('directive') !== -1 ) { type = 'directive'; } return type; } private getSymboleName(node): string { return node.name.text; } private getComponentSelector(props: NodeObject[]): string { return this.getSymbolDeps(props, 'selector').pop(); } private getComponentExportAs(props: NodeObject[]): string { return this.getSymbolDeps(props, 'exportAs').pop(); } private getModuleProviders(props: NodeObject[]): Deps[] { return this.getSymbolDeps(props, 'providers').map((providerName) => { return this.parseDeepIndentifier(providerName); }); } private findProps(visitedNode) { if(visitedNode.expression.arguments.length > 0) { return visitedNode.expression.arguments.pop().properties; } else { return ''; } } private getModuleDeclations(props: NodeObject[]): Deps[] { return this.getSymbolDeps(props, 'declarations').map((name) => { let component = this.findComponentSelectorByName(name); if (component) { return component; } return this.parseDeepIndentifier(name); }); } private getModuleImportsRaw(props: NodeObject[]): Deps[] { return this.getSymbolDepsRaw(props, 'imports'); } private getModuleImports(props: NodeObject[]): Deps[] { return this.getSymbolDeps(props, 'imports').map((name) => { return this.parseDeepIndentifier(name); }); } private getModuleExports(props: NodeObject[]): Deps[] { return this.getSymbolDeps(props, 'exports').map((name) => { return this.parseDeepIndentifier(name); }); } private getComponentHost(props: NodeObject[]): Object { return this.getSymbolDepsObject(props, 'host'); } private getModuleBootstrap(props: NodeObject[]): Deps[] { return this.getSymbolDeps(props, 'bootstrap').map((name) => { return this.parseDeepIndentifier(name); }); } private getComponentInputsMetadata(props: NodeObject[]): string[] { return this.getSymbolDeps(props, 'inputs'); } private getDecoratorOfType(node, decoratorType) { var decorators = node.decorators || []; for (var i = 0; i < decorators.length; i++) { if (decorators[i].expression.expression.text === decoratorType) { return decorators[i]; } } return null; } private visitInput(property, inDecorator) { var inArgs = inDecorator.expression.arguments, _return = { name: inArgs.length ? inArgs[0].text : property.name.text, defaultValue: property.initializer ? this.stringifyDefaultValue(property.initializer) : undefined, description: marked(LinkParser.resolveLinks(ts.displayPartsToString(property.symbol.getDocumentationComment()))) }; if (property.type) { _return.type = this.visitType(property); } else { // handle NewExpression if (property.initializer) { if (property.initializer.kind === ts.SyntaxKind.NewExpression) { if (property.initializer.expression) { _return.type = property.initializer.expression.text; } } } } return _return; } private visitType(node) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ return node ? this.typeCheckerComponent.typeToString(this.typeCheckerComponent.getTypeAtLocation(node)) : 'void'; } private visitOutput(property, outDecorator) { var outArgs = outDecorator.expression.arguments, _return = { name: outArgs.length ? outArgs[0].text : property.name.text, description: marked(LinkParser.resolveLinks(ts.displayPartsToString(property.symbol.getDocumentationComment()))) }; if (property.type) { _return.type = this.visitType(property); } else { // handle NewExpression if (property.initializer) { if (property.initializer.kind === ts.SyntaxKind.NewExpression) { if (property.initializer.expression) { _return.type = property.initializer.expression.text; } } } } return _return; } private isPublic(member): boolean { if (member.modifiers) { const isPublic: boolean = member.modifiers.some(function(modifier) { return modifier.kind === ts.SyntaxKind.PublicKeyword; }); if (isPublic) { return true; } } return this.isHiddenMember(member); } private isPrivate(member): boolean { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ if (member.modifiers) { const isPrivate: boolean = member.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.PrivateKeyword); if (isPrivate) { return true; } } return this.isHiddenMember(member); } private isInternal(member): boolean { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ const internalTags: string[] = ['internal']; if (member.jsDoc) { for (const doc of member.jsDoc) { if (doc.tags) { for (const tag of doc.tags) { if (internalTags.indexOf(tag.tagName.text) > -1) { return true; } } } } } return false; } private isHiddenMember(member): boolean { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ const internalTags: string[] = ['hidden']; if (member.jsDoc) { for (const doc of member.jsDoc) { if (doc.tags) { for (const tag of doc.tags) { if (internalTags.indexOf(tag.tagName.text) > -1) { return true; } } } } } return false; } private isAngularLifecycleHook(methodName) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ const ANGULAR_LIFECYCLE_METHODS = [ 'ngOnInit', 'ngOnChanges', 'ngDoCheck', 'ngOnDestroy', 'ngAfterContentInit', 'ngAfterContentChecked', 'ngAfterViewInit', 'ngAfterViewChecked', 'writeValue', 'registerOnChange', 'registerOnTouched', 'setDisabledState' ]; return ANGULAR_LIFECYCLE_METHODS.indexOf(methodName) >= 0; } private visitConstructorDeclaration(method) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var result = { name: 'constructor', description: marked(LinkParser.resolveLinks(ts.displayPartsToString(method.symbol.getDocumentationComment()))), args: method.parameters ? method.parameters.map((prop) => this.visitArgument(prop)) : [] }, jsdoctags = JSDocTagsParser.getJSDocs(method), markedtags = function(tags) { var mtags = tags; _.forEach(mtags, (tag) => { tag.comment = marked(LinkParser.resolveLinks(tag.comment)); }); return mtags; }; if (method.modifiers) { if (method.modifiers.length > 0) { result.modifierKind = method.modifiers[0].kind; } } if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { result.jsdoctags = markedtags(jsdoctags[0].tags); } } return result; } private visitConstructorProperties(method) { var that = this; if (method.parameters) { var _parameters = [], i = 0, len = method.parameters.length; for(i; i < len; i++) { if (that.isPublic(method.parameters[i])) { _parameters.push(that.visitArgument(method.parameters[i])); } } return _parameters; } else { return []; } } private visitCallDeclaration(method) { return { description: marked(LinkParser.resolveLinks(ts.displayPartsToString(method.symbol.getDocumentationComment()))), args: method.parameters ? method.parameters.map((prop) => this.visitArgument(prop)) : [], returnType: this.visitType(method.type) } } private visitIndexDeclaration(method) { return { description: marked(LinkParser.resolveLinks(ts.displayPartsToString(method.symbol.getDocumentationComment()))), args: method.parameters ? method.parameters.map((prop) => this.visitArgument(prop)) : [], returnType: this.visitType(method.type) } } private visitMethodDeclaration(method) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var result = { name: method.name.text, args: method.parameters ? method.parameters.map((prop) => this.visitArgument(prop)) : [], returnType: this.visitType(method.type) }, jsdoctags = JSDocTagsParser.getJSDocs(method), markedtags = function(tags) { var mtags = tags; _.forEach(mtags, (tag) => { tag.comment = marked(LinkParser.resolveLinks(tag.comment)); }); return mtags; }; if (method.symbol) { result.description = marked(LinkParser.resolveLinks(ts.displayPartsToString(method.symbol.getDocumentationComment()))); } if (method.modifiers) { if (method.modifiers.length > 0) { result.modifierKind = method.modifiers[0].kind; } } if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { result.jsdoctags = markedtags(jsdoctags[0].tags); } } return result; } private visitArgument(arg) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ return { name: arg.name.text, type: this.visitType(arg) } } private getNamesCompareFn(name) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ name = name || 'name'; var t = (a, b) => { if (a[name]) { return a[name].localeCompare(b[name]) } else { return 0; } }; return t; } private stringifyDefaultValue(node) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ if (node.text) { return node.text; } else if (node.kind === ts.SyntaxKind.FalseKeyword) { return 'false'; } else if (node.kind === ts.SyntaxKind.TrueKeyword) { return 'true'; } } private visitProperty(property) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var result = { name: property.name.text, defaultValue: property.initializer ? this.stringifyDefaultValue(property.initializer) : undefined, type: this.visitType(property), description: marked(LinkParser.resolveLinks(ts.displayPartsToString(property.symbol.getDocumentationComment()))) } if (property.modifiers) { if (property.modifiers.length > 0) { result.modifierKind = property.modifiers[0].kind; } } return result; } private visitMembers(members) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var inputs = [], outputs = [], methods = [], properties = [], indexSignatures = [], kind, inputDecorator, constructor, outDecorator; for (var i = 0; i < members.length; i++) { inputDecorator = this.getDecoratorOfType(members[i], 'Input'); outDecorator = this.getDecoratorOfType(members[i], 'Output'); kind = members[i].kind; if (inputDecorator) { inputs.push(this.visitInput(members[i], inputDecorator)); } else if (outDecorator) { outputs.push(this.visitOutput(members[i], outDecorator)); } else if (!this.isHiddenMember(members[i])) { if ( (this.isPrivate(members[i]) || this.isInternal(members[i])) && this.configuration.mainData.disablePrivateOrInternalSupport) {} else { if ((members[i].kind === ts.SyntaxKind.MethodDeclaration || members[i].kind === ts.SyntaxKind.MethodSignature) && !this.isAngularLifecycleHook(members[i].name.text)) { methods.push(this.visitMethodDeclaration(members[i])); } else if ( members[i].kind === ts.SyntaxKind.PropertyDeclaration || members[i].kind === ts.SyntaxKind.PropertySignature || members[i].kind === ts.SyntaxKind.GetAccessor) { properties.push(this.visitProperty(members[i])); } else if (members[i].kind === ts.SyntaxKind.CallSignature) { properties.push(this.visitCallDeclaration(members[i])); } else if (members[i].kind === ts.SyntaxKind.IndexSignature) { indexSignatures.push(this.visitIndexDeclaration(members[i])); } else if (members[i].kind === ts.SyntaxKind.Constructor) { let _constructorProperties = this.visitConstructorProperties(members[i]), j = 0, len = _constructorProperties.length; for(j; j<len; j++) { properties.push(_constructorProperties[j]); } constructor = this.visitConstructorDeclaration(members[i]); } } } } inputs.sort(this.getNamesCompareFn()); outputs.sort(this.getNamesCompareFn()); properties.sort(this.getNamesCompareFn()); indexSignatures.sort(this.getNamesCompareFn()); return { inputs, outputs, methods, properties, indexSignatures, kind, constructor }; } private visitDirectiveDecorator(decorator) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var selector; var exportAs; var properties = decorator.expression.arguments[0].properties; for (var i = 0; i < properties.length; i++) { if (properties[i].name.text === 'selector') { // TODO: this will only work if selector is initialized as a string literal selector = properties[i].initializer.text; } if (properties[i].name.text === 'exportAs') { // TODO: this will only work if selector is initialized as a string literal exportAs = properties[i].initializer.text; } } return { selector, exportAs }; } private isPipeDecorator(decorator) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ return decorator.expression.expression.text === 'Pipe'; } private isModuleDecorator(decorator) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ return decorator.expression.expression.text === 'NgModule'; } private isDirectiveDecorator(decorator) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var decoratorIdentifierText = decorator.expression.expression.text; return decoratorIdentifierText === 'Directive' || decoratorIdentifierText === 'Component'; } private isServiceDecorator(decorator) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ return decorator.expression.expression.text === 'Injectable'; } private visitClassDeclaration(fileName, classDeclaration) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var symbol = this.program.getTypeChecker().getSymbolAtLocation(classDeclaration.name); var description = marked(LinkParser.resolveLinks(ts.displayPartsToString(symbol.getDocumentationComment()))); var className = classDeclaration.name.text; var directiveInfo; var members; var jsdoctags = []; if (symbol.valueDeclaration) { jsdoctags = JSDocTagsParser.getJSDocs(symbol.valueDeclaration); } if (classDeclaration.decorators) { for (var i = 0; i < classDeclaration.decorators.length; i++) { if (this.isDirectiveDecorator(classDeclaration.decorators[i])) { directiveInfo = this.visitDirectiveDecorator(classDeclaration.decorators[i]); members = this.visitMembers(classDeclaration.members); return { description, inputs: members.inputs, outputs: members.outputs, properties: members.properties, methods: members.methods, indexSignatures: members.indexSignatures, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags }; } else if (this.isServiceDecorator(classDeclaration.decorators[i])) { members = this.visitMembers(classDeclaration.members); return [{ fileName, className, description, methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor }]; } else if (this.isPipeDecorator(classDeclaration.decorators[i]) || this.isModuleDecorator(classDeclaration.decorators[i])) { return [{ fileName, className, description, jsdoctags: jsdoctags }]; } } } else if (description) { members = this.visitMembers(classDeclaration.members); return [{ description, methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor }]; } else { members = this.visitMembers(classDeclaration.members); return [{ methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor }]; } return []; } private visitFunctionDeclaration(method) { let mapTypes = function(type) { switch (type) { case 94: return 'Null'; case 118: return 'Any'; case 121: return 'Boolean'; case 129: return 'Never'; case 132: return 'Number'; case 134: return 'String'; case 137: return 'Undefined'; case 157: return 'TypeReference'; } } let visitArgument = function(arg) { var result = { name: arg.name.text, type: mapTypes(arg.type.kind) }; if (arg.type.kind === 157) { //try replace TypeReference with typeName if (arg.type.typeName) { result.type = arg.type.typeName.text; } } return result; } var result = { name: method.name.text, args: method.parameters ? method.parameters.map((prop) => visitArgument(prop)) : [], returnType: this.visitType(method.type) }, jsdoctags = JSDocTagsParser.getJSDocs(method), markedtags = function(tags) { var mtags = tags; _.forEach(mtags, (tag) => { tag.comment = marked(LinkParser.resolveLinks(tag.comment)); }); return mtags; }; if (method.modifiers) { if (method.modifiers.length > 0) { result.modifierKind = method.modifiers[0].kind; } } if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { result.jsdoctags = markedtags(jsdoctags[0].tags); } } return result; } private visitVariableDeclaration(node) { if( node.declarationList.declarations ) { let i = 0, len = node.declarationList.declarations.length; for(i; i<len; i++) { var result = { name: node.declarationList.declarations[i].name.text, defaultValue: node.declarationList.declarations[i].initializer ? this.stringifyDefaultValue(node.declarationList.declarations[i].initializer) : undefined } if(node.declarationList.declarations[i].type) { result.type = this.visitType(node.declarationList.declarations[i].type); } return result; } } } private visitEnumDeclaration(fileName, node) { if( node.declarationList.declarations ) { let i = 0, len = node.declarationList.declarations.length; for(i; i<len; i++) { if(node.declarationList.declarations[i].type) { if(node.declarationList.declarations[i].type.typeName && node.declarationList.declarations[i].type.typeName.text === 'Routes') { RouterParser.addRoute({ name: node.declarationList.declarations[i].name.text, data: generate(node.declarationList.declarations[i].initializer) }); return [{ routes: generate(node.declarationList.declarations[i].initializer) }]; } } } } return []; } private getRouteIO(filename, sourceFile) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var res = sourceFile.statements.reduce((directive, statement) => { if (statement.kind === ts.SyntaxKind.VariableStatement) { return directive.concat(this.visitEnumDeclaration(filename, statement)); } return directive; }, []) return res[0] || {}; } private getComponentIO(filename: string, sourceFile) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var res = sourceFile.statements.reduce((directive, statement) => { if (statement.kind === ts.SyntaxKind.ClassDeclaration) { return directive.concat(this.visitClassDeclaration(filename, statement)); } return directive; }, []) return res[0] || {}; } private getInterfaceIO(filename: string, sourceFile, node) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ var res = sourceFile.statements.reduce((directive, statement) => { if (statement.kind === ts.SyntaxKind.InterfaceDeclaration) { if (statement.pos === node.pos && statement.end === node.end) { return directive.concat(this.visitClassDeclaration(filename, statement)); } } return directive; }, []) return res[0] || {}; } private getComponentOutputs(props: NodeObject[]): string[] { return this.getSymbolDeps(props, 'outputs'); } private getComponentProviders(props: NodeObject[]): Deps[] { return this.getSymbolDeps(props, 'providers').map((name) => { return this.parseDeepIndentifier(name); }); } private getComponentViewProviders(props: NodeObject[]): Deps[] { return this.getSymbolDeps(props, 'viewProviders').map((name) => { return this.parseDeepIndentifier(name); }); } private getComponentDirectives(props: NodeObject[]): Deps[] { return this.getSymbolDeps(props, 'directives').map((name) => { let identifier = this.parseDeepIndentifier(name); identifier.selector = this.findComponentSelectorByName(name); identifier.label = ''; return identifier; }); } private parseDeepIndentifier(name: string): any { let nsModule = name.split('.'), type = this.getType(name); if (nsModule.length > 1) { // cache deps with the same namespace (i.e Shared.*) if (this.__nsModule[nsModule[0]]) { this.__nsModule[nsModule[0]].push(name) } else { this.__nsModule[nsModule[0]] = [name]; } return { ns: nsModule[0], name, type: type } } return { name, type: type }; } private getComponentTemplateUrl(props: NodeObject[]): string[] { return this.sanitizeUrls(this.getSymbolDeps(props, 'templateUrl')); } private getComponentTemplate(props: NodeObject[]): string { let t = this.getSymbolDeps(props, 'template', true).pop() if(t) { t = detectIndent(t, 0); t = t.replace(/\n/, ''); t = t.replace(/ +$/gm, ''); } return t; } private getComponentStyleUrls(props: NodeObject[]): string[] { return this.sanitizeUrls(this.getSymbolDeps(props, 'styleUrls')); } private getComponentStyles(props: NodeObject[]): string[] { return this.getSymbolDeps(props, 'styles'); } private getComponentModuleId(props: NodeObject[]): string { return this.getSymbolDeps(props, 'moduleId').pop(); } private getComponentChangeDetection(props: NodeObject[]): string { return this.getSymbolDeps(props, 'changeDetection').pop(); } private getComponentEncapsulation(props: NodeObject[]): string[] { return this.getSymbolDeps(props, 'encapsulation'); } private sanitizeUrls(urls: string[]) { return urls.map(url => url.replace('./', '')); } private getSymbolDepsObject(props: NodeObject[], type: string, multiLine?: boolean): Object { let deps = props.filter((node: NodeObject) => { return node.name.text === type; }); let parseProperties = (node: NodeObject): Object => { let obj = {}; (node.initializer.properties || []).forEach((prop: NodeObject) => { obj[prop.name.text] = prop.initializer.text; }); return obj; }; return deps.map(parseProperties).pop(); } private getSymbolDepsRaw(props: NodeObject[], type: string, multiLine?: boolean): any { let deps = props.filter((node: NodeObject) => { return node.name.text === type; }); return deps || []; } private getSymbolDeps(props: NodeObject[], type: string, multiLine?: boolean): string[] { let deps = props.filter((node: NodeObject) => { return node.name.text === type; }); let parseSymbolText = (text: string) => { if (text.indexOf('/') !== -1 && !multiLine) { text = text.split('/').pop(); } return [ text ]; }; let buildIdentifierName = (node: NodeObject, name = '') => { if (node.expression) { name = name ? `.${name}` : name; let nodeName = this.unknown; if (node.name) { nodeName = node.name.text; } else if (node.text) { nodeName = node.text; } else if (node.expression) { if (node.expression.text) { nodeName = node.expression.text;