Skip to content

Commit a089067

Browse files
committed
Strongly type enums + Fix bug in enums when key is empty in docs (SCREENFADE 0)
1 parent ea210e0 commit a089067

File tree

3 files changed

+143
-5
lines changed

3 files changed

+143
-5
lines changed

__tests__/api-writer/glua-api-writer.spec.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { apiDefinition as structApiDefinition, markup as structMarkup, json as s
66
import { markup as panelMarkup, apiDefinition as panelApiDefinition } from '../test-data/offline-sites/gmod-wiki/panel-slider';
77
import { markup as multiReturnFuncMarkup, apiDefinition as multiReturnFuncApiDefinition } from '../test-data/offline-sites/gmod-wiki/library-function-concommand-gettable';
88
import { markup as varargsFuncMarkup, apiDefinition as varargsFuncApiDefinition } from '../test-data/offline-sites/gmod-wiki/library-function-coroutine-resume';
9-
import { LibraryFunction, WikiPage, WikiPageMarkupScraper } from '../../src/scrapers/wiki-page-markup-scraper';
9+
import { Enum, LibraryFunction, WikiPage, WikiPageMarkupScraper } from '../../src/scrapers/wiki-page-markup-scraper';
1010
import { GluaApiWriter } from '../../src/api-writer/glua-api-writer';
1111
import fetchMock from "jest-fetch-mock";
1212

@@ -149,6 +149,79 @@ describe('GLua API Writer', () => {
149149
expect(api).toMatch(new RegExp(`^${overrideStart}`));
150150
});
151151

152+
it('should create aliasses for global enumerations', () => {
153+
const writer = new GluaApiWriter();
154+
const api = writer.writePage(<Enum>{
155+
type: 'enum',
156+
name: 'MATERIAL_FOG',
157+
address: 'Enums/MATERIAL_FOG',
158+
description: 'The fog mode.',
159+
realm: 'Client',
160+
items: [
161+
{
162+
key: 'MATERIAL_FOG_NONE',
163+
value: '0',
164+
description: 'No fog',
165+
},
166+
{
167+
key: 'MATERIAL_FOG_LINEAR',
168+
value: '1',
169+
description: 'Linear fog',
170+
},
171+
{
172+
key: 'MATERIAL_FOG_LINEAR_BELOW_FOG_Z',
173+
value: '2',
174+
}
175+
],
176+
});
177+
178+
expect(api).toEqual(`---@alias MATERIAL_FOG 0|1|2\n--- No fog\nMATERIAL_FOG_NONE = 0\n--- Linear fog\nMATERIAL_FOG_LINEAR = 1\nMATERIAL_FOG_LINEAR_BELOW_FOG_Z = 2\n\n\n`);
179+
});
180+
181+
it('should create enums for table enumerations', () => {
182+
const writer = new GluaApiWriter();
183+
const api = writer.writePage(<Enum>{
184+
type: 'enum',
185+
name: 'SCREENFADE',
186+
address: 'Enums/SCREENFADE',
187+
description: 'The screen fade mode.',
188+
realm: 'Client',
189+
items: [
190+
{
191+
key: '',
192+
value: '0',
193+
description: 'Instant fade in',
194+
},
195+
{
196+
key: 'SCREENFADE.IN',
197+
value: '1',
198+
description: 'Instant fade in',
199+
},
200+
{
201+
key: 'SCREENFADE.OUT',
202+
value: '2',
203+
description: 'Slowly fade in',
204+
},
205+
{
206+
key: 'SCREENFADE.MODULATE',
207+
value: '4',
208+
},
209+
{
210+
key: 'SCREENFADE.STAYOUT',
211+
value: '8',
212+
description: 'Never disappear',
213+
},
214+
{
215+
key: 'SCREENFADE.PURGE',
216+
value: '16',
217+
description: 'Used to purge all currently active screen fade effects...\nMultiple\nLines',
218+
},
219+
],
220+
});
221+
222+
expect(api).toEqual(`---@enum SCREENFADE\n--- The screen fade mode.\nSCREENFADE = {\n --- Instant fade in\n IN = 1,\n --- Slowly fade in\n OUT = 2,\n MODULATE = 4,\n --- Never disappear\n STAYOUT = 8,\n --- Used to purge all currently active screen fade effects...\n --- Multiple\n --- Lines\n PURGE = 16,\n}\n\n`);
223+
});
224+
152225
it('should convert table<type> to type[]', () => {
153226
const writer = new GluaApiWriter();
154227
const api = writer.writePage(<LibraryFunction>{
@@ -319,6 +392,32 @@ describe('GLua API Writer', () => {
319392
expect(api).toEqual(`---[CLIENT] Returns where on the screen the specified position vector would appear.\n---\n---[(View on wiki)](na)\n---@return ToScreenData # The created Structures/ToScreenData.\nfunction Vector.ToScreen() end\n\n`);
320393
});
321394

395+
// number{ENUM_NAME} -> ENUM_NAME
396+
it('should support enum type', () => {
397+
const writer = new GluaApiWriter();
398+
const api = writer.writePage(<LibraryFunction>{
399+
name: 'FogMode',
400+
address: 'render.FogMode',
401+
parent: 'render',
402+
dontDefineParent: true,
403+
description: 'Sets the fog mode.',
404+
realm: 'Client',
405+
type: 'libraryfunc',
406+
url: 'na',
407+
arguments: [
408+
{
409+
args: [{
410+
name: 'mode',
411+
type: 'number{MATERIAL_FOG}',
412+
description: 'The fog mode.',
413+
}]
414+
}
415+
],
416+
});
417+
418+
expect(api).toEqual(`---[CLIENT] Sets the fog mode.\n---\n---[(View on wiki)](na)\n---@param mode MATERIAL_FOG The fog mode.\nfunction render.FogMode(mode) end\n\n`);
419+
});
420+
322421
// it('should be able to write Annotated API files directly from wiki pages', async () => {
323422
// const baseUrl = 'https://wiki.facepunch.com/gmod/GM:AcceptInput';
324423
// fetchMock.mockResponseOnce(html, { url: baseUrl });

src/api-writer/glua-api-writer.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ClassFunction, Enum, Function, HookFunction, LibraryFunction, TypePage, Panel, PanelFunction, Realm, Struct, WikiPage, isPanel, FunctionArgument, FunctionCallback } from '../scrapers/wiki-page-markup-scraper.js';
2-
import { escapeSingleQuotes, putCommentBeforeEachLine, removeNewlines, safeFileName, toLowerCamelCase } from '../utils/string.js';
2+
import { escapeSingleQuotes, indentText, putCommentBeforeEachLine, removeNewlines, safeFileName, toLowerCamelCase } from '../utils/string.js';
33
import {
44
isClassFunction,
55
isHookFunction,
@@ -225,22 +225,44 @@ export class GluaApiWriter {
225225

226226
private writeEnum(_enum: Enum) {
227227
let api: string = '';
228-
const isContainedInTable = _enum.items[0]?.key.includes('.') ?? false;
228+
229+
// If the first key is empty (like SCREENFADE has), check the second key
230+
const isContainedInTable =
231+
_enum.items[0]?.key === ''
232+
? _enum.items[1]?.key.includes('.')
233+
: _enum.items[0]?.key.includes('.');
229234

230235
if (_enum.deprecated)
231236
api += `---@deprecated ${removeNewlines(_enum.deprecated)}\n`;
232237

233-
api += `---@enum ${_enum.name}\n`;
238+
if (isContainedInTable) {
239+
api += `---@enum ${_enum.name}\n`;
240+
} else {
241+
// Until LuaLS supports global enumerations (https://github.com/LuaLS/lua-language-server/issues/2721) we
242+
// will use @alias as a workaround
243+
const validEnumerations = _enum.items.map(item => item.value).join('|');
244+
api += `---@alias ${_enum.name} ${validEnumerations}\n`;
245+
}
234246

235247
if (isContainedInTable) {
236248
api += _enum.description ? `${putCommentBeforeEachLine(_enum.description.trim(), false)}\n` : '';
237249
api += `${_enum.name} = {\n`;
238250
}
239251

240252
const writeItem = (key: string, item: typeof _enum.items[0]) => {
253+
if (key === '') {
254+
// Happens for SCREENFADE which has a blank key to describe what 0 does.
255+
return;
256+
}
257+
241258
if (isContainedInTable) {
242259
key = key.split('.')[1];
243-
api += ` ${key} = ${item.value}, ` + (item.description ? `--[[ ${item.description} ]]` : '') + '\n';
260+
261+
if (item.description?.trim()) {
262+
api += `${indentText(putCommentBeforeEachLine(item.description.trim(), false), 2)}\n`;
263+
}
264+
265+
api += ` ${key} = ${item.value},\n`;
244266
} else {
245267
api += item.description ? `${putCommentBeforeEachLine(item.description.trim(), false)}\n` : '';
246268
if (item.deprecated)
@@ -364,6 +386,13 @@ export class GluaApiWriter {
364386

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

389+
return innerType;
390+
} else if (type.startsWith('number{')) {
391+
// Convert number{MATERIAL_FOG} to MATERIAL_FOG enum for LuaLS
392+
let innerType = type.match(/{([^}]+)}/)?.[1];
393+
394+
if (!innerType) throw new Error(`Invalid number type: ${type}`);
395+
367396
return innerType;
368397
}
369398

src/utils/string.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,13 @@ export function escapeSingleQuotes(str: string) {
5858
return str.replace(/'/g, '\\\'');
5959
}
6060

61+
/**
62+
* Indents all lines in a string by a given amount
63+
*
64+
* @param text The text to indent
65+
* @param indent The amount of spaces to indent by
66+
* @returns The indented text
67+
*/
68+
export function indentText(text: string, indent: number) {
69+
return text.split('\n').map(line => ' '.repeat(indent) + line).join('\n');
70+
}

0 commit comments

Comments
 (0)