PokéRogue
    Preparing search index...

    Commenting code

    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.

    Important

    These are general guidelines, not "hard and fast" rules. When in doubt, use common sense and err on the side of readability.

    DO:

    • Keep comments meaningful
      • Focus on explaining why a line or block of code exists - a post hoc understanding of the reasons is infinitely more useful.
      • Comments should NOT repeat what code does[^1] or explain concepts obvious to someone with a basic understanding of the language at hand.
    • Keep comments readable
      • A comment's verbosity should roughly scale with the complexity of its subject matter. Some people naturally write shorter or longer comments, but summarizing a 300-line function with "does a thing" is about as good as writing nothing. Conversely, writing a paragraph-level response where a basic one-liner would suffice is just as undesirable.
      • Long comments should be broken into multiple lines at around 100-120 characters to avoid unnecessary scrolling in terminals and IDEs.
    • Make sure comments exist on Functions, Classes, Methods, and Properties.
      • These tend to be the most important things to comment. When someone goes to use a function/class/method/etc., having a comment reduces the need to flip back and forth between files to figure out what XYZ does. Peek Definition is great until you're three nesting levels deep.

    DON'T:

    • Leave comments for code you don't understand
      • Incorrect information is worse than no information. If you aren't sure how something works, don't make something up to explain it - ask for help or mark it as TODO.
    • Over-comment
      • Adding too many comments can risk distracting from the actual code in favor of repeating the self-evident.
      • Where possible, try to summarize blocks of code instead of singular lines where possible, always preferring giving a reason over stating a fact. Single line comments should call out specific oddities or features.

    [^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.

    Tip

    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:

    Reference Example
    /**
    * 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:

    • Use proper English sentences for descriptors

      Since these comments are going onto a website, annotations for properties, methods and functions should be well-formed, present-tense English sentences where possible.

      • The only exceptions are single-sentence @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;
    • Default values must be mentioned if present

      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.

      • Classes & interfaces can make use of the @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.

    Important

    Any properties or methods unique to the class must still be documented!