Skip to content
Dmitry Lavrentev edited this page Feb 12, 2024 · 10 revisions

GMEdit supports standard and less-standard JSDoc tags for benefit of syntax highlighting, auto-completion, and argument information.

On this page, [text] inside "syntax" examples means that something is optional.

Table of contents:

  • @param: function arguments
  • @returns: return types
  • @interface: declare script as interface
  • @implements: implement an interface
  • @self: indicate type of self in scripts
  • @hint: advanced uses (mostly for GMS≥2.3)
  • @is: indicate types of instance/struct/global variables
  • @template: set up template types and type constraints
  • @typedef: set up project-wide type aliases
  • @init: mark events as containing variable/function definitions
  • @index_redirect: stop/override indexing process for the current file

@param

This tag is pretty consistent with how the base IDE works.

Zero or more of these can be present per function to annotate each argument.
This information is then used in auto-completion, error checking, and argument hints (seen in status bar).

Syntax:

/// @param [{argType}] argName [description]

Types can be provided in curly brackets {}, but are as of yet unused.

Optional parameters can be marked via [argName], ?argName, or argName=value.

Rest-argument (allowing 0 or more additional arguments to be added at the end) can be marked as ...argName.

Any additional text after the argument name is considered to be the description and is currently unused.

Examples:

/// @param an_arg
/// @param {int} a_number
/// @param ?optional_arg
/// @param depth=0
/// @param ...rest

Aliases: @arg, @argument

@returns

Marks the return type for a script/function.

Syntax:

/// @returns {returnType} [description]

This doesn't affect anything, but the type will be shown inside auto-completion and argument help.

Aliases: @return

@interface

Marks a script as an interface, for use with @implements.

Syntax:

/// @interface [{InterfaceName}]

Top-level (read: not nested inside {} - e.g. name = value) variable assignments within an interface-script will be considered fields and will show up in syntax highlighting/auto-completion for structures and objects that implement it.

Example (GMS≥2.3, manually named):

/// @interface {IMovable}
function scr_move_init() {
	xspeed = 0;
	yspeed = 0;
}

Example (GMS≤2.2, automatically named):

/// scr_move_init()
/// @interface

which would later let you /// @implements {IMovable} and /// @implements {scr_move_init} accordingly.

@implements

Indicates that a constructor or an object implements an interface.

Syntax:

/// @implements {InterfaceName}
scr_name(); /// @implements

As result, syntax highlighting and auto-completion information will include fields from the said interface.

A constructor/object can have any number of interfaces; interfaces themselves may implement other interfaces.

Example (2.3 constructor and a named interface):

/// @implements {IMovable}
function Some() constructor {
	// after saving, writing `self.` here would now bring up `xspeed` and `yspeed`.
}

Example (object's Create event and an automatically named interface):

scr_move_init(); /// @implements
// after saving, writing `self.` here would now bring up `xspeed` and `yspeed`.

@self

Indicates what type self would be for a script

Syntax:

/// @self {InterfaceOrObjectName}

While GMEdit will automatically determine what type self inside constructors and objects, doing so for arbitrary scripts is a task that varies from hard to impossible, so you may mark the type yourself.

Example (GMS≥2.3, named interface):

/// @self {IMovable}
function scr_move(input_x, input_y) {
    // after saving, writing `self.` here would now bring up variables from IMovable
}

Example (GMS≤2.2, objects):

/// scr_move(input_x, input_y)
/// @param input_x
/// @param input_y
/// @self {obj_some}
// after saving, writing `self.` here would now bring up variables from obj_some

Aliases: @this

@hint

Hint at variables, functions, constructors, interfaces, or anything else that is not or cannot be automatically inferred from your source code.

Syntax:

/// @hint [new] [Type][.:][field][(...arguments)[->returnType]] [description]
/// @hint TypeName extends ParentTypeName
/// @hint TypeName implements InterfaceName

See the dedicated wiki page for more information.

@is

Specify types of struct, instance, and global variables.

Syntax:

// in Create or inside a constructor
varName = value; /// @is {type}

// anywhere
global.varName = value; /// @is {type}
globalvar varName; /// @is {type}
globalvar varName; varName = value; /// @is {type}

Example:

function Vec2(_x, _y) constructor {
    x = _x; /// @is {number}
    y = _y; /// @is {number}
}
global.v2 = new Vec2(0, 0); /// @is {Vec2}
globalvar g_vec2; g_vec2 = new Vec2(0, 0); /// @is {Vec2}
#macro m_vec2 global.v2

afterwards, typing global.v2., g_vec2., or m_vec2., or typing var v:Vec2, saving, and then typing v. would show you auto-completion for x/y and check types for them when reading/writing.

@template

Specify template types and type constraints for scripts, functions, and constructors.

/// @template T1[,T2,...]
/// @template {ConstraintType} T1[,T2,...]

Example: If you were to do the following in GMS≤2.2.5 (in a script called select)

/// @template T
/// @param {int} index
/// @param {T} ...values
/// @returns {T}
return argument[argument[0] + 1];

or the following in GMS≥2.3

/// @template T
/// @param {int} index
/// @param {T} ...values
/// @returns {T}
function select() {
    return argument[argument[0] + 1];
}

doing select(i, "a", "b") would give you a string-typed value and doing select(i, "a", 0) would give you a warning due to mixing types.

Or, for constraints with local type syntactic sugar

/// @template {obj_entity} E
function find_first_alive_entity(obj:E)->E {
    with (obj) if (hp > 0) return self;
    return noone;
}

and then typing find_first_alive_entity(obj_player). would automatically offer you variables from obj_player; trying to pass in an object that is not a descendant of obj_entity will show a warning.

@typedef

This allows you to define a shorter and/or more descriptive name for a type.

/// @typedef {type} alias

Example: suppose you have a 3d vector type, tuple<x:number, y:number, z:number>.
That's a bit of text to type every time you use it. So you could make an alias for it via

/// @typedef {tuple<x:number, y:number, z:number>} vec3

and subsequently do var v:vec3 or myVec = [1, 2, 3]; /// @is {vec3}

@init

By default, GMEdit will pick up variables from Create event for syntax highlighting and auto-completion. With this, however, you can mark another event (such as a User Event) as containing variables.

// Create:
event_user(0);
an_int = 1;

User 0:

/// @init
a_string = "hi!";

This way both an_int and a_string would show in auto-completion and highlight accordingly.

@index_redirect

Tells the indexer to stop processing the current file (and, optionally, to start processing a different file as if it's a continuation of the current one).

/// @index_redirect
/// @index_redirect path

This is handy in a few cases:

  1. You have code (perhaps auto-generated, or not written by you, or both) that you don't want to see in your auto-completion and generally pretend that it doesn't exist.
  2. You have code that you want to be processed not in the way it looks - most commonly involving macros.

If the path starts with a /, it'll be assumed to be relative to the project folder (so that you can do paths like /notes/mynote/mynote.txt). Otherwise it's assumed to be relative to the current file's location. Omitting a path just stops indexing the current file with no further action.

For example, suppose you decided to make yourself a JS-like setTimeout script that's called like setTimeout(func, delay, arg1, arg2, ...). But also you want to get warnings when you mismatch argument types, which isn't something that most editors/programming languages (except C++ and TypeScript) can help you with as template type parameters have to be extracted out of the function type except for the last one (being return type). And if your arguments come before the function, it gets much worse than this. Anyway,

You could create separate variations of the function for different argument counts that in reality are just aliases for the original, like so

/// setTimeout_macros
/// @index_redirect setTimeout_macros.txt
#macro setTimeout_1 setTimeout
#macro setTimeout_2 setTimeout

and then in setTimeout_macros.txt (created in script's folder)

/// @template A
function setTimeout_1(fn:function<A,void>, delay:number, arg1:A) {}
/// @template A,B
function setTimeout_2(fn:function<A,B,void>, delay:number, arg1:A, arg2:B) {}

consequently, both setTimeouts would warn about passing arguments with type not matching the function's arguments (provided that those were set) as they would warn about passing the wrong number of arguments. And all of this would come at zero runtime cost because those function definitions in a .txt file aren't even real code.