method-shorthand-jsdoc
Uses method shorthand in return objects to ensure JSDoc comments remain visible when consumers hover over methods.
Install
mkdir -p .claude/skills/method-shorthand-jsdoc && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4061" && unzip -o skill.zip -d .claude/skills/method-shorthand-jsdoc && rm skill.zipInstalls to .claude/skills/method-shorthand-jsdoc
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Method shorthand in return objects for JSDoc preservation. Use when factory functions have internal helpers that should expose docs, or hovering over returned methods shows no JSDoc.Key capabilities
- →Preserve JSDoc comments on factory methods
- →Expose documentation through shorthand syntax
- →Improve IDE hover visibility for factory outputs
- →Restructure internal helper functions
- →Ensure consumer-facing documentation accuracy
How it works
It refactors factory pattern code by migrating internal helper functions directly into the returned object property definitions.
Inputs & outputs
When to use method-shorthand-jsdoc
- →Refactor factory functions for JSDoc visibility
- →Document internal helpers in returned objects
- →Fix missing documentation on factory method calls
- →Improve developer experience with IDE hover documentation
About this skill
Method Shorthand for JSDoc Preservation
When factory functions have helper functions that are only used by returned methods, move them INTO the return object using method shorthand. This ensures JSDoc comments are properly passed through to consumers.
Related Skills: See
factory-function-compositionfor the four-zone factory anatomy and thethisdecision rule.
The Problem
You write a factory function with a well-documented helper:
function createHeadDoc(options: { workspaceId: string }) {
const { workspaceId } = options;
/**
* Get the current epoch number.
*
* Computes the maximum of all client-proposed epochs.
* This ensures concurrent bumps converge to the same version.
*
* @returns The current epoch (0 if no bumps have occurred)
*/
function getEpoch(): number {
let max = 0;
for (const value of epochsMap.values()) {
max = Math.max(max, value);
}
return max;
}
return {
workspaceId,
getEpoch, // JSDoc is NOT visible when hovering on returned object!
bumpEpoch(): number {
const next = getEpoch() + 1; // Calling internal helper
return next;
},
};
}
When you hover over head.getEpoch() in your IDE, you see... nothing. The JSDoc is lost.
The Solution
Move the helper INTO the return object using method shorthand:
function createHeadDoc(options: { workspaceId: string }) {
const { workspaceId } = options;
return {
workspaceId,
/**
* Get the current epoch number.
*
* Computes the maximum of all client-proposed epochs.
* This ensures concurrent bumps converge to the same version.
*
* @returns The current epoch (0 if no bumps have occurred)
*/
getEpoch(): number {
let max = 0;
for (const value of epochsMap.values()) {
max = Math.max(max, value);
}
return max;
},
bumpEpoch(): number {
const next = this.getEpoch() + 1; // Use this.methodName()
return next;
},
};
}
Now hovering over head.getEpoch() shows the full JSDoc.
This matters even more when the public type derives from the factory:
export type HeadDoc = ReturnType<typeof createHeadDoc>;
With ReturnType, the returned object is the public type source. Put consumer-facing JSDoc directly on the returned method or getter so hover, completion, and Go to Definition all land on the same member.
JSDoc preservation and Go-to-Definition flow from the same choice: where the consumer's tools resolve to. Method shorthand in zone 4 keeps both on the real definition. A hand-written interface annotation, a destructure-re-export of a module-level object, or a wrapper that just re-emits a function are the regressions that split them apart. See typescript "Go-to-Definition Awareness" for the navigation-side rules.
Why This Works
- JSDoc attaches to the method definition site - when methods are inline in the return object, the JSDoc is directly on the property TypeScript sees
- Method shorthand uses
functionsemantics -thisis bound to the object, sothis.getEpoch()works - No separate helper needed - if it's only used by sibling methods, it belongs in the same object
The Pattern
// BAD: Helper defined separately, JSDoc lost on return
function createService(client) {
/** Fetches user data with caching. */
function fetchUser(id: string) { ... }
return {
fetchUser, // JSDoc not visible to consumers!
getProfile(id: string) {
return fetchUser(id); // Works, but consumers can't see docs
},
};
}
// GOOD: Method shorthand, JSDoc preserved
function createService(client) {
return {
/** Fetches user data with caching. */
fetchUser(id: string) { ... },
getProfile(id: string) {
return this.fetchUser(id); // Use this.method()
},
};
}
Decision Rule
Move a helper into the returned object when it is only used by returned methods, consumers need hover JSDoc on that method, and the helper does not run during initialization.
Keep helpers separate when they are called before return, shared across factories, or truly internal and not exposed.
Arrow Functions Don't Work
Arrow functions don't have their own this:
// BAD: Arrow function, this is undefined
return {
getEpoch: () => { ... },
bumpEpoch: () => {
this.getEpoch(); // ERROR: this is undefined!
},
};
// GOOD: Method shorthand has correct this binding
return {
getEpoch() { ... },
bumpEpoch() {
this.getEpoch(); // Works!
},
};
Real Example
From packages/epicenter/src/core/docs/head-doc.ts:
export function createHeadDoc(options: { workspaceId: string; ydoc?: Y.Doc }) {
const { workspaceId } = options;
const ydoc = options.ydoc ?? new Y.Doc({ guid: workspaceId });
const epochsMap = ydoc.getMap<number>('epochs');
return {
ydoc,
workspaceId,
/**
* Get the current epoch number.
*
* Computes the maximum of all client-proposed epochs.
* This ensures concurrent bumps converge to the same version
* without skipping epoch numbers.
*
* @returns The current epoch (0 if no bumps have occurred)
*/
getEpoch(): number {
let max = 0;
for (const value of epochsMap.values()) {
max = Math.max(max, value);
}
return max;
},
/**
* Bump the epoch to the next version.
*
* @returns The new epoch number after bumping
*/
bumpEpoch(): number {
const next = this.getEpoch() + 1;
epochsMap.set(ydoc.clientID.toString(), next);
return next;
},
// ... other methods using this.getEpoch()
};
}
Summary
| Approach | JSDoc Visible? | this Works? |
|---|---|---|
| Separate helper + reference | No | N/A |
| Arrow function in return | Yes | No |
| Method shorthand in return | Yes | Yes |
Method shorthand is the only approach that preserves JSDoc AND allows methods to call each other via this.
Where This Fits in the Factory Function Anatomy
Factory functions follow a four-zone internal shape: immutable state → mutable state → private helpers → return object. Method shorthand lives in the return object (zone 4): the public API.
The this.method() vs direct-call decision depends on which zone the function lives in:
| Situation | Where it lives | How to call it |
|---|---|---|
| Only used by sibling methods in the return object | Zone 4 (return object, method shorthand) | this.method() |
| Used by return-object methods AND pre-return init logic | Zone 3 (private helper, standalone function) | Direct call: helperFn() |
| Used during initialization only, not exposed | Zone 3 (private helper) | Direct call: helperFn() |
When a helper needs to be in zone 3, its JSDoc won't be visible to consumers; that's correct, because it's a private implementation detail. Only zone 4 methods need consumer-facing JSDoc.
See Closures Are Better Privacy Than Keywords for the full factory function anatomy.
References
- docs/articles/method-shorthand-jsdoc-preservation.md - Same content as article
- docs/articles/closures-are-better-privacy-than-keywords.md - Factory function anatomy and zone system
When not to use it
- →Simple functions without JSDoc
- →Performance-critical loops where object creation overhead matters
Limitations
- →Requires minor refactoring of legacy code
- →Limited by how specific IDEs parse shorthand methods
- →Requires explicit JSDoc tags to be effective
How it compares
Focuses on IDE documentation preservation, not just functionality or pattern optimization.
Compared to similar skills
method-shorthand-jsdoc side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| method-shorthand-jsdoc (this skill) | 1 | 2mo | No flags | Intermediate |
| schema-markup | 10 | 6mo | No flags | Intermediate |
| coding-standards | 7 | 2mo | Review | Intermediate |
| antfu | 6 | 3mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by EpicenterHQ
View all by EpicenterHQ →You might also like
schema-markup
davila7
When the user wants to add, fix, or optimize schema markup and structured data on their site. Also use when the user mentions "schema markup," "structured data," "JSON-LD," "rich snippets," "schema.org," "FAQ schema," "product schema," "review schema," or "breadcrumb schema." For broader SEO issues, see seo-audit.
coding-standards
affaan-m
适用于TypeScript、JavaScript、React和Node.js开发的通用编码标准、最佳实践和模式。
antfu
antfu
Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects. Use when setting up new projects, configuring ESLint/Prettier alternatives, monorepos, library publishing, or when the user mentions Anthony Fu's preferences.
explain-code
AgnosticUI
Explain what code does in plain English
formatting-standards
crmagz
Formatting and linting standards using GTS, ESLint, and Prettier. Use when writing or formatting TypeScript code in this project.
jsdoc
shift-editor
Add or revise source-level JSDoc for Shift APIs. Use this skill before writing or editing documentation comments for exported classes, methods, constructors, domain data structures, render frames, reactive state, or any API where caller intent, side effects, lifetime, ownership, or nullability are e