import { getNamesCompareFn, mergeTagsAndArgs, markedtags } from '../../../../utils/utils'; import { kindToType } from '../../../../utils/kind-to-type'; import * as _ from 'lodash'; import * as util from 'util'; import * as path from 'path'; import * as ts from 'typescript'; import { ConfigurationInterface } from '../../../interfaces/configuration.interface'; import { JsdocParserUtil } from '../../../../utils/jsdoc-parser.util'; import { ImportsUtil } from '../../../../utils/imports.util'; import { logger } from '../../../../logger'; const marked = require('8fold-marked'); export class ClassHelper { private jsdocParserUtil = new JsdocParserUtil(); private importsUtil = new ImportsUtil(); constructor( private typeChecker: ts.TypeChecker, private configuration: ConfigurationInterface) { } public stringifyDefaultValue(node: ts.Node): string { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ if (node.getText()) { return node.getText(); } else if (node.kind === ts.SyntaxKind.FalseKeyword) { return 'false'; } else if (node.kind === ts.SyntaxKind.TrueKeyword) { return 'true'; } } private visitTypeName(typeName: ts.Identifier) { if(typeName.text) { return typeName.text; } return `${this.visitTypeName(typeName.left)}.${this.visitTypeName(typeName.right)}`; } public visitType(node): string { let _return = 'void'; if (!node) { return _return; } if (node.typeName) { _return = this.visitTypeName(node.typeName); } else if (node.type) { if (node.type.kind) { _return = kindToType(node.type.kind); } if (node.type.typeName) { _return = this.visitTypeName(node.type.typeName); } if (node.type.typeArguments) { _return += '<'; const typeArguments = []; for (const argument of node.type.typeArguments) { typeArguments.push(this.visitType(argument)); } _return += typeArguments.join(' | '); _return += '>'; } if (node.type.elementType) { const _firstPart = this.visitType(node.type.elementType); _return = _firstPart + kindToType(node.type.kind); } if (node.type.types && ts.isUnionTypeNode(node.type)) { _return = ''; let i = 0; let len = node.type.types.length; for (i; i < len; i++) { let type = node.type.types[i]; _return += kindToType(type.kind); if (ts.isLiteralTypeNode(type) && type.literal) { _return += '"' + type.literal.text + '"'; } if (type.typeName) { _return += this.visitTypeName(type.typeName); } if (i < len - 1) { _return += ' | '; } } } } else if (node.elementType) { _return = kindToType(node.elementType.kind) + kindToType(node.kind); } else if (node.types && ts.isUnionTypeNode(node)) { _return = ''; let i = 0; let len = node.types.length; for (i; i < len; i++) { let type = node.types[i]; _return += kindToType(type.kind); if (ts.isLiteralTypeNode(type) && type.literal) { _return += '"' + type.literal.text + '"'; } if (type.typeName) { _return += this.visitTypeName(type.typeName); } if (i < len - 1) { _return += ' | '; } } } else if (node.dotDotDotToken) { _return = 'any[]'; } else { _return = kindToType(node.kind); } if (node.typeArguments && node.typeArguments.length > 0) { _return += '<'; for (const argument of node.typeArguments) { _return += this.visitType(argument); } _return += '>'; } return _return; } public visitClassDeclaration( fileName: string, classDeclaration: ts.ClassDeclaration | ts.InterfaceDeclaration, sourceFile?: ts.SourceFile): any { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ let symbol = this.typeChecker.getSymbolAtLocation(classDeclaration.name); let description = ''; if (symbol) { description = marked(ts.displayPartsToString(symbol.getDocumentationComment())); } let className = classDeclaration.name.text; let directiveInfo; let members; let implementsElements = []; let extendsElement; let jsdoctags = []; if (typeof ts.getClassImplementsHeritageClauseElements !== 'undefined') { let implementedTypes = ts.getClassImplementsHeritageClauseElements(classDeclaration); if (implementedTypes) { let i = 0; let len = implementedTypes.length; for (i; i < len; i++) { if (implementedTypes[i].expression) { implementsElements.push(implementedTypes[i].expression.text); } } } } if (typeof ts.getClassExtendsHeritageClauseElement !== 'undefined') { let extendsTypes = ts.getClassExtendsHeritageClauseElement(classDeclaration); if (extendsTypes) { if (extendsTypes.expression) { extendsElement = extendsTypes.expression.text; } } } if (symbol) { if (symbol.valueDeclaration) { jsdoctags = this.jsdocParserUtil.getJSDocs(symbol.valueDeclaration); } } if (classDeclaration.decorators) { for (let i = 0; i < classDeclaration.decorators.length; i++) { if (this.isDirectiveDecorator(classDeclaration.decorators[i])) { directiveInfo = this.visitDirectiveDecorator(classDeclaration.decorators[i], sourceFile); members = this.visitMembers(classDeclaration.members, sourceFile); return { description, inputs: members.inputs, outputs: members.outputs, hostBindings: members.hostBindings, hostListeners: members.hostListeners, properties: members.properties, methods: members.methods, indexSignatures: members.indexSignatures, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElement, implements: implementsElements, accessors: members.accessors }; } else if (this.isServiceDecorator(classDeclaration.decorators[i])) { members = this.visitMembers(classDeclaration.members, sourceFile); return [{ fileName, className, description, methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElement, implements: implementsElements, accessors: members.accessors }]; } else if (this.isPipeDecorator(classDeclaration.decorators[i])) { members = this.visitMembers(classDeclaration.members, sourceFile); return [{ fileName, className, description, jsdoctags: jsdoctags, properties: members.properties, methods: members.methods }]; } else if (this.isModuleDecorator(classDeclaration.decorators[i])) { return [{ fileName, className, description, jsdoctags: jsdoctags }]; } else { members = this.visitMembers(classDeclaration.members, sourceFile); return [{ description, methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElement, implements: implementsElements, accessors: members.accessors }]; } } } else if (description) { members = this.visitMembers(classDeclaration.members, sourceFile); return [{ description, methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElement, implements: implementsElements, accessors: members.accessors }]; } else { members = this.visitMembers(classDeclaration.members, sourceFile); return [{ methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElement, implements: implementsElements, accessors: members.accessors }]; } return []; } private visitDirectiveDecorator(decorator, sourceFile: ts.SourceFile) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ let selector; let exportAs; let properties; if (decorator.expression.arguments.length > 0) { let firstArgument = decorator.expression.arguments[0], properties; if (firstArgument.kind && firstArgument.kind === ts.SyntaxKind.ObjectLiteralExpression) { properties = decorator.expression.arguments[0].properties; } let searchInProperties = () => { for (let 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; } } } if (properties) { // if decorator.expression.arguments[0].kind && decorator.expression.arguments[0].kind === ObjectLiteralExpression = 178 // we have object literal definition of the decorator searchInProperties(); } else { // if not, may be it is an import properties = this.importsUtil.merge(firstArgument.text, sourceFile); searchInProperties(); } } return { selector, exportAs }; } private isDirectiveDecorator(decorator: ts.Decorator): boolean { if (decorator.expression.expression) { let decoratorIdentifierText = decorator.expression.expression.text; return decoratorIdentifierText === 'Directive' || decoratorIdentifierText === 'Component'; } else { return false; } } private isServiceDecorator(decorator) { return (decorator.expression.expression) ? decorator.expression.expression.text === 'Injectable' : false; } private addAccessor(accessors, nodeAccessor, sourceFile) { let nodeName = ''; if (nodeAccessor.name) { nodeName = nodeAccessor.name.escapedText; let jsdoctags = this.jsdocParserUtil.getJSDocs(nodeAccessor); if (!accessors[nodeName]) { accessors[nodeName] = { 'name': nodeName, 'setSignature': undefined, 'getSignature': undefined } } if (nodeAccessor.kind === ts.SyntaxKind.SetAccessor) { let setSignature = { 'name': nodeName, 'type': 'void', 'args': nodeAccessor.parameters.map((param) => { return { 'name': param.name.escapedText, 'type': (param.type) ? kindToType(param.type.kind) : '' }; }), returnType: (nodeAccessor.type) ? this.visitType(nodeAccessor.type) : 'void', line: this.getPosition(nodeAccessor, sourceFile).line + 1 }; if (nodeAccessor.jsDoc && nodeAccessor.jsDoc.length >= 1) { let comment = nodeAccessor.jsDoc[0].comment; if (typeof comment !== 'undefined') { setSignature.description = marked(comment); } } if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { setSignature.jsdoctags = markedtags(jsdoctags[0].tags); } } if (setSignature.jsdoctags && setSignature.jsdoctags.length > 0) { setSignature.jsdoctags = mergeTagsAndArgs(setSignature.args, setSignature.jsdoctags); } else if (setSignature.args && setSignature.args.length > 0) { setSignature.jsdoctags = mergeTagsAndArgs(setSignature.args); } accessors[nodeName].setSignature = setSignature; } if (nodeAccessor.kind === ts.SyntaxKind.GetAccessor) { let getSignature = { 'name': nodeName, 'type': (nodeAccessor.type) ? kindToType(nodeAccessor.type.kind) : '', returnType: (nodeAccessor.type) ? this.visitType(nodeAccessor.type) : '', line: this.getPosition(nodeAccessor, sourceFile).line + 1 } if (nodeAccessor.jsDoc && nodeAccessor.jsDoc.length >= 1) { let comment = nodeAccessor.jsDoc[0].comment; if (typeof comment !== 'undefined') { getSignature.description = marked(comment); } } if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { getSignature.jsdoctags = markedtags(jsdoctags[0].tags); } } accessors[nodeName].getSignature = getSignature; } } } private visitMembers(members, sourceFile) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ let inputs = []; let outputs = []; let hostBindings = []; let hostListeners = []; let methods = []; let properties = []; let indexSignatures = []; let kind; let inputDecorator; let hostBinding; let hostListener; let constructor; let outDecorator; let accessors = {}; let result = {}; for (let i = 0; i < members.length; i++) { // Allows typescript guess type when using ts.is* let member = members[i]; inputDecorator = this.getDecoratorOfType(member, 'Input'); outDecorator = this.getDecoratorOfType(member, 'Output'); hostBinding = this.getDecoratorOfType(member, 'HostBinding'); hostListener = this.getDecoratorOfType(member, 'HostListener'); kind = member.kind; if (inputDecorator) { inputs.push(this.visitInputAndHostBinding(member, inputDecorator, sourceFile)); if (ts.isSetAccessorDeclaration(member)) { this.addAccessor(accessors, members[i], sourceFile); } } else if (outDecorator) { outputs.push(this.visitOutput(member, outDecorator, sourceFile)); } else if (hostBinding) { hostBindings.push(this.visitInputAndHostBinding(member, hostBinding, sourceFile)); } else if (hostListener) { hostListeners.push(this.visitHostListener(member, hostListener, sourceFile)); } else if (!this.isHiddenMember(member)) { if (!(this.isPrivate(member) && this.configuration.mainData.disablePrivate)) { if (!(this.isInternal(member) && this.configuration.mainData.disableInternal)) { if (!(this.isProtected(member) && this.configuration.mainData.disableProtected)) { if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) { methods.push(this.visitMethodDeclaration(member, sourceFile)); } else if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) { properties.push(this.visitProperty(member, sourceFile)); } else if (ts.isCallSignatureDeclaration(member)) { properties.push(this.visitCallDeclaration(member, sourceFile)); } else if (ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) { this.addAccessor(accessors, members[i], sourceFile); } else if (ts.isIndexSignatureDeclaration(member)) { indexSignatures.push(this.visitIndexDeclaration(member, sourceFile)); } else if (ts.isConstructorDeclaration(member)) { let _constructorProperties = this.visitConstructorProperties(member, sourceFile); let j = 0; let len = _constructorProperties.length; for (j; j < len; j++) { properties.push(_constructorProperties[j]); } constructor = this.visitConstructorDeclaration(member, sourceFile); } } } } } } inputs.sort(getNamesCompareFn()); outputs.sort(getNamesCompareFn()); hostBindings.sort(getNamesCompareFn()); hostListeners.sort(getNamesCompareFn()); properties.sort(getNamesCompareFn()); methods.sort(getNamesCompareFn()); indexSignatures.sort(getNamesCompareFn()); result = { inputs, outputs, hostBindings, hostListeners, methods, properties, indexSignatures, kind, constructor } if (Object.keys(accessors).length) { result['accessors'] = accessors; } return result; } private visitCallDeclaration(method: ts.CallSignatureDeclaration, sourceFile: ts.SourceFile) { let result: any = { id: 'call-declaration-' + Date.now(), description: marked(ts.displayPartsToString(method.symbol.getDocumentationComment())), args: method.parameters ? method.parameters.map((prop) => this.visitArgument(prop)) : [], returnType: this.visitType(method.type), line: this.getPosition(method, sourceFile).line + 1 }; let jsdoctags = this.jsdocParserUtil.getJSDocs(method); if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { result.jsdoctags = markedtags(jsdoctags[0].tags); } } return result; } private visitIndexDeclaration(method: ts.IndexSignatureDeclaration, sourceFile?: ts.SourceFile) { return { id: 'index-declaration-' + Date.now(), description: marked(ts.displayPartsToString(method.symbol.getDocumentationComment())), args: method.parameters ? method.parameters.map((prop) => this.visitArgument(prop)) : [], returnType: this.visitType(method.type), line: this.getPosition(method, sourceFile).line + 1 }; } 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 isProtected(member): boolean { if (member.modifiers) { const isProtected: boolean = member.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ProtectedKeyword); if (isProtected) { 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 visitConstructorDeclaration(method: ts.ConstructorDeclaration, sourceFile?: ts.SourceFile) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ let result: any = { name: 'constructor', description: '', args: method.parameters ? method.parameters.map((prop) => this.visitArgument(prop)) : [], line: this.getPosition(method, sourceFile).line + 1 }; let jsdoctags = this.jsdocParserUtil.getJSDocs(method); if (method.symbol) { result.description = marked(ts.displayPartsToString(method.symbol.getDocumentationComment())); } if (method.modifiers) { if (method.modifiers.length > 0) { let kinds = method.modifiers.map((modifier) => { return modifier.kind; }).reverse(); if (_.indexOf(kinds, ts.SyntaxKind.PublicKeyword) !== -1 && _.indexOf(kinds, ts.SyntaxKind.StaticKeyword) !== -1) { kinds = kinds.filter((kind) => kind !== ts.SyntaxKind.PublicKeyword); } result.modifierKind = kinds; } } if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { result.jsdoctags = markedtags(jsdoctags[0].tags); } } if (result.jsdoctags && result.jsdoctags.length > 0) { result.jsdoctags = mergeTagsAndArgs(result.args, result.jsdoctags); } else if (result.args.length > 0) { result.jsdoctags = mergeTagsAndArgs(result.args); } return result; } private getDecoratorOfType(node, decoratorType) { let decorators = node.decorators || []; for (let i = 0; i < decorators.length; i++) { if (decorators[i].expression.expression) { if (decorators[i].expression.expression.text === decoratorType) { return decorators[i]; } } } return undefined; } private visitProperty(property: ts.PropertySignature, sourceFile) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ let result: any = { name: property.name.text, defaultValue: property.initializer ? this.stringifyDefaultValue(property.initializer) : undefined, type: this.visitType(property), description: '', line: this.getPosition(property, sourceFile).line + 1 }; let jsdoctags; if (property.jsDoc) { jsdoctags = this.jsdocParserUtil.getJSDocs(property); } if (property.symbol) { result.description = marked(ts.displayPartsToString(property.symbol.getDocumentationComment())); } if (property.decorators) { result.decorators = this.formatDecorators(property.decorators); } if (property.modifiers) { if (property.modifiers.length > 0) { let kinds = property.modifiers.map((modifier) => { return modifier.kind; }).reverse(); if (_.indexOf(kinds, ts.SyntaxKind.PublicKeyword) !== -1 && _.indexOf(kinds, ts.SyntaxKind.StaticKeyword) !== -1) { kinds = kinds.filter((kind) => kind !== ts.SyntaxKind.PublicKeyword); } result.modifierKind = kinds; } } if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { result.jsdoctags = markedtags(jsdoctags[0].tags); } } return result; } private visitConstructorProperties(constr, sourceFile) { let that = this; if (constr.parameters) { let _parameters = []; let i = 0; let len = constr.parameters.length; for (i; i < len; i++) { if (this.isPublic(constr.parameters[i])) { _parameters.push(this.visitProperty(constr.parameters[i], sourceFile)); } } return _parameters; } else { return []; } } private isPublic(member): boolean { if (member.modifiers) { const isPublic: boolean = member.modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.PublicKeyword); if (isPublic) { return true; } } return this.isHiddenMember(member); } 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 visitInputAndHostBinding(property, inDecorator, sourceFile?) { let inArgs = inDecorator.expression.arguments; let _return: any = {}; _return.name = (inArgs.length > 0) ? inArgs[0].text : property.name.text; _return.defaultValue = property.initializer ? this.stringifyDefaultValue(property.initializer) : undefined; if (!_return.description) { if (property.jsDoc) { if (property.jsDoc.length > 0) { if (typeof property.jsDoc[0].comment !== 'undefined') { _return.description = marked(property.jsDoc[0].comment); } } } } _return.line = this.getPosition(property, sourceFile).line + 1; if (property.type) { _return.type = this.visitType(property); } else { // handle NewExpression if (property.initializer) { if (ts.isNewExpression(property.initializer)) { if (property.initializer.expression) { _return.type = property.initializer.expression.text; } } } } if (property.kind === ts.SyntaxKind.SetAccessor) { // For setter accessor, find type in first parameter if (property.parameters && property.parameters.length === 1) { if (property.parameters[0].type) { _return.type = kindToType(property.parameters[0].type.kind); } } } return _return; } private formatDecorators(decorators) { let _decorators = []; _.forEach(decorators, (decorator: any) => { if (decorator.expression) { if (decorator.expression.text) { _decorators.push({ name: decorator.expression.text }); } if (decorator.expression.expression) { let info: any = { name: decorator.expression.expression.text }; if (decorator.expression.expression.arguments) { if (decorator.expression.expression.arguments.length > 0) { info.args = decorator.expression.expression.arguments; } } _decorators.push(info); } } }); return _decorators; } private visitMethodDeclaration(method: ts.MethodDeclaration, sourceFile: ts.SourceFile) { let result: any = { name: method.name.text, args: method.parameters ? method.parameters.map((prop) => this.visitArgument(prop)) : [], returnType: this.visitType(method.type), line: this.getPosition(method, sourceFile).line + 1 }; let jsdoctags = this.jsdocParserUtil.getJSDocs(method); if (typeof method.type === 'undefined') { // Try to get inferred type if (method.symbol) { let symbol: ts.Symbol = method.symbol; if (symbol.valueDeclaration) { let symbolType = this.typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration); if (symbolType) { try { const signature = this.typeChecker.getSignatureFromDeclaration(method); const returnType = signature.getReturnType(); result.returnType = this.typeChecker.typeToString(returnType); // tslint:disable-next-line:no-empty } catch (error) { } } } } } if (method.symbol) { result.description = marked(ts.displayPartsToString(method.symbol.getDocumentationComment())); } if (method.decorators) { result.decorators = this.formatDecorators(method.decorators); } if (method.modifiers) { if (method.modifiers.length > 0) { let kinds = method.modifiers.map((modifier) => { return modifier.kind; }).reverse(); if (_.indexOf(kinds, ts.SyntaxKind.PublicKeyword) !== -1 && _.indexOf(kinds, ts.SyntaxKind.StaticKeyword) !== -1) { kinds = kinds.filter((kind) => kind !== ts.SyntaxKind.PublicKeyword); } result.modifierKind = kinds; } } if (jsdoctags && jsdoctags.length >= 1) { if (jsdoctags[0].tags) { result.jsdoctags = markedtags(jsdoctags[0].tags); } } if (result.jsdoctags && result.jsdoctags.length > 0) { result.jsdoctags = mergeTagsAndArgs(result.args, result.jsdoctags); } else if (result.args.length > 0) { result.jsdoctags = mergeTagsAndArgs(result.args); } return result; } private isPipeDecorator(decorator) { return (decorator.expression.expression) ? decorator.expression.expression.text === 'Pipe' : false; } private isModuleDecorator(decorator) { return (decorator.expression.expression) ? decorator.expression.expression.text === 'NgModule' : false; } private visitOutput(property: ts.PropertyDeclaration, outDecorator: ts.Decorator, sourceFile?: ts.SourceFile) { let inArgs = outDecorator.expression.arguments; let _return: any = { name: (inArgs.length > 0) ? inArgs[0].text : property.name.text, defaultValue: property.initializer ? this.stringifyDefaultValue(property.initializer) : undefined }; if (property.symbol) { _return.description = marked(ts.displayPartsToString(property.symbol.getDocumentationComment())); } if (!_return.description) { if (property.jsDoc && property.jsDoc.length > 0) { if (typeof property.jsDoc[0].comment !== 'undefined') { _return.description = marked(property.jsDoc[0].comment); } } } _return.line = this.getPosition(property, sourceFile).line + 1; if (property.type) { _return.type = this.visitType(property); } else { // handle NewExpression if (property.initializer) { if (ts.isNewExpression(property.initializer)) { if (property.initializer.expression) { _return.type = property.initializer.expression.text; } } } } return _return; } private visitArgument(arg: ts.ParameterDeclaration) { let _result: any = { name: arg.name.text, type: this.visitType(arg) }; if (arg.dotDotDotToken) { _result.dotDotDotToken = true; } if (arg.questionToken) { _result.optional = true; } if (arg.type) { if (arg.type.kind) { if (ts.isFunctionTypeNode(arg.type)) { _result.function = arg.type.parameters ? arg.type.parameters.map((prop) => this.visitArgument(prop)) : []; } } } return _result; } private getPosition(node: ts.Node, sourceFile: ts.SourceFile): ts.LineAndCharacter { let position: ts.LineAndCharacter; if (node.name && node.name.end) { position = ts.getLineAndCharacterOfPosition(sourceFile, node.name.end); } else { position = ts.getLineAndCharacterOfPosition(sourceFile, node.pos); } return position; } private visitHostListener(property, hostListenerDecorator, sourceFile?) { let inArgs = hostListenerDecorator.expression.arguments; let _return: any = {}; _return.name = (inArgs.length > 0) ? inArgs[0].text : property.name.text; _return.args = property.parameters ? property.parameters.map((prop) => this.visitArgument(prop)) : []; _return.argsDecorator = (inArgs.length > 1) ? inArgs[1].elements.map((prop) => { return prop.text; }) : []; if (property.symbol) { _return.description = marked(ts.displayPartsToString(property.symbol.getDocumentationComment())); } if (!_return.description) { if (property.jsDoc) { if (property.jsDoc.length > 0) { if (typeof property.jsDoc[0].comment !== 'undefined') { _return.description = marked(property.jsDoc[0].comment); } } } } _return.line = this.getPosition(property, sourceFile).line + 1; return _return; } }