UNPKG

@compodoc/compodoc

Version:

The missing documentation tool for your Angular application

1,230 lines (1,142 loc) 64.1 kB
import * as _ from 'lodash'; import { ts, SyntaxKind } from 'ts-morph'; import { getNamesCompareFn, mergeTagsAndArgs, markedtags } from '../../../../../utils/utils'; import { kindToType } from '../../../../../utils/kind-to-type'; import { JsdocParserUtil } from '../../../../../utils/jsdoc-parser.util'; import { isIgnore } from '../../../../../utils'; import AngularVersionUtil from '../../../../..//utils/angular-version.util'; import BasicTypeUtil from '../../../../../utils/basic-type.util'; import { StringifyObjectLiteralExpression } from '../../../../../utils/object-literal-expression.util'; import DependenciesEngine from '../../../../engines/dependencies.engine'; import Configuration from '../../../../configuration'; import { StringifyArrowFunction } from '../../../../../utils/arrow-function.util'; import { getNodeDecorators, nodeHasDecorator } from '../../../../../utils/node.util'; import { markedAcl } from '../../../../../utils/marked.acl'; const crypto = require('crypto'); export class ClassHelper { private jsdocParserUtil = new JsdocParserUtil(); constructor(private typeChecker: ts.TypeChecker) {} /** * HELPERS */ public stringifyDefaultValue(node: ts.Node): string { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ if (node && (node as any).getText && node.getText()) { return node.getText(); } else if (node && node.kind === SyntaxKind.FalseKeyword) { return 'false'; } else if (node && node.kind === SyntaxKind.TrueKeyword) { return 'true'; } return ''; } private checkForDeprecation(tags: any[], result: { [key in string | number]: any }) { _.forEach(tags, tag => { if (tag.tagName && tag.tagName.text && tag.tagName.text.indexOf('deprecated') > -1) { result.deprecated = true; result.deprecationMessage = tag.comment || ''; } }); } /** * Process JSDoc tags and apply them to a result object */ private processJSDocTags( jsdoctags: any, result: any, includeTagsArray: boolean = true ): void { if (jsdoctags && jsdoctags.length >= 1) { const jsdoc = jsdoctags[0]; if (jsdoc && jsdoc.tags) { this.checkForDeprecation(jsdoc.tags as unknown as any[], result); if (includeTagsArray) { result.jsdoctags = markedtags(jsdoc.tags as unknown as any[]); } } } } /** * Extract and process JSDoc comment for a node */ private extractAndProcessJSDocComment( node: any, sourceFile: ts.SourceFile, result: any ): void { if (node.jsDoc) { const comment = this.jsdocParserUtil.getMainCommentOfNode(node, sourceFile); if (typeof comment !== 'undefined') { const cleanedDescription = this.jsdocParserUtil.parseComment(comment); result.rawdescription = cleanedDescription; result.description = markedAcl(cleanedDescription); } } } /** * Initialize common fields for documented items */ private initializeDocumentationFields(): { deprecated: boolean; deprecationMessage: string; } { return { deprecated: false, deprecationMessage: '' }; } /** * Extract and filter modifier kinds from a node */ private extractModifierKinds(node: any): number[] | undefined { if (!node.modifiers || node.modifiers.length === 0) { return undefined; } let kinds = node.modifiers.map(modifier => modifier.kind); if ( _.indexOf(kinds, SyntaxKind.PublicKeyword) !== -1 && _.indexOf(kinds, SyntaxKind.StaticKeyword) !== -1 ) { kinds = kinds.filter(kind => kind !== SyntaxKind.PublicKeyword); } return kinds; } /** * Ensure private keyword is added for ECMAScript private fields */ private ensurePrivateKeyword(result: any, node: any): void { if (this.isPrivate(node)) { if (!result.modifierKind) { result.modifierKind = []; } const hasAlreadyPrivateKeyword = result.modifierKind.includes(SyntaxKind.PrivateKeyword); if (!hasAlreadyPrivateKeyword) { result.modifierKind.push(SyntaxKind.PrivateKeyword); } } } /** * Set fallback description from jsDoc[0].comment if no description exists */ private setFallbackDescription(result: any, node: any): void { if (!result.description && node.jsDoc && node.jsDoc.length > 0) { if (typeof node.jsDoc[0].comment !== 'undefined') { const rawDescription = node.jsDoc[0].comment; result.rawdescription = rawDescription; result.description = markedAcl(rawDescription); } } } private getDecoratorOfType(node, decoratorType) { let decorators = getNodeDecorators(node) || []; let result = []; const len = decorators.length; if (len > 1) { for (let i = 0; i < decorators.length; i++) { const expr = decorators[i].expression as any; if (expr.expression) { if (expr.expression.text === decoratorType) { result.push(decorators[i]); } } } if (result.length > 0) { return result; } } else { if (len === 1) { const expr = decorators[0].expression as any; if (expr && expr.expression) { if (expr.expression.text === decoratorType) { result.push(decorators[0]); return result; } } } } return undefined; } 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.arguments) { info.stringifiedArguments = this.stringifyArguments( decorator.expression.arguments ); } _decorators.push(info); } } }); return _decorators; } private handleFunction(arg): string { if (arg.function.length === 0) { return `${arg.name}${this.getOptionalString(arg)}: () => void`; } let argums = arg.function.map(argu => { let _result = DependenciesEngine.find(argu.type); if (_result) { if (_result.source === 'internal') { let path = _result.data.type; if (_result.data.type === 'class') { path = 'classe'; } return `${argu.name}${this.getOptionalString(arg)}: <a href="../${path}s/${ _result.data.name }.html">${argu.type}</a>`; } else { let path = AngularVersionUtil.getApiLink( _result.data, Configuration.mainData.angularVersion ); return `${argu.name}${this.getOptionalString( arg )}: <a href="${path}" target="_blank">${argu.type}</a>`; } } else if (BasicTypeUtil.isKnownType(argu.type)) { let path = BasicTypeUtil.getTypeUrl(argu.type); return `${argu.name}${this.getOptionalString( arg )}: <a href="${path}" target="_blank">${argu.type}</a>`; } else { if (argu.name && argu.type) { return `${argu.name}${this.getOptionalString(arg)}: ${argu.type}`; } else { if (argu.name) { return `${argu.name.text}`; } else { return ''; } } } }); return `${arg.name}${this.getOptionalString(arg)}: (${argums}) => void`; } private getOptionalString(arg): string { return arg.optional ? '?' : ''; } private stringifyArguments(args) { let stringifyArgs = []; stringifyArgs = args .map(arg => { const _result = DependenciesEngine.find(arg.type); if (_result) { if (_result.source === 'internal') { let path = _result.data.type; if (_result.data.type === 'class') { path = 'classe'; } return `${arg.name}${this.getOptionalString(arg)}: <a href="../${path}s/${ _result.data.name }.html">${arg.type}</a>`; } else { let path = AngularVersionUtil.getApiLink( _result.data, Configuration.mainData.angularVersion ); return `${arg.name}${this.getOptionalString( arg )}: <a href="${path}" target="_blank">${arg.type}</a>`; } } else if (arg.dotDotDotToken) { return `...${arg.name}: ${arg.type}`; } else if (arg.function) { return this.handleFunction(arg); } else if (arg.expression && arg.name) { return arg.expression.text + '.' + arg.name.text; } else if (arg.expression && arg.kind === SyntaxKind.NewExpression) { return 'new ' + arg.expression.text + '()'; } else if (arg.kind && arg.kind === SyntaxKind.StringLiteral) { return `'` + arg.text + `'`; } else if ( arg.kind && arg.kind === SyntaxKind.ArrayLiteralExpression && arg.elements && arg.elements.length > 0 ) { let i = 0, len = arg.elements.length, result = '['; for (i; i < len; i++) { result += `'` + arg.elements[i].text + `'`; if (i < len - 1) { result += ', '; } } result += ']'; return result; } else if ( arg.kind && arg.kind === SyntaxKind.ArrowFunction && arg.parameters && arg.parameters.length > 0 ) { return StringifyArrowFunction(arg); } else if (arg.kind && arg.kind === SyntaxKind.ObjectLiteralExpression) { return StringifyObjectLiteralExpression(arg); } else if (BasicTypeUtil.isKnownType(arg.type)) { const path = BasicTypeUtil.getTypeUrl(arg.type); return `${arg.name}${this.getOptionalString( arg )}: <a href="${path}" target="_blank">${arg.type}</a>`; } else { if (arg.type) { let finalStringifiedArgument = ''; let separator = ':'; if (arg.name) { finalStringifiedArgument += arg.name; } if ( arg.kind === SyntaxKind.AsExpression && arg.expression && arg.expression.text ) { finalStringifiedArgument += arg.expression.text; separator = ' as'; } if (arg.optional) { finalStringifiedArgument += this.getOptionalString(arg); } if (arg.type) { finalStringifiedArgument += separator + ' ' + this.visitType(arg.type); } return finalStringifiedArgument; } else if (arg.text) { return `${arg.text}`; } else { return `${arg.name}${this.getOptionalString(arg)}`; } } }) .join(', '); return stringifyArgs; } private getPosition(node: ts.Node, sourceFile: ts.SourceFile): ts.LineAndCharacter { let position: ts.LineAndCharacter; if ((node as any).name && (node as any).name.end) { position = ts.getLineAndCharacterOfPosition(sourceFile, (node as any).name.end); } else { position = ts.getLineAndCharacterOfPosition(sourceFile, node.pos); } return position; } private addAccessor(accessors, nodeAccessor, sourceFile) { let nodeName = ''; if (nodeAccessor.name) { nodeName = nodeAccessor.name.text; let jsdoctags = this.jsdocParserUtil.getJSDocs(nodeAccessor); if (!accessors[nodeName]) { accessors[nodeName] = { name: nodeName, setSignature: undefined, getSignature: undefined }; } if (nodeAccessor.kind === SyntaxKind.SetAccessor) { let setSignature: any = { name: nodeName, type: 'void', ...this.initializeDocumentationFields(), args: nodeAccessor.parameters.map(param => this.visitArgument(param)), returnType: nodeAccessor.type ? this.visitType(nodeAccessor.type) : 'void', line: this.getPosition(nodeAccessor, sourceFile).line + 1 }; this.extractAndProcessJSDocComment(nodeAccessor, sourceFile, setSignature); this.processJSDocTags(jsdoctags, setSignature); 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 === SyntaxKind.GetAccessor) { let getSignature: any = { name: nodeName, type: nodeAccessor.type ? kindToType(nodeAccessor.type.kind) : '', returnType: nodeAccessor.type ? this.visitType(nodeAccessor.type) : '', line: this.getPosition(nodeAccessor, sourceFile).line + 1 }; this.extractAndProcessJSDocComment(nodeAccessor, sourceFile, getSignature); this.processJSDocTags(jsdoctags, getSignature); accessors[nodeName].getSignature = getSignature; } } } private hasDecoratorType(decorator: ts.Decorator, ...types: string[]): boolean { if ((decorator.expression as any).expression) { const decoratorText = (decorator.expression as any).expression.text; return types.includes(decoratorText); } return false; } private isDirectiveDecorator(decorator: ts.Decorator): boolean { return this.hasDecoratorType(decorator, 'Directive', 'Component'); } private isServiceDecorator(decorator) { return this.hasDecoratorType(decorator, 'Injectable'); } private isPrivate(member): boolean { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ if (member.modifiers) { const isPrivate: boolean = member.modifiers.some( modifier => modifier.kind === SyntaxKind.PrivateKeyword ); if (isPrivate) { return true; } } // Check for ECMAScript Private Fields if (member.name && member.name.escapedText) { const isPrivate: boolean = member.name.escapedText.indexOf('#') === 0; if (isPrivate) { return true; } } return this.isHiddenMember(member); } private isProtected(member): boolean { if (member.modifiers) { const isProtected: boolean = member.modifiers.some( modifier => modifier.kind === 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 isPublic(member): boolean { if (member.modifiers) { const isPublic: boolean = member.modifiers.some( modifier => modifier.kind === 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 isPipeDecorator(decorator) { return this.hasDecoratorType(decorator, 'Pipe'); } private isControllerDecorator(decorator) { return this.hasDecoratorType(decorator, 'Controller'); } private isModuleDecorator(decorator) { return this.hasDecoratorType(decorator, 'NgModule', 'Module'); } /** * VISITERS */ public visitClassDeclaration( fileName: string, classDeclaration: ts.ClassDeclaration | ts.InterfaceDeclaration, sourceFile?: ts.SourceFile, astFile?: ts.SourceFile ): any { let symbol = this.typeChecker.getSymbolAtLocation(classDeclaration.name); let rawdescription = ''; let deprecation = this.initializeDocumentationFields(); let description = ''; let jsdoctags: any[] = []; if (symbol) { const comment = this.jsdocParserUtil.getMainCommentOfNode(classDeclaration, sourceFile); rawdescription = this.jsdocParserUtil.parseComment(comment); description = markedAcl(rawdescription); if (symbol.valueDeclaration && isIgnore(symbol.valueDeclaration)) { return [{ ignore: true }]; } if (symbol.declarations && symbol.declarations.length > 0) { let declarationsjsdoctags = this.jsdocParserUtil.getJSDocs(symbol.declarations[0]); this.processJSDocTags(declarationsjsdoctags, deprecation, false); if (isIgnore(symbol.declarations[0])) { return [{ ignore: true }]; } } if (symbol.valueDeclaration) { jsdoctags = this.jsdocParserUtil.getJSDocs(symbol.valueDeclaration) as unknown as any[]; if (jsdoctags && jsdoctags.length >= 1) { const jsdoc = jsdoctags[0] as any; if (jsdoc && jsdoc.tags) { const tempDeprecation = this.initializeDocumentationFields(); this.checkForDeprecation(jsdoc.tags, tempDeprecation); deprecation = tempDeprecation; jsdoctags = markedtags(jsdoc.tags); } } } } let className = classDeclaration.name.text; let members; let implementsElements = []; let extendsElements = []; if (typeof (ts as any).getEffectiveImplementsTypeNodes !== 'undefined') { let implementedTypes = (ts as any).getEffectiveImplementsTypeNodes(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 as any).getClassExtendsHeritageElement !== 'undefined') { if (astFile) { let interfaceOrClassNode = (astFile as any).getInterface(className); if (!interfaceOrClassNode) { interfaceOrClassNode = (astFile as any).getClass(className); } if (interfaceOrClassNode) { const extendsListRaw = interfaceOrClassNode.getExtends(); let extendsList = []; if (extendsListRaw) { if (Array.isArray(extendsListRaw)) { if (extendsListRaw.length > 0) { extendsListRaw.forEach(extendElement => { const extendElementExpression = extendElement.getExpression(); if (extendElementExpression) { const text = extendElementExpression.getText(); if (text) { extendsList.push(text); } } }); } } else { const extendElementExpression = extendsListRaw.getExpression(); if (extendElementExpression) { const text = extendElementExpression.getText(); if (text) { extendsList.push(text); } } } } extendsElements = extendsList; } } } members = this.visitMembers(classDeclaration.members, sourceFile); if (nodeHasDecorator(classDeclaration)) { const classDecorators = getNodeDecorators(classDeclaration); // Loop and search for official decorators at top-level : // Angular : @NgModule, @Component, @Directive, @Injectable, @Pipe // Nestjs : @Controller, @Module, @Injectable // Stencil : @Component let isDirective = false; let isService = false; let isPipe = false; let isModule = false; let isController = false; for (let a = 0; a < classDecorators.length; a++) { //console.log(classDeclaration.decorators[i].expression); // RETURN TOO EARLY FOR MANY DECORATORS !!!! // iterating through the decorators array we have to keep the flags `true` values from the previous loop iteration isDirective = isDirective || this.isDirectiveDecorator(classDecorators[a]); isService = isService || this.isServiceDecorator(classDecorators[a]); isPipe = isPipe || this.isPipeDecorator(classDecorators[a]); isModule = isModule || this.isModuleDecorator(classDecorators[a]); isController = isController || this.isControllerDecorator(classDecorators[a]); } if (isDirective) { return { deprecated: deprecation.deprecated, deprecationMessage: deprecation.deprecationMessage, description, rawdescription: rawdescription, 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: extendsElements, implements: implementsElements, accessors: members.accessors }; } else if (isService) { return [ { fileName, className, deprecated: deprecation.deprecated, deprecationMessage: deprecation.deprecationMessage, description, rawdescription: rawdescription, methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElements, implements: implementsElements, accessors: members.accessors } ]; } else if (isPipe) { return [ { fileName, className, deprecated: deprecation.deprecated, deprecationMessage: deprecation.deprecationMessage, description, rawdescription: rawdescription, jsdoctags: jsdoctags, properties: members.properties, methods: members.methods } ]; } else if (isModule) { return [ { fileName, className, deprecated: deprecation.deprecated, deprecationMessage: deprecation.deprecationMessage, description, rawdescription: rawdescription, jsdoctags: jsdoctags, methods: members.methods } ]; } else { return [ { deprecated: deprecation.deprecated, deprecationMessage: deprecation.deprecationMessage, description, rawdescription: rawdescription, methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElements, implements: implementsElements, accessors: members.accessors } ]; } } if (description) { return [ { deprecated: deprecation.deprecated, deprecationMessage: deprecation.deprecationMessage, description, rawdescription: rawdescription, inputs: members.inputs, outputs: members.outputs, hostBindings: members.hostBindings, hostListeners: members.hostListeners, methods: members.methods, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElements, implements: implementsElements, accessors: members.accessors } ]; } else { return [ { deprecated: deprecation.deprecated, deprecationMessage: deprecation.deprecationMessage, methods: members.methods, inputs: members.inputs, outputs: members.outputs, hostBindings: members.hostBindings, hostListeners: members.hostListeners, indexSignatures: members.indexSignatures, properties: members.properties, kind: members.kind, constructor: members.constructor, jsdoctags: jsdoctags, extends: extendsElements, implements: implementsElements, accessors: members.accessors } ]; } } private visitMembers(members: any, sourceFile: any) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ let inputs = []; let outputs = []; let methods = []; let properties = []; let indexSignatures = []; let kind; let inputDecorator; let hostBindings = []; let hostListeners = []; let constructor; let outputDecorator; 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'); outputDecorator = this.getDecoratorOfType(member, 'Output'); const parsedHostBindings = this.getDecoratorOfType(member, 'HostBinding'); const parsedHostListeners = this.getDecoratorOfType(member, 'HostListener'); kind = member.kind; if (isIgnore(member)) { continue; } if (this.isInternal(member) && Configuration.mainData.disableInternal) { continue; } if (inputDecorator && inputDecorator.length > 0) { inputs.push(this.visitInputAndHostBinding(member, inputDecorator[0], sourceFile)); if (ts.isSetAccessorDeclaration(member)) { this.addAccessor(accessors, members[i], sourceFile); } } else if (outputDecorator && outputDecorator.length > 0) { outputs.push(this.visitOutput(member, outputDecorator[0], sourceFile)); } else if (parsedHostBindings && parsedHostBindings.length > 0) { let k = 0; const lenHB = parsedHostBindings.length; for (k; k < lenHB; k++) { hostBindings.push( this.visitInputAndHostBinding(member, parsedHostBindings[k], sourceFile) ); } } else if (parsedHostListeners && parsedHostListeners.length > 0) { let l = 0; const lenHL = parsedHostListeners.length; for (l; l < lenHL; l++) { hostListeners.push( this.visitHostListener(member, parsedHostListeners[l], sourceFile) ); } } if (!this.isHiddenMember(member)) { if (!(this.isPrivate(member) && Configuration.mainData.disablePrivate)) { if (!(this.isInternal(member) && Configuration.mainData.disableInternal)) { if ( !(this.isProtected(member) && Configuration.mainData.disableProtected) ) { if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) { methods.push(this.visitMethodDeclaration(member, sourceFile)); } else if ( ts.isPropertyDeclaration(member) || ts.isPropertySignature(member) ) { if (!inputDecorator && !outputDecorator) { 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 visitTypeName(typeName: ts.Identifier) { if (typeName.escapedText) { return typeName.escapedText; } if (typeName.text) { return typeName.text; } if ((typeName as any).left && (typeName as any).right) { return this.visitTypeName((typeName as any).left) + '.' + this.visitTypeName((typeName as any).right); } return ''; } public visitTypeIndex(node): string { let _return = ''; if (!node) { return _return; } if ( node.type && node.type.kind === SyntaxKind.IndexedAccessType && node.type.indexType && node.type.indexType.literal ) { return this.visitTypeName(node.type.indexType.literal); } return _return; } 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 && !ts.isUnionTypeNode(node.type) && !ts.isTupleTypeNode(node.type) ) { _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.elementType.kind === SyntaxKind.ParenthesizedType) { _return = '(' + _firstPart + ')' + kindToType(node.type.kind); } } const parseTypesOrElements = (arr, separator) => { let i = 0; let len = arr.length; for (i; i < len; i++) { let type = arr[i]; if (type.elementType) { const _firstPart = this.visitType(type.elementType); if (type.elementType.kind === SyntaxKind.ParenthesizedType) { _return += '(' + _firstPart + ')' + kindToType(type.kind); } else { _return += _firstPart + kindToType(type.kind); } } else { if (ts.isLiteralTypeNode(type) && type.literal) { if ((type.literal as any).text) { _return += '"' + (type.literal as any).text + '"'; } else { _return += kindToType(type.literal.kind); } } else if ((type as any).typeName) { _return += this.visitTypeName((type as any).typeName); } else if (type.kind === SyntaxKind.RestType && type.type) { _return += '...' + this.visitType(type.type); } else { _return += kindToType(type.kind); } if (type.typeArguments) { _return += '<'; const typeArguments = []; for (const argument of type.typeArguments) { typeArguments.push(this.visitType(argument)); } _return += typeArguments.join(separator); _return += '>'; } } if (i < len - 1) { _return += separator; } } }; if (node.type.elements && ts.isTupleTypeNode(node.type)) { _return = '['; parseTypesOrElements(node.type.elements, ', '); _return += ']'; } if (node.type.types && ts.isUnionTypeNode(node.type)) { _return = ''; parseTypesOrElements(node.type.types, ' | '); } if (node.type.elementTypes) { let elementTypes = node.type.elementTypes; let i = 0; let len = elementTypes.length; if (len > 0) { _return = '['; for (i; i < len; i++) { let type = elementTypes[i]; if (type.kind === SyntaxKind.ArrayType && type.elementType) { _return += kindToType(type.elementType.kind); _return += kindToType(type.kind); } else if ((type as any).typeName) { // For type references, use the type name directly instead of kindToType + typeName _return += this.visitTypeName((type as any).typeName); } else { _return += kindToType(type.kind); } if (ts.isLiteralTypeNode(type) && type.literal) { if ((type.literal as any).text) { _return += '"' + (type.literal as any).text + '"'; } else { _return += kindToType(type.literal.kind); } } if (type.kind === SyntaxKind.RestType && type.type) { _return += '...' + this.visitType(type.type); } if ( type.kind === SyntaxKind.TypeReference && type.typeName && typeof type.typeName.escapedText !== 'undefined' && type.typeName.escapedText === '' ) { continue; } if (i < len - 1) { _return += ', '; } } _return += ']'; } } if ( node.type && node.type.kind === SyntaxKind.IndexedAccessType && node.type.objectType && node.type.objectType.typeName ) { _return = this.visitTypeName(node.type.objectType.typeName); } } else if (node.elementType) { _return = kindToType(node.elementType.kind) + kindToType(node.kind); if (node.elementType.typeName) { _return = this.visitTypeName(node.elementType.typeName) + 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]; if (ts.isLiteralTypeNode(type) && type.literal) { if ((type.literal as any).text) { _return += '"' + (type.literal as any).text + '"'; } else { _return += kindToType(type.literal.kind); } } else if ((type as any).typeName) { _return += this.visitTypeName((type as any).typeName); } else { _return += kindToType(type.kind); } if (i < len - 1) { _return += ' | '; } } } else if (node.dotDotDotToken) { _return = 'any[]'; } else { _return = kindToType(node.kind); if ( (_return === '' || _return === 'unknown') && node.initializer && node.initializer.kind && (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.Parameter) ) { _return = kindToType(node.initializer.kind); } if (node.kind === SyntaxKind.TypeParameter) { _return = node.name.text; } if (node.kind === SyntaxKind.LiteralType) { _return = node.literal.text; } } if (node.typeArguments && node.typeArguments.length > 0) { _return += '<'; let i = 0, len = node.typeArguments.length; for (i; i < len; i++) { let argument = node.typeArguments[i]; _return += this.visitType(argument); if (i >= 0 && i < len - 1) { _return += ', '; } } _return += '>'; } return _return; } private visitCallDeclaration(method: ts.CallSignatureDeclaration, sourceFile: ts.SourceFile) { let sourceCode = sourceFile.getText(); let hash = crypto.createHash('sha512').update(sourceCode).digest('hex'); let result: any = { id: 'call-declaration-' + hash, args: method.parameters ? method.parameters.map(prop => this.visitArgument(prop)) : [], returnType: this.visitType(method.type), line: this.getPosition(method, sourceFile).line + 1, ...this.initializeDocumentationFields() }; this.extractAndProcessJSDocComment(method, sourceFile, result); const jsdoctags = this.jsdocParserUtil.getJSDocs(method); this.processJSDocTags(jsdoctags, result); return result; } private visitIndexDeclaration( method: ts.IndexSignatureDeclaration, sourceFile?: ts.SourceFile ) { let sourceCode = sourceFile.getText(); let hash = crypto.createHash('sha512').update(sourceCode).digest('hex'); let result = { id: 'index-declaration-' + hash, args: method.parameters ? method.parameters.map(prop => this.visitArgument(prop)) : [], returnType: this.visitType(method.type), line: this.getPosition(method, sourceFile).line + 1, ...this.initializeDocumentationFields() }; this.extractAndProcessJSDocComment(method, sourceFile, result); const jsdoctags = this.jsdocParserUtil.getJSDocs(method); this.processJSDocTags(jsdoctags, result); return result; } private visitConstructorDeclaration( method: ts.ConstructorDeclaration, sourceFile?: ts.SourceFile ) { /** * Copyright https://github.com/ng-bootstrap/ng-bootstrap */ let result: any = { name: 'constructor', description: '', ...this.initializeDocumentationFields(), args: method.parameters ? method.parameters.map(prop => this.visitArgument(prop)) : [], line: this.getPosition(method, sourceFile).line + 1 }; this.extractAndProcessJSDocComment(method, sourceFile, result); const kinds = this.extractModifierKinds(method); if (kinds) { result.modifierKind = kinds; } const jsdoctags = this.jsdocParserUtil.getJSDocs(method); this.processJSDocTags(jsdoctags, result); 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 visitProperty(property: ts.PropertyDeclaration | ts.PropertySignature, sourceFile) { // PropertySignature (interfaces) don't have initializer, PropertyDeclaration (classes) do const initializer = ts.isPropertyDeclaration(property) ? property.initializer : undefined; // Extract property name, handling different node types: // - Identifier: regular property names // - PrivateIdentifier: ECMAScript private fields like #privateField // - ComputedPropertyName: computed names like ['__allAnd'] let propertyName = ''; // Check for mock objects first (for testing) if ((property.name as any).text) { propertyName = (property.name as any).text; } else if (ts.isIdentifier(property.name)) { propertyName = property.name.text; } else if (ts.isPrivateIdentifier(property.name)) { propertyName = property.name.text; // includes the # prefix } else if (ts.isComputedPropertyName(property.name)) { // Handle computed property names like ['__allAnd'] if (ts.is