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] Adds package-defined TSDoc tag support #1950

Merged
merged 24 commits into from
Apr 17, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a3010f7
adds tsdoc-config and updates tsdoc to have compatible type signatures
nicholasrice Jun 19, 2020
03b0127
construct tsdoc configuration in ExtractorConfig
nicholasrice Jun 19, 2020
ff63440
wire tsdocConfiguration throughout the api-extractor
nicholasrice Jun 19, 2020
5451da7
remove references to AedocDefinitions and replace with configurable t…
nicholasrice Jun 20, 2020
61bdcb6
update test fixtures
nicholasrice Jun 20, 2020
6bd65e4
rush change results
nicholasrice Jun 20, 2020
7ef3840
move tsdoc configuration construction to DeserializerContext
nicholasrice Jun 30, 2020
b1004b0
Bump version
octogonz Jan 16, 2021
26d4b2c
rush update
octogonz Jan 16, 2021
0a5d0a4
rush rebuild
octogonz Jan 16, 2021
32b306e
rush update
nicholasrice Mar 22, 2021
9ecc08f
update tsdoc and tsdoc-config
nicholasrice Mar 22, 2021
52829d8
implement config emission and parsing, add test cases, and rush rebuild
nicholasrice Mar 23, 2021
0ce7230
Merge remote-tracking branch 'remotes/origin/master' into users/niric…
octogonz Apr 16, 2021
7d49b39
rush update
octogonz Apr 16, 2021
9bbf43f
Upgrade to tsdoc-config 0.15.0 which adds the loadFromObject() API
octogonz Apr 16, 2021
ad628a8
rush update
octogonz Apr 16, 2021
00b9aee
Move tsdoc-config to nonbrowser-approved-packages.json
octogonz Apr 16, 2021
ff5f108
rush update
octogonz Apr 17, 2021
bbcbe92
Use TSConfigFile.loadFromObject() to deserialize the TSDoc configuration
octogonz Apr 17, 2021
156aed9
Increment the ApiJsonSchemaVersion and implement backwards compatibility
octogonz Apr 17, 2021
8a5d5c2
Move the AEDoc definitions from ./tsdoc.json --> extends/tsdoc-base.json
octogonz Apr 17, 2021
f55ebc2
Add launch.json config for debugging Jest tests
octogonz Apr 17, 2021
00ed463
Update ExtractorConfig to load tsdoc-base.json, and add an example ts…
octogonz Apr 17, 2021
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
2 changes: 1 addition & 1 deletion apps/api-documenter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"typings": "dist/rollup.d.ts",
"dependencies": {
"@microsoft/api-extractor-model": "workspace:*",
"@microsoft/tsdoc": "0.12.24",
"@microsoft/tsdoc": "0.13.0",
"@rushstack/node-core-library": "workspace:*",
"@rushstack/ts-command-line": "workspace:*",
"colors": "~1.2.1",
Expand Down
2 changes: 1 addition & 1 deletion apps/api-extractor-model/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"build": "heft test --clean"
},
"dependencies": {
"@microsoft/tsdoc": "0.12.24",
"@microsoft/tsdoc": "0.13.0",
"@rushstack/node-core-library": "workspace:*"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions apps/api-extractor-model/src/aedoc/AedocDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TSDocConfiguration, TSDocTagDefinition, TSDocTagSyntaxKind, StandardTag

/**
* @internal
* @deprecated - tsdoc configuration is now constructed from tsdoc.json files associated with each package.
*/
export class AedocDefinitions {
public static readonly betaDocumentation: TSDocTagDefinition = new TSDocTagDefinition({
Expand Down
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 @@ -47,7 +46,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.tsdocConfiguration);

// 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
60 changes: 57 additions & 3 deletions apps/api-extractor-model/src/model/ApiPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ import { ApiDocumentedItem, IApiDocumentedItemOptions } from '../items/ApiDocume
import { ApiEntryPoint } from './ApiEntryPoint';
import { IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin';
import { DeserializerContext, ApiJsonSchemaVersion } from './DeserializerContext';
import { TSDocConfiguration, TSDocTagDefinition, TSDocTagSyntaxKind } from '@microsoft/tsdoc';

interface ITagConfigJson {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@octogonz defining these here is odd to me because they're essentially a copy of the interfaces defined in tsdoc-config and are the product of saveToObject(), but saveToObject() returns unknown. Should I do something to address this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was easy, so I went ahead and implemented the missing loadFromObject() operation in microsoft/tsdoc#288

tagName: string;
syntaxKind: 'inline' | 'block' | 'modifier';
allowMultiple?: boolean;
}

interface ITSDocConfigJson {
tagDefinitions: ITagConfigJson[];
supportForTags: { [tagName: string]: boolean };
}

/**
* Constructor options for {@link ApiPackage}.
Expand All @@ -22,7 +34,12 @@ import { DeserializerContext, ApiJsonSchemaVersion } from './DeserializerContext
export interface IApiPackageOptions
extends IApiItemContainerMixinOptions,
IApiNameMixinOptions,
IApiDocumentedItemOptions {}
IApiDocumentedItemOptions {
/**
* The TSDoc tag definitions and support for the package
*/
tsDocConfig: ITSDocConfigJson;
}

export interface IApiPackageMetadataJson {
/**
Expand Down Expand Up @@ -58,6 +75,11 @@ export interface IApiPackageMetadataJson {
* `IApiPackageMetadataJson.schemaVersion`.
*/
oldestForwardsCompatibleVersion?: ApiJsonSchemaVersion;

/**
* The TSDoc tags used by the package
*/
tsDocConfig: ITSDocConfigJson;
}

export interface IApiPackageJson extends IApiItemJson {
Expand Down Expand Up @@ -105,8 +127,15 @@ export interface IApiPackageSaveOptions extends IJsonFileSaveOptions {
* @public
*/
export class ApiPackage extends ApiItemContainerMixin(ApiNameMixin(ApiDocumentedItem)) {
/**
* TSDoc Tags for to the package.
*/
private readonly _tsdocConfig: ITSDocConfigJson;

public constructor(options: IApiPackageOptions) {
super(options);

this._tsdocConfig = options.tsDocConfig;
}

public static loadFromJsonFile(apiJsonFilename: string): ApiPackage {
Expand Down Expand Up @@ -157,11 +186,35 @@ export class ApiPackage extends ApiItemContainerMixin(ApiNameMixin(ApiDocumented
}
}

const tsdocConfiguration: TSDocConfiguration = new TSDocConfiguration();
tsdocConfiguration.clear(true);
const { tagDefinitions, supportForTags } = jsonObject.metadata.tsDocConfig;
tsdocConfiguration.addTagDefinitions(
tagDefinitions.map((definition) => {
const { syntaxKind } = definition;
const formattedSyntaxKind: TSDocTagSyntaxKind =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the re-mapping I am referring to in my previous comment. Should there be a mechanism from tsdoc-config to build up a TSDocConfigFile from the product of TSDocConfigFile.saveToObject()?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicholasrice The right solution would be to implement a corresponding TSDocConfigFile.loadFromObject() to handle loading of the object. Ideally it would be integrated with the TSDocConfigFile.loadFile() implementation, although there is an interesting nuance that loadFile() considers file paths and "extends" resolution which assume that the objects have meaningful file paths. So a bit of abstraction might be needed to handle that. (Perhaps that is why I procrastinated it?)

If you want to make a PR for TSDoc that would be great. However your PR has been open for a very long time, so if it helps speed things along, I would also be okay with merging a less ideal solution to get you unblocked. And then we could come back later and improve the tsdoc-config API.

I am away on vacation right now, but I will review this PR as soon as I get back. Sorry about the delays.

For future reference if you need help getting PRs merged @iclanton's team at Microsoft is a good point of contact, and also we now have a #contributor-helpline chatroom for getting attention of the maintainers. Thanks!

syntaxKind === 'block'
? TSDocTagSyntaxKind.BlockTag
: syntaxKind === 'inline'
? TSDocTagSyntaxKind.InlineTag
: TSDocTagSyntaxKind.ModifierTag;
return new TSDocTagDefinition({ ...definition, syntaxKind: formattedSyntaxKind });
})
);

Object.entries(supportForTags).forEach(([name, supported]) => {
const tag: TSDocTagDefinition | undefined = tsdocConfiguration.tryGetTagDefinition(name);
if (tag) {
tsdocConfiguration.setSupportForTag(tag, supported);
}
});

const context: DeserializerContext = new DeserializerContext({
apiJsonFilename,
toolPackage: jsonObject.metadata.toolPackage,
toolVersion: jsonObject.metadata.toolVersion,
versionToDeserialize: versionToDeserialize
versionToDeserialize: versionToDeserialize,
tsdocConfiguration
});

return ApiItem.deserialize(jsonObject, context) as ApiPackage;
Expand Down Expand Up @@ -208,7 +261,8 @@ export class ApiPackage extends ApiItemContainerMixin(ApiNameMixin(ApiDocumented
// the version is bumped. Instead we write a placeholder string.
toolVersion: options.testMode ? '[test mode]' : options.toolVersion || packageJson.version,
schemaVersion: ApiJsonSchemaVersion.LATEST,
oldestForwardsCompatibleVersion: ApiJsonSchemaVersion.OLDEST_FORWARDS_COMPATIBLE
oldestForwardsCompatibleVersion: ApiJsonSchemaVersion.OLDEST_FORWARDS_COMPATIBLE,
tsDocConfig: this._tsdocConfig
}
} as IApiPackageJson;
this.serializeInto(jsonObject);
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 { TSDocConfiguration } from '@microsoft/tsdoc';

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

/**
* The TSDoc configuration for the context.
*/
public readonly tsdocConfiguration: TSDocConfiguration;

public constructor(options: DeserializerContext) {
this.apiJsonFilename = options.apiJsonFilename;
this.toolPackage = options.toolPackage;
this.toolVersion = options.toolVersion;
this.versionToDeserialize = options.versionToDeserialize;
this.tsdocConfiguration = options.tsdocConfiguration;
}
}
3 changes: 2 additions & 1 deletion apps/api-extractor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
},
"dependencies": {
"@microsoft/api-extractor-model": "workspace:*",
"@microsoft/tsdoc": "0.12.24",
"@microsoft/tsdoc-config": "~0.14.0",
"@microsoft/tsdoc": "0.13.0",
"@rushstack/node-core-library": "workspace:*",
"@rushstack/rig-package": "workspace:*",
"@rushstack/ts-command-line": "workspace:*",
Expand Down
3 changes: 2 additions & 1 deletion apps/api-extractor/src/api/Extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ export class Extractor {
messageCallback: options.messageCallback,
messagesConfig: extractorConfig.messages || {},
showVerboseMessages: !!options.showVerboseMessages,
showDiagnostics: !!options.showDiagnostics
showDiagnostics: !!options.showDiagnostics,
tsdocConfiguration: extractorConfig.tsdocConfiguration
});

this._checkCompilerCompatibility(extractorConfig, messageRouter);
Expand Down
22 changes: 22 additions & 0 deletions apps/api-extractor/src/api/ExtractorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { RigConfig } from '@rushstack/rig-package';
import { IConfigFile, IExtractorMessagesConfig } from './IConfigFile';
import { PackageMetadataManager } from '../analyzer/PackageMetadataManager';
import { MessageRouter } from '../collector/MessageRouter';
import { TSDocConfiguration } from '@microsoft/tsdoc';
import { TSDocConfigFile } from '@microsoft/tsdoc-config';

/**
* Tokens used during variable expansion of path fields from api-extractor.json.
Expand Down Expand Up @@ -149,6 +151,7 @@ interface IExtractorConfigParameters {
omitTrimmingComments: boolean;
tsdocMetadataEnabled: boolean;
tsdocMetadataFilePath: string;
tsdocConfiguration: TSDocConfiguration;
newlineKind: NewlineKind;
messages: IExtractorMessagesConfig;
testMode: boolean;
Expand Down Expand Up @@ -236,6 +239,11 @@ export class ExtractorConfig {
/** {@inheritDoc IConfigTsdocMetadata.tsdocMetadataFilePath} */
public readonly tsdocMetadataFilePath: string;

/**
* The TSDocConfiguration to use for parsing TSDoc comments
*/
public readonly tsdocConfiguration: TSDocConfiguration;

/**
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
* will be written with Windows-style newlines.
Expand Down Expand Up @@ -269,6 +277,7 @@ export class ExtractorConfig {
this.omitTrimmingComments = parameters.omitTrimmingComments;
this.tsdocMetadataEnabled = parameters.tsdocMetadataEnabled;
this.tsdocMetadataFilePath = parameters.tsdocMetadataFilePath;
this.tsdocConfiguration = parameters.tsdocConfiguration;
this.newlineKind = parameters.newlineKind;
this.messages = parameters.messages;
this.testMode = parameters.testMode;
Expand Down Expand Up @@ -901,6 +910,18 @@ export class ExtractorConfig {
break;
}

const packageTSDocConfigPath: string = TSDocConfigFile.findConfigPathForFolder(projectFolder);
const tsdocConfigFile: TSDocConfigFile = TSDocConfigFile.loadForFolder(
FileSystem.exists(packageTSDocConfigPath) ? packageTSDocConfigPath : __filename
);

if (tsdocConfigFile.hasErrors) {
throw new Error(tsdocConfigFile.getErrorSummary());
}

const tsdocConfiguration: TSDocConfiguration = new TSDocConfiguration();
tsdocConfigFile.configureParser(tsdocConfiguration);

return new ExtractorConfig({
projectFolder: projectFolder,
packageJson,
Expand All @@ -922,6 +943,7 @@ export class ExtractorConfig {
omitTrimmingComments,
tsdocMetadataEnabled,
tsdocMetadataFilePath,
tsdocConfiguration,
newlineKind,
messages: configObject.messages || {},
testMode: !!configObject.testMode
Expand Down
58 changes: 58 additions & 0 deletions apps/api-extractor/src/api/test/Extractor-custom-tags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { StandardTags } from '@microsoft/tsdoc';
import * as path from 'path';

import { ExtractorConfig } from '../ExtractorConfig';

const testDataFolder: string = path.join(__dirname, 'test-data');

describe('Extractor-custom-tags', () => {
describe('should use a TSDocConfiguration', () => {
it.only("with custom TSDoc tags defined in the package's tsdoc.json", () => {
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
path.join(testDataFolder, 'custom-tsdoc-tags/api-extractor.json')
);
const { tsdocConfiguration } = extractorConfig;

expect(tsdocConfiguration.tryGetTagDefinition('@block')).not.toBe(undefined);
expect(tsdocConfiguration.tryGetTagDefinition('@inline')).not.toBe(undefined);
expect(tsdocConfiguration.tryGetTagDefinition('@modifier')).not.toBe(undefined);
});
it.only("with custom TSDoc tags enabled per the package's tsdoc.json", () => {
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
path.join(testDataFolder, 'custom-tsdoc-tags/api-extractor.json')
);
const { tsdocConfiguration } = extractorConfig;
const block = tsdocConfiguration.tryGetTagDefinition('@block')!;
const inline = tsdocConfiguration.tryGetTagDefinition('@inline')!;
const modifier = tsdocConfiguration.tryGetTagDefinition('@modifier')!;

expect(tsdocConfiguration.isTagSupported(block)).toBe(true);
expect(tsdocConfiguration.isTagSupported(inline)).toBe(true);
expect(tsdocConfiguration.isTagSupported(modifier)).toBe(false);
});
it.only("with standard tags and API Extractor custom tags defined and supported when the package's tsdoc.json extends API Extractor's tsdoc.json", () => {
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
path.join(testDataFolder, 'custom-tsdoc-tags/api-extractor.json')
);
const { tsdocConfiguration } = extractorConfig;

expect(tsdocConfiguration.tryGetTagDefinition('@inline')).not.toBe(undefined);
expect(tsdocConfiguration.tryGetTagDefinition('@block')).not.toBe(undefined);
expect(tsdocConfiguration.tryGetTagDefinition('@modifier')).not.toBe(undefined);

StandardTags.allDefinitions
.concat([
tsdocConfiguration.tryGetTagDefinition('@betaDocumentation')!,
tsdocConfiguration.tryGetTagDefinition('@internalRemarks')!,
tsdocConfiguration.tryGetTagDefinition('@preapproved')!
])
.forEach((tag) => {
expect(tsdocConfiguration.tagDefinitions.includes(tag));
expect(tsdocConfiguration.supportedTagDefinitions.includes(tag));
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",

"mainEntryPointFilePath": "<projectFolder>/index.d.ts",

"apiReport": {
"enabled": true
},

"docModel": {
"enabled": true
},

"dtsRollup": {
"enabled": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @block
*
* @inline test
* @modifier
*/
interface CustomTagsTestInterface {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "config-lookup1",
"version": "1.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "http://json.schemastore.org/tsconfig",

"compilerOptions": {
"outDir": "lib",
"rootDir": "src",

"forceConsistentCasingInFileNames": true,
"jsx": "react",
"declaration": true,
"sourceMap": true,
"declarationMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"types": ["heft-jest", "node"],

"module": "commonjs",
"target": "es2017",
"lib": ["es2017"]
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "lib"]
}
Loading