| name | lpcdoc |
| description | LPCDoc comment block generation guide. Consult when writing or updating documentation headers for LPC functions, classes, and modules. |
LPCDoc Generation Guide
This document provides instructions for generating LPCDoc comment blocks for
LPC source code. Follow these guidelines to create accurate, consistent, and
useful documentation.
Documentation Requirements
- All
public, protected, and private functions require an
LPCDoc block. Lifecycle entry points and forward declarations
are exempt (see "What Not to Document" below). The LSP relies on
@param types to resolve parameters in helpers, so private
helpers are not optional in this project.
- File-global variables require an LPCDoc block only when the
@type annotation gives the LSP shape information it cannot
already infer. Document globals whose type is an object
(STD_* macros or file paths), a structured mapping
(([ string: int ])), a class/struct, an array of any of those,
or a union of such types. Plain primitives (int, string,
float) and untyped containers do not need an LPCDoc block —
the type is already obvious from the declaration and adds no
LSP value. The point of documenting variables is to feed the
LSP, not to narrate the obvious.
- A description is mandatory on every LPCDoc block. Every doc
comment must begin with a description of the element it documents.
- Types are mandatory on
@param and @returns. The {type}
annotation is required, not optional — every @param must include
a type and every @returns must include a type.
Updating Existing Code
When working on a file, update any existing comments and headers that do not
match these conventions. This includes:
- File headers that use old formats (convert to the standard
@file format)
- Function docs that use snake_case names (update to camelCase)
- Comments with American spelling (update to Canadian English)
- Missing or incomplete documentation on functions you are modifying
- Malformed or unclear comment blocks
Do not go out of your way to document the entire file — but if you encounter
non-conforming documentation while working, fix it.
Comment Block Structure
LPCDoc comments must be placed directly before the code element they document
and follow this structure:
Key points:
- Start with
/** and end with */
- Each line within the comment begins with
* (space, asterisk, space)
- Always leave a blank doc line (
*) between the description and
the first tag. This is mandatory even for short one-line descriptions.
- Place tags after the description
Function Documentation
For functions, document:
- Purpose/behaviour of the function
- Each parameter
- Return value
- Any errors/exceptions
Example:
int calculateDamage(int attack, int defence) {
}
Variable Documentation
For variables, document:
- Purpose/use of the variable
- Type information
Example:
int MAX_PLAYER_HP = 100;
Tags Reference
@param
Documents a function parameter.
Syntax: @param {type} name - Description
A hyphen (-) is required between the parameter name and its
description.
Optional parameters
Wrap the parameter name in brackets to indicate it is optional.
Default values
Append =value inside the brackets to document a default.
int unlock(string which) {
which = which || "door";
}
Reference parameters
In FluffOS, parameters passed by reference can be documented with &.
void increment(int ref value) {
value++;
}
Variadic parameters
For a varargs rest parameter (declared type name... or
type *name...), the variadic marker belongs inside the type
braces, not after the parameter name. The LPC language server only
recognises ... as a variadic marker when it appears within the
{type} expression — a ... written after the name is silently
swallowed as comment text and conveys nothing to the LSP.
Always use the LPC-style trailing form {type...} — the marker mirrors
the declaration, where ... follows the type. This is the normative
form for this project:
void schedule(string func, mixed arg...) {
}
The JS-style leading form {...type} is also accepted by the parser
and appears in older code, but do not use it in new documentation;
convert it to {type...} when you encounter it.
Do not write the marker after the name — this does not register as
variadic:
@returns
Documents the return value of a function.
Syntax: @returns {type} Description
Do not use a hyphen between the type and the description.
When a function may return different types depending on conditions, use a
union type.
Type Predicates
A special form of @returns enables type narrowing in conditional
branches. Instead of documenting a return type, it declares that the
function acts as a type guard for one of its parameters.
Syntax: @returns {paramName is type}
Where paramName is the name of a parameter in the function signature
and type is the type it should be narrowed to when the function
returns a truthy value.
int stringp(mixed arg);
When this function is called inside a conditional, the language server
narrows the tested variable to the predicate type within the true
branch:
void test() {
mixed o;
if(stringp(o)) {
string s = o;
}
}
The type predicate does not change the function's actual return type.
The function still returns its declared type (e.g., int); the
predicate only informs the language server's flow analysis.
Type predicates work with any valid type, including primitives,
composites, and object file paths:
int pointerp(mixed arg);
int is_living(mixed o);
When to use type predicates: If a function validates the type of
a parameter and returns a truthy or falsy result, it should use a type
predicate. This includes simple type-checking wrappers (like
valid_function), but is not limited to them — any function whose
return value implies a type guarantee about a parameter is a
candidate. A description may follow the predicate to document the
return value for human readers:
object get_user(object ob) {
return ob->is_user() ? ob : 0;
}
Preprocessor defines are resolved in type predicates, so you can use
macros as the target type:
#define STD_USER "/std/user.lpc"
int is_user(object ob);
@throws
Documents conditions that cause a throw(). A throw() is a soft
error — it can be intercepted by catch() and does not generate a
stack trace. This is the mechanism for recoverable exceptions.
Syntax: @throws Description of the condition
Multiple @throws tags can be used when a function has several throw
conditions.
@errors
Documents conditions that trigger a hard error — error() in FluffOS
or raise_error() in LDMud. Unlike throw(), a hard error generates
a full stack trace and is expensive. LPC distinguishes between soft
errors (throw()) and hard errors, and @errors exists to document
that distinction.
Syntax: @errors Description of the condition
This will be evident by the appearance of the error() function
within the function body. Also applies to assert() and
assert_arg().
@type
Documents the type of a variable or expression.
Syntax: @type {type}
Variable annotation
int MAX_PLAYER_HP = 1000;
mapping resistances = ([ "fire": 10, "cold": 5, "physical": 3 ]);
Inline expression casting
You can annotate an expression inline to assert its type.
object p = (get_player());
Inline Parameter Type Narrowing
The @type tag can also be used inline in a function signature to
narrow an object parameter for the LSP. Place it as a comment
immediately before the parameter:
mixed main( object caller, string str) {
This lets the LSP resolve call_other methods (e.g.,
caller->set_env(...)) that exist on the typed object. Pick the
macro by what the object is known to be, per Named Objects
below — not by the methods this function happens to call. See the
lpc-coding-style skill for full guidance.
@var
Documents the type of an inherited variable. Use this when a variable
is declared in a parent object and you want to provide type information
in the inheriting file.
Syntax: @var {type} Description
@typedef
Defines a named type alias or a structured shape. This is useful for
documenting complex data structures — like the expected shape of a
mapping — without needing a class or struct definition. The
language server resolves object paths in @typedef tags and provides
IntelliSense for the defined type.
Syntax: @typedef {type} Name or @typedef Name followed by
@property tags
Simple type alias
Structured shape with properties
Use @property tags to define the members of the type. This is the
"shape definer" — it lets you describe what keys a mapping or data
structure is expected to have, along with their types.
You can then use the typedef name in other annotations:
mapping get_player_data(string player_name) {
}
Object path resolution
Object paths used within @typedef are resolved by the language
server, giving you full IntelliSense when referencing those types:
@callback
Documents a function that is passed as an argument to another function.
Use this to describe the expected signature of callback parameters.
Syntax: @callback name
@property
Documents a property of a class or struct. Used in the doc comment
immediately above the class/struct definition.
Syntax: @property {type} name - Description
class ShopItem {
string short;
string file;
int cost;
int stock;
}
@overload
Documents multiple calling signatures for a single varargs function
that accepts mixed *args.... Each @overload block describes one
valid way to call the function, with its own @param and @returns
tags.
Place the description before the first @overload. Each @overload
starts a new signature — the @param and @returns tags that follow
it belong to that overload until the next @overload or the end of
the comment.
varargs void tell(mixed *args...) {
}
Tags that apply to all overloads (such as @errors, @throws, or
@example) should be placed after the last @overload block.
@example
Provides an example code snippet demonstrating usage.
Syntax: @example followed by code on subsequent lines
@deprecated
Marks a function, variable, or other element as deprecated. Include a
description of what to use instead.
Syntax: @deprecated Description or replacement
int get_exp(string player_name) {
return find_player(player_name)->query_experience();
}
@file
Provides file-level documentation. Placed at the top of a file to
describe its purpose.
Syntax: @file path/to/file.lpc
@see
Creates a reference to another function, file, or resource.
Syntax: @see reference
@override
Indicates that a function overrides an inherited definition.
void receive_message(string msg) {
}
@inheritdoc
Indicates that a function's documentation should be inherited from
the parent definition. When the language server encounters this tag,
it pulls the description, parameters, and return documentation from
the inherited function.
This is particularly useful in LPC where inherit is common —
rather than duplicating documentation across overrides, you can
inherit it and only document what changes.
void create() {
::create();
}
You can also add to the inherited documentation. Your description
and any additional tags are merged with the parent's:
void create() {
::create();
init_combat();
}
@author
Identifies the author of the code.
Syntax: @author name
@version
Specifies the version of the code.
Syntax: @version version
@since
Indicates when a feature was introduced.
Syntax: @since version or date
@private / @protected / @public
Documents the visibility of a function or variable. These tags are
useful when the visibility cannot be inferred from the code or when
you want to be explicit.
static int is_valid_name(string name) {
}
@link
Creates an inline link to another element. Used within descriptions,
wrapped in {@link ...}.
Syntax: {@link reference}
Types Reference
Primitive Types
int - Integer
string - Text string
float - Floating-point number
object - Generic object
mapping - Key-value structure
function - Function reference
buffer - Binary data
mixed - Any type
void - No return value
class/struct - Structured data types
Named Objects
For objects of specific types, use the closest matching STD_* macro rather
than a raw file path:
{STD_MACRO}
If no macro exists, fall back to a full path:
{"/path/to/object.lpc"}
Choosing the right macro: Go shallowest first — pick the shallowest
type the object is known to be, the one you can get the most out of. Shallow
and deep are used here in the inherit-traversal sense, as in
deep_inherit_list(): parents are deeper, so STD_PLAYER is shallower than
STD_BODY, which is shallower than STD_OBJECT. Descending toward
STD_OBJECT only hides members you are entitled to call.
Reason from where the object comes from, not from which methods this function
calls today:
- It arrives as a command's
caller/tp, or from this_body() on a command
path, and the command lives under cmds/adm/ or cmds/dev/ — no NPC walks
that path, so it is a player: {STD_PLAYER}.
- It could be a player or an NPC (combat, vitals, anything a mob reaches) —
{STD_BODY}.
- It could genuinely be a bag, a room, or a body —
{STD_CONTAINER}, or
{STD_OBJECT} if even that is more than the facts support.
Descend only as far as the uncertainty actually forces, and fall back to bare
{object} only when the type is genuinely unknowable at that point.
In short: the type should reflect what the object provably is, not the
minimum interface the function currently touches. Having to move the annotation
shallower later, because a new call needs it, is churn it should have avoided.
Class/Struct Types
For class or struct instances, prefix with class or struct:
{class ShopItem}
For arrays of class instances:
{class ShopItem*}
Arrays
For arrays of a specific type:
{type*}
Example: {string*} for string array
Typed arrays work with any type — primitives, named objects, classes,
and other composites:
{("/std/player.lpc")*}
{([ string: int ])*}
{class ShopItem*} // array of class instances
Mappings
For mappings with specific key/value types:
{([ keytype: valuetype ])}
Example: {([ string: int ])} for string->int mapping
Union Types
For values that could be multiple types:
{type1 | type2}
Example: {int | string} for int or string
Function Types
For function references with signature:
{function(paramtype1, paramtype2): returntype}
Example: {function(int, int): int}
Optional Parameters
For optional parameters:
@param {type} [name] - Description
With default values:
@param {type} [name=default] - Description
Special Types
Undefined
Use undefined to distinguish between a legitimate 0 return and a
"not found" result. Combine it with the pipe union operator for
multiple possible return types:
@returns {int | undefined} The score, or undefined if not found.
Any Type (*)
For cases where any type is acceptable and you want to document this
explicitly, distinct from mixed:
@param {*} value - Any value to be stored.
Tuples
Tuples are represented as arrays with the member types within:
({ string, int, float })
Nested Composite Types
Complex data structures can use nested type annotations:
{([ string: ([ string: int ]) ])}
Practical Guidelines
- Be concise but complete in descriptions
- Document all parameters and return values
- Note any side effects or state changes
- Include examples for complex functions
- Specify types as precisely as possible
- Document error conditions and exceptions
Example Documentation for Complex Function
Classes
Classes should be documented to describe their properties:
class ClassGMCP {
}
Key points for class documentation:
- Document each property with @property tags
- Include property types in curly braces
- Add descriptions for both class and properties
- Optional additional paragraph for implementation details at the top
Documentation Order
- Visibility tags (@public, @protected, @private) should always come first
- Other tags should follow in this order:
- @override (if applicable)
- @apply (if applicable)
- @overload (if applicable — each followed by its own @param
and @returns)
- @param (when not using @overload)
- @returns (when not using @overload)
- @errors (only if error() is called)
- @throws (only if throw() is called)
- @example
- Any other tags
Line Length
- All documentation lines should wrap at 79 characters
- Maintain proper indentation when wrapping
- Use complete sentences even when wrapping
What Not to Document
Unless specifically instructed, do not document:
- Forward declarations
- Ubiquitous entry points and lifecycle functions — these are
required infrastructure repeated in every file of their kind
and are not notable enough to warrant documentation:
mixed main() in commands
void setup()
void mudlib_setup()
void base_setup()
void pre_setup_0() through void pre_setup_4()
void post_setup_0() through void post_setup_4()
- Preprocessor directives (#include, #define, etc.)
- Inherit statements
File-global variables only require documentation when the @type
annotation gives the LSP shape information it cannot already infer
from the declaration. Document globals that are objects (STD_*
macros or file paths), structured mappings, classes, or arrays of
those — anywhere the type narrowing helps reference sites. Plain
primitives (int, string, float) and untyped containers do not
need a doc block; the declaration already tells the LSP everything
it needs.
Header Documentation
File headers should use this format:
Structural rules — each of these blank lines is required:
- A blank line between
@file and the description.
- A blank line between the description and the
@created block.
- A blank line between
@last_modified and @history.
When encountering file headers that do not follow this format, update them.
Preserve the existing information (author, dates, history) but restructure
into the standard format. Clarify any vague or unclear descriptions.
Additional Notes
- Overridden lfuns may be documented with an @override tag
- Driver applies may use the tag @apply before its params
- Nested data structures should document the expected structure as precisely
as possible. For complex structures, use nested type annotations
- If you see assert() or assert_arg() these are @errors, and not @throws
- Use Canadian English spelling in all documentation (colour, behaviour,
defence, initialise, etc.)
Otherwise, if unsure, ask.
Unsupported Tags
The following tags are not supported and should be removed on sight
when encountered in existing documentation. Do not introduce them in
new documentation.
@description — Remove the tag and keep the text as the leading
description paragraph (the description is always the first thing in
the comment block; it does not need a tag).
Suppression Directives
The LPC Language Services extension supports comment directives that
suppress or control diagnostic output. Unlike LPCDoc tags (which
provide documentation and type information), these are standalone
single-line comments that control the type checker's behaviour.
@lpc-ignore
Suppresses all diagnostics on the immediately following line.
string foo = 123;
@lpc-nocheck
Disables all diagnostics for the entire file. Must be placed at the
top of the file.
@lpc-expect-error
Asserts that the next line produces a diagnostic. If the expected
error does not occur, the directive itself becomes an error.
Useful for intentional type violations.
o->foo();
Imperative Information
Do not touch the code. For documentation purposes, we only need comments.
There will be no reason to opine on things like parens placement, semicolon
changes, etc., by updating the code sections of a file. Restrict activities
to only documentation.