Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Extractor: Add support for custom TSDoc tags #1628

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 45 additions & 33 deletions apps/api-extractor-model/src/aedoc/AedocDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,44 +27,56 @@ export class AedocDefinitions {
syntaxKind: TSDocTagSyntaxKind.ModifierTag
});

/**
* @deprecated Use `AedocDefinitions.getTsdocConfiguration()` instead, to allow customization of supported tags
* without polluting a global object.
*/
public static get tsdocConfiguration(): TSDocConfiguration {
if (!AedocDefinitions._tsdocConfiguration) {
const configuration: TSDocConfiguration = new TSDocConfiguration();
configuration.addTagDefinitions([
AedocDefinitions.betaDocumentation,
AedocDefinitions.internalRemarks,
AedocDefinitions.preapprovedTag
], true);

configuration.setSupportForTags(
[
StandardTags.alpha,
StandardTags.beta,
StandardTags.defaultValue,
StandardTags.deprecated,
StandardTags.eventProperty,
StandardTags.example,
StandardTags.inheritDoc,
StandardTags.internal,
StandardTags.link,
StandardTags.override,
StandardTags.packageDocumentation,
StandardTags.param,
StandardTags.privateRemarks,
StandardTags.public,
StandardTags.readonly,
StandardTags.remarks,
StandardTags.returns,
StandardTags.sealed,
StandardTags.virtual
],
true
);

AedocDefinitions._tsdocConfiguration = configuration;
AedocDefinitions._tsdocConfiguration = AedocDefinitions.getTsdocConfiguration([]);
}
return AedocDefinitions._tsdocConfiguration;
}

private static _tsdocConfiguration: TSDocConfiguration | undefined;

/**
* Gets a TSDoc configuration, optionally with additional supported tags.
*/
public static getTsdocConfiguration(additionalTags: ReadonlyArray<TSDocTagDefinition> = []): TSDocConfiguration {
const configuration: TSDocConfiguration = new TSDocConfiguration();
configuration.addTagDefinitions([
AedocDefinitions.betaDocumentation,
AedocDefinitions.internalRemarks,
AedocDefinitions.preapprovedTag,
...additionalTags
], true);

configuration.setSupportForTags(
[
StandardTags.alpha,
StandardTags.beta,
StandardTags.defaultValue,
StandardTags.deprecated,
StandardTags.eventProperty,
StandardTags.example,
StandardTags.inheritDoc,
StandardTags.internal,
StandardTags.link,
StandardTags.override,
StandardTags.packageDocumentation,
StandardTags.param,
StandardTags.privateRemarks,
StandardTags.public,
StandardTags.readonly,
StandardTags.remarks,
StandardTags.returns,
StandardTags.sealed,
StandardTags.virtual
],
true
);

return configuration;
}
}
3 changes: 1 addition & 2 deletions apps/api-extractor-model/src/items/ApiDocumentedItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import * as tsdoc from '@microsoft/tsdoc';
import { ApiItem, IApiItemOptions, IApiItemJson } from './ApiItem';
import { AedocDefinitions } from '../aedoc/AedocDefinitions';
import { DeserializerContext } from '../model/DeserializerContext';

/**
Expand Down Expand Up @@ -45,7 +44,7 @@ export class ApiDocumentedItem extends ApiItem {
const documentedJson: IApiDocumentedItemJson = jsonObject as IApiDocumentedItemJson;

if (documentedJson.docComment) {
const tsdocParser: tsdoc.TSDocParser = new tsdoc.TSDocParser(AedocDefinitions.tsdocConfiguration);
const tsdocParser: tsdoc.TSDocParser = new tsdoc.TSDocParser(context.tsdocConfig);

// NOTE: For now, we ignore TSDoc errors found in a serialized .api.json file.
// Normally these errors would have already been reported by API Extractor during analysis.
Expand Down
23 changes: 19 additions & 4 deletions apps/api-extractor-model/src/model/ApiModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@
// See LICENSE in the project root for license information.

import { DeclarationReference } from '@microsoft/tsdoc/lib/beta/DeclarationReference';
import * as tsdoc from '@microsoft/tsdoc';
import { ApiItem, ApiItemKind } from '../items/ApiItem';
import { ApiItemContainerMixin } from '../mixins/ApiItemContainerMixin';
import { ApiPackage } from './ApiPackage';
import { PackageName } from '@microsoft/node-core-library';
import { ModelReferenceResolver, IResolveDeclarationReferenceResult } from './ModelReferenceResolver';
import { DocDeclarationReference } from '@microsoft/tsdoc';
import { AedocDefinitions } from '../aedoc/AedocDefinitions';

/**
* Constructor options for {@link ApiModel}.
* @public
*/
export interface IApiModelOptions {
/**
* Custom TSDoc tags to support when parsing documentation comments.
*/
tsdocTags?: ReadonlyArray<tsdoc.TSDocTagDefinition>;
}

/**
* A serializable representation of a collection of API declarations.
Expand Down Expand Up @@ -51,17 +63,19 @@ import { DocDeclarationReference } from '@microsoft/tsdoc';
*/
export class ApiModel extends ApiItemContainerMixin(ApiItem) {
private readonly _resolver: ModelReferenceResolver;
private readonly _tsdocConfig: tsdoc.TSDocConfiguration;

private _packagesByName: Map<string, ApiPackage> | undefined = undefined;

public constructor() {
public constructor(options: IApiModelOptions = {}) {
super({ });

this._resolver = new ModelReferenceResolver(this);
this._tsdocConfig = AedocDefinitions.getTsdocConfiguration(options.tsdocTags || []);
}

public loadPackage(apiJsonFilename: string): ApiPackage {
const apiPackage: ApiPackage = ApiPackage.loadFromJsonFile(apiJsonFilename);
const apiPackage: ApiPackage = ApiPackage.loadFromJsonFile(apiJsonFilename, this._tsdocConfig);
this.addMember(apiPackage);
return apiPackage;
}
Expand Down Expand Up @@ -137,8 +151,9 @@ export class ApiModel extends ApiItemContainerMixin(ApiItem) {
return this._packagesByName.get(packageName);
}

public resolveDeclarationReference(declarationReference: DocDeclarationReference,
public resolveDeclarationReference(declarationReference: tsdoc.DocDeclarationReference,
contextApiItem: ApiItem | undefined): IResolveDeclarationReferenceResult {

return this._resolver.resolve(declarationReference, contextApiItem);
}

Expand Down
6 changes: 4 additions & 2 deletions apps/api-extractor-model/src/model/ApiPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See LICENSE in the project root for license information.

import { DeclarationReference } from '@microsoft/tsdoc/lib/beta/DeclarationReference';
import * as tsdoc from '@microsoft/tsdoc';
import { ApiItem, ApiItemKind, IApiItemJson } from '../items/ApiItem';
import { ApiItemContainerMixin, IApiItemContainerMixinOptions } from '../mixins/ApiItemContainerMixin';
import { JsonFile, IJsonFileSaveOptions, PackageJsonLookup, IPackageJson } from '@microsoft/node-core-library';
Expand Down Expand Up @@ -106,7 +107,7 @@ export class ApiPackage extends ApiItemContainerMixin(ApiNameMixin(ApiDocumented
super(options);
}

public static loadFromJsonFile(apiJsonFilename: string): ApiPackage {
public static loadFromJsonFile(apiJsonFilename: string, tsdocConfig?: tsdoc.TSDocConfiguration): ApiPackage {
const jsonObject: IApiPackageJson = JsonFile.load(apiJsonFilename);

if (!jsonObject
Expand Down Expand Up @@ -152,7 +153,8 @@ export class ApiPackage extends ApiItemContainerMixin(ApiNameMixin(ApiDocumented
apiJsonFilename,
toolPackage: jsonObject.metadata.toolPackage,
toolVersion: jsonObject.metadata.toolVersion,
versionToDeserialize: versionToDeserialize
versionToDeserialize: versionToDeserialize,
tsdocConfig
});

return ApiItem.deserialize(jsonObject, context) as ApiPackage;
Expand Down
8 changes: 8 additions & 0 deletions apps/api-extractor-model/src/model/DeserializerContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import * as tsdoc from '@microsoft/tsdoc';

export enum ApiJsonSchemaVersion {
/**
* The initial release.
Expand Down Expand Up @@ -72,10 +74,16 @@ export class DeserializerContext {
*/
public readonly versionToDeserialize: ApiJsonSchemaVersion;

/**
* TSDoc configuration to use when deserializing documentation comments.
*/
public readonly tsdocConfig?: tsdoc.TSDocConfiguration;

public constructor(options: DeserializerContext) {
this.apiJsonFilename = options.apiJsonFilename;
this.toolPackage = options.toolPackage;
this.toolVersion = options.toolVersion;
this.versionToDeserialize = options.versionToDeserialize;
this.tsdocConfig = options.tsdocConfig;
}
}
32 changes: 31 additions & 1 deletion apps/api-extractor/src/api/ExtractorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import * as path from 'path';
import * as resolve from 'resolve';
import lodash = require('lodash');
import * as tsdoc from '@microsoft/tsdoc';

import {
JsonFile,
Expand All @@ -18,10 +19,12 @@ import {
} from '@microsoft/node-core-library';
import {
IConfigFile,
IExtractorMessagesConfig
IExtractorMessagesConfig,
IConfigTsdocTagDefinition
} from './IConfigFile';
import { PackageMetadataManager } from '../analyzer/PackageMetadataManager';
import { MessageRouter } from '../collector/MessageRouter';
import { AedocDefinitions } from '@microsoft/api-extractor-model';

/**
* Tokens used during variable expansion of path fields from api-extractor.json.
Expand Down Expand Up @@ -111,6 +114,7 @@ interface IExtractorConfigParameters {
omitTrimmingComments: boolean;
tsdocMetadataEnabled: boolean;
tsdocMetadataFilePath: string;
tsdocConfig: tsdoc.TSDocConfiguration;
messages: IExtractorMessagesConfig;
testMode: boolean;
}
Expand Down Expand Up @@ -195,6 +199,11 @@ export class ExtractorConfig {
/** {@inheritDoc IConfigTsdocMetadata.tsdocMetadataFilePath} */
public readonly tsdocMetadataFilePath: string;

/**
* Configuration to use when parsing TSDoc comments.
*/
public readonly tsdocConfig: tsdoc.TSDocConfiguration;

/** {@inheritDoc IConfigFile.messages} */
public readonly messages: IExtractorMessagesConfig;

Expand Down Expand Up @@ -222,6 +231,7 @@ export class ExtractorConfig {
this.omitTrimmingComments = parameters.omitTrimmingComments;
this.tsdocMetadataEnabled = parameters.tsdocMetadataEnabled;
this.tsdocMetadataFilePath = parameters.tsdocMetadataFilePath;
this.tsdocConfig = parameters.tsdocConfig;
this.messages = parameters.messages;
this.testMode = parameters.testMode;
}
Expand Down Expand Up @@ -654,6 +664,25 @@ export class ExtractorConfig {
omitTrimmingComments = !!configObject.dtsRollup.omitTrimmingComments;
}

let tsdocConfig: tsdoc.TSDocConfiguration = AedocDefinitions.tsdocConfiguration;
if (configObject.tsdoc && configObject.tsdoc.tagDefinitions) {
const customTags: tsdoc.TSDocTagDefinition[] = configObject.tsdoc.tagDefinitions.map(
(tag: IConfigTsdocTagDefinition): tsdoc.TSDocTagDefinition => {
const tagParams: tsdoc.ITSDocTagDefinitionParameters = {
tagName: tag.tagName,
syntaxKind: tsdoc.TSDocTagSyntaxKind.BlockTag,
allowMultiple: tag.allowMultiple
};
if (tag.syntaxKind === 'inline') {
tagParams.syntaxKind = tsdoc.TSDocTagSyntaxKind.InlineTag;
} else if (tag.syntaxKind === 'modifier') {
tagParams.syntaxKind = tsdoc.TSDocTagSyntaxKind.ModifierTag;
}
return new tsdoc.TSDocTagDefinition(tagParams);
});
tsdocConfig = AedocDefinitions.getTsdocConfiguration(customTags);
}

return new ExtractorConfig({
projectFolder: projectFolder,
packageJson,
Expand All @@ -675,6 +704,7 @@ export class ExtractorConfig {
omitTrimmingComments,
tsdocMetadataEnabled,
tsdocMetadataFilePath,
tsdocConfig,
messages: configObject.messages || { },
testMode: !!configObject.testMode
});
Expand Down
52 changes: 52 additions & 0 deletions apps/api-extractor/src/api/IConfigFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,53 @@ export interface IConfigTsdocMetadata {
tsdocMetadataFilePath?: string;
}

/**
* Additional tags to support when parsing documentation comments with TSDoc.
*
* @remarks
* This is part of the {@link IConfigFile} structure.

* @public
*/
export interface IConfigTsdocTagDefinition {
/**
* Name of the custom tag. TSDoc tag names start with an at-sign (`@`) followed
* by ASCII letters using "camelCase" capitalization.
*/
tagName: string;

/**
* Syntax kind of the custom tag.
*
* @remarks
* `"inline"` means that this tag can appear inside other documentation sections (example: `{@link}`).
* `"block"` means that this tag starts a new documentation section (example: `@remarks`).
* `"modifier"` means that this tag's presence indicates an aspect of the associated API item (example: `@internal`).
*/
syntaxKind: 'inline' | 'block' | 'modifier';

/**
* If true, then this tag may appear multiple times in a doc comment.
* By default, a tag may only appear once.
*/
allowMultiple?: boolean;
}

/**
* Custom configuration for TSDoc, including custom supported tags.
*
* @remarks
* This is part of the {@link IConfigFile} structure.

* @public
*/
export interface IConfigTsdoc {
/**
* {@inheritDoc IConfigTsdocTagDefinition}
*/
tagDefinitions?: ReadonlyArray<IConfigTsdocTagDefinition>;
}

/**
* Configures reporting for a given message identifier.
*
Expand Down Expand Up @@ -367,6 +414,11 @@ export interface IConfigFile {
*/
tsdocMetadata?: IConfigTsdocMetadata;

/**
* {@inheritDoc IConfigTsdoc}
*/
tsdoc?: IConfigTsdoc;

/**
* {@inheritDoc IExtractorMessagesConfig}
*/
Expand Down
2 changes: 1 addition & 1 deletion apps/api-extractor/src/collector/Collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class Collector {
this.program = options.program;
this.typeChecker = options.program.getTypeChecker();

this._tsdocParser = new tsdoc.TSDocParser(AedocDefinitions.tsdocConfiguration);
this._tsdocParser = new tsdoc.TSDocParser(this.extractorConfig.tsdocConfig);

const bundledPackageNames: Set<string> = new Set<string>(this.extractorConfig.bundledPackages);

Expand Down
Loading