api-extractor: add support for @ignore

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-11-16 17:26:41 +01:00
parent eaeac8fdac
commit c9e19092a7
+109 -3
View File
@@ -30,8 +30,15 @@ import {
ExtractorConfig,
CompilerState,
ExtractorLogLevel,
ExtractorMessage,
} from '@microsoft/api-extractor';
import { DocNode, IDocNodeContainerParameters } from '@microsoft/tsdoc';
import { Program } from 'typescript';
import {
DocNode,
IDocNodeContainerParameters,
TSDocTagSyntaxKind,
} from '@microsoft/tsdoc';
import { TSDocConfigFile } from '@microsoft/tsdoc-config';
import { ApiPackage, ApiModel } from '@microsoft/api-extractor-model';
import {
IMarkdownDocumenterOptions,
@@ -42,6 +49,7 @@ import { DocTableRow } from '@microsoft/api-documenter/lib/nodes/DocTableRow';
import { DocHeading } from '@microsoft/api-documenter/lib/nodes/DocHeading';
import { CustomMarkdownEmitter } from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter';
import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/MarkdownEmitter';
import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
const tmpDir = resolvePath(__dirname, '../node_modules/.cache/api-extractor');
@@ -79,11 +87,96 @@ const {
ApiReportGenerator,
} = require('@microsoft/api-extractor/lib/generators/ApiReportGenerator');
function patchFileMessageFetcher(
router: any,
transform: (messages: ExtractorMessage[], ast?: AstDeclaration) => void,
) {
const {
fetchAssociatedMessagesForReviewFile,
fetchUnassociatedMessagesForReviewFile,
} = router;
router.fetchAssociatedMessagesForReviewFile =
function patchedFetchAssociatedMessagesForReviewFile(ast) {
const messages = fetchAssociatedMessagesForReviewFile.call(this, ast);
return transform(messages, ast);
};
router.fetchUnassociatedMessagesForReviewFile =
function patchedFetchUnassociatedMessagesForReviewFile() {
const messages = fetchUnassociatedMessagesForReviewFile.call(this);
return transform(messages);
};
}
const originalGenerateReviewFileContent =
ApiReportGenerator.generateReviewFileContent;
ApiReportGenerator.generateReviewFileContent =
function decoratedGenerateReviewFileContent(...args) {
const content = originalGenerateReviewFileContent.apply(this, args);
function decoratedGenerateReviewFileContent(collector, ...moreArgs) {
const program = collector.program as Program;
// The purpose of this override is to allow the @ignore tag to be used to ignore warnings
// of the form "Warning: (ae-forgotten-export) The symbol "FooBar" needs to be exported by the entry point index.d.ts"
patchFileMessageFetcher(
collector.messageRouter,
(messages: ExtractorMessage[]) => {
return messages.filter(message => {
if (message.messageId !== 'ae-forgotten-export') {
return true;
}
// Symbol name has to be extracted from the message :(
// There's frequently no AST for these exports because type literals
// aren't traversed by the generator.
const symbolMatch = message.text.match(/The symbol "([^"]+)"/);
if (!symbolMatch) {
throw new Error(
`Failed to extract symbol name from message "${message.text}"`,
);
}
const [, symbolName] = symbolMatch;
const sourceFile = program.getSourceFile(message.sourceFilePath);
if (!sourceFile) {
throw new Error(
`Failed to find source file in program at path "${message.sourceFilePath}"`,
);
}
// NOTE: we limit the @internal functionality to only apply to types that are declared
// in the same module as where they're being referenced from. This limitation makes
// the implementation here simpler but could be revisited if needed.
// The local name of the symbol within the file, rather than the exported name
const localName = (sourceFile as any).identifiers?.get(symbolName);
if (!localName) {
return true;
}
// The local AST node of the export that we're missing
const local = (sourceFile as any).locals?.get(localName);
if (!local) {
return true;
}
// If any of the TSDoc comments contain a @ignore tag, we ignore this message
const isIgnored = local.declarations.some(declaration => {
const tags = [declaration.jsDoc]
.flat()
.filter(Boolean)
.flatMap((tagNode: any) => tagNode.tags);
return tags.some(tag => tag?.tagName.text === 'ignore');
});
return !isIgnored;
});
},
);
const content = originalGenerateReviewFileContent.call(
this,
collector,
...moreArgs,
);
return prettier.format(content, {
...require('@spotify/prettier-config'),
parser: 'markdown',
@@ -134,6 +227,18 @@ async function findPackageDirs() {
return packageDirs;
}
async function getTsDocConfig() {
const tsdocConfigFile = await TSDocConfigFile.loadFile(
require.resolve('@microsoft/api-extractor/extends/tsdoc-base.json'),
);
tsdocConfigFile.addTagDefinition({
tagName: '@ignore',
syntaxKind: TSDocTagSyntaxKind.ModifierTag,
});
tsdocConfigFile.setSupportForTag('@ignore', true);
return tsdocConfigFile;
}
function logApiReportInstructions() {
console.log('');
console.log(
@@ -236,6 +341,7 @@ async function runApiExtraction({
},
configObjectFullPath: projectFolder,
packageJsonFullPath: resolvePath(projectFolder, 'package.json'),
tsdocConfigFile: await getTsDocConfig(),
});
// The `packageFolder` needs to point to the location within `dist-types` in order for relative