Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions __tests__/api-writer/glua-api-writer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ describe('GLua API Writer', () => {
url: 'https://wiki.facepunch.com/gmod/Structures/EntityStruct',
});

expect(api).toContain('---@field entity Entity="NULL"');
expect(api).toContain('---@field entity Entity=NULL');
});

it('should be able to write Lua API definitions directly from wiki json data for a fake enum', async () => {
Expand All @@ -236,7 +236,7 @@ describe('GLua API Writer', () => {
const api = writer.makeApiFromPages(writer.getPages(mockFilePath));
expect(api).toContain('---@realm server');
expect(api).toContain('---@source https://wiki.facepunch.com/gmod/Enums/NavCorner');
expect(api).toContain('--- @alias NavCorner 0 | 1 | 2 | 3 | 4 | number');
expect(api).toContain('---@alias NavCorner 0 | 1 | 2 | 3 | 4 | number');
});

it('should handle deprecated in description', async () => {
Expand Down
36 changes: 36 additions & 0 deletions __tests__/api-writer/write-type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,40 @@ describe('writeType', () => {
expect(result).toEqual('fun(count: number, arg1: string):(ret0: number, ret1: string)');
});
});

describe('sequential tables', () => {
it('should convert table<X> to X[]', async () => {
expect(GluaApiWriter.transformType('table<Player>')).toEqual('Player[]');
});

it('should strip the Structures page path from the element type', async () => {
expect(GluaApiWriter.transformType('table<Structures/LocalLight>')).toEqual('LocalLight[]');
});

it('should convert a nested element type', async () => {
expect(GluaApiWriter.transformType('table<table{Undo}>')).toEqual('Undo[]');
});

it('should leave table<x, y> untouched', async () => {
expect(GluaApiWriter.transformType('table<string, number>')).toEqual('table<string, number>');
});
});

describe('unions', () => {
it('should keep nil alongside a converted sequential table', async () => {
expect(GluaApiWriter.transformType('table<Structures/Sky3DParams>|nil')).toEqual('Sky3DParams[]|nil');
});

it('should keep nil alongside a converted struct table', async () => {
expect(GluaApiWriter.transformType('table{AngPos}|nil')).toEqual('AngPos|nil');
});

it('should convert members that are not first', async () => {
expect(GluaApiWriter.transformType('string|table{FormattedTime}')).toEqual('string|FormattedTime');
});

it('should leave plain unions untouched', async () => {
expect(GluaApiWriter.transformType('table|boolean|nil')).toEqual('table|boolean|nil');
});
});
});
16 changes: 4 additions & 12 deletions custom/HTTPRequest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@
--- * DELETE
--- * PATCH
--- * OPTIONS
---
---Default: `GET`
---@field method? string
---@field method string="GET"
---The target url.
---@field url string
---KeyValue table for [URL parameters](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams).
Expand All @@ -72,22 +70,16 @@
--- Supported by methods such as `POST`, `PUT`, `PATCH`, and `DELETE`.
---@field body? string
---Content type for body.
---
---Default: `text/plain; charset=utf-8`
---@field type? string
---@field type string="text/plain; charset=utf-8"
---The timeout for the connection.
---
---Default: `60`
---@field timeout? number
---@field timeout number=60
local HTTPRequest = {}

---`GET`, `POST`, and `HEAD` requests may include URL parameters.
--- Omitting `method` is treated as `GET`.
---@class (exact) HTTPRequestWithParameters : HTTPRequest
---Request method, case insensitive.
---
---Default: `GET`
---@field method? HTTPRequestMethodWithParameters
---@field method HTTPRequestMethodWithParameters="GET"
---KeyValue table for [URL parameters](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams).
---
--- Valid only for `GET`, `POST`, and `HEAD`.
Expand Down
8 changes: 2 additions & 6 deletions custom/ServerQueryData.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
---@source https://wiki.facepunch.com/gmod/Structures/ServerQueryData
---@class (partial) ServerQueryData
---The game directory to get the servers for.
---
--- Default: `garrysmod`
---@field GameDir string
---@field GameDir string="garrysmod"
---Type of servers to retrieve. Valid values are `internet`, `favorite`, `history` and `lan`.
---@field Type string
---Steam application ID to get the servers for.
---
--- Default: `4000`
---@field AppID number
---@field AppID number=4000
---Called when a new server is found and queried.
---@field Callback fun(ping: number, name: string, desc: string, map: string, players: number, maxplayers: number, botplayers: number, pass: boolean, lastplayed: number, address: string, gamemode: string, workshopid: number, isanon: boolean, netversion: string, luaversion: string, localization: string, gmcategory: string):(stop: boolean)
---Called if the query has failed, called with the server IP address.
Expand Down
2 changes: 1 addition & 1 deletion custom/_globals.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---@meta

--- Source: https://wiki.facepunch.com/gmod/Global_Variables
---@source https://wiki.facepunch.com/gmod/Global_Variables

--[[
Global Tables
Expand Down
24 changes: 24 additions & 0 deletions custom/class.CreationMenu.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--- The content half of the spawn menu, holding every tab registered with
--- [spawnmenu.AddCreationTab](https://wiki.facepunch.com/gmod/spawnmenu.AddCreationTab).
---@source garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu.lua
---@class CreationMenu : DPropertySheet
---@field CreationTabs table<string, CreationMenuTab> The created tabs, keyed by tab name.
local CreationMenu = {}

---@class CreationMenuTab : DPropertySheetSheet
---@field ContentPanel? Panel The panel built by the tab's populate function. Only set once the tab has been populated.

---Returns a single creation tab by name.
---@realm client
---@param id string The tab name, as passed to spawnmenu.AddCreationTab.
---@return CreationMenuTab? # The tab, or `nil` if no tab with that name exists.
function CreationMenu:GetCreationTab(id) end

---Returns every creation tab on this menu.
---@realm client
---@return table<string, CreationMenuTab> # The created tabs, keyed by tab name.
function CreationMenu:GetCreationTabs() end

---Creates a tab for every creation tab registered with spawnmenu.AddCreationTab.
---@realm client
function CreationMenu:Populate() end
4 changes: 4 additions & 0 deletions custom/class.DHorizontalDivider.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---@class DHorizontalDivider : DPanel
---@field m_DragBar DHorizontalDividerBar The drag handle between the left and right panels.
---@field _OldCookieW number The last left width restored from cookies.
local DHorizontalDivider = {}
5 changes: 5 additions & 0 deletions custom/class.DHorizontalDividerBar.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--- The drag handle of a [DHorizontalDivider](https://wiki.facepunch.com/gmod/DHorizontalDivider).
--- Created automatically by the divider and stored in its `m_DragBar` field.
---@source garrysmod/lua/vgui/dhorizontaldivider.lua
---@class DHorizontalDividerBar : DPanel
local DHorizontalDividerBar = {}
3 changes: 3 additions & 0 deletions custom/class.DVerticalDivider.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---@class DVerticalDivider : DPanel
---@field m_DragBar DVerticalDividerBar The drag handle between the top and bottom panels.
local DVerticalDivider = {}
5 changes: 5 additions & 0 deletions custom/class.DVerticalDividerBar.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--- The drag handle of a [DVerticalDivider](https://wiki.facepunch.com/gmod/DVerticalDivider).
--- Created automatically by the divider and stored in its `m_DragBar` field.
---@source garrysmod/lua/vgui/dverticaldivider.lua
---@class DVerticalDividerBar : DPanel
local DVerticalDividerBar = {}
2 changes: 1 addition & 1 deletion custom/class.DriveMethod.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

---Runtime drive mode table returned by drive.GetMethod.
---
--- Source: https://github.com/Facepunch/garrysmod/blob/master/garrysmod/lua/drive/drive_base.lua
---@source https://github.com/Facepunch/garrysmod/blob/master/garrysmod/lua/drive/drive_base.lua
---@class DriveMethod
---@field Entity Entity Driven entity.
---@field Player Player Driving player.
Expand Down
5 changes: 2 additions & 3 deletions custom/class.GM.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
--- Source:
--- - garrysmod/gamemodes/base/gamemode/shared.lua
--- - garrysmod/gamemodes/sandbox/gamemode/shared.lua
---@source garrysmod/gamemodes/base/gamemode/shared.lua
---@source garrysmod/gamemodes/sandbox/gamemode/shared.lua
---@class GM
---@field Name string Gamemode display name.
---@field Author string Gamemode author.
Expand Down
32 changes: 16 additions & 16 deletions custom/class.PlayerClass.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
---@field ClassID? number Network string ID of the active player class. Injected at runtime by player_manager.
---@field Func? fun() Internal no-op placeholder. Injected at runtime by player_manager.
---@field DisplayName? string Human-readable display name for this player class.
---@field SlowWalkSpeed? number Movement speed when slow-walking (+WALK). Default: 200.
---@field WalkSpeed? number Movement speed when walking (not running). Default: 400.
---@field RunSpeed? number Movement speed when running. Default: 600.
---@field CrouchedWalkSpeed? number Multiplier applied to move speed while crouching. Default: 0.3.
---@field DuckSpeed? number Speed of transition from standing to crouching. Default: 0.3.
---@field UnDuckSpeed? number Speed of transition from crouching to standing. Default: 0.3.
---@field JumpPower? number Vertical impulse strength on jump. Default: 200.
---@field CanUseFlashlight? boolean Whether the player can use the flashlight. Default: true.
---@field MaxHealth? number Maximum health the player can have. Default: 100.
---@field MaxArmor? number Maximum armor the player can have. Default: 100.
---@field StartHealth? number Health given to the player on spawn. Default: 100.
---@field StartArmor? number Armor given to the player on spawn. Default: 0.
---@field DropWeaponOnDie? boolean Whether to drop the active weapon on death. Default: false.
---@field TeammateNoCollide? boolean Whether teammates pass through each other. Default: true.
---@field AvoidPlayers? boolean Whether the player auto-swerves around others. Default: true.
---@field UseVMHands? boolean Whether to use viewmodel hands. Default: true.
---@field SlowWalkSpeed number=200 Movement speed when slow-walking (+WALK).
---@field WalkSpeed number=400 Movement speed when walking (not running).
---@field RunSpeed number=600 Movement speed when running.
---@field CrouchedWalkSpeed number=0.3 Multiplier applied to move speed while crouching.
---@field DuckSpeed number=0.3 Speed of transition from standing to crouching.
---@field UnDuckSpeed number=0.3 Speed of transition from crouching to standing.
---@field JumpPower number=200 Vertical impulse strength on jump.
---@field CanUseFlashlight boolean=true Whether the player can use the flashlight.
---@field MaxHealth number=100 Maximum health the player can have.
---@field MaxArmor number=100 Maximum armor the player can have.
---@field StartHealth number=100 Health given to the player on spawn.
---@field StartArmor number=0 Armor given to the player on spawn.
---@field DropWeaponOnDie boolean=false Whether to drop the active weapon on death.
---@field TeammateNoCollide boolean=true Whether teammates pass through each other.
---@field AvoidPlayers boolean=true Whether the player auto-swerves around others.
---@field UseVMHands boolean=true Whether to use viewmodel hands.
PlayerClass = {}
2 changes: 1 addition & 1 deletion custom/class.SKIN.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---@meta

--- Source: https://github.com/Facepunch/garrysmod/blob/b2bff902adf7f5b87ec543f873e74e3267e93f26/garrysmod/lua/skins/default.lua
---@source https://github.com/Facepunch/garrysmod/blob/b2bff902adf7f5b87ec543f873e74e3267e93f26/garrysmod/lua/skins/default.lua

---@class SKINColoursState
---@field Normal Color
Expand Down
2 changes: 1 addition & 1 deletion custom/class.TauntCamera.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--- A taunt camera object returned by [TauntCamera](https://wiki.facepunch.com/gmod/Global.TauntCamera).
--- Used by player classes to drive a third-person taunt view.
--- Source: garrysmod/gamemodes/base/gamemode/player_class/taunt_camera.lua
---@source garrysmod/gamemodes/base/gamemode/player_class/taunt_camera.lua
---@class TauntCamera
local TauntCamera = {}

Expand Down
32 changes: 22 additions & 10 deletions src/api-writer/glua-api-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ export class GluaApiWriter {

const literalUnion = enumValues.join(' | ');
const enumAliasValue = literalUnion.length > 0 ? `${literalUnion} | number` : 'number';
api += `--- @alias ${_enum.name} ${enumAliasValue}\n`;
api += `---@alias ${_enum.name} ${enumAliasValue}\n`;
} else {
// Garry's Mod enums are flat globals, so the field list names each constant.
// Completion then offers `EF_BONEMERGE` rather than the raw value it holds.
Expand Down Expand Up @@ -502,26 +502,27 @@ export class GluaApiWriter {
api += `---${wrapInComment(comment)}\n`;

const type = GluaApiWriter.transformType(field.type, field.callback);
const { optional, inlineDefault } = this.getStructFieldDefaultAnnotation(field.default);
const { optional, inlineDefault } = this.getStructFieldDefaultAnnotation(field);
api += `---@field ${GluaApiWriter.safeName(field.name)}${optional} ${type}${inlineDefault}\n`;

return api;
}

private getStructFieldDefaultAnnotation(defaultValue: string | undefined): { optional: string; inlineDefault: string } {
private getStructFieldDefaultAnnotation(field: Struct['fields'][number]): { optional: string; inlineDefault: string } {
const defaultValue = field.default;
if (defaultValue === undefined)
return { optional: '', inlineDefault: '' };

const trimmedDefault = String(defaultValue).trim();
if (trimmedDefault.toLowerCase() === 'nil')
return { optional: '?', inlineDefault: '' };

const normalizedDefault = this.normalizeInlineDefaultValue(trimmedDefault);
const normalizedDefault = this.normalizeInlineDefaultValue(trimmedDefault, field.type);
return { optional: '', inlineDefault: `=${normalizedDefault}` };
}

private normalizeInlineDefaultValue(defaultValue: string): string {
if (/^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(defaultValue))
private normalizeInlineDefaultValue(defaultValue: string, fieldType?: string): string {
if (/^[+-]?(?:0[xX][0-9a-fA-F]+|\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(defaultValue))
return defaultValue;

if (/^(true|false)$/i.test(defaultValue))
Expand All @@ -531,13 +532,18 @@ export class GluaApiWriter {
const boldMatch = unwrappedCode.match(/^\*\*(.*)\*\*$/);
const normalizedText = (boldMatch ? boldMatch[1] : unwrappedCode).trim();

if (normalizedText.toLowerCase() === 'empty')
if (normalizedText.toLowerCase() === 'empty' || normalizedText.toLowerCase() === '<empty string>')
return '""';

if (/^"(?:[^"\\]|\\.)*"$/.test(normalizedText) || /^'(?:[^'\\]|\\.)*'$/.test(normalizedText))
return normalizedText;

return JSON.stringify(normalizedText);
const isStringType = fieldType !== undefined && fieldType.trim().toLowerCase() === 'string';
if (isStringType) {
return JSON.stringify(normalizedText);
}

return normalizedText;
}

private writeStruct(struct: Struct) {
Expand Down Expand Up @@ -830,10 +836,14 @@ export class GluaApiWriter {
}
}

public static transformType(type: string, callback?: FunctionCallback) {
public static transformType(type: string, callback?: FunctionCallback): string {
if (type === 'vararg')
return 'any';

// Transform each member of a union separately, so `table<X>|nil` keeps its `nil`
if (type.includes('|'))
return type.split('|').map(member => GluaApiWriter.transformType(member, callback)).join('|');

// Convert `function` type to `fun(cmd: string, args: string):(returnValueName: string[]?)`
if (type === 'function' && callback) {
let callbackString = `fun(`;
Expand Down Expand Up @@ -893,7 +903,9 @@ export class GluaApiWriter {

if (!innerType) throw new Error(`Invalid table type: ${type}`);

return `${innerType}[]`;
// The wiki writes struct element types as page paths (`table<Structures/LocalLight>`)
// and can nest its own syntax inside (`table<table{Undo}>`)
return `${GluaApiWriter.transformType(innerType.replace(/^Structures\//, ''))}[]`;
} else if (type.startsWith('table{') || type.startsWith('Panel{')) {
// Convert `table{ToScreenData}` structures to `ToScreenData` class for LuaLS
// Also converts `Panel{DVScrollBar}` to `DVScrollBar` class for LuaLS
Expand Down
Loading