Skip to content
Open
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
5 changes: 5 additions & 0 deletions apps/api-extractor/extends/tsdoc-base.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",

Choose a reason for hiding this comment

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

Since @module is already defined by JSDoc, we should probably make it a standard TSDoc tag rather than an AEDoc custom tag. If the JSDoc syntax is inconsistent or has different semantics, then maybe TSDoc should consider a different name like @moduleDocumentation.

"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand All @@ -51,6 +55,7 @@
"@internal": true,
"@label": true,
"@link": true,
"@module": true,
"@override": true,
"@packageDocumentation": true,
"@param": true,
Expand Down
56 changes: 56 additions & 0 deletions apps/api-extractor/src/aedoc/ModuleDocComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import * as ts from 'typescript';

import type { Collector } from '../collector/Collector';
import { ExtractorMessageId } from '../api/ExtractorMessageId';

export class ModuleDocComment {

Choose a reason for hiding this comment

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

DRY - this is essentially a copy+paste of the code+comments from PackageDocComment.ts, introducing a second full scan of the AST. And it does not conider a comment block containing both @module and @packageDocumentation, which probably should be an error. I feel like probably these tags should be detected in a single unified scan. But we need to generalize it a bit, because the @packageDocumentation code assumed that it can only appear in the entry point file, whereas @module can appear in any file.

/**
* For the given source file, see if it starts with a TSDoc comment containing the `@module` tag.
*/
public static tryFindInSourceFile(
sourceFile: ts.SourceFile,
collector: Collector
): ts.TextRange | undefined {
// The @module comment is special because it is not attached to an AST
// definition. Instead, it is part of the "trivia" tokens that the compiler treats
// as irrelevant white space.
//
// This implementation assumes that the "@module" will be in the first TSDoc comment
// that appears in the source file.
let moduleCommentRange: ts.TextRange | undefined = undefined;
let foundFirstJSDocComment: boolean = false;

for (const commentRange of ts.getLeadingCommentRanges(sourceFile.text, sourceFile.getFullStart()) || []) {
if (commentRange.kind === ts.SyntaxKind.MultiLineCommentTrivia) {
const commentBody: string = sourceFile.text.substring(commentRange.pos, commentRange.end);

// Choose the first JSDoc-style comment
if (/^\s*\/\*\*/.test(commentBody)) {
if (!foundFirstJSDocComment) {
foundFirstJSDocComment = true;
// But only if it looks like it's trying to be @module
// (The TSDoc parser will validate this more rigorously)
if (/\@module/i.test(commentBody)) {
moduleCommentRange = commentRange;
}
} else {
// If we find another JSDoc comment with @module, report an error
if (/\@module/i.test(commentBody)) {
collector.messageRouter.addAnalyzerIssueForPosition(
ExtractorMessageId.MisplacedModuleTag,
'The @module comment should only appear once at the top of the source file',
sourceFile,
commentRange.pos
);
}
}
}
}
}

return moduleCommentRange;
}
}
6 changes: 6 additions & 0 deletions apps/api-extractor/src/api/ExtractorMessageId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ export enum ExtractorMessageId {
*/
MisplacedPackageTag = 'ae-misplaced-package-tag',

/**
* "The `@module` comment must only appear on modules that are re-exported as namespaces."
*/
MisplacedModuleTag = 'ae-misplaced-module-tag',

/**
* "The symbol ___ needs to be exported by the entry point ___."
*/
Expand Down Expand Up @@ -130,6 +135,7 @@ export const allExtractorMessageIds: Set<string> = new Set<string>([
'ae-incompatible-release-tags',
'ae-missing-release-tag',
'ae-misplaced-package-tag',
'ae-misplaced-module-tag',
'ae-forgotten-export',
'ae-internal-missing-underscore',
'ae-internal-mixed-release-tag',
Expand Down
22 changes: 22 additions & 0 deletions apps/api-extractor/src/generators/DtsRollupGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { AstNamespaceImport } from '../analyzer/AstNamespaceImport';
import type { IAstModuleExportInfo } from '../analyzer/AstModule';
import { SourceFileLocationFormatter } from '../analyzer/SourceFileLocationFormatter';
import type { AstEntity } from '../analyzer/AstEntity';
import { ModuleDocComment } from '../aedoc/ModuleDocComment';

/**
* Used with DtsRollupGenerator.writeTypingsFile()
Expand Down Expand Up @@ -180,6 +181,21 @@ export class DtsRollupGenerator {
// signatures may reference them directly (without using the namespace qualifier).

writer.ensureSkippedLine();

// Check if the source file has a @module comment and emit it before the namespace declaration
const sourceFile: ts.SourceFile = astEntity.astModule.sourceFile;
const moduleCommentRange: ts.TextRange | undefined = ModuleDocComment.tryFindInSourceFile(

Choose a reason for hiding this comment

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

The API Extractor architecture generally does not perform this kind of analysis in the generator layer. It should happen earlier (e.g. in the collector), and the comment needs to be parsed as TSDoc, not literally copy+pasted into the output without any analysis.

Choose a reason for hiding this comment

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

These comments also need to be emitted into the .api.json model file. If you implement the ApiModelGenerator part, then you will realize why parsing needs to happen upstream from the generators.

sourceFile,
collector
);
if (moduleCommentRange) {
const moduleComment: string = sourceFile.text.substring(
moduleCommentRange.pos,
moduleCommentRange.end
);
writer.writeLine(moduleComment);
}

if (entity.shouldInlineExport) {
writer.write('export ');
}
Expand Down Expand Up @@ -265,6 +281,12 @@ export class DtsRollupGenerator {
span.modification.skipAll();
}

// If the @module comment seems to be attached to one of the regular API items,
// omit it. It gets emitted with the namespace declaration.
if (span.node.getText().match(/(?:\s|\*)@module(?:\s|\*)/gi)) {
span.modification.skipAll();
}
Comment on lines +284 to +288
Copy link
Member

Choose a reason for hiding this comment

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

@copilot - This should be an error case, shouldn't it? The @module comment should only be valid for a module re-exported as a namespace, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, @module comments on regular API items are silently skipped (similar to how @packageDocumentation is handled). The @module comment from the source file is only used when that module is re-exported as a namespace.

Should we report an error if a file contains @module but is NOT re-exported as a namespace? Or is silent skipping the right behavior? I'd like clarification on the intended validation rules.


// For now, we don't transform JSDoc comment nodes at all
recurseChildren = false;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@module",
"syntaxKind": "modifier"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
Expand Down Expand Up @@ -171,6 +175,7 @@
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@module": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
Expand Down
Loading
Loading