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] Deeply linked types #1337

Merged
merged 9 commits into from
Sep 10, 2019
320 changes: 205 additions & 115 deletions apps/api-documenter/src/documenters/YamlDocumenter.ts

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions apps/api-documenter/src/yaml/IYamlApiFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,19 @@ export interface IYamlParameter {
* declared in a separate YAML file.
*/
export interface IYamlReference {
uid?: string;
name?: string;
fullName?: string;
'spec.typeScript'?: IYamlReferenceSpec[];
}

/**
* Part of the IYamlApiFile structure. Represents a text specification for a reference.
*/
export interface IYamlReferenceSpec {
uid?: string;
name?: string;
fullName?: string;
}

/**
Expand Down
24 changes: 24 additions & 0 deletions apps/api-documenter/src/yaml/typescript.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,35 @@
"type": "object",
"additionalProperties": false,
"properties": {
"uid": {
"type": "string"
},
"name": {
"type": "string"
},
"fullName": {
"type": "string"
},
"spec.typeScript": {
"type": "array",
"items": {
"$ref": "#/definitions/spec"
}
}
}
},
"spec": {
"type": "object",
"additionalProperties": false,
"properties": {
"uid": {
"type": "string"
},
"name": {
"type": "string"
},
"fullName": {
"type": "string"
}
}
},
Expand Down
15 changes: 13 additions & 2 deletions apps/api-extractor-model/src/items/ApiDeclaredItem.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.s

import { DeclarationReference } from '@microsoft/tsdoc/lib/beta/DeclarationReference';
import { ApiDocumentedItem, IApiDocumentedItemJson, IApiDocumentedItemOptions } from './ApiDocumentedItem';
import { Excerpt, ExcerptToken, IExcerptTokenRange, IExcerptToken } from '../mixins/Excerpt';
import { DeserializerContext } from '../model/DeserializerContext';
Expand Down Expand Up @@ -47,7 +48,11 @@ export class ApiDeclaredItem extends ApiDocumentedItem {
public constructor(options: IApiDeclaredItemOptions) {
super(options);

this._excerptTokens = options.excerptTokens.map(x => new ExcerptToken(x.kind, x.text));
this._excerptTokens = options.excerptTokens.map(x => {
const canonicalReference: DeclarationReference | undefined = x.canonicalReference === undefined ? undefined :
DeclarationReference.parse(x.canonicalReference);
return new ExcerptToken(x.kind, x.text, canonicalReference);
});
this._excerpt = new Excerpt(this.excerptTokens, { startIndex: 0, endIndex: this.excerptTokens.length });
}

Expand Down Expand Up @@ -99,7 +104,13 @@ export class ApiDeclaredItem extends ApiDocumentedItem {
/** @override */
public serializeInto(jsonObject: Partial<IApiDeclaredItemJson>): void {
super.serializeInto(jsonObject);
jsonObject.excerptTokens = this.excerptTokens.map(x => ({ kind: x.kind, text: x.text }));
jsonObject.excerptTokens = this.excerptTokens.map(x => {
const excerptToken: IExcerptToken = { kind: x.kind, text: x.text };
if (x.canonicalReference !== undefined) {
excerptToken.canonicalReference = x.canonicalReference.toString();
}
return excerptToken;
});
}

/**
Expand Down
10 changes: 9 additions & 1 deletion apps/api-extractor-model/src/mixins/Excerpt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { DeclarationReference } from '@microsoft/tsdoc/lib/beta/DeclarationReference';
import { Text } from '@microsoft/node-core-library';

/** @public */
Expand All @@ -21,20 +22,23 @@ export interface IExcerptTokenRange {
export interface IExcerptToken {
readonly kind: ExcerptTokenKind;
text: string;
canonicalReference?: string;
}

/** @public */
export class ExcerptToken {
private readonly _kind: ExcerptTokenKind;
private readonly _text: string;
private readonly _canonicalReference: DeclarationReference | undefined;

public constructor(kind: ExcerptTokenKind, text: string) {
public constructor(kind: ExcerptTokenKind, text: string, canonicalReference?: DeclarationReference) {
this._kind = kind;

// Standardize the newlines across operating systems. Even though this may deviate from the actual
// input source file that was parsed, it's useful because the newline gets serialized inside
// a string literal in .api.json, which cannot be automatically normalized by Git.
this._text = Text.convertToLf(text);
this._canonicalReference = canonicalReference;
}

public get kind(): ExcerptTokenKind {
Expand All @@ -44,6 +48,10 @@ export class ExcerptToken {
public get text(): string {
return this._text;
}

public get canonicalReference(): DeclarationReference | undefined {
return this._canonicalReference;
}
}

/**
Expand Down
8 changes: 3 additions & 5 deletions apps/api-extractor/src/analyzer/AstSymbolTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,19 +227,17 @@ export class AstSymbolTable {
* ```
*/
public static getLocalNameForSymbol(symbol: ts.Symbol): string {
const symbolName: string = symbol.name;

// TypeScript binds well-known ECMAScript symbols like "[Symbol.iterator]" as "__@iterator".
// Decode it back into "[Symbol.iterator]".
const wellKnownSymbolName: string | undefined = TypeScriptHelpers.tryDecodeWellKnownSymbolName(symbolName);
const wellKnownSymbolName: string | undefined = TypeScriptHelpers.tryDecodeWellKnownSymbolName(symbol.escapedName);
if (wellKnownSymbolName) {
return wellKnownSymbolName;
}

const isUniqueSymbol: boolean = TypeScriptHelpers.isUniqueSymbolName(symbolName);
const isUniqueSymbol: boolean = TypeScriptHelpers.isUniqueSymbolName(symbol.escapedName);

// We will try to obtain the name from a declaration; otherwise we'll fall back to the symbol name.
let unquotedName: string = symbolName;
let unquotedName: string = symbol.name;

for (const declaration of symbol.declarations || []) {
// Handle cases such as "export default class X { }" where the symbol name is "default"
Expand Down
8 changes: 4 additions & 4 deletions apps/api-extractor/src/analyzer/TypeScriptHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ export class TypeScriptHelpers {
* If `name` is of this form, then `tryGetWellKnownSymbolName()` converts it back into e.g. `[Symbol.iterator]`.
* If the string does not start with `__@` then `undefined` is returned.
*/
public static tryDecodeWellKnownSymbolName(name: string): string | undefined {
const match: RegExpExecArray | null = TypeScriptHelpers._wellKnownSymbolNameRegExp.exec(name);
public static tryDecodeWellKnownSymbolName(name: ts.__String): string | undefined {
const match: RegExpExecArray | null = TypeScriptHelpers._wellKnownSymbolNameRegExp.exec(name as string);
if (match) {
const identifier: string = match[1];
return `[Symbol.${identifier}]`;
Expand All @@ -238,8 +238,8 @@ export class TypeScriptHelpers {
/**
* Returns whether the provided name was generated for a TypeScript `unique symbol`.
*/
public static isUniqueSymbolName(name: string): boolean {
return TypeScriptHelpers._uniqueSymbolNameRegExp.test(name);
public static isUniqueSymbolName(name: ts.__String): boolean {
return TypeScriptHelpers._uniqueSymbolNameRegExp.test(name as string);
}

/**
Expand Down
23 changes: 23 additions & 0 deletions apps/api-extractor/src/generators/ApiModelGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,23 @@ import { Collector } from '../collector/Collector';
import { AstDeclaration } from '../analyzer/AstDeclaration';
import { ExcerptBuilder, IExcerptBuilderNodeToCapture } from './ExcerptBuilder';
import { AstSymbol } from '../analyzer/AstSymbol';
import { DeclarationReferenceGenerator } from './DeclarationReferenceGenerator';

export class ApiModelGenerator {
private readonly _collector: Collector;
private readonly _cachedOverloadIndexesByDeclaration: Map<AstDeclaration, number>;
private readonly _apiModel: ApiModel;
private readonly _referenceGenerator: DeclarationReferenceGenerator;

public constructor(collector: Collector) {
this._collector = collector;
this._cachedOverloadIndexesByDeclaration = new Map<AstDeclaration, number>();
this._apiModel = new ApiModel();
this._referenceGenerator = new DeclarationReferenceGenerator(
collector.packageJsonLookup,
collector.workingPackage.name,
collector.program,
collector.typeChecker);
}

public get apiModel(): ApiModel {
Expand Down Expand Up @@ -195,6 +202,7 @@ export class ApiModelGenerator {
const parameters: IApiParameterOptions[] = this._captureParameters(nodesToCapture, callSignature.parameters);

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -225,6 +233,7 @@ export class ApiModelGenerator {
constructorDeclaration.parameters);

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -274,6 +283,7 @@ export class ApiModelGenerator {
}

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
stopBeforeChildKind: ts.SyntaxKind.FirstPunctuation, // FirstPunctuation = "{"
nodesToCapture
Expand Down Expand Up @@ -314,6 +324,7 @@ export class ApiModelGenerator {
const parameters: IApiParameterOptions[] = this._captureParameters(nodesToCapture, constructSignature.parameters);

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand All @@ -337,6 +348,7 @@ export class ApiModelGenerator {

if (apiEnum === undefined) {
const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
stopBeforeChildKind: ts.SyntaxKind.FirstPunctuation // FirstPunctuation = "{"
});
Expand Down Expand Up @@ -368,6 +380,7 @@ export class ApiModelGenerator {
nodesToCapture.push({ node: enumMember.initializer, tokenRange: initializerTokenRange });

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -408,6 +421,7 @@ export class ApiModelGenerator {
functionDeclaration.parameters);

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -441,6 +455,7 @@ export class ApiModelGenerator {
const parameters: IApiParameterOptions[] = this._captureParameters(nodesToCapture, indexSignature.parameters);

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -483,6 +498,7 @@ export class ApiModelGenerator {
}

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
stopBeforeChildKind: ts.SyntaxKind.FirstPunctuation, // FirstPunctuation = "{"
nodesToCapture
Expand Down Expand Up @@ -525,6 +541,7 @@ export class ApiModelGenerator {
const parameters: IApiParameterOptions[] = this._captureParameters(nodesToCapture, methodDeclaration.parameters);

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -564,6 +581,7 @@ export class ApiModelGenerator {
const parameters: IApiParameterOptions[] = this._captureParameters(nodesToCapture, methodSignature.parameters);

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand All @@ -587,6 +605,7 @@ export class ApiModelGenerator {

if (apiNamespace === undefined) {
const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
stopBeforeChildKind: ts.SyntaxKind.ModuleBlock // ModuleBlock = the "{ ... }" block
});
Expand Down Expand Up @@ -622,6 +641,7 @@ export class ApiModelGenerator {
nodesToCapture.push({ node: propertyDeclaration.type, tokenRange: propertyTypeTokenRange });

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -654,6 +674,7 @@ export class ApiModelGenerator {
nodesToCapture.push({ node: propertySignature.type, tokenRange: propertyTypeTokenRange });

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -691,6 +712,7 @@ export class ApiModelGenerator {
nodesToCapture.push({ node: typeAliasDeclaration.type, tokenRange: typeTokenRange });

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down Expand Up @@ -723,6 +745,7 @@ export class ApiModelGenerator {
nodesToCapture.push({ node: variableDeclaration.type, tokenRange: variableTypeTokenRange });

const excerptTokens: IExcerptToken[] = ExcerptBuilder.build({
referenceGenerator: this._referenceGenerator,
startingNode: astDeclaration.declaration,
nodesToCapture
});
Expand Down
Loading