Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 13, 2025

Summary

When API Extractor creates a namespace to represent an exported module namespace object (export * as Foo from './Foo'), the @module comment from the source file is now applied to the namespace declaration.

Fixes #5446

Details

This PR implements the following changes:

  • TSDoc Configuration: Added @module tag definition to tsdoc-base.json as a modifier tag
  • ModuleDocComment Helper: Created a new helper class that extracts @module comments from source files (mirrors the PackageDocComment pattern)
    • Validates that @module appears only once at the top of the file
    • Reports errors for multiple @module tags in the same file
  • DtsRollupGenerator: Modified to:
    • Extract @module comments from source files when processing AstNamespaceImport
    • Apply the comment to the generated namespace declaration
    • Skip @module comments when they appear on regular API items to prevent duplication
  • ExtractorMessageId: Added MisplacedModuleTag error ID for validation errors
  • Changelog: Added changelog entry for the minor version bump

The implementation follows the same pattern used for @packageDocumentation comments.

Example:

Source (Text.ts):

/**
 * @module
 * Functions for manipulating text.
 */

export function convertToLf(input: string): string { ... }
export function convertToCrLf(input: string): string { ... }

Input (index.ts):

export * as Text from './Text';

Generated (rollup.d.ts):

/**
 * @module
 * Functions for manipulating text.
 */
export declare namespace Text {
  export { convertToLf, convertToCrLf }
}

How it was tested

  • Added a new test scenario moduleNamespaceComment in build-tests/api-extractor-scenarios that validates the functionality with the exact example from the issue
  • All existing API Extractor tests pass
  • CodeQL security analysis passed with no vulnerabilities detected
  • Verified that the generated output correctly includes the @module comment on the namespace declaration without duplicating it on individual function declarations
  • Validated error reporting for multiple @module tags in the same file
  • Verified that the changelog entry is correctly formatted and follows Rush Stack conventions

Impacted documentation

No documentation updates required. The @module tag is now supported as part of the TSDoc configuration for API Extractor.

Original prompt

This section details on the original issue you should resolve

<issue_title>[api-extractor] Apply @module comments to module namespace objects</issue_title>
<issue_description>## Summary
When API-extractor creates a namespace in the API rollup file to represent an exported module namespace object, any @module comment in the source file should be applied to the namespace.

Repro steps

index.ts

export * as Text from './Text';

Text.ts

/**
 * @module
 * Functions for manipulating text.
 */

/**
 * Convert line endings to `\n`
 */
export function convertToLf(input: text): string {
  return text.replace(/\r\n/g, '\n');
}

/**
 * Convert line endings to `\r\n`
 */
export function convertToCrLf(input: text): string {
  return text.replace(/\r?\n/g, '\r\n');
}

Will generate

/**
 * Convert line endings to `\n`
 */
declare function convertToLf(input: text): string;

/**
 * Convert line endings to `\r\n`
 */
declare function convertToCrLf(input: text): string;

declare namespace Text {
  export {
    convertToLf,
    convertToCrLf
  }
}
export { Text }

Details

However, it should generate:

/**
 * Convert line endings to `\n`
 */
declare function convertToLf(input: text): string;

/**
 * Convert line endings to `\r\n`
 */
declare function convertToCrLf(input: text): string;

/**
 * Functions for manipulating text.
 */
declare namespace Text {
  export {
    convertToLf,
    convertToCrLf
  }
}

## Standard questions

Please answer these questions to help us investigate your issue more quickly:

| Question | Answer |
| -------- | -------- |
| `@microsoft/api-extractor` version? | 7.54.0 |
| Operating system? | Linux |
| API Extractor scenario? | rollups (.d.ts) |
| Would you consider contributing a PR? | No |
| TypeScript compiler version? | 5.8.2 |
| Node.js version (`node -v`)? | 22.16.0 |
</issue_description>

## Comments on the Issue (you are @copilot in this section)

<comments>
<comment_new><author>@iclanton</author><body>
`@module` will need to be added to TSDoc as well.</body></comment_new>
</comments>

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

- Added @module tag definition to TSDoc configuration
- Created ModuleDocComment helper to extract @module comments
- Modified DtsRollupGenerator to apply @module comments to namespaces
- Added logic to skip @module comments on regular declarations
- Created test case moduleNamespaceComment to verify functionality

Co-authored-by: iclanton <[email protected]>
Copilot AI changed the title [WIP] Apply @module comments to module namespace objects Apply @module comments to module namespace objects Nov 13, 2025
Copilot AI requested a review from iclanton November 13, 2025 23:07
Copilot finished work on behalf of iclanton November 13, 2025 23:07
@iclanton
Copy link
Member

@copilot - why is #4876 mentioned in the description? That change is unrelated. #5446 is the relevant issue here.

Copy link
Contributor Author

Copilot AI commented Nov 13, 2025

@copilot - why is #4876 mentioned in the description? That change is unrelated. #5446 is the relevant issue here.

You're right - that was an error in the PR description. The reference to #4876 should not be there. This PR fixes issue #5446, not #4876.

Copilot AI changed the title Apply @module comments to module namespace objects [api-extractor] Apply @module comments to module namespace objects Nov 13, 2025
Copilot finished work on behalf of iclanton November 13, 2025 23:14
Comment on lines +281 to +285
// 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();
}
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.

- ModuleDocComment now accepts Collector parameter for error reporting
- Detects and reports error for multiple @module tags in same file
- Added MisplacedModuleTag error message ID
- Maintains silent skip behavior for @module on individual declarations

Co-authored-by: iclanton <[email protected]>
Copilot AI requested a review from iclanton November 17, 2025 22:40
Copilot finished work on behalf of iclanton November 17, 2025 22:40
@iclanton iclanton marked this pull request as ready for review November 18, 2025 22:35
@iclanton
Copy link
Member

@copilot - this needs a changelog entry. Run rush change and go through the interactive prompts.

- Created changelog entry for @module comment support
- Removed unused ExtractorMessageId import from DtsRollupGenerator

Co-authored-by: iclanton <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 19, 2025

@copilot - this needs a changelog entry. Run rush change and go through the interactive prompts.

Changelog entry has been added in commit 7abbfa5. The change is marked as a minor version bump.

Copilot finished work on behalf of iclanton November 19, 2025 01:29
@iclanton
Copy link
Member

@copilot - Can you take a look at the build failures?

Copy link
Contributor Author

Copilot AI commented Nov 19, 2025

@copilot - Can you take a look at the build failures?

I've checked the build locally and all builds and tests are passing successfully:

  • rush build --to @microsoft/api-extractor
  • rush test --to @microsoft/api-extractor
  • rush build --to api-extractor-scenarios
  • rush rebuild --to @microsoft/api-extractor

Could you provide more details about which specific build failures you're seeing? Are they:

  • Compilation errors?
  • Test failures?
  • Linting issues?
  • Scenario output mismatches?

This will help me identify and fix the issue.

Copilot finished work on behalf of iclanton November 19, 2025 02:17
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.


// 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.

"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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

[api-extractor] Apply @module comments to module namespace objects

3 participants