mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-11 09:03:29 +01:00
feat(api-extractor): support multiple entrypoints (#10829)
* feat(api-extractor): support multiple entrypoints * chore: initial support in generateSplitDocumentation * chore: bring in line with upstream * refactor: multiple entrypoints in scripts * fix: split docs * feat: website * fix: docs failing on next * fix: don't include dtypes for now * refactor: don't fetch entrypoint if there is none --------- Co-authored-by: iCrawl <buechler.noel@outlook.com>
This commit is contained in:
@@ -51,6 +51,7 @@ import type { AstModule } from '../analyzer/AstModule.js';
|
||||
import { AstNamespaceImport } from '../analyzer/AstNamespaceImport.js';
|
||||
import { AstSymbol } from '../analyzer/AstSymbol.js';
|
||||
import { TypeScriptInternals } from '../analyzer/TypeScriptInternals.js';
|
||||
import type { ExtractorConfig } from '../api/ExtractorConfig';
|
||||
import type { ApiItemMetadata } from '../collector/ApiItemMetadata.js';
|
||||
import type { Collector } from '../collector/Collector.js';
|
||||
import type { DeclarationMetadata } from '../collector/DeclarationMetadata.js';
|
||||
@@ -210,6 +211,16 @@ interface IProcessAstEntityContext {
|
||||
parentDocgenJson?: DocgenContainerJson | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export interface IApiModelGenerationOptions {
|
||||
/**
|
||||
* The release tags to trim.
|
||||
*/
|
||||
releaseTagsToTrim: Set<ReleaseTag>;
|
||||
}
|
||||
|
||||
const linkRegEx =
|
||||
/{@link\s(?:(?<class>\w+)(?:[#.](?<event>event:)?(?<prop>[\w()]+))?|(?<url>https?:\/\/[^\s}]*))(?<name>\s[^}]*)?}/g;
|
||||
|
||||
@@ -239,12 +250,25 @@ export class ApiModelGenerator {
|
||||
|
||||
private readonly _referenceGenerator: DeclarationReferenceGenerator;
|
||||
|
||||
private readonly _releaseTagsToTrim: Set<ReleaseTag> | undefined;
|
||||
|
||||
public readonly docModelEnabled: boolean;
|
||||
|
||||
private readonly _jsDocJson: DocgenJson | undefined;
|
||||
|
||||
public constructor(collector: Collector) {
|
||||
public constructor(collector: Collector, extractorConfig: ExtractorConfig) {
|
||||
this._collector = collector;
|
||||
this._apiModel = new ApiModel();
|
||||
this._referenceGenerator = new DeclarationReferenceGenerator(collector);
|
||||
|
||||
const apiModelGenerationOptions: IApiModelGenerationOptions | undefined = extractorConfig.docModelGenerationOptions;
|
||||
if (apiModelGenerationOptions) {
|
||||
this._releaseTagsToTrim = apiModelGenerationOptions.releaseTagsToTrim;
|
||||
this.docModelEnabled = true;
|
||||
} else {
|
||||
this.docModelEnabled = false;
|
||||
}
|
||||
|
||||
// @ts-expect-error we reuse the private tsdocParser from collector here
|
||||
this._tsDocParser = collector._tsdocParser;
|
||||
}
|
||||
@@ -270,20 +294,22 @@ export class ApiModelGenerator {
|
||||
});
|
||||
this._apiModel.addMember(apiPackage);
|
||||
|
||||
const apiEntryPoint: ApiEntryPoint = new ApiEntryPoint({ name: '' });
|
||||
apiPackage.addMember(apiEntryPoint);
|
||||
for (const [entryPoint, entities] of this._collector.entities.entries()) {
|
||||
const apiEntryPoint: ApiEntryPoint = new ApiEntryPoint({ name: entryPoint.modulePath });
|
||||
apiPackage.addMember(apiEntryPoint);
|
||||
|
||||
for (const entity of this._collector.entities) {
|
||||
// Only process entities that are exported from the entry point. Entities that are exported from
|
||||
// `AstNamespaceImport` entities will be processed by `_processAstNamespaceImport`. However, if
|
||||
// we are including forgotten exports, then process everything.
|
||||
if (entity.exportedFromEntryPoint || this._collector.extractorConfig.docModelIncludeForgottenExports) {
|
||||
this._processAstEntity(entity.astEntity, {
|
||||
name: entity.nameForEmit!,
|
||||
isExported: entity.exportedFromEntryPoint,
|
||||
parentApiItem: apiEntryPoint,
|
||||
parentDocgenJson: this._jsDocJson,
|
||||
});
|
||||
for (const entity of entities) {
|
||||
// Only process entities that are exported from the entry point. Entities that are exported from
|
||||
// `AstNamespaceImport` entities will be processed by `_processAstNamespaceImport`. However, if
|
||||
// we are including forgotten exports, then process everything.
|
||||
if (entity.exportedFromEntryPoint || this._collector.extractorConfig.docModelIncludeForgottenExports) {
|
||||
this._processAstEntity(entity.astEntity, {
|
||||
name: entity.nameForEmit!,
|
||||
isExported: entity.exportedFromEntryPoint,
|
||||
parentApiItem: apiEntryPoint,
|
||||
parentDocgenJson: this._jsDocJson,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,8 +388,8 @@ export class ApiModelGenerator {
|
||||
|
||||
const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration);
|
||||
const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag;
|
||||
if (releaseTag === ReleaseTag.Internal) {
|
||||
return; // trim out items marked as "@internal"
|
||||
if (this._releaseTagsToTrim?.has(releaseTag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (astDeclaration.declaration.kind) {
|
||||
@@ -435,15 +461,31 @@ export class ApiModelGenerator {
|
||||
this._processApiTypeAlias(astDeclaration, context);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.VariableDeclaration:
|
||||
this._processApiVariable(astDeclaration, context);
|
||||
case ts.SyntaxKind.VariableDeclaration: {
|
||||
// check for arrow functions in variable declaration
|
||||
const functionDeclaration: ts.FunctionDeclaration | undefined =
|
||||
this._tryFindFunctionDeclaration(astDeclaration);
|
||||
if (functionDeclaration) {
|
||||
this._processApiFunction(astDeclaration, context, functionDeclaration);
|
||||
} else {
|
||||
this._processApiVariable(astDeclaration, context);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// ignore unknown types
|
||||
}
|
||||
}
|
||||
|
||||
private _tryFindFunctionDeclaration(astDeclaration: AstDeclaration): ts.FunctionDeclaration | undefined {
|
||||
const children: readonly ts.Node[] = astDeclaration.declaration.getChildren(
|
||||
astDeclaration.declaration.getSourceFile(),
|
||||
);
|
||||
return children.find(ts.isFunctionTypeNode) as ts.FunctionDeclaration | undefined;
|
||||
}
|
||||
|
||||
private _processChildDeclarations(astDeclaration: AstDeclaration, context: IProcessAstEntityContext): void {
|
||||
for (const childDeclaration of astDeclaration.children) {
|
||||
this._processDeclaration(childDeclaration, {
|
||||
@@ -817,7 +859,11 @@ export class ApiModelGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private _processApiFunction(astDeclaration: AstDeclaration, context: IProcessAstEntityContext): void {
|
||||
private _processApiFunction(
|
||||
astDeclaration: AstDeclaration,
|
||||
context: IProcessAstEntityContext,
|
||||
altFunctionDeclaration?: ts.FunctionDeclaration,
|
||||
): void {
|
||||
const { name, isExported, parentApiItem } = context;
|
||||
|
||||
const overloadIndex: number = this._collector.getOverloadIndex(astDeclaration);
|
||||
@@ -828,7 +874,8 @@ export class ApiModelGenerator {
|
||||
const jsDoc = parent?.functions.find((fun) => fun.name === name);
|
||||
|
||||
if (apiFunction === undefined) {
|
||||
const functionDeclaration: ts.FunctionDeclaration = astDeclaration.declaration as ts.FunctionDeclaration;
|
||||
const functionDeclaration: ts.FunctionDeclaration =
|
||||
altFunctionDeclaration ?? (astDeclaration.declaration as ts.FunctionDeclaration);
|
||||
|
||||
const nodesToCapture: IExcerptBuilderNodeToCapture[] = [];
|
||||
|
||||
@@ -1799,11 +1846,20 @@ export class ApiModelGenerator {
|
||||
.flatMap((typ, index) => {
|
||||
const result = typ.reduce<IExcerptToken[]>((arr, [type, symbol]) => {
|
||||
const astEntity =
|
||||
(this._collector.entities.find(
|
||||
(entity) => entity.nameForEmit === type && 'astDeclarations' in entity.astEntity,
|
||||
)?.astEntity as AstSymbol | undefined) ??
|
||||
(this._collector.entities.find((entity) => entity.nameForEmit === type && 'astSymbol' in entity.astEntity)
|
||||
?.astEntity as AstImport | undefined);
|
||||
[...this._collector.entities.values()].reduce<AstSymbol | undefined>(
|
||||
(found, entities) =>
|
||||
found ??
|
||||
(entities.find((entity) => entity.nameForEmit === type && 'astDeclarations' in entity.astEntity)
|
||||
?.astEntity as AstSymbol | undefined),
|
||||
undefined,
|
||||
) ??
|
||||
[...this._collector.entities.values()].reduce<AstImport | undefined>(
|
||||
(found, entities) =>
|
||||
found ??
|
||||
(entities.find((entity) => entity.nameForEmit === type && 'astSymbol' in entity.astEntity)
|
||||
?.astEntity as AstImport | undefined),
|
||||
undefined,
|
||||
);
|
||||
const astSymbol = astEntity instanceof AstImport ? astEntity.astSymbol : astEntity;
|
||||
const match = astEntity instanceof AstImport ? moduleNameRegEx.exec(astEntity.modulePath) : null;
|
||||
const pkg = match?.groups!.package ?? this._apiModel.packages[0]!.name;
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as ts from 'typescript';
|
||||
import { AstDeclaration } from '../analyzer/AstDeclaration.js';
|
||||
import type { AstEntity } from '../analyzer/AstEntity.js';
|
||||
import { AstImport } from '../analyzer/AstImport.js';
|
||||
import type { AstModuleExportInfo } from '../analyzer/AstModule.js';
|
||||
import type { IAstModuleExportInfo } from '../analyzer/AstModule.js';
|
||||
import { AstNamespaceImport } from '../analyzer/AstNamespaceImport.js';
|
||||
import { AstSymbol } from '../analyzer/AstSymbol.js';
|
||||
import { SourceFileLocationFormatter } from '../analyzer/SourceFileLocationFormatter.js';
|
||||
@@ -17,12 +17,18 @@ import { Span } from '../analyzer/Span.js';
|
||||
import { TypeScriptHelpers } from '../analyzer/TypeScriptHelpers.js';
|
||||
import type { ExtractorMessage } from '../api/ExtractorMessage.js';
|
||||
import { ExtractorMessageId } from '../api/ExtractorMessageId.js';
|
||||
import type { ApiReportVariant } from '../api/IConfigFile';
|
||||
import type { ApiItemMetadata } from '../collector/ApiItemMetadata.js';
|
||||
import { Collector } from '../collector/Collector.js';
|
||||
import type { CollectorEntity } from '../collector/CollectorEntity.js';
|
||||
import type { SymbolMetadata } from '../collector/SymbolMetadata';
|
||||
import { DtsEmitHelpers } from './DtsEmitHelpers.js';
|
||||
import { IndentedWriter } from './IndentedWriter.js';
|
||||
|
||||
function capitalizeFirstLetter(input: string): string {
|
||||
return input === '' ? '' : `${input[0]!.toLocaleUpperCase()}${input.slice(1)}`;
|
||||
}
|
||||
|
||||
export class ApiReportGenerator {
|
||||
private static _trimSpacesRegExp: RegExp = / +$/gm;
|
||||
|
||||
@@ -40,208 +46,235 @@ export class ApiReportGenerator {
|
||||
return normalizedActual === normalizedExpected;
|
||||
}
|
||||
|
||||
public static generateReviewFileContent(collector: Collector): string {
|
||||
const writer: IndentedWriter = new IndentedWriter();
|
||||
writer.trimLeadingSpaces = true;
|
||||
/**
|
||||
* Generates and returns the API report contents as a string.
|
||||
*
|
||||
* @param collector - The collector that has the entities.
|
||||
* @param reportVariant - The release level with which the report is associated.
|
||||
* Can also be viewed as the minimal release level of items that should be included in the report.
|
||||
*/
|
||||
public static generateReviewFileContent(collector: Collector, reportVariant: ApiReportVariant): Map<string, string> {
|
||||
// mapping from entrypoint name to its file content
|
||||
const fileContentMap: Map<string, string> = new Map<string, string>();
|
||||
|
||||
writer.writeLine(
|
||||
[
|
||||
`## API Report File for "${collector.workingPackage.name}"`,
|
||||
``,
|
||||
`> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).`,
|
||||
``,
|
||||
].join('\n'),
|
||||
);
|
||||
for (const [entryPoint, entryPointEntities] of collector.entities) {
|
||||
const writer: IndentedWriter = new IndentedWriter();
|
||||
writer.trimLeadingSpaces = true;
|
||||
|
||||
// Write the opening delimiter for the Markdown code fence
|
||||
writer.writeLine('```ts\n');
|
||||
// For backwards compatibility, don't emit "complete" in report text for untrimmed reports.
|
||||
const releaseLevelPrefix: string = reportVariant === 'complete' ? '' : `${capitalizeFirstLetter(reportVariant)} `;
|
||||
writer.writeLine(
|
||||
[
|
||||
`## ${releaseLevelPrefix}API Report File for "${collector.workingPackage.name}${entryPoint.modulePath ? '/' : ''}${
|
||||
entryPoint.modulePath
|
||||
}"`,
|
||||
``,
|
||||
`> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).`,
|
||||
``,
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
// Emit the triple slash directives
|
||||
for (const typeDirectiveReference of Array.from(collector.dtsTypeReferenceDirectives).sort()) {
|
||||
// https://github.com/microsoft/TypeScript/blob/611ebc7aadd7a44a4c0447698bfda9222a78cb66/src/compiler/declarationEmitter.ts#L162
|
||||
writer.writeLine(`/// <reference types="${typeDirectiveReference}" />`);
|
||||
}
|
||||
// Write the opening delimiter for the Markdown code fence
|
||||
writer.writeLine('```ts\n');
|
||||
|
||||
for (const libDirectiveReference of Array.from(collector.dtsLibReferenceDirectives).sort()) {
|
||||
writer.writeLine(`/// <reference lib="${libDirectiveReference}" />`);
|
||||
}
|
||||
|
||||
writer.ensureSkippedLine();
|
||||
|
||||
// Emit the imports
|
||||
for (const entity of collector.entities) {
|
||||
if (entity.astEntity instanceof AstImport) {
|
||||
DtsEmitHelpers.emitImport(writer, entity, entity.astEntity);
|
||||
// Emit the triple slash directives
|
||||
for (const typeDirectiveReference of Array.from(collector.dtsTypeReferenceDirectives).sort()) {
|
||||
// https://github.com/microsoft/TypeScript/blob/611ebc7aadd7a44a4c0447698bfda9222a78cb66/src/compiler/declarationEmitter.ts#L162
|
||||
writer.writeLine(`/// <reference types="${typeDirectiveReference}" />`);
|
||||
}
|
||||
}
|
||||
|
||||
writer.ensureSkippedLine();
|
||||
for (const libDirectiveReference of Array.from(collector.dtsLibReferenceDirectives).sort()) {
|
||||
writer.writeLine(`/// <reference lib="${libDirectiveReference}" />`);
|
||||
}
|
||||
|
||||
// Emit the regular declarations
|
||||
for (const entity of collector.entities) {
|
||||
const astEntity: AstEntity = entity.astEntity;
|
||||
if (entity.consumable || collector.extractorConfig.apiReportIncludeForgottenExports) {
|
||||
// First, collect the list of export names for this symbol. When reporting messages with
|
||||
// ExtractorMessage.properties.exportName, this will enable us to emit the warning comments alongside
|
||||
// the associated export statement.
|
||||
interface IExportToEmit {
|
||||
readonly associatedMessages: ExtractorMessage[];
|
||||
readonly exportName: string;
|
||||
writer.ensureSkippedLine();
|
||||
|
||||
// Emit the imports
|
||||
for (const entity of entryPointEntities) {
|
||||
if (entity.astEntity instanceof AstImport) {
|
||||
DtsEmitHelpers.emitImport(writer, entity, entity.astEntity);
|
||||
}
|
||||
const exportsToEmit: Map<string, IExportToEmit> = new Map<string, IExportToEmit>();
|
||||
}
|
||||
|
||||
for (const exportName of entity.exportNames) {
|
||||
if (!entity.shouldInlineExport) {
|
||||
exportsToEmit.set(exportName, { exportName, associatedMessages: [] });
|
||||
writer.ensureSkippedLine();
|
||||
|
||||
// Emit the regular declarations
|
||||
for (const entity of entryPointEntities) {
|
||||
const astEntity: AstEntity = entity.astEntity;
|
||||
const symbolMetadata: SymbolMetadata | undefined = collector.tryFetchMetadataForAstEntity(astEntity);
|
||||
const maxEffectiveReleaseTag: ReleaseTag = symbolMetadata?.maxEffectiveReleaseTag ?? ReleaseTag.None;
|
||||
|
||||
if (!this._shouldIncludeReleaseTag(maxEffectiveReleaseTag, reportVariant)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.consumable || collector.extractorConfig.apiReportIncludeForgottenExports) {
|
||||
// First, collect the list of export names for this symbol. When reporting messages with
|
||||
// ExtractorMessage.properties.exportName, this will enable us to emit the warning comments alongside
|
||||
// the associated export statement.
|
||||
interface IExportToEmit {
|
||||
readonly associatedMessages: ExtractorMessage[];
|
||||
readonly exportName: string;
|
||||
}
|
||||
}
|
||||
const exportsToEmit: Map<string, IExportToEmit> = new Map<string, IExportToEmit>();
|
||||
|
||||
if (astEntity instanceof AstSymbol) {
|
||||
// Emit all the declarations for this entity
|
||||
for (const astDeclaration of astEntity.astDeclarations || []) {
|
||||
// Get the messages associated with this declaration
|
||||
const fetchedMessages: ExtractorMessage[] =
|
||||
collector.messageRouter.fetchAssociatedMessagesForReviewFile(astDeclaration);
|
||||
for (const exportName of entity.exportNames) {
|
||||
if (!entity.shouldInlineExport) {
|
||||
exportsToEmit.set(exportName, { exportName, associatedMessages: [] });
|
||||
}
|
||||
}
|
||||
|
||||
// Peel off the messages associated with an export statement and store them
|
||||
// in IExportToEmit.associatedMessages (to be processed later). The remaining messages will
|
||||
// added to messagesToReport, to be emitted next to the declaration instead of the export statement.
|
||||
const messagesToReport: ExtractorMessage[] = [];
|
||||
for (const message of fetchedMessages) {
|
||||
if (message.properties.exportName) {
|
||||
const exportToEmit: IExportToEmit | undefined = exportsToEmit.get(message.properties.exportName);
|
||||
if (exportToEmit) {
|
||||
exportToEmit.associatedMessages.push(message);
|
||||
continue;
|
||||
if (astEntity instanceof AstSymbol) {
|
||||
// Emit all the declarations for this entity
|
||||
for (const astDeclaration of astEntity.astDeclarations || []) {
|
||||
// Get the messages associated with this declaration
|
||||
const fetchedMessages: ExtractorMessage[] =
|
||||
collector.messageRouter.fetchAssociatedMessagesForReviewFile(astDeclaration);
|
||||
|
||||
// Peel off the messages associated with an export statement and store them
|
||||
// in IExportToEmit.associatedMessages (to be processed later). The remaining messages will
|
||||
// added to messagesToReport, to be emitted next to the declaration instead of the export statement.
|
||||
const messagesToReport: ExtractorMessage[] = [];
|
||||
for (const message of fetchedMessages) {
|
||||
if (message.properties.exportName) {
|
||||
const exportToEmit: IExportToEmit | undefined = exportsToEmit.get(message.properties.exportName);
|
||||
if (exportToEmit) {
|
||||
exportToEmit.associatedMessages.push(message);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
messagesToReport.push(message);
|
||||
}
|
||||
|
||||
messagesToReport.push(message);
|
||||
if (this._shouldIncludeDeclaration(collector, astDeclaration, reportVariant)) {
|
||||
writer.ensureSkippedLine();
|
||||
writer.write(ApiReportGenerator._getAedocSynopsis(collector, astDeclaration, messagesToReport));
|
||||
|
||||
const span: Span = new Span(astDeclaration.declaration);
|
||||
|
||||
const apiItemMetadata: ApiItemMetadata = collector.fetchApiItemMetadata(astDeclaration);
|
||||
if (apiItemMetadata.isPreapproved) {
|
||||
ApiReportGenerator._modifySpanForPreapproved(span);
|
||||
} else {
|
||||
ApiReportGenerator._modifySpan(collector, span, entity, astDeclaration, false, reportVariant);
|
||||
}
|
||||
|
||||
span.writeModifiedText(writer);
|
||||
writer.ensureNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
writer.ensureSkippedLine();
|
||||
writer.write(ApiReportGenerator._getAedocSynopsis(collector, astDeclaration, messagesToReport));
|
||||
|
||||
const span: Span = new Span(astDeclaration.declaration);
|
||||
|
||||
const apiItemMetadata: ApiItemMetadata = collector.fetchApiItemMetadata(astDeclaration);
|
||||
if (apiItemMetadata.isPreapproved) {
|
||||
ApiReportGenerator._modifySpanForPreapproved(span);
|
||||
} else {
|
||||
ApiReportGenerator._modifySpan(collector, span, entity, astDeclaration, false);
|
||||
}
|
||||
|
||||
span.writeModifiedText(writer);
|
||||
writer.ensureNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
if (astEntity instanceof AstNamespaceImport) {
|
||||
const astModuleExportInfo: AstModuleExportInfo = astEntity.fetchAstModuleExportInfo(collector);
|
||||
|
||||
if (entity.nameForEmit === undefined) {
|
||||
// This should never happen
|
||||
throw new InternalError('referencedEntry.nameForEmit is undefined');
|
||||
}
|
||||
|
||||
if (astModuleExportInfo.starExportedExternalModules.size > 0) {
|
||||
// We could support this, but we would need to find a way to safely represent it.
|
||||
throw new Error(
|
||||
`The ${entity.nameForEmit} namespace import includes a star export, which is not supported:\n` +
|
||||
SourceFileLocationFormatter.formatDeclaration(astEntity.declaration),
|
||||
);
|
||||
}
|
||||
if (astEntity instanceof AstNamespaceImport) {
|
||||
const astModuleExportInfo: IAstModuleExportInfo = astEntity.fetchAstModuleExportInfo(collector);
|
||||
|
||||
// Emit a synthetic declaration for the namespace. It will look like this:
|
||||
//
|
||||
// declare namespace example {
|
||||
// export {
|
||||
// f1,
|
||||
// f2
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Note that we do not try to relocate f1()/f2() to be inside the namespace because other type
|
||||
// signatures may reference them directly (without using the namespace qualifier).
|
||||
|
||||
writer.ensureSkippedLine();
|
||||
writer.writeLine(`declare namespace ${entity.nameForEmit} {`);
|
||||
|
||||
// all local exports of local imported module are just references to top-level declarations
|
||||
writer.increaseIndent();
|
||||
writer.writeLine('export {');
|
||||
writer.increaseIndent();
|
||||
|
||||
const exportClauses: string[] = [];
|
||||
for (const [exportedName, exportedEntity] of astModuleExportInfo.exportedLocalEntities) {
|
||||
const collectorEntity: CollectorEntity | undefined = collector.tryGetCollectorEntity(exportedEntity);
|
||||
if (collectorEntity === undefined) {
|
||||
if (entity.nameForEmit === undefined) {
|
||||
// This should never happen
|
||||
// top-level exports of local imported module should be added as collector entities before
|
||||
throw new InternalError(
|
||||
`Cannot find collector entity for ${entity.nameForEmit}.${exportedEntity.localName}`,
|
||||
throw new InternalError('referencedEntry.nameForEmit is undefined');
|
||||
}
|
||||
|
||||
if (astModuleExportInfo.starExportedExternalModules.size > 0) {
|
||||
// We could support this, but we would need to find a way to safely represent it.
|
||||
throw new Error(
|
||||
`The ${entity.nameForEmit} namespace import includes a star export, which is not supported:\n` +
|
||||
SourceFileLocationFormatter.formatDeclaration(astEntity.declaration),
|
||||
);
|
||||
}
|
||||
|
||||
if (collectorEntity.nameForEmit === exportedName) {
|
||||
exportClauses.push(collectorEntity.nameForEmit);
|
||||
} else {
|
||||
exportClauses.push(`${collectorEntity.nameForEmit} as ${exportedName}`);
|
||||
}
|
||||
}
|
||||
// Emit a synthetic declaration for the namespace. It will look like this:
|
||||
//
|
||||
// declare namespace example {
|
||||
// export {
|
||||
// f1,
|
||||
// f2
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Note that we do not try to relocate f1()/f2() to be inside the namespace because other type
|
||||
// signatures may reference them directly (without using the namespace qualifier).
|
||||
|
||||
writer.writeLine(exportClauses.join(',\n'));
|
||||
|
||||
writer.decreaseIndent();
|
||||
writer.writeLine('}'); // end of "export { ... }"
|
||||
writer.decreaseIndent();
|
||||
writer.writeLine('}'); // end of "declare namespace { ... }"
|
||||
}
|
||||
|
||||
// Now emit the export statements for this entity.
|
||||
for (const exportToEmit of exportsToEmit.values()) {
|
||||
// Write any associated messages
|
||||
if (exportToEmit.associatedMessages.length > 0) {
|
||||
writer.ensureSkippedLine();
|
||||
for (const message of exportToEmit.associatedMessages) {
|
||||
ApiReportGenerator._writeLineAsComments(writer, 'Warning: ' + message.formatMessageWithoutLocation());
|
||||
writer.writeLine(`declare namespace ${entity.nameForEmit} {`);
|
||||
|
||||
// all local exports of local imported module are just references to top-level declarations
|
||||
writer.increaseIndent();
|
||||
writer.writeLine('export {');
|
||||
writer.increaseIndent();
|
||||
|
||||
const exportClauses: string[] = [];
|
||||
for (const [exportedName, exportedEntity] of astModuleExportInfo.exportedLocalEntities) {
|
||||
const collectorEntity: CollectorEntity | undefined = collector.tryGetCollectorEntity(exportedEntity);
|
||||
if (collectorEntity === undefined) {
|
||||
// This should never happen
|
||||
// top-level exports of local imported module should be added as collector entities before
|
||||
throw new InternalError(
|
||||
`Cannot find collector entity for ${entity.nameForEmit}.${exportedEntity.localName}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (collectorEntity.nameForEmit === exportedName) {
|
||||
exportClauses.push(collectorEntity.nameForEmit);
|
||||
} else {
|
||||
exportClauses.push(`${collectorEntity.nameForEmit} as ${exportedName}`);
|
||||
}
|
||||
}
|
||||
|
||||
writer.writeLine(exportClauses.join(',\n'));
|
||||
|
||||
writer.decreaseIndent();
|
||||
writer.writeLine('}'); // end of "export { ... }"
|
||||
writer.decreaseIndent();
|
||||
writer.writeLine('}'); // end of "declare namespace { ... }"
|
||||
}
|
||||
|
||||
DtsEmitHelpers.emitNamedExport(writer, exportToEmit.exportName, entity);
|
||||
// Now emit the export statements for this entity.
|
||||
for (const exportToEmit of exportsToEmit.values()) {
|
||||
// Write any associated messages
|
||||
if (exportToEmit.associatedMessages.length > 0) {
|
||||
writer.ensureSkippedLine();
|
||||
for (const message of exportToEmit.associatedMessages) {
|
||||
ApiReportGenerator._writeLineAsComments(writer, 'Warning: ' + message.formatMessageWithoutLocation());
|
||||
}
|
||||
}
|
||||
|
||||
DtsEmitHelpers.emitNamedExport(writer, exportToEmit.exportName, entity);
|
||||
}
|
||||
|
||||
writer.ensureSkippedLine();
|
||||
}
|
||||
}
|
||||
|
||||
DtsEmitHelpers.emitStarExports(writer, collector);
|
||||
|
||||
// Write the unassociated warnings at the bottom of the file
|
||||
const unassociatedMessages: ExtractorMessage[] = collector.messageRouter.fetchUnassociatedMessagesForReviewFile();
|
||||
if (unassociatedMessages.length > 0) {
|
||||
writer.ensureSkippedLine();
|
||||
ApiReportGenerator._writeLineAsComments(writer, 'Warnings were encountered during analysis:');
|
||||
ApiReportGenerator._writeLineAsComments(writer, '');
|
||||
for (const unassociatedMessage of unassociatedMessages) {
|
||||
ApiReportGenerator._writeLineAsComments(
|
||||
writer,
|
||||
unassociatedMessage.formatMessageWithLocation(collector.workingPackage.packageFolder),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DtsEmitHelpers.emitStarExports(writer, collector);
|
||||
|
||||
// Write the unassociated warnings at the bottom of the file
|
||||
const unassociatedMessages: ExtractorMessage[] = collector.messageRouter.fetchUnassociatedMessagesForReviewFile();
|
||||
if (unassociatedMessages.length > 0) {
|
||||
writer.ensureSkippedLine();
|
||||
ApiReportGenerator._writeLineAsComments(writer, 'Warnings were encountered during analysis:');
|
||||
ApiReportGenerator._writeLineAsComments(writer, '');
|
||||
for (const unassociatedMessage of unassociatedMessages) {
|
||||
ApiReportGenerator._writeLineAsComments(
|
||||
writer,
|
||||
unassociatedMessage.formatMessageWithLocation(collector.workingPackage.packageFolder),
|
||||
);
|
||||
if (collector.workingPackage.tsdocComment === undefined) {
|
||||
writer.ensureSkippedLine();
|
||||
ApiReportGenerator._writeLineAsComments(writer, '(No @packageDocumentation comment for this package)');
|
||||
}
|
||||
}
|
||||
|
||||
if (collector.workingPackage.tsdocComment === undefined) {
|
||||
// Write the closing delimiter for the Markdown code fence
|
||||
writer.ensureSkippedLine();
|
||||
ApiReportGenerator._writeLineAsComments(writer, '(No @packageDocumentation comment for this package)');
|
||||
writer.writeLine('```');
|
||||
|
||||
// Remove any trailing spaces
|
||||
fileContentMap.set(entryPoint.modulePath, writer.toString().replace(ApiReportGenerator._trimSpacesRegExp, ''));
|
||||
}
|
||||
|
||||
// Write the closing delimiter for the Markdown code fence
|
||||
writer.ensureSkippedLine();
|
||||
writer.writeLine('```');
|
||||
|
||||
// Remove any trailing spaces
|
||||
return writer.toString().replace(ApiReportGenerator._trimSpacesRegExp, '');
|
||||
return fileContentMap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,10 +286,11 @@ export class ApiReportGenerator {
|
||||
entity: CollectorEntity,
|
||||
astDeclaration: AstDeclaration,
|
||||
insideTypeLiteral: boolean,
|
||||
reportVariant: ApiReportVariant,
|
||||
): void {
|
||||
// Should we process this declaration at all?
|
||||
|
||||
if ((astDeclaration.modifierFlags & ts.ModifierFlags.Private) !== 0) {
|
||||
if (!ApiReportGenerator._shouldIncludeDeclaration(collector, astDeclaration, reportVariant)) {
|
||||
span.modification.skipAll();
|
||||
return;
|
||||
}
|
||||
@@ -373,7 +407,14 @@ export class ApiReportGenerator {
|
||||
|
||||
case ts.SyntaxKind.ImportType:
|
||||
DtsEmitHelpers.modifyImportTypeSpan(collector, span, astDeclaration, (childSpan, childAstDeclaration) => {
|
||||
ApiReportGenerator._modifySpan(collector, childSpan, entity, childAstDeclaration, insideTypeLiteral);
|
||||
ApiReportGenerator._modifySpan(
|
||||
collector,
|
||||
childSpan,
|
||||
entity,
|
||||
childAstDeclaration,
|
||||
insideTypeLiteral,
|
||||
reportVariant,
|
||||
);
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -408,11 +449,56 @@ export class ApiReportGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
ApiReportGenerator._modifySpan(collector, child, entity, childAstDeclaration, insideTypeLiteral);
|
||||
ApiReportGenerator._modifySpan(collector, child, entity, childAstDeclaration, insideTypeLiteral, reportVariant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static _shouldIncludeDeclaration(
|
||||
collector: Collector,
|
||||
astDeclaration: AstDeclaration,
|
||||
reportVariant: ApiReportVariant,
|
||||
): boolean {
|
||||
// Private declarations are not included in the API report
|
||||
if ((astDeclaration.modifierFlags & ts.ModifierFlags.Private) !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const apiItemMetadata: ApiItemMetadata = collector.fetchApiItemMetadata(astDeclaration);
|
||||
|
||||
return this._shouldIncludeReleaseTag(apiItemMetadata.effectiveReleaseTag, reportVariant);
|
||||
}
|
||||
|
||||
private static _shouldIncludeReleaseTag(releaseTag: ReleaseTag, reportVariant: ApiReportVariant): boolean {
|
||||
switch (reportVariant) {
|
||||
case 'complete':
|
||||
return true;
|
||||
case 'alpha':
|
||||
return (
|
||||
releaseTag === ReleaseTag.Alpha ||
|
||||
releaseTag === ReleaseTag.Beta ||
|
||||
releaseTag === ReleaseTag.Public ||
|
||||
// NOTE: No specified release tag is implicitly treated as `@public`.
|
||||
releaseTag === ReleaseTag.None
|
||||
);
|
||||
case 'beta':
|
||||
return (
|
||||
releaseTag === ReleaseTag.Beta ||
|
||||
releaseTag === ReleaseTag.Public ||
|
||||
// NOTE: No specified release tag is implicitly treated as `@public`.
|
||||
releaseTag === ReleaseTag.None
|
||||
);
|
||||
case 'public':
|
||||
return (
|
||||
releaseTag === ReleaseTag.Public ||
|
||||
// NOTE: No specified release tag is implicitly treated as `@public`.
|
||||
releaseTag === ReleaseTag.None
|
||||
);
|
||||
default:
|
||||
throw new Error(`Unrecognized release level: ${reportVariant}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For declarations marked as `@preapproved`, this is used instead of _modifySpan().
|
||||
*/
|
||||
@@ -482,30 +568,59 @@ export class ApiReportGenerator {
|
||||
if (!collector.isAncillaryDeclaration(astDeclaration)) {
|
||||
const footerParts: string[] = [];
|
||||
const apiItemMetadata: ApiItemMetadata = collector.fetchApiItemMetadata(astDeclaration);
|
||||
|
||||
// 1. Release tag (if present)
|
||||
if (!apiItemMetadata.releaseTagSameAsParent && apiItemMetadata.effectiveReleaseTag !== ReleaseTag.None) {
|
||||
footerParts.push(releaseTagGetTagName(apiItemMetadata.effectiveReleaseTag));
|
||||
}
|
||||
|
||||
if (apiItemMetadata.isSealed) {
|
||||
// 2. Enumerate configured tags, reporting standard system tags first and then other configured tags.
|
||||
// Note that the ordering we handle the standard tags is important for backwards compatibility.
|
||||
// Also note that we had special mechanisms for checking whether or not an item is documented with these tags,
|
||||
// so they are checked specially.
|
||||
const {
|
||||
'@sealed': reportSealedTag,
|
||||
'@virtual': reportVirtualTag,
|
||||
'@override': reportOverrideTag,
|
||||
'@eventProperty': reportEventPropertyTag,
|
||||
'@deprecated': reportDeprecatedTag,
|
||||
...otherTagsToReport
|
||||
} = collector.extractorConfig.tagsToReport;
|
||||
|
||||
// 2.a Check for standard tags and report those that are both configured and present in the metadata.
|
||||
if (reportSealedTag && apiItemMetadata.isSealed) {
|
||||
footerParts.push('@sealed');
|
||||
}
|
||||
|
||||
if (apiItemMetadata.isVirtual) {
|
||||
if (reportVirtualTag && apiItemMetadata.isVirtual) {
|
||||
footerParts.push('@virtual');
|
||||
}
|
||||
|
||||
if (apiItemMetadata.isOverride) {
|
||||
if (reportOverrideTag && apiItemMetadata.isOverride) {
|
||||
footerParts.push('@override');
|
||||
}
|
||||
|
||||
if (apiItemMetadata.isEventProperty) {
|
||||
if (reportEventPropertyTag && apiItemMetadata.isEventProperty) {
|
||||
footerParts.push('@eventProperty');
|
||||
}
|
||||
|
||||
if (apiItemMetadata.tsdocComment?.deprecatedBlock) {
|
||||
if (reportDeprecatedTag && apiItemMetadata.tsdocComment?.deprecatedBlock) {
|
||||
footerParts.push('@deprecated');
|
||||
}
|
||||
|
||||
// 2.b Check for other configured tags and report those that are present in the tsdoc metadata.
|
||||
for (const [tag, reportTag] of Object.entries(otherTagsToReport)) {
|
||||
// If the tag was not handled specially, check if it is present in the metadata.
|
||||
if (
|
||||
reportTag &&
|
||||
(apiItemMetadata.tsdocComment?.customBlocks.some((block) => block.blockTag.tagName === tag) ||
|
||||
apiItemMetadata.tsdocComment?.modifierTagSet.hasTagName(tag))
|
||||
) {
|
||||
footerParts.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. If the item is undocumented, append notice at the end of the list
|
||||
if (apiItemMetadata.undocumented) {
|
||||
footerParts.push('(undocumented)');
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ export class DeclarationReferenceGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private _getPackageName(sourceFile: ts.SourceFile): string {
|
||||
private _getEntryPointName(sourceFile: ts.SourceFile): string {
|
||||
if (this._collector.program.isSourceFileFromExternalLibrary(sourceFile)) {
|
||||
const packageJson: INodePackageJson | undefined = this._collector.packageJsonLookup.tryLoadNodePackageJsonFor(
|
||||
sourceFile.fileName,
|
||||
@@ -354,18 +354,25 @@ export class DeclarationReferenceGenerator {
|
||||
return DeclarationReferenceGenerator.unknownReference;
|
||||
}
|
||||
|
||||
return this._collector.workingPackage.name;
|
||||
let modulePath = '';
|
||||
for (const entryPoint of this._collector.workingPackage.entryPoints) {
|
||||
if (entryPoint.sourceFile === sourceFile) {
|
||||
modulePath = entryPoint.modulePath;
|
||||
}
|
||||
}
|
||||
|
||||
return `${this._collector.workingPackage.name}${modulePath ? `/${modulePath}` : ''}`;
|
||||
}
|
||||
|
||||
private _sourceFileToModuleSource(sourceFile: ts.SourceFile | undefined): GlobalSource | ModuleSource {
|
||||
if (sourceFile && ts.isExternalModule(sourceFile)) {
|
||||
const packageName: string = this._getPackageName(sourceFile);
|
||||
const packageName: string = this._getEntryPointName(sourceFile);
|
||||
|
||||
if (this._collector.bundledPackageNames.has(packageName)) {
|
||||
// The api-extractor.json config file has a "bundledPackages" setting, which causes imports from
|
||||
// certain NPM packages to be treated as part of the working project. In this case, we need to
|
||||
// substitute the working package name.
|
||||
return new ModuleSource(this._collector.workingPackage.name);
|
||||
return new ModuleSource(this._collector.workingPackage.name); // TODO: make it work with multiple entrypoints
|
||||
} else {
|
||||
return new ModuleSource(packageName);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as ts from 'typescript';
|
||||
import { AstDeclaration } from '../analyzer/AstDeclaration.js';
|
||||
import type { AstEntity } from '../analyzer/AstEntity.js';
|
||||
import { AstImport } from '../analyzer/AstImport.js';
|
||||
import type { AstModuleExportInfo } from '../analyzer/AstModule.js';
|
||||
import type { IAstModuleExportInfo } from '../analyzer/AstModule.js';
|
||||
import { AstNamespaceImport } from '../analyzer/AstNamespaceImport.js';
|
||||
import { AstSymbol } from '../analyzer/AstSymbol.js';
|
||||
import { SourceFileLocationFormatter } from '../analyzer/SourceFileLocationFormatter.js';
|
||||
@@ -103,8 +103,10 @@ export class DtsRollupGenerator {
|
||||
|
||||
writer.ensureSkippedLine();
|
||||
|
||||
// dtsRollup doesn't support multiple entry points. We throw error if dtsRollup is enabled while more than one entry points are specified.
|
||||
// So at this point, we can safely assume there is only one entry point in collector.entities
|
||||
// Emit the imports
|
||||
for (const entity of collector.entities) {
|
||||
for (const entity of [...collector.entities.values()][0]!) {
|
||||
if (entity.astEntity instanceof AstImport) {
|
||||
const astImport: AstImport = entity.astEntity;
|
||||
|
||||
@@ -124,7 +126,7 @@ export class DtsRollupGenerator {
|
||||
writer.ensureSkippedLine();
|
||||
|
||||
// Emit the regular declarations
|
||||
for (const entity of collector.entities) {
|
||||
for (const entity of [...collector.entities.values()][0]!) {
|
||||
const astEntity: AstEntity = entity.astEntity;
|
||||
const symbolMetadata: SymbolMetadata | undefined = collector.tryFetchMetadataForAstEntity(astEntity);
|
||||
const maxEffectiveReleaseTag: ReleaseTag = symbolMetadata
|
||||
@@ -159,7 +161,7 @@ export class DtsRollupGenerator {
|
||||
}
|
||||
|
||||
if (astEntity instanceof AstNamespaceImport) {
|
||||
const astModuleExportInfo: AstModuleExportInfo = astEntity.fetchAstModuleExportInfo(collector);
|
||||
const astModuleExportInfo: IAstModuleExportInfo = astEntity.fetchAstModuleExportInfo(collector);
|
||||
|
||||
if (entity.nameForEmit === undefined) {
|
||||
// This should never happen
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
import { IndentedWriter } from '../IndentedWriter.js';
|
||||
|
||||
test('01 Demo from docs', () => {
|
||||
const indentedWriter: IndentedWriter = new IndentedWriter();
|
||||
indentedWriter.write('begin\n');
|
||||
indentedWriter.increaseIndent();
|
||||
indentedWriter.write('one\ntwo\n');
|
||||
indentedWriter.decreaseIndent();
|
||||
indentedWriter.increaseIndent();
|
||||
indentedWriter.decreaseIndent();
|
||||
indentedWriter.write('end');
|
||||
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('02 Indent something', () => {
|
||||
const indentedWriter: IndentedWriter = new IndentedWriter();
|
||||
indentedWriter.write('a');
|
||||
indentedWriter.write('b');
|
||||
indentedWriter.increaseIndent();
|
||||
indentedWriter.writeLine('c');
|
||||
indentedWriter.writeLine('d');
|
||||
indentedWriter.decreaseIndent();
|
||||
indentedWriter.writeLine('e');
|
||||
|
||||
indentedWriter.increaseIndent('>>> ');
|
||||
indentedWriter.writeLine();
|
||||
indentedWriter.writeLine();
|
||||
indentedWriter.writeLine('g');
|
||||
indentedWriter.decreaseIndent();
|
||||
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('03 Indent something with indentBlankLines=true', () => {
|
||||
const indentedWriter: IndentedWriter = new IndentedWriter();
|
||||
indentedWriter.indentBlankLines = true;
|
||||
|
||||
indentedWriter.write('a');
|
||||
indentedWriter.write('b');
|
||||
indentedWriter.increaseIndent();
|
||||
indentedWriter.writeLine('c');
|
||||
indentedWriter.writeLine('d');
|
||||
indentedWriter.decreaseIndent();
|
||||
indentedWriter.writeLine('e');
|
||||
|
||||
indentedWriter.increaseIndent('>>> ');
|
||||
indentedWriter.writeLine();
|
||||
indentedWriter.writeLine();
|
||||
indentedWriter.writeLine('g');
|
||||
indentedWriter.decreaseIndent();
|
||||
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('04 Two kinds of indents', () => {
|
||||
const indentedWriter: IndentedWriter = new IndentedWriter();
|
||||
|
||||
indentedWriter.writeLine('---');
|
||||
indentedWriter.indentScope(() => {
|
||||
indentedWriter.write('a\nb');
|
||||
indentedWriter.indentScope(() => {
|
||||
indentedWriter.write('c\nd\n');
|
||||
});
|
||||
indentedWriter.write('e\n');
|
||||
}, '> ');
|
||||
indentedWriter.writeLine('---');
|
||||
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('05 Edge cases for ensureNewLine()', () => {
|
||||
let indentedWriter: IndentedWriter = new IndentedWriter();
|
||||
indentedWriter.ensureNewLine();
|
||||
indentedWriter.write('line');
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
|
||||
indentedWriter = new IndentedWriter();
|
||||
indentedWriter.write('previous');
|
||||
indentedWriter.ensureNewLine();
|
||||
indentedWriter.write('line');
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('06 Edge cases for ensureSkippedLine()', () => {
|
||||
let indentedWriter: IndentedWriter = new IndentedWriter();
|
||||
indentedWriter.ensureSkippedLine();
|
||||
indentedWriter.write('line');
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
|
||||
indentedWriter = new IndentedWriter();
|
||||
indentedWriter.write('previous');
|
||||
indentedWriter.ensureSkippedLine();
|
||||
indentedWriter.write('line');
|
||||
indentedWriter.ensureSkippedLine();
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('06 trimLeadingSpaces=true', () => {
|
||||
const indentedWriter: IndentedWriter = new IndentedWriter();
|
||||
indentedWriter.trimLeadingSpaces = true;
|
||||
|
||||
// Example from doc comment
|
||||
indentedWriter.increaseIndent(' ');
|
||||
indentedWriter.write(' a\n b c\n');
|
||||
indentedWriter.decreaseIndent();
|
||||
indentedWriter.ensureSkippedLine();
|
||||
indentedWriter.increaseIndent('>>');
|
||||
indentedWriter.write(' ');
|
||||
indentedWriter.write(' ');
|
||||
indentedWriter.write(' a');
|
||||
indentedWriter.writeLine(' b');
|
||||
indentedWriter.writeLine('\ttab'); // does not get indented
|
||||
indentedWriter.writeLine('c ');
|
||||
expect(indentedWriter.toString()).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`01 Demo from docs 1`] = `
|
||||
"begin
|
||||
one
|
||||
two
|
||||
end"
|
||||
`;
|
||||
|
||||
exports[`02 Indent something 1`] = `
|
||||
"abc
|
||||
d
|
||||
e
|
||||
|
||||
|
||||
>>> g
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`03 Indent something with indentBlankLines=true 1`] = `
|
||||
"abc
|
||||
d
|
||||
e
|
||||
>>>
|
||||
>>>
|
||||
>>> g
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`04 Two kinds of indents 1`] = `
|
||||
"---
|
||||
> a
|
||||
> bc
|
||||
> d
|
||||
> e
|
||||
---
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`05 Edge cases for ensureNewLine() 1`] = `"line"`;
|
||||
|
||||
exports[`05 Edge cases for ensureNewLine() 2`] = `
|
||||
"previous
|
||||
line"
|
||||
`;
|
||||
|
||||
exports[`06 Edge cases for ensureSkippedLine() 1`] = `"line"`;
|
||||
|
||||
exports[`06 Edge cases for ensureSkippedLine() 2`] = `
|
||||
"previous
|
||||
|
||||
line
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`06 trimLeadingSpaces=true 1`] = `
|
||||
" a
|
||||
b c
|
||||
|
||||
>>a b
|
||||
>> tab
|
||||
>>c
|
||||
"
|
||||
`;
|
||||
Reference in New Issue
Block a user