- Prettier Plugin for TSDoc
A Prettier plugin that formats TSDoc comments consistently.
- Structural Formatting: Consistent leading
/**
, aligned*
, controlled blank lines - Tag Normalization: Normalize common tag spelling variants (e.g.,
@return
→@returns
) - Parameter Alignment: Align parameter descriptions across
@param
tags - Tag Ordering: Canonical ordering of TSDoc tags for improved readability
- Example Formatting: Automatic blank lines before
@example
tags - Markdown & Code Support: Format markdown and fenced code blocks within comments
- Release Tag Management:
- AST-aware insertion: Only add release tags to exported API constructs
- API Extractor compatible: Follows inheritance rules for class/interface members
- Automatic insertion of default release tags (
@internal
by default) - Deduplication of duplicate release tags (
@public
,@beta
, etc.) - Preservation of existing release tags
- Legacy Migration Support: Automatic transformation of legacy Closure Compiler annotations to modern TSDoc syntax
- Multi-language Code Formatting: Enhanced support for TypeScript, JavaScript, HTML, CSS, and more
- Performance Optimized: Efficient parsing with telemetry and debug support
- Highly Configurable: 14+ configuration options via Prettier config
- TypeDoc/AEDoc Compatible: Support for extended tag sets beyond core TSDoc
npm install prettier-tsdoc-plugin
Add the plugin to your Prettier configuration:
{
"plugins": ["prettier-tsdoc-plugin"]
}
All options are configured under the tsdoc
namespace in your Prettier
configuration:
{
"plugins": ["prettier-tsdoc-plugin"],
"tsdoc": {
"fencedIndent": "space",
"normalizeTagOrder": true,
"dedupeReleaseTags": true,
"splitModifiers": true,
"singleSentenceSummary": false,
"alignParamTags": false,
"defaultReleaseTag": "@internal",
"onlyExportedAPI": true,
"inheritanceAware": true,
"closureCompilerCompat": true,
"extraTags": [],
"normalizeTags": {
"@return": "@returns",
"@prop": "@property"
},
"releaseTagStrategy": "keep-first"
}
}
Option | Type | Default | Description |
---|---|---|---|
fencedIndent |
"space" | "none" |
"space" |
Indentation style for fenced code blocks |
normalizeTagOrder |
boolean |
true |
Normalize tag order based on conventional patterns (see below) |
dedupeReleaseTags |
boolean |
true |
Deduplicate release tags (@public , @beta , etc.) |
splitModifiers |
boolean |
true |
Split modifiers to separate lines |
singleSentenceSummary |
boolean |
false |
Enforce single sentence summaries |
alignParamTags |
boolean |
false |
Align parameter descriptions across @param tags |
defaultReleaseTag |
string | null |
"@internal" |
Default release tag when none exists (null to disable) |
onlyExportedAPI |
boolean |
true |
Only add release tags to exported API constructs (AST-aware) |
inheritanceAware |
boolean |
true |
Respect inheritance rules - skip tagging class/interface members |
closureCompilerCompat |
boolean |
true |
Enable legacy Closure Compiler annotation transformations |
extraTags |
string[] |
[] |
Additional custom tags to recognize |
normalizeTags |
Record<string, string> |
{} |
Custom tag spelling normalizations |
releaseTagStrategy |
"keep-first" | "keep-last" |
"keep-first" |
Strategy for release tag deduplication |
The plugin includes these built-in normalizations:
@return
→@returns
@prop
→@property
You can add custom normalizations or override built-in ones using the
normalizeTags
option.
When normalizeTagOrder
is enabled (default: true
), TSDoc tags are reordered
into a canonical structure for improved readability:
- Input Parameters:
@param
and@typeParam
tags - Output:
@returns
tag - Error Conditions:
@throws
tags - Deprecation Notices:
@deprecated
tag - Cross-references:
@see
tags - Release Tags:
@public
,@internal
,@beta
, etc. - Examples:
@example
tags (always last, with automatic blank lines)
Before (mixed order):
/**
* A complex function.
* @see https://example.com
* @beta
* @throws {Error} If input is invalid
* @returns The result
* @param a The first number
* @deprecated Use newFunction instead
* @example
* ```ts
* complexFunction(1, 2);
* ```
*/
After (canonical order):
/**
* A complex function.
*
* @param a - The first number
* @returns The result
* @throws {Error} If input is invalid
* @deprecated Use newFunction instead
* @see https://example.com
* @beta
*
* @example
* ```ts
* complexFunction(1, 2);
* ```
*/
Note: When normalizeTagOrder
is false
, the original tag order is
preserved as much as possible, though TSDoc's parsing structure may still impose
some organization.
The following tags are considered release tags and can be deduplicated:
@public
@beta
@alpha
@internal
@experimental
The plugin uses AST analysis to intelligently determine which comments need release tags, following API Extractor conventions:
- Only exported declarations receive default release tags
- Class/interface members inherit from their container's release tag
- Namespace members inherit from the namespace's release tag
- Non-exported code remains untagged (not part of public API)
Configuration Options:
{
"tsdoc": {
"defaultReleaseTag": "@internal", // Default tag to add
"defaultReleaseTag": "@public", // Use @public instead
"defaultReleaseTag": null // Disable feature
}
}
Example - AST-aware insertion for exported functions:
Input:
/**
* Exported helper function.
* @param value - Input value
* @returns Processed value
*/
export function helper(value: string): string {
return value.trim();
}
/**
* Internal helper (not exported).
* @param value - Input value
*/
function internal(value: string): void {
console.log(value);
}
Output:
/**
* Exported helper function.
* @internal
* @param value - Input value
* @returns Processed value
*/
export function helper(value: string): string {
return value.trim();
}
/**
* Internal helper (not exported).
* @param value - Input value
*/
function internal(value: string): void {
console.log(value);
}
Example - Existing tags are preserved:
Input:
/**
* Public API function.
* @public
* @param data - Input data
*/
function publicApi(data: any): void {}
Output (no change):
/**
* Public API function.
* @public
* @param data - Input data
*/
function publicApi(data: any): void {}
Example - Inheritance rules (class members inherit from class):
Input:
/**
* Widget class for the public API.
* @public
*/
export class Widget {
/**
* Method that inherits @public from class.
* @param value - Input value
*/
process(value: string): void {
// implementation
}
}
Output (no change - method inherits @public from class):
/**
* Widget class for the public API.
* @public
*/
export class Widget {
/**
* Method that inherits @public from class.
* @param value - Input value
*/
process(value: string): void {
// implementation
}
}
Phase 130 Feature - The plugin provides automatic transformation of legacy Google Closure Compiler annotations to modern TSDoc/JSDoc syntax, making it easy to migrate older JavaScript codebases to modern tooling.
{
"tsdoc": {
"closureCompilerCompat": true // Default: true - enabled by default
}
}
The plugin automatically modernizes the following legacy annotations:
@export
→@public
@protected
→@internal
@private
→@internal
@param {type} name
→@param name
@throws {Error}
→@throws
(when type is the only content)@this {type}
→@this
@extends {BaseClass}
→ (removed)@implements {IInterface}
→ (removed)
Note: Only curly-brace syntax is removed. Modern TypeDoc overrides like
@extends BaseClass
(without braces) are preserved.
@constructor
→ (removed)@const
→ (removed)@define
→ (removed)@noalias
→ (removed)@nosideeffects
→ (removed)
@see http://example.com
→@see {@link http://example.com}
@see MyClass
→@see {@link MyClass}
(code constructs only)@see Also check the documentation
→ (unchanged - descriptive text preserved)
{@code expression}
→`expression`
@tutorial tutorialName
→@document tutorialName
- References: TypeDoc @document tag
@default value
→@defaultValue value
@fileoverview description
→ Summary content +@packageDocumentation
- Special handling: Content is moved to summary, tag is placed at bottom
Example 1: Core Legacy Transformations
Before (Legacy Closure Compiler):
/**
* Creates a new widget with configuration.
* @constructor
* @param {string} id - The unique identifier for the widget.
* @param {object} [options] - Configuration options.
* @extends {BaseWidget}
* @implements {IWidget}
* @export
* @see MyOtherClass
* @see http://example.com/docs
*/
After (Modern TSDoc):
/**
* Creates a new widget with configuration.
*
* @param id - The unique identifier for the widget.
* @param [options] - Configuration options.
* @public
* @see {@link MyOtherClass}
* @see {@link http://example.com/docs}
*/
Example 2: New Tag Transformations
Before (Legacy with new transformations):
/**
* @fileoverview This module provides utility functions for data processing.
* It includes various helper methods for validation and transformation.
*/
/**
* Processes values with {@code let x = getValue();} syntax.
* @tutorial getting-started
* @default null
* @param value - The input value
*/
function processValue(value: string = null) {
// implementation
}
After (Modern TSDoc):
/**
* This module provides utility functions for data processing.
* It includes various helper methods for validation and transformation.
*
* @packageDocumentation
*/
/**
* Processes values with `let x = getValue();` syntax.
*
* @param value - The input value
* @document getting-started
* @defaultValue null
*/
function processValue(value: string = null) {
// implementation
}
The transformation engine uses intelligent pattern recognition to avoid false positives:
- Code blocks are protected: Transformations skip content inside ``` fenced blocks
- Prose detection:
@see First reference
is NOT transformed (common English words) - Code construct detection:
@see MyClass
IS transformed (follows naming patterns) - Partial transformations:
@throws {Error} When invalid
preserves the description
Legacy transformations work seamlessly with all other plugin features:
- Tag ordering: Transformed tags participate in canonical ordering
- Release tag deduplication: Duplicate tags are removed after transformation
- Parameter alignment: Transformed
@param
tags align properly - Markdown formatting: Content formatting applies after transformation
To disable legacy transformations (e.g., for modern codebases):
{
"tsdoc": {
"closureCompilerCompat": false
}
}
- Enable the plugin with default settings (
closureCompilerCompat: true
) - Run Prettier on your legacy codebase - transformations happen automatically
- Review changes - the process is conservative and avoids false positives
- Commit transformed code - all legacy annotations are now modern TSDoc
- Optional: Disable
closureCompilerCompat
once migration is complete
Input:
/**
* Calculate the sum of two numbers.
* @param a - First number
* @return Second number result
*/
function add(a: number, b: number): number {
return a + b;
}
Output:
/**
* Calculate the sum of two numbers.
* @param a - First number
* @returns Second number result
*/
function add(a: number, b: number): number {
return a + b;
}
Input:
/**
* Process data with example:
* ```typescript
* const result=process({value:42});
* ```
*/
function process(data: any): any {
return data;
}
Output:
/**
* Process data with example:
* ```typescript
* const result = process({ value: 42 });
* ```
*/
function process(data: any): any {
return data;
}
Input:
/**
* Internal function.
* @public
* @param x - Value
* @public
* @beta
*/
function internalFn(x: number): void {}
Output (with dedupeReleaseTags: true, releaseTagStrategy: "keep-first"
):
/**
* Internal function.
* @public
* @param x - Value
* @beta
*/
function internalFn(x: number): void {}
With alignParamTags: true
:
Input:
/**
* Function with parameters.
* @param shortName - Short description
* @param veryLongParameterName - Long description that may wrap
* @param id - ID value
* @returns Result
*/
function example(
shortName: string,
veryLongParameterName: string,
id: number
): string {
return '';
}
Output:
/**
* Function with parameters.
* @param shortName - Short description
* @param veryLongParameterName - Long description that may wrap
* @param id - ID value
* @returns Result
*/
function example(
shortName: string,
veryLongParameterName: string,
id: number
): string {
return '';
}
- Small comments (< 100 chars): ~5ms average formatting time
- Medium comments (100-500 chars): ~15-20ms average formatting time
- Large comments (> 500 chars): ~40-50ms average formatting time
- Memory usage: Stable, no memory leaks detected
- Cache efficiency: Parser and configuration caching for optimal performance
- Use consistent configuration: Avoid changing TSDoc options frequently to benefit from parser caching
- Limit custom tags: Excessive
extraTags
can reduce parser cache efficiency - Consider comment size: Very large comments (> 1000 chars) may exceed 10ms formatting budget
- Enable markdown caching: Repeated markdown/code blocks are automatically cached
- Monitor with debug mode: Use
PRETTIER_TSDOC_DEBUG=1
to track performance metrics
Set the PRETTIER_TSDOC_DEBUG=1
environment variable to enable debug telemetry:
PRETTIER_TSDOC_DEBUG=1 npx prettier --write "**/*.ts"
This will log performance metrics including:
- Comments processed count
- Parse error frequency
- Average formatting time per comment
- Cache hit rates
- Memory usage patterns
Run the included benchmarks to measure performance on your system:
npm run benchmark
This project uses Rollup to create a single bundled entry point. The build process includes:
- TypeScript compilation: TypeScript is compiled to JavaScript with source maps
- Bundling: All source files are bundled into a single
dist/index.js
file - Source maps: Generated for debugging support
# Build the plugin bundle
npm run build
# Type checking only (no JavaScript output)
npm run typecheck
# Run tests
npm test
The build output consists of:
dist/index.js
- Single bundled entry pointdist/index.js.map
- Source map for debugging
The plugin is designed to be backward-compatible. New AST-aware features are enabled by default:
- AST-aware release tags: Enabled by default (
onlyExportedAPI: true
). Set tofalse
for legacy behavior. - Inheritance awareness: Enabled by default (
inheritanceAware: true
). Set tofalse
to tag all constructs. - Default release tags: Enabled by default with
@internal
. Set tonull
to disable. - Parameter alignment: Disabled by default. Set
alignParamTags: true
to enable. - Tag normalization: Only built-in normalizations (
@return
→@returns
) are applied by default.
- Check comment syntax: Only
/** */
comments are processed, not/* */
or//
- Check debug output: Use
PRETTIER_TSDOC_DEBUG=1
to see which comments are being processed
- Large files: Comments > 1000 characters may take longer to format
- Custom tags: Excessive
extraTags
can impact performance - Debug mode: Use
PRETTIER_TSDOC_DEBUG=1
to identify slow comments
- Tag normalization: Built-in normalizations are applied by default
- Legacy Closure Compiler transformations: Enabled by default
(
closureCompilerCompat: true
) - AST-aware release tag insertion: Only exported declarations get default tags
- Inheritance rules: Class/interface members inherit from container
- Custom normalizations: Check your
normalizeTags
configuration
- Check export status: Only exported declarations get default tags with
onlyExportedAPI: true
- Check inheritance: Class members inherit from class release tag
- Disable AST analysis: Set
onlyExportedAPI: false
for legacy behavior - Debug AST analysis: Use
PRETTIER_TSDOC_DEBUG=1
to see analysis results
To validate your configuration, use this TypeScript interface:
interface TSDocPluginOptions {
fencedIndent?: 'space' | 'none';
normalizeTagOrder?: boolean;
dedupeReleaseTags?: boolean;
splitModifiers?: boolean;
singleSentenceSummary?: boolean;
alignParamTags?: boolean;
defaultReleaseTag?: string | null;
onlyExportedAPI?: boolean;
inheritanceAware?: boolean;
closureCompilerCompat?: boolean;
extraTags?: string[];
normalizeTags?: Record<string, string>;
releaseTagStrategy?: 'keep-first' | 'keep-last';
}
Phase 130 (Legacy Closure Compiler Support) - ✅ COMPLETED
All phases of the implementation plan have been completed successfully:
- ✅ Phase 1: Bootstrap
- ✅ Phase 2: Parser Detection
- ✅ Phase 3: Summary & Remarks
- ✅ Phase 4: Tags & Alignment
- ✅ Phase 5: Markdown & Codeblocks
- ✅ Phase 6: Configuration & Normalization
- ✅ Phase 7: Edge Cases & Performance
- ✅ Phase 8: Release Tags
- ✅ Phase 110: Newline and Tag Management
- ✅ Phase 130: Legacy Closure Compiler Support
See agents/context.md for the detailed specification.
MIT