Skip to content

Commit f5fddb7

Browse files
committed
Fix #72 wrap return values of function arguments and show their name + fix incorrect return value if function returns a function with a return value
1 parent ba7b5ca commit f5fddb7

File tree

5 files changed

+187
-19
lines changed

5 files changed

+187
-19
lines changed

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,76 @@ describe('GLua API Writer', () => {
423423
expect(api).toEqual(`---![(Client)](https://github.com/user-attachments/assets/a5f6ba64-374d-42f0-b2f4-50e5c964e808) Sets the fog mode.\n---\n---[View wiki](na)\n---@param mode MATERIAL_FOG The fog mode.\nfunction render.FogMode(mode) end\n\n`);
424424
});
425425

426+
it('should parse functions with callbacks correctly', () => {
427+
const writer = new GluaApiWriter();
428+
const api = writer.writePage(<LibraryFunction>{
429+
name: 'Generate',
430+
address: 'sound.Generate',
431+
parent: 'sound',
432+
dontDefineParent: true,
433+
description: 'Creates a sound from a function.',
434+
realm: 'client',
435+
type: 'libraryfunc',
436+
url: 'na',
437+
arguments: [
438+
{
439+
args: [
440+
{
441+
name: 'indentifier',
442+
type: 'string',
443+
description: 'An unique identified for the sound.'
444+
},
445+
{
446+
name: 'samplerate',
447+
type: 'number',
448+
description: 'The sample rate of the sound. Must be `11025`, `22050` or `44100`.'
449+
},
450+
{
451+
name: 'length',
452+
type: 'number',
453+
description: 'The length in seconds of the sound to generate.'
454+
},
455+
{
456+
name: 'callbackOrData',
457+
type: 'function',
458+
altType: 'table',
459+
description: "A function which will be called to generate every sample on the sound.",
460+
callback: {
461+
arguments: [
462+
{
463+
name: 'sampleIndex',
464+
type: 'number',
465+
description: 'The current sample number.'
466+
}
467+
],
468+
returns: [
469+
{
470+
name: 'sampleValue',
471+
type: 'number',
472+
description: 'The return value must be between `-1.0` and `1.0`.'
473+
},
474+
{
475+
name: 'fake',
476+
type: 'string',
477+
},
478+
],
479+
},
480+
},
481+
{
482+
name: 'loopStart',
483+
type: 'number',
484+
description: 'Sample ID of the loop start. If given, the sound will be looping and will restart playing at given position after reaching its end.',
485+
default: 'nil'
486+
},
487+
],
488+
},
489+
],
490+
returns: [],
491+
});
492+
493+
expect(api).toEqual(`---![(Client)](https://github.com/user-attachments/assets/a5f6ba64-374d-42f0-b2f4-50e5c964e808) Creates a sound from a function.\n---\n---[View wiki](na)\n---@param indentifier string An unique identified for the sound.\n---@param samplerate number The sample rate of the sound. Must be \`11025\`, \`22050\` or \`44100\`.\n---@param length number The length in seconds of the sound to generate.\n---@param callbackOrData fun(sampleIndex: number):(sampleValue: number, fake: string)|table A function which will be called to generate every sample on the sound.\n---@param loopStart? number Sample ID of the loop start. If given, the sound will be looping and will restart playing at given position after reaching its end.\nfunction sound.Generate(indentifier, samplerate, length, callbackOrData, loopStart) end\n\n`);
494+
});
495+
426496
// it('should be able to write Annotated API files directly from wiki pages', async () => {
427497
// const baseUrl = 'https://wiki.facepunch.com/gmod/GM:AcceptInput';
428498
// fetchMock.mockResponseOnce(html, { url: baseUrl });

__tests__/scrapers/wiki-page-markup-scraper.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ClassFunction, Enum, HookFunction, LibraryFunction, Struct, WikiPageMar
44
import { markup as hookMarkup, json as hookJson } from '../test-data/offline-sites/gmod-wiki/hook-player-initial-spawn';
55
import { markup as structMarkup, json as structJson } from '../test-data/offline-sites/gmod-wiki/struct-ang-pos';
66
import { markup as enumMarkup, json as enumJson } from '../test-data/offline-sites/gmod-wiki/enums-use';
7+
import { markup as callbackInDescriptionMarkup, json as callbackInDescriptionJson } from '../test-data/offline-sites/gmod-wiki/library-function-spawnmenu-getcontenttype';
78
import fetchMock from "jest-fetch-mock";
89

910
describe('GMod Wiki Page Markup Parse', () => {
@@ -65,4 +66,15 @@ describe('GMod Wiki Page Markup Parse', () => {
6566

6667
expect(scrapeCallback(responseMock, structMarkup)).toEqual([<Struct>structJson]);
6768
});
69+
70+
it('should handle callbacks in description', async () => {
71+
fetchMock.mockResponseOnce(callbackInDescriptionMarkup);
72+
73+
const responseMock = <Response>{
74+
url: callbackInDescriptionJson.url,
75+
};
76+
const scrapeCallback = new WikiPageMarkupScraper(responseMock.url).getScrapeCallback();
77+
78+
expect(scrapeCallback(responseMock, callbackInDescriptionMarkup)).toEqual([<LibraryFunction>callbackInDescriptionJson]);
79+
});
6880
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
export const markup = `<function name="GetContentType" parent="spawnmenu" type="libraryfunc">
2+
<description>Returns the function to create an vgui element for a specified content type, previously defined by <page>spawnmenu.AddContentType</page>.</description>
3+
<realm>Client</realm>
4+
<file line="289-L299">lua/includes/modules/spawnmenu.lua</file>
5+
<args>
6+
<arg name="contentType" type="string">The content type name.</arg>
7+
</args>
8+
<rets>
9+
<ret name="" type="function">The panel creation function.
10+
11+
<callback>
12+
<arg type="Panel" name="container">The container panel to parent the created icon to.</arg>
13+
<arg type="table" name="data">Data for the content type passed from <page>spawnmenu.CreateContentIcon</page>.</arg>
14+
15+
<ret type="Panel" name="pnl">The created panel</ret>
16+
</callback>
17+
</ret>
18+
</rets>
19+
</function>`;
20+
21+
export const json = {
22+
url: 'https://wiki.facepunch.com/gmod/spawnmenu.GetContentType',
23+
type: 'libraryfunc',
24+
parent: 'spawnmenu',
25+
name: 'GetContentType',
26+
address: 'spawnmenu.GetContentType',
27+
deprecated: undefined,
28+
description: 'Returns the function to create an vgui element for a specified content type, previously defined by [spawnmenu.AddContentType](https://wiki.facepunch.com/gmod/spawnmenu.AddContentType).',
29+
realm: 'client',
30+
arguments: [
31+
{
32+
args: [{
33+
altType: undefined,
34+
callback: undefined,
35+
name: 'contentType',
36+
type: 'string',
37+
description: 'The content type name.',
38+
} ] },
39+
],
40+
returns: [
41+
{
42+
callback: {
43+
arguments: [
44+
{
45+
default: undefined,
46+
description: 'The container panel to parent the created icon to.',
47+
name: 'container',
48+
type: 'Panel',
49+
},
50+
{
51+
default: undefined,
52+
description: 'Data for the content type passed from spawnmenu.CreateContentIcon.',
53+
name: 'data',
54+
type: 'table',
55+
},
56+
],
57+
returns: [
58+
{
59+
default: undefined,
60+
description: 'The created panel',
61+
name: 'pnl',
62+
type: 'Panel',
63+
},
64+
],
65+
},
66+
description: 'The panel creation function.\n\n\n\nFunction argument(s):\n* Panel `container` - The container panel to parent the created icon to.\n* table `data` - Data for the content type passed from spawnmenu.CreateContentIcon.\n\nFunction return value(s):\n* Panel `pnl` - The created panel\n\n',
67+
name: '',
68+
type: 'function',
69+
},
70+
],
71+
};

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

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ export class GluaApiWriter {
342342
}
343343

344344
public makeApiFromPages(pages: IndexedWikiPage[]) {
345-
let api = "";
345+
let api = '';
346346

347347
pages.sort((a, b) => a.index - b.index);
348348

@@ -371,7 +371,7 @@ export class GluaApiWriter {
371371
let api = this.makeApiFromPages(pages);
372372

373373
if (api.length > 0) {
374-
fs.appendFileSync(filePath, "---@meta\n\n" + api);
374+
fs.appendFileSync(filePath, '---@meta\n\n' + api);
375375
}
376376
});
377377
}
@@ -380,44 +380,59 @@ export class GluaApiWriter {
380380
if (type === 'vararg')
381381
return 'any';
382382

383-
// fun(cmd: string, args: string): string[]?
384-
if (type === "function" && callback) {
385-
let cbStr = `fun(`;
383+
// Convert `function` type to `fun(cmd: string, args: string):(returnValueName: string[]?)`
384+
if (type === 'function' && callback) {
385+
let callbackString = `fun(`;
386386

387387
for (const arg of callback.arguments || []) {
388388
if (!arg.name) arg.name = arg.type;
389389
if (arg.type === 'vararg') arg.name = '...';
390390

391-
cbStr += `${GluaApiWriter.safeName(arg.name)}: ${this.transformType(arg.type)}${arg.default !== undefined ? `?` : ''}, `;
391+
callbackString += `${GluaApiWriter.safeName(arg.name)}: ${this.transformType(arg.type)}${arg.default !== undefined ? `?` : ''}, `;
392+
}
393+
394+
// Remove trailing comma and space
395+
if (callbackString.endsWith(', '))
396+
callbackString = callbackString.substring(0, callbackString.length - 2);
397+
398+
callbackString += ')';
399+
400+
if (callback.returns?.length) {
401+
callbackString += ':(';
392402
}
393-
if (cbStr.endsWith(", ")) cbStr = cbStr.substring(0, cbStr.length - 2);
394-
cbStr += ")";
395403

396404
for (const ret of callback.returns || []) {
397405
if (!ret.name) ret.name = ret.type;
398406
if (ret.type === 'vararg') ret.name = '...';
399407

400-
cbStr += `: ${this.transformType(ret.type)}${ret.default !== undefined ? `?` : ''}, `;
408+
callbackString += `${ret.name}: ${this.transformType(ret.type)}${ret.default !== undefined ? `?` : ''}, `;
409+
}
410+
411+
// Remove trailing comma and space
412+
if (callbackString.endsWith(', '))
413+
callbackString = callbackString.substring(0, callbackString.length - 2);
414+
415+
if (callback.returns?.length) {
416+
callbackString += ')';
401417
}
402-
if (cbStr.endsWith(", ")) cbStr = cbStr.substring(0, cbStr.length - 2);
403418

404-
return cbStr;
419+
return callbackString;
405420
} else if (type.startsWith('table<') && !type.includes(',')) {
406-
// Convert table<Player> to Player[] for LuaLS (but leave table<x, y> untouched)
421+
// Convert `table<Player>` to `Player[]` for LuaLS (but leave table<x, y> untouched)
407422
let innerType = type.match(/<([^>]+)>/)?.[1];
408423

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

411426
return `${innerType}[]`;
412427
} else if (type.startsWith('table{')) {
413-
// Convert table{ToScreenData} structures to ToScreenData class for LuaLS
428+
// Convert `table{ToScreenData}` structures to `ToScreenData` class for LuaLS
414429
let innerType = type.match(/{([^}]+)}/)?.[1];
415430

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

418433
return innerType;
419434
} else if (type.startsWith('number{')) {
420-
// Convert number{MATERIAL_FOG} to MATERIAL_FOG enum for LuaLS
435+
// Convert `number{MATERIAL_FOG}` to `MATERIAL_FOG` enum for LuaLS
421436
let innerType = type.match(/{([^}]+)}/)?.[1];
422437

423438
if (!innerType) throw new Error(`Invalid number type: ${type}`);
@@ -464,22 +479,22 @@ export class GluaApiWriter {
464479
// TODO: I'm assuming for now that there is no such case in the GMod API.
465480
// Split any existing types, append the (deprecated) alt and join them back together
466481
// while transforming each type to a LuaLS compatible type.
467-
let types = arg.type.split("|");
482+
let types = arg.type.split('|');
468483

469484
if (arg.altType) {
470485
types.push(arg.altType);
471486
}
472487

473488
let typesString = types.map(type => this.transformType(type, arg.callback))
474-
.join("|");
489+
.join('|');
475490

476-
luaDocComment += `---@param ${GluaApiWriter.safeName(arg.name)}${arg.default !== undefined ? `?` : ''} ${typesString} ${putCommentBeforeEachLine(arg.description!.trimEnd())}\n`;
491+
luaDocComment += `---@param ${GluaApiWriter.safeName(arg.name)}${arg.default !== undefined ? `?` : ''} ${typesString} ${putCommentBeforeEachLine(arg.description!.trim())}\n`;
477492
});
478493
}
479494

480495
if (func.returns) {
481496
func.returns.forEach(ret => {
482-
const description = putCommentBeforeEachLine(ret.description!.trimEnd());
497+
const description = putCommentBeforeEachLine(ret.description!.trim());
483498

484499
luaDocComment += `---@return `;
485500

src/scrapers/wiki-page-markup-scraper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ export class WikiPageMarkupScraper extends Scraper<WikiPage> {
374374
argumentList.push({ args: arguments_ });
375375
}
376376

377-
const returns = $('rets ret').map(function () {
377+
const returns = $('rets > ret').map(function () {
378378
const $el = $(this);
379379
const ret = <FunctionReturn>{
380380
name: $el.attr('name')!,

0 commit comments

Comments
 (0)