diff --git a/typescript/mocks/tsParser/multipleLeadingComments.ts b/typescript/mocks/tsParser/multipleLeadingComments.ts new file mode 100644 index 00000000..a7f8b7c7 --- /dev/null +++ b/typescript/mocks/tsParser/multipleLeadingComments.ts @@ -0,0 +1,10 @@ +/** + * Not a license comment. + */ + +/** + * This is a test function + */ +export function test(a: string) { + return a; +} diff --git a/typescript/src/api-doc-types/AccessorInfoDoc.ts b/typescript/src/api-doc-types/AccessorInfoDoc.ts index 824ed57d..6d870091 100644 --- a/typescript/src/api-doc-types/AccessorInfoDoc.ts +++ b/typescript/src/api-doc-types/AccessorInfoDoc.ts @@ -1,4 +1,5 @@ import { Declaration } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { MethodMemberDoc } from './MethodMemberDoc'; import { PropertyMemberDoc } from './PropertyMemberDoc'; @@ -13,7 +14,10 @@ export class AccessorInfoDoc extends MethodMemberDoc { aliases = this.propertyDoc.aliases.map(alias => `${alias}:${this.accessorType}`); anchor = this.name; - constructor(public accessorType: 'get'|'set', public propertyDoc: PropertyMemberDoc, declaration: Declaration) { - super(propertyDoc.containerDoc, propertyDoc.symbol, declaration); + constructor(host: Host, + public accessorType: 'get'|'set', + public propertyDoc: PropertyMemberDoc, + declaration: Declaration) { + super(host, propertyDoc.containerDoc, propertyDoc.symbol, declaration); } } diff --git a/typescript/src/api-doc-types/ApiDoc.ts b/typescript/src/api-doc-types/ApiDoc.ts index bd494d53..1bd767b1 100644 --- a/typescript/src/api-doc-types/ApiDoc.ts +++ b/typescript/src/api-doc-types/ApiDoc.ts @@ -1,4 +1,5 @@ import { Declaration, Symbol, TypeChecker } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { FileInfo } from '../services/TsParser/FileInfo'; import { getContent } from '../services/TsParser/getContent'; import { ModuleDoc } from './ModuleDoc'; @@ -31,7 +32,7 @@ export abstract class BaseApiDoc implements ApiDoc { startingLine = this.fileInfo.location.start.line + (this.fileInfo.location.start.character ? 1 : 0); endingLine = this.fileInfo.location.end.line; - content = getContent(this.declaration); + content = this.host.getContent(this.declaration); path: string = ''; outputPath: string = ''; @@ -39,8 +40,10 @@ export abstract class BaseApiDoc implements ApiDoc { .replace(new RegExp("\." + this.fileInfo.extension + "$"), ""); typeChecker: TypeChecker = this.moduleDoc.typeChecker; - constructor(public moduleDoc: ModuleDoc, + constructor(public host: Host, + public moduleDoc: ModuleDoc, public symbol: Symbol, public declaration: Declaration, - public aliasSymbol?: Symbol) {} + public aliasSymbol?: Symbol) { + } } diff --git a/typescript/src/api-doc-types/ClassExportDoc.ts b/typescript/src/api-doc-types/ClassExportDoc.ts index 4d791955..691542a5 100644 --- a/typescript/src/api-doc-types/ClassExportDoc.ts +++ b/typescript/src/api-doc-types/ClassExportDoc.ts @@ -4,6 +4,7 @@ import { ClassLikeExportDoc } from '../api-doc-types/ClassLikeExportDoc'; import { MemberDoc } from '../api-doc-types/MemberDoc'; import { MethodMemberDoc } from '../api-doc-types/MethodMemberDoc'; import { ModuleDoc } from '../api-doc-types/ModuleDoc'; +import { Host } from '../services/ts-host/host'; /** * Classes are Class-like but also can contain static members @@ -15,11 +16,12 @@ export class ClassExportDoc extends ClassLikeExportDoc { statics: MemberDoc[] = []; isAbstract = this.declaration.modifiers && this.declaration.modifiers.some(modifier => modifier.kind === SyntaxKind.AbstractKeyword); - constructor( - moduleDoc: ModuleDoc, - symbol: Symbol, - aliasSymbol?: Symbol) { - super(moduleDoc, symbol, symbol.valueDeclaration!, aliasSymbol); + constructor(host: Host, + moduleDoc: ModuleDoc, + symbol: Symbol, + aliasSymbol?: Symbol) { + super(host, moduleDoc, symbol, symbol.valueDeclaration!, aliasSymbol); + if (symbol.exports) { this.statics = this.getMemberDocs(symbol.exports, moduleDoc.hidePrivateMembers); } @@ -41,10 +43,10 @@ export class ClassExportDoc extends ClassLikeExportDoc { constructorSymbol.getDeclarations()!.forEach(declaration => { if ((declaration as FunctionLikeDeclaration).body) { // This is the "real" declaration of the method - constructorDoc = new MethodMemberDoc(this, constructorSymbol, declaration, overloads); + constructorDoc = new MethodMemberDoc(this.host, this, constructorSymbol, declaration, overloads); } else { // This is an overload signature of the method - overloads.push(new MethodMemberDoc(this, constructorSymbol, declaration, overloads)); + overloads.push(new MethodMemberDoc(this.host, this, constructorSymbol, declaration, overloads)); } }); return constructorDoc || overloads.shift(); diff --git a/typescript/src/api-doc-types/ClassLikeExportDoc.ts b/typescript/src/api-doc-types/ClassLikeExportDoc.ts index 60541e80..41339d82 100644 --- a/typescript/src/api-doc-types/ClassLikeExportDoc.ts +++ b/typescript/src/api-doc-types/ClassLikeExportDoc.ts @@ -1,6 +1,15 @@ /* tslint:disable:no-bitwise */ /* tslint:disable:max-classes-per-file */ -import { Declaration, ExpressionWithTypeArguments, HeritageClause, Symbol, SymbolFlags, SyntaxKind, symbolName } from 'typescript'; +import { + Declaration, + ExpressionWithTypeArguments, + HeritageClause, + Symbol, + SymbolFlags, + symbolName, + SyntaxKind, +} from 'typescript'; +import { Host } from '../services/ts-host/host'; import { getDecorators, ParsedDecorator } from "../services/TsParser/getDecorators"; import { getTypeText } from '../services/TsParser/getTypeText'; @@ -23,15 +32,15 @@ export abstract class ClassLikeExportDoc extends ContainerExportDoc { descendants: ClassLikeExportDoc[] = []; typeParams = this.computeTypeParams(); - constructor( - moduleDoc: ModuleDoc, - symbol: Symbol, - declaration: Declaration, - aliasSymbol?: Symbol) { - super(moduleDoc, symbol, declaration, aliasSymbol); - this.computeHeritageClauses(); - this.addAliases(); - } + constructor(host: Host, moduleDoc: ModuleDoc, + symbol: Symbol, + declaration: Declaration, + aliasSymbol?: Symbol) { + super(host, moduleDoc, symbol, declaration, aliasSymbol); + + this.computeHeritageClauses(); + this.addAliases(); + } private computeTypeParams() { if (this.symbol.members) { diff --git a/typescript/src/api-doc-types/ConstExportDoc.ts b/typescript/src/api-doc-types/ConstExportDoc.ts index 60ef8267..c32f0788 100644 --- a/typescript/src/api-doc-types/ConstExportDoc.ts +++ b/typescript/src/api-doc-types/ConstExportDoc.ts @@ -1,5 +1,5 @@ -import { Symbol, Type, TypeFormatFlags, VariableDeclaration } from 'typescript'; -import { getDeclarationTypeText } from '../services/TsParser/getDeclarationTypeText'; +import { Symbol, VariableDeclaration } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { ExportDoc } from './ExportDoc'; import { ModuleDoc } from './ModuleDoc'; @@ -8,8 +8,11 @@ export class ConstExportDoc extends ExportDoc { variableDeclaration = this.declaration as VariableDeclaration; type = this.getTypeString(); - constructor(moduleDoc: ModuleDoc, symbol: Symbol, aliasSymbol?: Symbol) { - super(moduleDoc, symbol, symbol.valueDeclaration!, aliasSymbol); + constructor(host: Host, + moduleDoc: ModuleDoc, + symbol: Symbol, + aliasSymbol?: Symbol) { + super(host, moduleDoc, symbol, symbol.valueDeclaration!, aliasSymbol); } private getTypeString() { diff --git a/typescript/src/api-doc-types/ContainerExportDoc.ts b/typescript/src/api-doc-types/ContainerExportDoc.ts index 0bab75eb..8615dbc0 100644 --- a/typescript/src/api-doc-types/ContainerExportDoc.ts +++ b/typescript/src/api-doc-types/ContainerExportDoc.ts @@ -1,13 +1,12 @@ /* tslint:disable:no-bitwise */ import { - FunctionLikeDeclaration, - GetAccessorDeclaration, - Map, - SetAccessorDeclaration, - Symbol, - SymbolFlags, - SyntaxKind, - UnderscoreEscapedMap + FunctionLikeDeclaration, + GetAccessorDeclaration, + SetAccessorDeclaration, + Symbol, + SymbolFlags, + SyntaxKind, + UnderscoreEscapedMap, } from 'typescript'; import { getAccessibility } from "../services/TsParser/getAccessibility"; import { ExportDoc } from './ExportDoc'; @@ -56,13 +55,13 @@ export abstract class ContainerExportDoc extends ExportDoc { setAccessorDeclaration = declaration as SetAccessorDeclaration; } else if ((declaration as FunctionLikeDeclaration).body) { // This is the "real" declaration of the method - memberDoc = new MethodMemberDoc(this, member, declaration, overloads); + memberDoc = new MethodMemberDoc(this.host, this, member, declaration, overloads); } else { // This is an overload signature of the method - overloads.push(new MethodMemberDoc(this, member, declaration, overloads)); + overloads.push(new MethodMemberDoc(this.host, this, member, declaration, overloads)); } } else if (flags & PropertyMemberFlags) { - memberDoc = new PropertyMemberDoc(this, member, declaration, null, null); + memberDoc = new PropertyMemberDoc(this.host, this, member, declaration, null, null); } else { throw new Error(`Unknown member type for member ${member.name}`); } @@ -70,7 +69,8 @@ export abstract class ContainerExportDoc extends ExportDoc { // If at least one of the declarations was an accessor then the whole member is a property. if (getAccessorDeclaration || setAccessorDeclaration) { - memberDoc = new PropertyMemberDoc(this, member, null, getAccessorDeclaration, setAccessorDeclaration); + memberDoc = new PropertyMemberDoc(this.host, this, member, null, getAccessorDeclaration, + setAccessorDeclaration); } // If there is no member doc then we are in an interface or abstract class and we just take the first overload diff --git a/typescript/src/api-doc-types/EnumExportDoc.ts b/typescript/src/api-doc-types/EnumExportDoc.ts index f641f818..3e1321b0 100644 --- a/typescript/src/api-doc-types/EnumExportDoc.ts +++ b/typescript/src/api-doc-types/EnumExportDoc.ts @@ -1,4 +1,5 @@ import { Declaration, Symbol } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { ContainerExportDoc } from './ContainerExportDoc'; import { ModuleDoc } from './ModuleDoc'; @@ -9,11 +10,12 @@ import { ModuleDoc } from './ModuleDoc'; export class EnumExportDoc extends ContainerExportDoc { docType = 'enum'; additionalDeclarations: Declaration[] = []; - constructor( - moduleDoc: ModuleDoc, - symbol: Symbol, - aliasSymbol?: Symbol) { - super(moduleDoc, symbol, symbol.valueDeclaration!, aliasSymbol); + constructor(host: Host, + moduleDoc: ModuleDoc, + symbol: Symbol, + aliasSymbol?: Symbol) { + super(host, moduleDoc, symbol, symbol.valueDeclaration!, aliasSymbol); + this.additionalDeclarations = symbol.getDeclarations()!.filter(declaration => declaration !== this.declaration); if (symbol.exports) { this.members = this.getMemberDocs(symbol.exports, true); diff --git a/typescript/src/api-doc-types/FunctionExportDoc.ts b/typescript/src/api-doc-types/FunctionExportDoc.ts index ecd9a427..ef7e982d 100644 --- a/typescript/src/api-doc-types/FunctionExportDoc.ts +++ b/typescript/src/api-doc-types/FunctionExportDoc.ts @@ -1,4 +1,5 @@ import { Declaration, Symbol } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { getDeclarationTypeText } from '../services/TsParser/getDeclarationTypeText'; import { ModuleDoc } from './ModuleDoc'; import { OverloadInfo } from './OverloadInfo'; @@ -18,11 +19,13 @@ export class FunctionExportDoc extends ParameterizedExportDoc implements Paramet readonly parameterDocs: ParameterDoc[] = getParameters(this); readonly parameters = this.parameterDocs.map(p => p.paramText); - constructor( - public containerDoc: ModuleDoc, - symbol: Symbol, - aliasSymbol?: Symbol) { - super(containerDoc, symbol, findRealDeclaration(symbol.getDeclarations()!), aliasSymbol); + constructor(host: Host, + public containerDoc: ModuleDoc, + symbol: Symbol, + aliasSymbol?: Symbol) { + + super(host, containerDoc, symbol, findRealDeclaration(symbol.getDeclarations()!), + aliasSymbol); } } diff --git a/typescript/src/api-doc-types/InterfaceExportDoc.ts b/typescript/src/api-doc-types/InterfaceExportDoc.ts index fb6d735d..db00310d 100644 --- a/typescript/src/api-doc-types/InterfaceExportDoc.ts +++ b/typescript/src/api-doc-types/InterfaceExportDoc.ts @@ -1,6 +1,7 @@ import { Declaration, Symbol } from 'typescript'; import { ClassLikeExportDoc } from '../api-doc-types/ClassLikeExportDoc'; import { ModuleDoc } from '../api-doc-types/ModuleDoc'; +import { Host } from '../services/ts-host/host'; /** * Interfaces are class-like but can also have multiple declarations that are merged together @@ -8,12 +9,20 @@ import { ModuleDoc } from '../api-doc-types/ModuleDoc'; export class InterfaceExportDoc extends ClassLikeExportDoc { docType = 'interface'; additionalDeclarations: Declaration[] = []; - constructor( - moduleDoc: ModuleDoc, - symbol: Symbol, - aliasSymbol?: Symbol) { - super(moduleDoc, symbol, symbol.valueDeclaration || symbol.getDeclarations()![0]!, aliasSymbol); - if (symbol.members) this.members = this.getMemberDocs(symbol.members, true); - this.additionalDeclarations = symbol.getDeclarations()!.filter(declaration => declaration !== this.declaration); + + constructor(host: Host, + moduleDoc: ModuleDoc, + symbol: Symbol, + aliasSymbol?: Symbol) { + + super(host, moduleDoc, symbol, symbol.valueDeclaration || symbol.getDeclarations()![0]!, + aliasSymbol); + + if (symbol.members) { + this.members = this.getMemberDocs(symbol.members, true); + } + + this.additionalDeclarations = symbol.getDeclarations()! + .filter(declaration => declaration !== this.declaration); } } diff --git a/typescript/src/api-doc-types/MemberDoc.ts b/typescript/src/api-doc-types/MemberDoc.ts index fa12aaa8..6de1d0b8 100644 --- a/typescript/src/api-doc-types/MemberDoc.ts +++ b/typescript/src/api-doc-types/MemberDoc.ts @@ -1,8 +1,8 @@ /* tslint:disable:no-bitwise */ import { Declaration, Symbol, SymbolFlags, SyntaxKind, TypeChecker } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { FileInfo } from '../services/TsParser/FileInfo'; import { getAccessibility } from "../services/TsParser/getAccessibility"; -import { getContent } from "../services/TsParser/getContent"; import { getDeclarationTypeText } from "../services/TsParser/getDeclarationTypeText"; import { getDecorators, ParsedDecorator } from "../services/TsParser/getDecorators"; import { ApiDoc } from './ApiDoc'; @@ -22,7 +22,7 @@ export abstract class MemberDoc implements ApiDoc { path: string = ''; outputPath: string = ''; - content = getContent(this.declaration); + content = this.host.getContent(this.declaration); basePath = this.containerDoc.basePath; fileInfo = new FileInfo(this.declaration, this.basePath); startingLine = this.fileInfo.location.start.line + (this.fileInfo.location.start.character ? 1 : 0); @@ -43,6 +43,7 @@ export abstract class MemberDoc implements ApiDoc { isStatic = !!this.declaration.modifiers && this.declaration.modifiers.some(modifier => modifier.kind === SyntaxKind.StaticKeyword); constructor( + public host: Host, public containerDoc: ContainerExportDoc, public symbol: Symbol, public declaration: Declaration) { diff --git a/typescript/src/api-doc-types/MethodMemberDoc.ts b/typescript/src/api-doc-types/MethodMemberDoc.ts index 2332812c..277b9310 100644 --- a/typescript/src/api-doc-types/MethodMemberDoc.ts +++ b/typescript/src/api-doc-types/MethodMemberDoc.ts @@ -1,5 +1,6 @@ /* tslint:disable:no-bitwise */ import { Declaration, Symbol } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { getTypeParametersText } from '../services/TsParser/getTypeParametersText'; import { ContainerExportDoc } from './ContainerExportDoc'; import { MemberDoc } from './MemberDoc'; @@ -15,12 +16,14 @@ export class MethodMemberDoc extends MemberDoc implements ParameterContainer { readonly aliases = this.computeAliases(); readonly typeParameters = getTypeParametersText(this.declaration); - constructor( - containerDoc: ContainerExportDoc, - symbol: Symbol, - declaration: Declaration, - public overloads: MethodMemberDoc[] = []) { - super(containerDoc, symbol, declaration); + constructor(host: Host, + containerDoc: ContainerExportDoc, + symbol: Symbol, + declaration: Declaration, + public overloads: MethodMemberDoc[] = []) { + + super(host, containerDoc, symbol, declaration); + // fix up parameter ids and aliases, now that we have computed the id for this doc this.parameterDocs.forEach(param => { param.id = `${this.id}~${param.name}`; diff --git a/typescript/src/api-doc-types/ModuleDoc.ts b/typescript/src/api-doc-types/ModuleDoc.ts index 75ca689f..403681f0 100644 --- a/typescript/src/api-doc-types/ModuleDoc.ts +++ b/typescript/src/api-doc-types/ModuleDoc.ts @@ -23,5 +23,8 @@ export class ModuleDoc implements ApiDoc { outputPath: string = ''; content: string = ''; - constructor(public symbol: ModuleSymbol, public basePath: string, public hidePrivateMembers: boolean, public typeChecker: TypeChecker) {} + constructor(public symbol: ModuleSymbol, + public basePath: string, + public hidePrivateMembers: boolean, + public typeChecker: TypeChecker) {} } diff --git a/typescript/src/api-doc-types/OverloadInfo.ts b/typescript/src/api-doc-types/OverloadInfo.ts index 9eb5ce2e..eb4587eb 100644 --- a/typescript/src/api-doc-types/OverloadInfo.ts +++ b/typescript/src/api-doc-types/OverloadInfo.ts @@ -1,10 +1,10 @@ import { Declaration } from 'typescript'; import { getDeclarationTypeText } from '../services/TsParser/getDeclarationTypeText'; +import { BaseApiDoc } from './ApiDoc'; import { FunctionExportDoc } from './FunctionExportDoc'; import { ModuleDoc } from './ModuleDoc'; import { getParameters, ParameterContainer } from './ParameterContainer'; import { ParameterDoc } from './ParameterDoc'; -import { BaseApiDoc } from './ApiDoc'; /** * This represents a single overload of an exported function. @@ -20,7 +20,8 @@ export class OverloadInfo extends BaseApiDoc implements ParameterContainer { containerDoc: ModuleDoc = this.functionDoc.containerDoc; constructor(public functionDoc: FunctionExportDoc, declaration: Declaration) { - super(functionDoc.moduleDoc, functionDoc.symbol, declaration); + super(functionDoc.host, functionDoc.moduleDoc, functionDoc.symbol, declaration); + // Give this overload doc a more specific id and aliases than it's container doc const paramString = `(${this.parameters.join(', ')})`; this.id += paramString; diff --git a/typescript/src/api-doc-types/ParameterContainer.ts b/typescript/src/api-doc-types/ParameterContainer.ts index 0273a78f..8b17a072 100644 --- a/typescript/src/api-doc-types/ParameterContainer.ts +++ b/typescript/src/api-doc-types/ParameterContainer.ts @@ -1,4 +1,5 @@ -import { Declaration,ParameterDeclaration, SignatureDeclaration, TypeChecker } from 'typescript'; +import { Declaration, ParameterDeclaration, SignatureDeclaration, TypeChecker } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { nodeToString } from '../services/TsParser/nodeToString'; import { ContainerExportDoc } from './ContainerExportDoc'; import { ModuleDoc } from './ModuleDoc'; @@ -24,6 +25,7 @@ export interface ParameterContainer { declaration: Declaration; basePath: string; typeChecker: TypeChecker; + host: Host; params?: ParamTag[]; } @@ -37,6 +39,6 @@ export function getParameters(callableDoc: ParameterContainer) { } return signature.getParameters().map(parameter => { - return new ParameterDoc(callableDoc, parameter, parameter.valueDeclaration as ParameterDeclaration) + return new ParameterDoc(callableDoc, parameter, parameter.valueDeclaration as ParameterDeclaration); }); } diff --git a/typescript/src/api-doc-types/ParameterDoc.ts b/typescript/src/api-doc-types/ParameterDoc.ts index fd1907de..ae8ef8f9 100644 --- a/typescript/src/api-doc-types/ParameterDoc.ts +++ b/typescript/src/api-doc-types/ParameterDoc.ts @@ -1,6 +1,5 @@ -import { Declaration, ParameterDeclaration, Symbol, SymbolFlags } from 'typescript'; -import { getDeclarationTypeText, getInitializer } from '../services/TsParser/getDeclarationTypeText'; -import { getTypeText } from '../services/TsParser/getTypeText'; +import { ParameterDeclaration, Symbol } from 'typescript'; +import { getDeclarationTypeText } from '../services/TsParser/getDeclarationTypeText'; import { nodeToString } from '../services/TsParser/nodeToString'; import { BaseApiDoc } from './ApiDoc'; import { ParameterContainer } from './ParameterContainer'; @@ -22,7 +21,7 @@ export class ParameterDoc extends BaseApiDoc { constructor(public container: ParameterContainer, public symbol: Symbol, public declaration: ParameterDeclaration) { - super(container.moduleDoc, symbol, declaration); + super(container.host, container.moduleDoc, symbol, declaration); this.id = `${this.container.id}~${this.name}`; this.aliases = (this.container.aliases || []).map(alias => `${alias}~${this.name}`); @@ -37,4 +36,4 @@ export class ParameterDoc extends BaseApiDoc { if (this.defaultValue) paramText += ' = ' + this.defaultValue; return paramText.trim(); } -} \ No newline at end of file +} diff --git a/typescript/src/api-doc-types/ParameterizedExportDoc.ts b/typescript/src/api-doc-types/ParameterizedExportDoc.ts index f1a1ae24..c531872d 100644 --- a/typescript/src/api-doc-types/ParameterizedExportDoc.ts +++ b/typescript/src/api-doc-types/ParameterizedExportDoc.ts @@ -1,4 +1,5 @@ import { Declaration, Symbol } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { getTypeParametersText } from '../services/TsParser/getTypeParametersText'; import { ExportDoc } from './ExportDoc'; import { ModuleDoc } from './ModuleDoc'; @@ -6,11 +7,12 @@ import { ModuleDoc } from './ModuleDoc'; export abstract class ParameterizedExportDoc extends ExportDoc { typeParameters = getTypeParametersText(this.declaration); - constructor(moduleDoc: ModuleDoc, + constructor(host: Host, + moduleDoc: ModuleDoc, symbol: Symbol, declaration: Declaration, aliasSymbol?: Symbol) { - super(moduleDoc, symbol, declaration, aliasSymbol); + super(host, moduleDoc, symbol, declaration, aliasSymbol); } } diff --git a/typescript/src/api-doc-types/PropertyMemberDoc.ts b/typescript/src/api-doc-types/PropertyMemberDoc.ts index 2fa9d489..3bb2c778 100644 --- a/typescript/src/api-doc-types/PropertyMemberDoc.ts +++ b/typescript/src/api-doc-types/PropertyMemberDoc.ts @@ -1,4 +1,5 @@ import { Declaration, GetAccessorDeclaration, SetAccessorDeclaration, Symbol } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { AccessorInfoDoc } from './AccessorInfoDoc'; import { ContainerExportDoc } from './ContainerExportDoc'; import { MemberDoc } from './MemberDoc'; @@ -11,7 +12,8 @@ export class PropertyMemberDoc extends MemberDoc { getAccessor: AccessorInfoDoc | null; setAccessor: AccessorInfoDoc | null; - constructor(containerDoc: ContainerExportDoc, + constructor(host: Host, + containerDoc: ContainerExportDoc, symbol: Symbol, declaration: Declaration | null, getAccessorDeclaration: GetAccessorDeclaration | null, @@ -20,11 +22,11 @@ export class PropertyMemberDoc extends MemberDoc { // For accessors, the declaration parameter will be null, and therefore the getter declaration // will be used for most of the things (e.g. determination of the type). If the getter doesn't // have a type or description, the setter will be checked manually later in this constructor. - super(containerDoc, symbol, (declaration || getAccessorDeclaration || setAccessorDeclaration)!); + super(host, containerDoc, symbol, (declaration || getAccessorDeclaration || setAccessorDeclaration)!); // If this property has accessors then compute the type based on that instead - this.getAccessor = getAccessorDeclaration && new AccessorInfoDoc('get', this, getAccessorDeclaration); - this.setAccessor = setAccessorDeclaration && new AccessorInfoDoc('set', this, setAccessorDeclaration); + this.getAccessor = getAccessorDeclaration && new AccessorInfoDoc(host, 'get', this, getAccessorDeclaration); + this.setAccessor = setAccessorDeclaration && new AccessorInfoDoc(host, 'set', this, setAccessorDeclaration); // As mentioned before, by default the get accessor declaration will be passed to the superclass, // to determine information about the property. With that approach, it can happen that a few diff --git a/typescript/src/api-doc-types/TypeAliasExportDoc.ts b/typescript/src/api-doc-types/TypeAliasExportDoc.ts index 941e8c64..93691d87 100644 --- a/typescript/src/api-doc-types/TypeAliasExportDoc.ts +++ b/typescript/src/api-doc-types/TypeAliasExportDoc.ts @@ -1,4 +1,5 @@ import { Declaration, Symbol, SyntaxKind } from 'typescript'; +import { Host } from '../services/ts-host/host'; import { getDeclarationTypeText } from '../services/TsParser/getDeclarationTypeText'; import { ModuleDoc } from './ModuleDoc'; import { ParameterizedExportDoc } from './ParameterizedExportDoc'; @@ -7,11 +8,13 @@ export class TypeAliasExportDoc extends ParameterizedExportDoc { docType = 'type-alias'; typeDefinition = getDeclarationTypeText(this.declaration); - constructor( - moduleDoc: ModuleDoc, - exportSymbol: Symbol, - aliasSymbol?: Symbol) { - super(moduleDoc, exportSymbol, getTypeAliasDeclaration(exportSymbol.getDeclarations()!), aliasSymbol); + constructor(host: Host, + moduleDoc: ModuleDoc, + exportSymbol: Symbol, + aliasSymbol?: Symbol) { + + super(host, moduleDoc, exportSymbol, getTypeAliasDeclaration(exportSymbol.getDeclarations()!), + aliasSymbol); } } diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 60c413ae..c4e28a94 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -6,6 +6,7 @@ import { readTypeScriptModules } from './processors/readTypeScriptModules'; import { convertPrivateClassesToInterfaces } from './services/convertPrivateClassesToInterfaces'; import { exportSymbolsToDocsMap } from './services/exportSymbolsToDocsMap'; import { modules } from './services/modules'; +import { Host } from './services/ts-host/host'; import { TsParser } from './services/TsParser'; // Define the dgeni package for generating the docs @@ -14,6 +15,7 @@ module.exports = new Package('typescript', [require('../jsdoc')]) // Register the services and file readers .factory(modules) .factory(exportSymbolsToDocsMap) +.factory('tsHost', () => new Host()) .factory('tsParser', function(log) { return new TsParser(log); }) .factory('convertPrivateClassesToInterfaces', function() { return convertPrivateClassesToInterfaces; }) diff --git a/typescript/src/processors/readTypeScriptModules/index.ts b/typescript/src/processors/readTypeScriptModules/index.ts index c30a2f62..73dc183a 100644 --- a/typescript/src/processors/readTypeScriptModules/index.ts +++ b/typescript/src/processors/readTypeScriptModules/index.ts @@ -12,6 +12,7 @@ import { ModuleDoc } from '../../api-doc-types/ModuleDoc'; import { ParameterDoc } from '../../api-doc-types/ParameterDoc'; import { PropertyMemberDoc } from '../../api-doc-types/PropertyMemberDoc'; import { TypeAliasExportDoc } from '../../api-doc-types/TypeAliasExportDoc'; +import {Host} from '../../services/ts-host/host'; import { getExportDocType, ModuleSymbols, TsParser } from '../../services/TsParser'; import { expandSourceFiles, SourcePattern } from './SourcePattern'; @@ -21,11 +22,13 @@ const path = require('canonical-path'); export function readTypeScriptModules( tsParser: TsParser, + tsHost: Host, modules: any, exportSymbolsToDocsMap: Map, createDocMessage: any, log: any) { - return new ReadTypeScriptModules(tsParser, modules, exportSymbolsToDocsMap, createDocMessage, log); + return new ReadTypeScriptModules(tsParser, tsHost, modules, exportSymbolsToDocsMap, + createDocMessage, log); } export class ReadTypeScriptModules implements Processor { @@ -54,6 +57,7 @@ export class ReadTypeScriptModules implements Processor { constructor( private tsParser: TsParser, + private host: Host, private modules: any, private exportSymbolsToDocsMap: Map, private createDocMessage: any, @@ -104,32 +108,32 @@ export class ReadTypeScriptModules implements Processor { switch (getExportDocType(resolvedExport)) { case 'class': - const classDoc = new ClassExportDoc(moduleDoc, resolvedExport, aliasSymbol); + const classDoc = new ClassExportDoc(this.host, moduleDoc, resolvedExport, aliasSymbol); this.addMemberDocs(docs, classDoc.members); this.addMemberDocs(docs, classDoc.statics); if (classDoc.constructorDoc) this.addMemberDocs(docs, [classDoc.constructorDoc]); this.addExportDoc(docs, moduleDoc, classDoc); break; case 'interface': - const interfaceDoc = new InterfaceExportDoc(moduleDoc, resolvedExport, aliasSymbol); + const interfaceDoc = new InterfaceExportDoc(this.host, moduleDoc, resolvedExport, aliasSymbol); this.addMemberDocs(docs, interfaceDoc.members); this.addExportDoc(docs, moduleDoc, interfaceDoc); break; case 'enum': - const enumDoc = new EnumExportDoc(moduleDoc, resolvedExport, aliasSymbol); + const enumDoc = new EnumExportDoc(this.host, moduleDoc, resolvedExport, aliasSymbol); enumDoc.members.forEach(doc => docs.push(doc)); this.addExportDoc(docs, moduleDoc, enumDoc); break; case 'const': case 'let': case 'var': - this.addExportDoc(docs, moduleDoc, new ConstExportDoc(moduleDoc, resolvedExport, aliasSymbol)); + this.addExportDoc(docs, moduleDoc, new ConstExportDoc(this.host, moduleDoc, resolvedExport, aliasSymbol)); break; case 'type-alias': - this.addExportDoc(docs, moduleDoc, new TypeAliasExportDoc(moduleDoc, resolvedExport, aliasSymbol)); + this.addExportDoc(docs, moduleDoc, new TypeAliasExportDoc(this.host, moduleDoc, resolvedExport, aliasSymbol)); break; case 'function': - const functionDoc = new FunctionExportDoc(moduleDoc, resolvedExport, aliasSymbol); + const functionDoc = new FunctionExportDoc(this.host, moduleDoc, resolvedExport, aliasSymbol); this.addExportDoc(docs, moduleDoc, functionDoc); this.addParamDocs(docs, functionDoc.parameterDocs); functionDoc.overloads.forEach(overloadDoc => { @@ -174,7 +178,7 @@ export class ReadTypeScriptModules implements Processor { private addParamDocs(docs: DocCollection, parameters: ParameterDoc[]) { parameters.forEach(parameter => docs.push(parameter)); } - } +} function convertToRegexCollection(items: Array|string|RegExp): RegExp[] { if (!items) return []; diff --git a/typescript/src/services/TsParser/getContent.spec.ts b/typescript/src/services/TsParser/getContent.spec.ts index b171053b..baf5c4df 100644 --- a/typescript/src/services/TsParser/getContent.spec.ts +++ b/typescript/src/services/TsParser/getContent.spec.ts @@ -29,4 +29,19 @@ describe('getContent', () => { expect(getContent(method)).toEqual('Some method'); expect(getContent(method.parameters[0])).toEqual('param 1'); }); + + it('should properly concatenate multiple leading comments', () => { + const parseInfo = parser.parse(['tsParser/multipleLeadingComments.ts'], basePath); + const module = parseInfo.moduleSymbols[0]; + + expect(getContent(module.exportArray[0].getDeclarations()![0])).toEqual('Not a license comment.\nThis is a test function'); + }); + + it('should be able to disable leading comments concatenation', () => { + const parseInfo = parser.parse(['tsParser/multipleLeadingComments.ts'], basePath); + const module = parseInfo.moduleSymbols[0]; + + expect(getContent(module.exportArray[0].getDeclarations()![0], false)) + .toEqual('This is a test function'); + }); }); diff --git a/typescript/src/services/TsParser/getContent.ts b/typescript/src/services/TsParser/getContent.ts index e0b81523..34041184 100644 --- a/typescript/src/services/TsParser/getContent.ts +++ b/typescript/src/services/TsParser/getContent.ts @@ -1,9 +1,24 @@ -import { getLeadingCommentRanges, getTrailingCommentRanges, ModuleDeclaration, Node, SyntaxKind, VariableDeclaration } from 'typescript'; +import { + CommentRange, + getLeadingCommentRanges, + getTrailingCommentRanges, + ModuleDeclaration, + Node, + SyntaxKind, +} from 'typescript'; + const LEADING_STAR = /^[^\S\r\n]*\*[^\S\n\r]?/gm; const ASTERISK = 42; const SLASH = 47; -export function getContent(node: Node|undefined) { +const syntaxKindsWithTrailingComments = [ + SyntaxKind.Parameter, + SyntaxKind.TypeParameter, + SyntaxKind.FunctionExpression, + SyntaxKind.ArrowFunction, +]; + +export function getContent(node: Node | undefined, concatLeadingComments = true) { let content = ''; @@ -30,7 +45,7 @@ export function getContent(node: Node|undefined) { // Get the source file of this node const sourceFile = node.getSourceFile(); - const commentRanges = getJSDocCommentRanges(node, sourceFile.text); + const commentRanges = getJSDocCommentRanges(node, sourceFile.text, concatLeadingComments); if (commentRanges) { commentRanges.forEach(commentRange => { @@ -47,15 +62,20 @@ export function getContent(node: Node|undefined) { return content.trim(); } -function getJSDocCommentRanges(node: Node, text: string) { - const commentRanges = ( - node.kind === SyntaxKind.Parameter || - node.kind === SyntaxKind.TypeParameter || - node.kind === SyntaxKind.FunctionExpression || - node.kind === SyntaxKind.ArrowFunction - ) ? - concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) : - getLeadingCommentRanges(text, node.pos); +function getJSDocCommentRanges(node: Node, text: string, concatLeadingComments: boolean) { + const commentRanges: CommentRange[] = []; + const leadingCommentRanges = getLeadingCommentRanges(text, node.pos) || []; + const trailingCommentRanges = getTrailingCommentRanges(text, node.pos) || []; + + if (syntaxKindsWithTrailingComments.includes(node.kind)) { + commentRanges.push(...trailingCommentRanges); + } + + if (concatLeadingComments) { + commentRanges.push(...leadingCommentRanges); + } else { + commentRanges.push(leadingCommentRanges[leadingCommentRanges.length - 1]); + } // True if the comment starts with '/**' but not if it is '/**/' if (commentRanges) { @@ -65,7 +85,3 @@ function getJSDocCommentRanges(node: Node, text: string) { text.charCodeAt(comment.pos + 3) !== SLASH); } } - -function concatenate(array1: T[] | undefined, array2: T[] | undefined) { - return array1 ? array2 ? array1.concat(array2) : array1 : array2; -} diff --git a/typescript/src/services/convertPrivateClassesToInterfaces.spec.ts b/typescript/src/services/convertPrivateClassesToInterfaces.spec.ts index de87579a..95cbdf57 100644 --- a/typescript/src/services/convertPrivateClassesToInterfaces.spec.ts +++ b/typescript/src/services/convertPrivateClassesToInterfaces.spec.ts @@ -1,8 +1,9 @@ -import { createSourceFile, ScriptTarget } from 'typescript'; import { DocCollection } from 'dgeni'; +import { createSourceFile, ScriptTarget } from 'typescript'; import { ClassExportDoc } from '../api-doc-types/ClassExportDoc'; import { HeritageInfo } from '../api-doc-types/ClassLikeExportDoc'; import { convertPrivateClassesToInterfaces } from './convertPrivateClassesToInterfaces'; +import { Host } from './ts-host/host'; import { FileInfo } from './TsParser/FileInfo'; describe('convertPrivateClassesToInterfaces', () => { @@ -21,7 +22,7 @@ describe('convertPrivateClassesToInterfaces', () => { beforeEach(() => { spyOn(FileInfo.prototype, 'getRealFilePath').and.callFake((filePath: string) => filePath); - classDoc = new ClassExportDoc(moduleDoc, classSymbol); + classDoc = new ClassExportDoc(new Host(), moduleDoc, classSymbol); classDoc.constructorDoc = { internal: true } as any; docs = [classDoc]; }); diff --git a/typescript/src/services/convertPrivateClassesToInterfaces.ts b/typescript/src/services/convertPrivateClassesToInterfaces.ts index 8d369e1d..fb0d1dcc 100644 --- a/typescript/src/services/convertPrivateClassesToInterfaces.ts +++ b/typescript/src/services/convertPrivateClassesToInterfaces.ts @@ -1,25 +1,33 @@ -import { DocCollection } from 'dgeni'; +import { DocCollection, Document } from 'dgeni'; +import { ClassExportDoc } from '../api-doc-types/ClassExportDoc'; import { ConstExportDoc } from '../api-doc-types/ConstExportDoc'; + export function convertPrivateClassesToInterfaces(exportDocs: DocCollection, addInjectableReference: boolean) { exportDocs.forEach(exportDoc => { // Search for classes with a constructor marked as `@internal` - if (exportDoc.docType === 'class' && exportDoc.constructorDoc && exportDoc.constructorDoc.internal) { - + if (isPrivateClassExportDoc(exportDoc)) { // Convert this class to an interface with no constructor exportDoc.docType = 'interface'; - exportDoc.constructorDoc = null; + exportDoc.constructorDoc = undefined; // convert the heritage since interfaces use `extends` not `implements` exportDoc.extendsClauses = exportDoc.extendsClauses.concat(exportDoc.implementsClauses); if (addInjectableReference) { // Add the `declare var SomeClass extends InjectableReference` construct - const constExportDoc = new ConstExportDoc(exportDoc.moduleDoc, exportDoc.symbol); + const constExportDoc = new ConstExportDoc(exportDoc.host, exportDoc.moduleDoc, + exportDoc.symbol, exportDoc.aliasSymbol); + constExportDoc.type = 'InjectableReference'; exportDocs.push(constExportDoc); } } }); } + +/** Whether the specified document is a class document with an internal constructor. */ +function isPrivateClassExportDoc(doc: Document): doc is ClassExportDoc { + return doc.docType === 'class' && doc.constructorDoc && doc.constructorDoc.internal; +} diff --git a/typescript/src/services/ts-host/host.spec.ts b/typescript/src/services/ts-host/host.spec.ts new file mode 100644 index 00000000..48765ca5 --- /dev/null +++ b/typescript/src/services/ts-host/host.spec.ts @@ -0,0 +1,51 @@ +import { Dgeni, Package } from 'dgeni'; +import { TsParser } from '../TsParser'; +import { Host } from './host'; + +const mockPackage = require('../../mocks/mockPackage'); +const path = require('canonical-path'); + +describe('Host', () => { + const basePath = path.resolve(__dirname, '../../mocks/tsParser'); + + let host: Host; + let parser: TsParser; + + /** + * Creates the Host instance through Dgeni dependency injection. Also allows passing a function + * that will run in Dgeni's configuration lifecycle and allows modifying the host factory. + */ + function setupTestDgeniInstance(configureFn: (host: Host) => void) { + const testPackage = mockPackage() as Package; + + testPackage.config((tsHost: Host) => configureFn(tsHost)); + + const dgeni = new Dgeni([testPackage]); + const injector = dgeni.configureInjector(); + + // Load factories from the Dgeni injector. + host = injector.get('tsHost'); + parser = injector.get('tsParser'); + } + + it("should read content of a declaration", () => { + setupTestDgeniInstance(h => h.concatMultipleLeadingComments = true); + + const parseInfo = parser.parse(['multipleLeadingComments.ts'], basePath); + const module = parseInfo.moduleSymbols[0]; + const declaration = module.exportArray[0].valueDeclaration!; + + expect(host.getContent(declaration)) + .toEqual('Not a license comment.\nThis is a test function'); + }); + + it('should be able to disable leading comment concatenation', () => { + setupTestDgeniInstance(h => h.concatMultipleLeadingComments = false); + + const parseInfo = parser.parse(['multipleLeadingComments.ts'], basePath); + const module = parseInfo.moduleSymbols[0]; + const declaration = module.exportArray[0].valueDeclaration!; + + expect(host.getContent(declaration)).toEqual('This is a test function'); + }); +}); diff --git a/typescript/src/services/ts-host/host.ts b/typescript/src/services/ts-host/host.ts new file mode 100644 index 00000000..cdf84f9c --- /dev/null +++ b/typescript/src/services/ts-host/host.ts @@ -0,0 +1,17 @@ +import {Declaration} from 'typescript'; +import {getContent} from '../TsParser'; + +/** + * Host that will be used for TypeScript AST operations that should be configurable or shared + * across multiple doc types. + */ +export class Host { + + /** Whether multiple leading comments for a TypeScript node should be concatenated. */ + concatMultipleLeadingComments: boolean = true; + + getContent(declaration: Declaration) { + return getContent(declaration, this.concatMultipleLeadingComments); + } + +}