• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

luttje / glua-api-snippets / 14571949027

21 Apr 2025 10:30AM UTC coverage: 80.397%. Remained the same
14571949027

push

github

luttje
make each return value optional for hook.Add overload

343 of 427 branches covered (80.33%)

Branch coverage included in aggregate %.

1806 of 2246 relevant lines covered (80.41%)

439.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.71
/src/api-writer/glua-api-writer.ts
1
import { ClassFunction, Enum, Function, HookFunction, LibraryFunction, TypePage, Panel, PanelFunction, Realm, Struct, WikiPage, isPanel, FunctionArgument, FunctionCallback } from '../scrapers/wiki-page-markup-scraper.js';
1✔
2
import { escapeSingleQuotes, indentText, wrapInComment, removeNewlines, safeFileName, toLowerCamelCase } from '../utils/string.js';
1✔
3
import {
1✔
4
  isClassFunction,
1✔
5
  isHookFunction,
1✔
6
  isLibraryFunction,
1✔
7
  isLibrary,
1✔
8
  isClass,
1✔
9
  isPanelFunction,
1✔
10
  isStruct,
1✔
11
  isEnum,
1✔
12
} from '../scrapers/wiki-page-markup-scraper.js';
1✔
13
import customPluginHookAdd from '../../custom/plugins/hook-add.js';
1✔
14
import fs from 'fs';
1✔
15

1✔
16
export const RESERVERD_KEYWORDS = new Set([
1✔
17
  'and',
1✔
18
  'break',
1✔
19
  'continue',
1✔
20
  'do',
1✔
21
  'else',
1✔
22
  'elseif',
1✔
23
  'end',
1✔
24
  'false',
1✔
25
  'for',
1✔
26
  'function',
1✔
27
  'goto',
1✔
28
  'if',
1✔
29
  'in',
1✔
30
  'local',
1✔
31
  'nil',
1✔
32
  'not',
1✔
33
  'or',
1✔
34
  'repeat',
1✔
35
  'return',
1✔
36
  'then',
1✔
37
  'true',
1✔
38
  'until',
1✔
39
  'while'
1✔
40
]);
1✔
41

1✔
42
type IndexedWikiPage = {
1✔
43
  index: number;
1✔
44
  page: WikiPage;
1✔
45
};
1✔
46

1✔
47
export class GluaApiWriter {
1✔
48
  private readonly writtenClasses: Set<string> = new Set();
1✔
49
  private readonly writtenLibraryGlobals: Set<string> = new Set();
1✔
50
  private readonly pageOverrides: Map<string, string> = new Map();
1✔
51

1✔
52
  private readonly files: Map<string, IndexedWikiPage[]> = new Map();
1✔
53

1✔
54
  constructor(
1✔
55
    public readonly outputDirectory: string = './output',
21✔
56
  ) { }
21✔
57

1✔
58
  public static safeName(name: string) {
1✔
59
    if (name.includes('/'))
112✔
60
      name = name.replace(/\//g, ' or ');
112✔
61

112✔
62
    if (name.includes('='))
112✔
63
      name = name.split('=')[0];
112✔
64

112✔
65
    if (name.includes(' '))
112✔
66
      name = toLowerCamelCase(name);
112✔
67

112✔
68
    // Remove any remaining characters not valid in a Lua variable/function name.
112✔
69
    name = name.replace(/[^A-Za-z\d_.]/g, '');
112✔
70

112✔
71
    if (RESERVERD_KEYWORDS.has(name))
112✔
72
      return `_${name}`;
112✔
73

89✔
74
    return name;
89✔
75
  }
89✔
76

1✔
77
  public addOverride(pageAddress: string, override: string) {
1✔
78
    this.pageOverrides.set(safeFileName(pageAddress, '.'), override);
2✔
79
  }
2✔
80

1✔
81
  public writePage(page: WikiPage) {
1✔
82
    const fileSafeAddress = safeFileName(page.address, '.');
21✔
83
    if (this.pageOverrides.has(fileSafeAddress)) {
21✔
84
      let api = '';
1✔
85

1✔
86
      if (isClassFunction(page))
1✔
87
        api += this.writeClassStart(page.parent, undefined, undefined, undefined, page.deprecated);
1!
88
      else if (isLibraryFunction(page))
1✔
89
        api += this.writeLibraryGlobalFallback(page);
1!
90

1✔
91
      api += this.pageOverrides.get(fileSafeAddress);
1✔
92

1✔
93
      return `${api}\n\n`;
1✔
94
    } else if (isClassFunction(page))
21✔
95
      return this.writeClassFunction(page);
20!
96
    else if (isLibraryFunction(page))
20✔
97
      return this.writeLibraryFunction(page);
20✔
98
    else if (isHookFunction(page))
7✔
99
      return this.writeHookFunction(page);
7✔
100
    else if (isPanel(page))
6✔
101
      return this.writePanel(page);
6✔
102
    else if (isPanelFunction(page))
5✔
103
      return this.writePanelFunction(page);
5✔
104
    else if (isEnum(page))
4✔
105
      return this.writeEnum(page);
4✔
106
    else if (isStruct(page))
2✔
107
      return this.writeStruct(page);
2✔
108
    else if (isLibrary(page))
×
109
      return this.writeLibraryGlobal(page);
×
110
    else if (isClass(page))
×
111
      return this.writeClassGlobal(page);
×
112
  }
21✔
113

1✔
114
  private writeClassStart(className: string, realm?: Realm, url?: string, parent?: string, deprecated?: string, description?: string) {
1✔
115
    let api: string = '';
4✔
116

4✔
117
    if (!this.writtenClasses.has(className)) {
4✔
118
      const classOverride = `class.${className}`;
4✔
119
      if (this.pageOverrides.has(classOverride)) {
4✔
120
        api += this.pageOverrides.get(classOverride)!.replace(/\n$/g, '') + '\n\n';
1✔
121
      } else {
4✔
122
        if (realm) {
3!
123
          api += `---${this.formatRealm(realm)} ${description ? `${wrapInComment(description)}\n` : ''}\n`;
×
124
        } else {
3✔
125
          api += description ? `${wrapInComment(description, false)}\n` : '';
3✔
126
        }
3✔
127

3✔
128
        if (url) {
3✔
129
          api += `---\n---[View wiki](${url})\n`;
1✔
130
        }
1✔
131

3✔
132
        if (deprecated)
3✔
133
          api += `---@deprecated ${removeNewlines(deprecated)}\n`;
3✔
134

3✔
135
        api += `---@class ${className}`;
3✔
136

3✔
137
        if (parent)
3✔
138
          api += ` : ${parent}`;
3✔
139

3✔
140
        api += '\n';
3✔
141

3✔
142
        // for PLAYER, WEAPON, etc. we want to define globals
3✔
143
        if (className !== className.toUpperCase()) api += 'local ';
3✔
144
        api += `${className} = {}\n\n`;
3✔
145
      }
3✔
146

4✔
147
      this.writtenClasses.add(className);
4✔
148
    }
4✔
149

4✔
150
    return api;
4✔
151
  }
4✔
152

1✔
153
  private writeLibraryGlobalFallback(func: LibraryFunction) {
1✔
154
    if (!func.dontDefineParent && !this.writtenLibraryGlobals.has(func.parent)) {
13✔
155
      let api = '';
2✔
156

2✔
157
      api += `--- Missing description.\n`;
2✔
158
      api += `${func.parent} = {}\n\n`;
2✔
159

2✔
160
      this.writtenLibraryGlobals.add(func.parent);
2✔
161

2✔
162
      return api;
2✔
163
    }
2✔
164

11✔
165
    return '';
11✔
166
  }
11✔
167

1✔
168
  private writeLibraryGlobal(page: TypePage) {
1✔
169
    if (!this.writtenLibraryGlobals.has(page.name)) {
×
170
      let api = '';
×
171

×
172
      api += page.description ? `${wrapInComment(page.description, false)}\n` : '';
×
173

×
174
      if (page.deprecated)
×
175
        api += `---@deprecated ${removeNewlines(page.deprecated)}\n`;
×
176

×
177
      api += `${page.name} = {}\n\n`;
×
178

×
179
      this.writtenLibraryGlobals.add(page.name);
×
180

×
181
      return api;
×
182
    }
×
183

×
184
    return '';
×
185
  }
×
186

1✔
187
  private writeClassGlobal(page: TypePage) {
1✔
188
    return this.writeClassStart(page.name, page.realm, page.url, page.parent, page.deprecated, page.description);
×
189
  }
×
190

1✔
191
  private writeClassFunction(func: ClassFunction) {
1✔
192
    let api: string = this.writeClassStart(func.parent, undefined, undefined, undefined, func.deprecated);
1✔
193

1✔
194
    if (!func.arguments || func.arguments.length === 0) func.arguments = [{}];
1!
195
    for (const argSet of func.arguments) {
1✔
196
      api += this.writeFunctionLuaDocComment(func, argSet.args, func.realm);
1✔
197
      api += this.writeFunctionDeclaration(func, argSet.args, ':');
1✔
198
    }
1✔
199

1✔
200
    return api;
1✔
201
  }
1✔
202

1✔
203
  private writeLibraryFunction(func: LibraryFunction) {
1✔
204
    let api: string = this.writeLibraryGlobalFallback(func);
13✔
205

13✔
206
    if (!func.arguments || func.arguments.length === 0) func.arguments = [{}];
13✔
207
    for (const argSet of func.arguments) {
13✔
208
      api += this.writeFunctionLuaDocComment(func, argSet.args, func.realm);
13✔
209
      api += this.writeFunctionDeclaration(func, argSet.args);
13✔
210
    }
13✔
211

12✔
212
    return api;
12✔
213
  }
12✔
214

1✔
215
  private writeHookFunction(func: HookFunction) {
1✔
216
    return this.writeClassFunction(func);
1✔
217
  }
1✔
218

1✔
219
  private writePanel(panel: Panel) {
1✔
220
    let api: string = this.writeClassStart(panel.name, undefined, undefined, panel.parent, panel.deprecated, panel.description);
1✔
221

1✔
222
    return api;
1✔
223
  }
1✔
224

1✔
225
  private writePanelFunction(func: PanelFunction) {
1✔
226
    let api: string = '';
1✔
227

1✔
228
    if (!func.arguments || func.arguments.length === 0) func.arguments = [{}];
1!
229
    for (const argSet of func.arguments) {
1✔
230
      api += this.writeFunctionLuaDocComment(func, argSet.args, func.realm);
1✔
231
      api += this.writeFunctionDeclaration(func, argSet.args, ':');
1✔
232
    }
1✔
233

1✔
234
    return api;
1✔
235
  }
1✔
236

1✔
237
  private writeEnum(_enum: Enum) {
1✔
238
    let api: string = '';
2✔
239

2✔
240
    // If the first key is empty (like SCREENFADE has), check the second key
2✔
241
    const isContainedInTable =
2✔
242
      _enum.items[0]?.key === ''
2!
243
        ? _enum.items[1]?.key.includes('.')
2!
244
        : _enum.items[0]?.key.includes('.');
2!
245

2✔
246
    if (_enum.deprecated)
2✔
247
      api += `---@deprecated ${removeNewlines(_enum.deprecated)}\n`;
2!
248

2✔
249
    if (isContainedInTable) {
2✔
250
      api += `---${this.formatRealm(_enum.realm)} ${_enum.description ? `${wrapInComment(_enum.description)}` : ''}\n`;
1!
251
      api += `---@enum ${_enum.name}\n`;
1✔
252
      api += `${_enum.name} = {\n`;
1✔
253
    }
1✔
254

2✔
255
    const writeItem = (key: string, item: typeof _enum.items[0]) => {
2✔
256
      if (key === '') {
10✔
257
        // Happens for SCREENFADE which has a blank key to describe what 0 does.
1✔
258
        return;
1✔
259
      }
1✔
260

9✔
261
      if (isNaN(Number(item.value.trim()))) {
10✔
262
        // Happens for TODO value in NAV_MESH_BLOCKED_LUA in https://wiki.facepunch.com/gmod/Enums/NAV_MESH
1✔
263
        console.warn(`Enum ${_enum.name} has a TODO value for key ${key}. Skipping.`);
1✔
264
        return;
1✔
265
      }
1✔
266

8✔
267
      if (isContainedInTable) {
10✔
268
        key = key.split('.')[1];
5✔
269

5✔
270
        if (item.description?.trim()) {
5✔
271
          api += `${indentText(wrapInComment(item.description, false), 2)}\n`;
4✔
272
        }
4✔
273

5✔
274
        api += `  ${key} = ${item.value},\n`;
5✔
275
      } else {
10✔
276
        api += item.description ? `${wrapInComment(item.description, false)}\n` : '';
3✔
277
        if (item.deprecated)
3✔
278
          api += `---@deprecated ${removeNewlines(item.deprecated)}\n`;
3!
279
        api += `${key} = ${item.value}\n`;
3✔
280
      }
3✔
281
    };
2✔
282

2✔
283
    for (const item of _enum.items)
2✔
284
      writeItem(item.key, item);
2✔
285

2✔
286
    if (isContainedInTable) {
2✔
287
      api += '}';
1✔
288
    } else {
1✔
289
      // TODO: Clean up this workaround when LuaLS supports global enumerations.
1✔
290
      // Until LuaLS supports global enumerations (https://github.com/LuaLS/lua-language-server/issues/2721) we
1✔
291
      // will use @alias as a workaround.
1✔
292
      // LuaLS doesn't nicely display annotations for aliasses, hence this is commented
1✔
293
      //api += `\n---${this.formatRealm(_enum.realm)} ${_enum.description ? `${wrapInComment(_enum.description)}` : ''}\n`;
1✔
294
      api += `\n---@alias ${_enum.name}\n`;
1✔
295

1✔
296
      for (const item of _enum.items) {
1✔
297
        if (item.key !== '' && !isNaN(Number(item.value.trim()))) {
4✔
298
          api += `---| \`${item.key}\`\n`;
3✔
299
        }
3✔
300
      }
4✔
301
    }
1✔
302

2✔
303
    api += `\n\n`;
2✔
304

2✔
305
    return api;
2✔
306
  }
2✔
307

1✔
308
  private writeType(type: string, value: any) {
1✔
309
    if (type === 'string')
×
310
      return `'${escapeSingleQuotes(value)}'`;
×
311

×
312
    if (type === 'Vector')
×
313
      return `Vector${value}`;
×
314

×
315
    return value;
×
316
  }
×
317

1✔
318
  private writeStruct(struct: Struct) {
1✔
319
    let api: string = this.writeClassStart(struct.name, struct.realm, struct.url, undefined, struct.deprecated, struct.description);
2✔
320

2✔
321
    for (const field of struct.fields) {
2✔
322
      if (field.deprecated)
34✔
323
        api += `---@deprecated ${removeNewlines(field.deprecated)}\n`;
34!
324

34✔
325
      api += `---${wrapInComment(field.description)}\n`;
34✔
326

34✔
327
      const type = GluaApiWriter.transformType(field.type, field.callback);
34✔
328
      api += `---@type ${type}\n`;
34✔
329
      api += `${struct.name}.${GluaApiWriter.safeName(field.name)} = ${field.default ? this.writeType(type, field.default) : 'nil'}\n\n`;
34!
330
    }
34✔
331

2✔
332
    return api;
2✔
333
  }
2✔
334

1✔
335
  public writePages(pages: WikiPage[], filePath: string, index: number = 0) {
1✔
336
    if (!this.files.has(filePath)) this.files.set(filePath, []);
7✔
337

7✔
338
    pages.forEach(page => {
7✔
339
      this.files.get(filePath)!.push({ index: index, page: page });
7✔
340
    });
7✔
341
  }
7✔
342

1✔
343
  public getPages(filePath: string) {
1✔
344
    return this.files.get(filePath) ?? [];
7!
345
  }
7✔
346

1✔
347
  public makeApiFromPages(pages: IndexedWikiPage[]) {
1✔
348
    let api = '';
7✔
349

7✔
350
    pages.sort((a, b) => a.index - b.index);
7✔
351

7✔
352
    // First we write the "header" types
7✔
353
    for (const page of pages.filter(x => isClass(x.page) || isLibrary(x.page) || isPanel(x.page))) {
7✔
354
      try {
1✔
355
        api += this.writePage(page.page);
1✔
356
      } catch (e) {
1!
357
        console.error(`Failed to write 'header' page ${page.page.address}: ${e}`);
×
358
      }
×
359
    }
1✔
360

7✔
361
    for (const page of pages.filter(x => !isClass(x.page) && !isLibrary(x.page) && !isPanel(x.page))) {
7✔
362
      try {
6✔
363
        api += this.writePage(page.page);
6✔
364
      } catch (e) {
6!
365
        console.error(`Failed to write page ${page.page.address}: ${e}`);
×
366
      }
×
367
    }
6✔
368

7✔
369
    return api;
7✔
370
  }
7✔
371

1✔
372
  public writeToDisk() {
1✔
373
    this.files.forEach((pages: IndexedWikiPage[], filePath: string) => {
×
374
      let api = this.makeApiFromPages(pages);
×
375

×
376
      if (api.length > 0) {
×
377
        fs.appendFileSync(filePath, '---@meta\n\n' + api);
×
378
      }
×
379
    });
×
380
  }
×
381

1✔
382
  public static transformType(type: string, callback?: FunctionCallback) {
1✔
383
    if (type === 'vararg')
76✔
384
      return 'any';
76✔
385

75✔
386
    // Convert `function` type to `fun(cmd: string, args: string):(returnValueName: string[]?)`
75✔
387
    if (type === 'function' && callback) {
76✔
388
      let callbackString = `fun(`;
3✔
389

3✔
390
      const callbackArgsLength = callback.arguments?.length || 0;
3!
391

3✔
392
      for (let i = 0; i < callbackArgsLength; i++) {
3✔
393
        const arg = callback.arguments![i];
5✔
394

5✔
395
        if (!arg.name) {
5✔
396
          arg.name = `arg${i}`;
1✔
397
        }
1✔
398

5✔
399
        if (arg.type === 'vararg')
5✔
400
          arg.name = '...';
5!
401

5✔
402
        callbackString += `${GluaApiWriter.safeName(arg.name)}: ${GluaApiWriter.transformType(arg.type)}${arg.default !== undefined ? `?` : ''}, `;
5!
403
      }
5✔
404

3✔
405
      // Remove trailing comma and space
3✔
406
      if (callbackString.endsWith(', '))
3✔
407
        callbackString = callbackString.substring(0, callbackString.length - 2);
3✔
408

3✔
409
      callbackString += ')';
3✔
410

3✔
411
      if (callback.returns?.length) {
3!
412
        callbackString += ':(';
3✔
413
      }
3✔
414

3✔
415
      const callbackReturnsLength = callback.returns?.length || 0;
3!
416

3✔
417
      for (let i = 0; i < callbackReturnsLength; i++) {
3✔
418
        const ret = callback.returns![i];
6✔
419

6✔
420
        if (!ret.name) {
6✔
421
          ret.name = `ret${i}`;
2✔
422
        }
2✔
423

6✔
424
        if (ret.type === 'vararg')
6✔
425
          ret.name = '...';
6!
426

6✔
427
        callbackString += `${ret.name}: ${this.transformType(ret.type)}${ret.default !== undefined ? `?` : ''}, `;
6!
428
      }
6✔
429

3✔
430
      // Remove trailing comma and space
3✔
431
      if (callbackString.endsWith(', '))
3✔
432
        callbackString = callbackString.substring(0, callbackString.length - 2);
3✔
433

3✔
434
      if (callback.returns?.length) {
3!
435
        callbackString += ')';
3✔
436
      }
3✔
437

3✔
438
      return callbackString;
3✔
439
    } else if (type.startsWith('table<') && !type.includes(',')) {
76✔
440
      // Convert `table<Player>` to `Player[]` for LuaLS (but leave table<x, y> untouched)
2✔
441
      let innerType = type.match(/<([^>]+)>/)?.[1];
2✔
442

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

1✔
445
      return `${innerType}[]`;
1✔
446
    } else if (type.startsWith('table{') || type.startsWith('Panel{')) {
72✔
447
      // Convert `table{ToScreenData}` structures to `ToScreenData` class for LuaLS
2✔
448
      // Also converts `Panel{DVScrollBar}` to `DVScrollBar` class for LuaLS
2✔
449
      let innerType = type.match(/{([^}]+)}/)?.[1];
2!
450

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

2✔
453
      return innerType;
2✔
454
    } else if (type.startsWith('number{')) {
70✔
455
      // Convert `number{MATERIAL_FOG}` to `MATERIAL_FOG` enum for LuaLS
1✔
456
      let innerType = type.match(/{([^}]+)}/)?.[1];
1!
457

1✔
458
      if (!innerType) throw new Error(`Invalid number type: ${type}`);
1!
459

1✔
460
      return innerType;
1✔
461
    }
1✔
462

67✔
463
    return type;
67✔
464
  }
67✔
465

1✔
466
  private formatRealm(realm: Realm) {
1✔
467
    // Formats to show the image, with the realm as the alt text
16✔
468
    switch (realm) {
16✔
469
      case 'menu':
16!
470
        return '![(Menu)](https://github.com/user-attachments/assets/62703d98-767e-4cf2-89b3-390b1c2c5cd9)';
×
471
      case 'client':
16✔
472
        return '![(Client)](https://github.com/user-attachments/assets/a5f6ba64-374d-42f0-b2f4-50e5c964e808)';
5✔
473
      case 'server':
16✔
474
        return '![(Server)](https://github.com/user-attachments/assets/d8fbe13a-6305-4e16-8698-5be874721ca1)';
1✔
475
      case 'shared':
16✔
476
        return '![(Shared)](https://github.com/user-attachments/assets/a356f942-57d7-4915-a8cc-559870a980fc)';
8✔
477
      case 'client and menu':
16!
478
        return '![(Client and menu)](https://github.com/user-attachments/assets/25d1a1c8-4288-4a51-9867-5e3bb51b9981)';
×
479
      case 'shared and menu':
16✔
480
        return '![(Shared and Menu)](https://github.com/user-attachments/assets/8f5230ff-38f7-493b-b9fc-cc70ffd5b3f4)';
2✔
481
      default:
16!
482
        throw new Error(`Unknown realm: ${realm}`);
×
483
    }
16✔
484
  }
16✔
485

1✔
486
  private writeFunctionLuaDocComment(func: Function, args: FunctionArgument[] | undefined, realm: Realm) {
1✔
487
    let luaDocComment = `---${this.formatRealm(realm)} ${wrapInComment(func.description!)}\n`;
15✔
488
    luaDocComment += `---\n---[View wiki](${func.url})\n`;
15✔
489

15✔
490
    if (args) {
15✔
491
      args.forEach((arg, index) => {
10✔
492
        if (!arg.name)
16✔
493
          arg.name = arg.type;
16!
494

16✔
495
        if (arg.type === 'vararg')
16✔
496
          arg.name = '...';
16✔
497

16✔
498
        // TODO: This splitting will fail in complicated cases like `table<string|number>|string`.
16✔
499
        // TODO: I'm assuming for now that there is no such case in the GMod API.
16✔
500
        // Split any existing types, append the (deprecated) alt and join them back together
16✔
501
        // while transforming each type to a LuaLS compatible type.
16✔
502
        let types = arg.type.split('|');
16✔
503

16✔
504
        if (arg.altType) {
16✔
505
          types.push(arg.altType);
3✔
506
        }
3✔
507

16✔
508
        let typesString = types.map(type => GluaApiWriter.transformType(type, arg.callback))
16✔
509
          .join('|');
16✔
510

16✔
511
        luaDocComment += `---@param ${GluaApiWriter.safeName(arg.name)}${arg.default !== undefined ? `?` : ''} ${typesString} ${wrapInComment(arg.description!)}\n`;
16✔
512
      });
10✔
513
    }
10✔
514

14✔
515
    if (func.returns) {
15✔
516
      func.returns.forEach(ret => {
9✔
517
        const description = wrapInComment(ret.description!);
9✔
518

9✔
519
        luaDocComment += `---@return `;
9✔
520

9✔
521
        if (ret.type === 'vararg')
9✔
522
          luaDocComment += 'any ...';
9✔
523
        else
8✔
524
          luaDocComment += `${GluaApiWriter.transformType(ret.type, ret.callback)}`;
8✔
525

9✔
526
        luaDocComment += ` # ${description}\n`;
9✔
527
      });
9✔
528
    }
9✔
529

14✔
530
    if (func.deprecated)
14✔
531
      luaDocComment += `---@deprecated ${removeNewlines(func.deprecated)}\n`;
15!
532

14✔
533
    // TODO: Write a nice API to allow customizing API output from custom/
14✔
534
    // See https://github.com/luttje/glua-api-snippets/issues/65
14✔
535
    if (func.address === 'hook.Add') {
15!
536
      luaDocComment += customPluginHookAdd(this, func);
×
537
    }
×
538

14✔
539
    return luaDocComment;
14✔
540
  }
14✔
541

1✔
542
  private writeFunctionDeclaration(func: Function, args: FunctionArgument[] | undefined, indexer: string = '.') {
1✔
543
    let declaration = `function ${func.parent ? `${func.parent}${indexer}` : ''}${GluaApiWriter.safeName(func.name)}(`;
14!
544

14✔
545
    if (args) {
14✔
546
      declaration += args.map(arg => {
9✔
547
        if (arg.type === 'vararg')
15✔
548
          return '...';
15✔
549

14✔
550
        return GluaApiWriter.safeName(arg.name!);
14✔
551
      }).join(', ');
9✔
552
    }
9✔
553

14✔
554
    declaration += ') end\n\n';
14✔
555

14✔
556
    return declaration;
14✔
557
  }
14✔
558
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc