Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
- Martin Fowler
The goal of programming is to write functioning code, not create comprehensive documentation.
However, programmers spend significantly more time reading code than writing it, especially those new to a codebase.
As such, comments and documentation are vital for any large codebase like this.
This document is intended to serve as a guide for how to (and not to) document code while working on PokéRogue.
These are general guidelines, not "hard and fast" rules. When in doubt, use common sense and err on the side of readability.
DO:
DON'T:
TODO.[^1]: With exceptions for extremely long, convoluted or unintuitive methods (though a dependence on said comments is likely a symptom of poorly structured code).
The codebase makes extensive use of TSDoc, a TypeScript-specific version of JSDoc with standardized syntax and Markdown support.
Most modern IDEs have functionality for showing JSDoc annotations upon hovering over attached constructs.
Some (like VS Code) also show @param descriptions for function parameters as you type them, helping keep track of arguments inside lengthy functions.
One of TSDoc's many upsides is its standardized parser that allows other tools to read and process module documentation.
We make use of one such tool (TypeDoc) to automatically generate API documentation from comments on classes, interfaces and the like[^2].
[^2]: You can preview the output by running pnpm typedoc, though Live Preview or a similar method of previewing local HTML files is recommended to make your life easier.
Note that certain features (like the "Go to Main/Beta" navigation bar links) are disabled on local docs builds due to relying on CI-exclusive environment variables.
For an example of how TSDoc comments work, here are some comments taken from src/data/moves/move.ts:
/**
* Attribute to put in a {@link https://bulbapedia.bulbagarden.net/wiki/Substitute_(doll) | Substitute Doll} for the user.
*
* Used for {@linkcode MoveId.SUBSTITUTE} and {@linkcode MoveId.SHED_TAIL}.
*/
export class AddSubstituteAttr extends MoveEffectAttr {
/** The percentage of the user's maximum HP that is required to apply this effect. */
private readonly hpCost: number;
/** Whether the damage taken should be rounded up (Shed Tail rounds up). */
private readonly roundUp: boolean;
constructor(hpCost: number, roundUp: boolean) {
// code removed
}
/**
* Helper function to compute the amount of HP required to create a substitute.
* @param user - The {@linkcode Pokemon} using the move
* @returns The amount of HP that required to create a substitute.
*/
private getHpCost(user: Pokemon): number {
// code removed
}
/**
* Remove a fraction of the user's maximum HP to create a 25% HP substitute doll.
* @param user - The {@linkcode Pokemon} using the move
* @param target - n/a
* @param move - The {@linkcode Move} being used
* @param args - n/a
* @returns Whether the attribute successfully applied.
*/
public override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// code removed
}
public override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number {
// code removed
}
getCondition(): MoveConditionFunc {
// code removed
}
public override getFailedText(user: Pokemon): string | undefined {
// code removed
}
}
Looking at the example given, you may notice certain terms are annotated with {@linkcode XYZ} tags. This provides a clickable hyperlink to the referenced type or object in most modern IDEs[^3].
Also notice the dashes (-) between the parameter names and descriptions - these are required under the TSDoc spec.
[^3]: For those curious, the difference between @linkcode and @link is that the former renders text in monospace, more clearly indicating a code symbol rather than a URL/hyperlink.
With all these in mind, here are a few TSDoc-specific guidelines to ensure readability of both rendered API documentation and IDE syntax hints:
Since these comments are going onto a website, annotations for properties, methods and functions should be well-formed, present-tense English sentences where possible.
@param/@typeParam lines - these should not end with periods and instead take the form of bullet-point declarations.
Example:/**
* Baseline arguments used to construct all {@linkcode PositionalTag}s,
* the contents of which are serialized and used to construct new tags. \
* Does not contain the `tagType` parameter (which is used to select the proper class constructor during tag loading).
* @privateRemarks
* All {@linkcode PositionalTag}s are intended to implement a sub-interface of this containing their respective parameters,
* and should refrain from adding extra serializable fields not contained in said interface.
* This ensures that all tags truly "become" their respective interfaces when converted to and from JSON.
*/
interface PositionalTagBaseArgs {
/**
* The number of turns remaining until this tag's activation. \
* Decremented by 1 at the end of each turn until reaching 0, at which point it will
* {@linkcode PositionalTag.trigger | trigger} the tag's effects and be removed.
*/
turnCount: number;
/**
* The {@linkcode BattlerIndex} targeted by this effect.
*/
readonly targetIndex: BattlerIndex;
}
/**
* Compute the geometric mean of multiple numbers.
* @param nums - The numbers whose mean will be computed
* @returns The geometric mean of `nums`.
* @remarks
* This is equivalent to Π(nums)^(1/nums.length).
* @see {@link https://en.wikipedia.org/wiki/Geometric_mean | Geometric Mean - Wikipedia}
*/
declare function geometricMean(nums: number[]): number;
TypeScript displays no information about default values in IDEs, so mentioning defaults inside doc comments is the easiest way to inform callers about a given property or parameter's default values.
@defaultValue tag to annotate property initial values.
As for function and method arguments, our codebase opts to include default value information immediately following the @param tag. This ensures the information is prominently visible in IDEs and similar tools.Example:
class BattleScene {
/**
* Tracker for whether the last run attempt failed.
* @defaultValue `false`
*/
public failedRunAway = false;
}
/**
* Print a copiously long, procedurally generated lorem ipsum-like placeholder string.
* @param charCount - (Default `1000`) The number of characters to create
*/
function printLorem(charCount = 1000): string {};
While most class methods should be fully documented, the main exception comes with inheritance - classes and interfaces will inherit documentation comments from any other classes/interfaces they extend/implement, provided no other comments are present on inherited symbols.
As such, do not document properties or methods in sub-classes that do not substantially differ from the superclass' implementation.
Any properties or methods unique to the class must still be documented!