cluaupp 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +12 -7
  3. package/bin/cluau.js +1 -1
  4. package/bin/cluaupp.js +1 -1
  5. package/docs/README.md +1 -1
  6. package/docs/architecture.md +52 -77
  7. package/docs/cli.md +29 -9
  8. package/docs/comparison.md +3 -3
  9. package/docs/config.md +6 -6
  10. package/docs/cpp-organization.md +2 -2
  11. package/docs/examples/index.md +2 -0
  12. package/docs/getting-started.md +7 -5
  13. package/docs/intellisense.md +49 -31
  14. package/docs/intro.md +2 -2
  15. package/docs/oop/file-tags.md +16 -17
  16. package/docs/oop/index.md +2 -2
  17. package/docs/oop/modules.md +3 -1
  18. package/docs/oop/services.md +7 -14
  19. package/docs/syntax.md +6 -5
  20. package/editors/vscode/extension.js +97 -146
  21. package/editors/vscode/package.json +6 -6
  22. package/examples/README.md +18 -0
  23. package/examples/game/.clangd +25 -0
  24. package/examples/game/.vscode/c_cpp_properties.json +27 -0
  25. package/examples/game/.vscode/extensions.json +5 -0
  26. package/examples/game/.vscode/settings.json +27 -0
  27. package/examples/game/Packages/.gitkeep +0 -0
  28. package/examples/game/cluaupp.config.json +6 -0
  29. package/examples/game/compile_flags.txt +6 -0
  30. package/examples/game/default.project.json +39 -0
  31. package/examples/game/package.json +9 -0
  32. package/examples/game/rokit.toml +5 -0
  33. package/examples/game/src/client/hud.client.cpp +12 -0
  34. package/{src → examples/game/src}/client/init.client.cpp +1 -0
  35. package/examples/game/src/server/combat.server.cpp +23 -0
  36. package/{src → examples/game/src}/server/leaderstats.server.cpp +1 -0
  37. package/examples/game/wally.toml +11 -0
  38. package/generated/api.js +51 -0
  39. package/generated/architecture.js +473 -0
  40. package/generated/ast.js +2 -0
  41. package/generated/cli.js +191 -0
  42. package/generated/compile.js +115 -0
  43. package/generated/editor-install.js +170 -0
  44. package/generated/emit.js +503 -0
  45. package/generated/emitter/luau-codegen.js +167 -0
  46. package/generated/emitter/translators.js +164 -0
  47. package/generated/headers.js +220 -0
  48. package/generated/index.html +49 -0
  49. package/generated/intellisense.js +277 -0
  50. package/generated/layout.js +112 -0
  51. package/generated/lex.js +146 -0
  52. package/generated/libs.js +484 -0
  53. package/generated/lsp.js +55 -0
  54. package/generated/package-info.js +11 -0
  55. package/generated/parse.js +607 -0
  56. package/generated/parser/collector.js +100 -0
  57. package/generated/parser/index.js +30 -0
  58. package/generated/preprocess.js +181 -0
  59. package/generated/system-understander.js +443 -0
  60. package/generated/transpile.js +66 -0
  61. package/generated/types.js +2 -0
  62. package/generated/understand.js +298 -0
  63. package/generated/utils/process-orchestrator.js +63 -0
  64. package/generated/utils/project.js +491 -0
  65. package/generated/utils/rojo-mapper.js +181 -0
  66. package/generated/utils/safe-paths.js +104 -0
  67. package/package.json +25 -8
  68. package/src/api.ts +53 -0
  69. package/src/{architecture.js → architecture.ts} +80 -33
  70. package/src/ast.ts +37 -0
  71. package/src/cli.ts +214 -0
  72. package/src/compile.ts +118 -0
  73. package/src/editor-install.ts +182 -0
  74. package/src/{emit.js → emit.ts} +5 -6
  75. package/src/emitter/luau-codegen.ts +187 -0
  76. package/src/emitter/translators.ts +168 -0
  77. package/src/headers.ts +233 -0
  78. package/src/intellisense.ts +286 -0
  79. package/src/{layout.js → layout.ts} +114 -129
  80. package/src/{lex.js → lex.ts} +4 -6
  81. package/src/{libs.js → libs.ts} +119 -46
  82. package/src/lsp.ts +60 -0
  83. package/src/package-info.ts +7 -0
  84. package/src/{parse.js → parse.ts} +3 -4
  85. package/src/parser/collector.ts +115 -0
  86. package/src/parser/index.ts +27 -0
  87. package/src/{preprocess.js → preprocess.ts} +64 -32
  88. package/src/system-understander.ts +501 -0
  89. package/src/transpile.ts +64 -0
  90. package/src/tree-sitter-cpp.d.ts +4 -0
  91. package/src/types.ts +81 -0
  92. package/src/{understand.js → understand.ts} +24 -213
  93. package/src/utils/process-orchestrator.ts +72 -0
  94. package/src/utils/project.ts +506 -0
  95. package/src/utils/rojo-mapper.ts +190 -0
  96. package/src/utils/safe-paths.ts +98 -0
  97. package/templates/game/.clangd +9 -0
  98. package/templates/game/.vscode/c_cpp_properties.json +1 -1
  99. package/templates/game/.vscode/extensions.json +5 -6
  100. package/templates/game/.vscode/settings.json +7 -6
  101. package/templates/game/cluaupp.config.json +2 -2
  102. package/templates/game/compile_flags.txt +1 -0
  103. package/templates/game/rokit.toml +5 -0
  104. package/templates/game/src/client/init.client.cpp +1 -0
  105. package/templates/game/src/server/leaderstats.server.cpp +1 -0
  106. package/src/api.js +0 -57
  107. package/src/cli.js +0 -481
  108. package/src/compile.js +0 -56
  109. package/src/editor-install.js +0 -218
  110. package/src/intellisense.js +0 -1368
  111. package/src/lsp.js +0 -227
  112. /package/{src → examples/game/src}/shared/config.cpp +0 -0
  113. /package/{src → examples/game/src}/shared/config.h +0 -0
@@ -1,129 +1,114 @@
1
- "use strict";
2
-
3
- const SECTION = {
4
- api: "API",
5
- constants: "CONSTANTES",
6
- variables: "VARIAVEIS",
7
- types: "TIPAGENS",
8
- support: "FUNÇÕES DE SUPORTE",
9
- principal: "FUNÇÕES PRINCIPAIS",
10
- cleanup: "LIMPEZA",
11
- returns: "RETORNO",
12
- };
13
-
14
- const CLEANUP_FN = /^(OnClose|OnPlayerRemoving|Cleanup|Shutdown|OnDestroy)$/i;
15
- const PRINCIPAL_FN = /^(init|start|main|setup)/i;
16
- const ENTRY_FN = /^(init|main)$/i;
17
-
18
- function isGetService(node) {
19
- return Boolean(node && node.type === "getService");
20
- }
21
-
22
- function isDataServiceAlias(node) {
23
- let current = node;
24
- while (current && current.type === "member") {
25
- if (current.object && current.object.type === "ident" && current.object.name === "DataService") {
26
- return true;
27
- }
28
- current = current.object;
29
- }
30
- return false;
31
- }
32
-
33
- function isJanitorDecl(decl) {
34
- if (!decl || decl.type !== "decl") {
35
- return false;
36
- }
37
- const name = String(decl.name || "").toLowerCase();
38
- const typeName = String(decl.valueType || "").toLowerCase();
39
- const created = decl.value && decl.value.type === "new" && decl.value.className === "Janitor";
40
- return name.includes("janitor") || typeName.includes("janitor") || created;
41
- }
42
-
43
- function isApiDecl(decl) {
44
- if (!decl || decl.type !== "decl") {
45
- return false;
46
- }
47
- return isGetService(decl.value) || isDataServiceAlias(decl.value);
48
- }
49
-
50
- function classifyDecl(decl) {
51
- if (!decl || decl.type === "proto") {
52
- return null;
53
- }
54
- if (decl.type === "decl") {
55
- if (isApiDecl(decl)) {
56
- return "api";
57
- }
58
- if (isJanitorDecl(decl)) {
59
- return "variables";
60
- }
61
- if (decl.isConst) {
62
- return "constants";
63
- }
64
- return "variables";
65
- }
66
- if (decl.type === "function") {
67
- if (CLEANUP_FN.test(decl.name || "")) {
68
- return "cleanup";
69
- }
70
- if (PRINCIPAL_FN.test(decl.name || "")) {
71
- return "principal";
72
- }
73
- return "support";
74
- }
75
- return null;
76
- }
77
-
78
- function organizeDecls(keep) {
79
- const groups = {
80
- api: [],
81
- constants: [],
82
- variables: [],
83
- support: [],
84
- principal: [],
85
- cleanup: [],
86
- };
87
- for (const decl of keep) {
88
- const bucket = classifyDecl(decl);
89
- if (bucket && groups[bucket]) {
90
- groups[bucket].push(decl);
91
- }
92
- }
93
- return groups;
94
- }
95
-
96
- function janitorNames(keep) {
97
- return (keep || []).filter(isJanitorDecl).map((decl) => decl.name);
98
- }
99
-
100
- function emitSection(_title, body) {
101
- const text = String(body || "").trimEnd();
102
- if (!text) {
103
- return "";
104
- }
105
- return `${text}\n\n`;
106
- }
107
-
108
- function joinBlocks(parts) {
109
- return parts.filter((part) => Boolean(part && String(part).trim())).join("\n\n");
110
- }
111
-
112
- function commentLine(title) {
113
- return `-- ${title}`;
114
- }
115
-
116
- module.exports = {
117
- SECTION,
118
- CLEANUP_FN,
119
- PRINCIPAL_FN,
120
- ENTRY_FN,
121
- isJanitorDecl,
122
- isApiDecl,
123
- classifyDecl,
124
- organizeDecls,
125
- janitorNames,
126
- emitSection,
127
- joinBlocks,
128
- commentLine,
129
- };
1
+ import type { AstNode } from "./ast.js";
2
+
3
+ export const SECTION = {
4
+ api: "API",
5
+ constants: "CONSTANTS",
6
+ variables: "VARIABLES",
7
+ types: "TYPES",
8
+ support: "HELPERS FUNCTIONS",
9
+ principal: "MAIN FUNCTIONS",
10
+ cleanup: "JANITOR",
11
+ returns: "RETURN",
12
+ };
13
+
14
+ export const CLEANUP_FN = /^(OnClose|OnPlayerRemoving|Cleanup|Shutdown|OnDestroy)$/i;
15
+ export const PRINCIPAL_FN = /^(init|start|main|setup)/i;
16
+ export const ENTRY_FN = /^(init|main)$/i;
17
+
18
+ function isGetService(node: AstNode | null | undefined): boolean {
19
+ return Boolean(node && node.type === "getService");
20
+ }
21
+
22
+ function isDataServiceAlias(node: AstNode | null | undefined): boolean {
23
+ let current: AstNode | undefined = node || undefined;
24
+ while (current && current.type === "member") {
25
+ if (current.object && current.object.type === "ident" && current.object.name === "DataService") {
26
+ return true;
27
+ }
28
+ current = current.object;
29
+ }
30
+ return false;
31
+ }
32
+
33
+ export function isJanitorDecl(decl: AstNode | null | undefined): boolean {
34
+ if (!decl || decl.type !== "decl") {
35
+ return false;
36
+ }
37
+ const name = String(decl.name || "").toLowerCase();
38
+ const typeName = String(decl.valueType || "").toLowerCase();
39
+ const created = decl.value && decl.value.type === "new" && decl.value.className === "Janitor";
40
+ return name.includes("janitor") || typeName.includes("janitor") || created;
41
+ }
42
+
43
+ export function isApiDecl(decl: AstNode | null | undefined): boolean {
44
+ if (!decl || decl.type !== "decl") {
45
+ return false;
46
+ }
47
+ return isGetService(decl.value) || isDataServiceAlias(decl.value);
48
+ }
49
+
50
+ export function classifyDecl(decl: AstNode | null | undefined): string | null {
51
+ if (!decl || decl.type === "proto") {
52
+ return null;
53
+ }
54
+ if (decl.type === "decl") {
55
+ if (isApiDecl(decl)) {
56
+ return "api";
57
+ }
58
+ if (isJanitorDecl(decl)) {
59
+ return "variables";
60
+ }
61
+ if (decl.isConst) {
62
+ return "constants";
63
+ }
64
+ return "variables";
65
+ }
66
+ if (decl.type === "function") {
67
+ if (CLEANUP_FN.test(decl.name || "")) {
68
+ return "cleanup";
69
+ }
70
+ if (PRINCIPAL_FN.test(decl.name || "")) {
71
+ return "principal";
72
+ }
73
+ return "support";
74
+ }
75
+ return null;
76
+ }
77
+
78
+ export function organizeDecls(keep: AstNode[]): Record<string, AstNode[]> {
79
+ const groups: Record<string, AstNode[]> = {
80
+ api: [],
81
+ constants: [],
82
+ variables: [],
83
+ support: [],
84
+ principal: [],
85
+ cleanup: [],
86
+ };
87
+ for (const decl of keep) {
88
+ const bucket = classifyDecl(decl);
89
+ if (bucket && groups[bucket]) {
90
+ groups[bucket].push(decl);
91
+ }
92
+ }
93
+ return groups;
94
+ }
95
+
96
+ export function janitorNames(keep: AstNode[] | null | undefined): string[] {
97
+ return (keep || []).filter(isJanitorDecl).map((decl) => decl.name);
98
+ }
99
+
100
+ export function emitSection(_title: string, body: string): string {
101
+ const text = String(body || "").trimEnd();
102
+ if (!text) {
103
+ return "";
104
+ }
105
+ return `${text}\n\n`;
106
+ }
107
+
108
+ export function joinBlocks(parts: Array<string | null | undefined>): string {
109
+ return parts.filter((part) => Boolean(part && String(part).trim())).join("\n\n");
110
+ }
111
+
112
+ export function commentLine(title: string): string {
113
+ return `-- ${title}`;
114
+ }
@@ -1,4 +1,4 @@
1
- "use strict";
1
+ import type { Token } from "./ast.js";
2
2
 
3
3
  const KEYWORDS = new Set([
4
4
  "if",
@@ -34,13 +34,13 @@ const KEYWORDS = new Set([
34
34
  "break",
35
35
  ]);
36
36
 
37
- function tokenize(source) {
38
- const tokens = [];
37
+ export function tokenize(source: string): Token[] {
38
+ const tokens: Token[] = [];
39
39
  let i = 0;
40
40
  let line = 1;
41
41
  let col = 1;
42
42
 
43
- const push = (type, value, startLine, startCol, start, end) => {
43
+ const push = (type: string, value: string, startLine: number, startCol: number, start: number, end: number) => {
44
44
  tokens.push({ type, value, line: startLine, col: startCol, start, end });
45
45
  };
46
46
 
@@ -153,5 +153,3 @@ function tokenize(source) {
153
153
  push("eof", "", line, col, source.length, source.length);
154
154
  return tokens;
155
155
  }
156
-
157
- module.exports = { tokenize };
@@ -1,6 +1,5 @@
1
- "use strict";
2
-
3
- const path = require("path");
1
+ // @ts-nocheck
2
+ import path from "node:path";
4
3
 
5
4
  const TYPE_EXPORTS = {
6
5
  Janitor: "Janitor",
@@ -299,17 +298,64 @@ function collectLibraries(source, ast) {
299
298
  return unique;
300
299
  }
301
300
 
301
+ const ROJO_ROOTS = {
302
+ shared: { service: "ReplicatedStorage", expr: "ReplicatedStorage.Cluaupp" },
303
+ server: { service: "ServerScriptService", expr: "ServerScriptService.Cluaupp" },
304
+ client: { service: "StarterPlayer", expr: "StarterPlayer.StarterPlayerScripts.Cluaupp" },
305
+ };
306
+
307
+ const SERVICE_ORDER = ["ReplicatedStorage", "ServerScriptService", "StarterPlayer"];
308
+
309
+ const SERVICE_GET = {
310
+ ReplicatedStorage: 'const ReplicatedStorage = game:GetService("ReplicatedStorage")',
311
+ ServerScriptService: 'const ServerScriptService = game:GetService("ServerScriptService")',
312
+ StarterPlayer: 'const StarterPlayer = game:GetService("StarterPlayer")',
313
+ };
314
+
302
315
  function requireCluauppLib(name) {
303
316
  return `require(ReplicatedStorage.CluauppLibs.${name})`;
304
317
  }
305
318
 
306
- function emitRequireParts(libraries) {
319
+ function parseOutRel(toOutRel) {
320
+ const posix = String(toOutRel || "module.luau")
321
+ .replace(/\\/g, "/")
322
+ .replace(/\.luau$/i, "");
323
+ const parts = posix.split("/").filter(Boolean);
324
+ const tree = parts[0];
325
+ const root = ROJO_ROOTS[tree] || null;
326
+ const rest = root ? parts.slice(1) : parts;
327
+ return { tree, root, rest, posix };
328
+ }
329
+
330
+ function robloxRequireFrom(_fromOutRel, toOutRel) {
331
+ const { root, rest } = parseOutRel(toOutRel);
332
+ if (root) {
333
+ const tail = rest.join(".");
334
+ return tail ? `require(${root.expr}.${tail})` : `require(${root.expr})`;
335
+ }
336
+ const fromDir = path.posix.dirname(String(_fromOutRel || "module.luau").replace(/\\/g, "/"));
337
+ const toMod = String(toOutRel || "module.luau")
338
+ .replace(/\\/g, "/")
339
+ .replace(/\.luau$/i, "");
340
+ let rel = path.posix.relative(fromDir, toMod);
341
+ if (!rel || rel === ".") {
342
+ rel = path.posix.basename(toMod);
343
+ }
344
+ const parts = rel.split("/");
345
+ let expr = "script.Parent";
346
+ for (const part of parts) {
347
+ if (part === "..") {
348
+ expr += ".Parent";
349
+ } else if (part && part !== ".") {
350
+ expr += `.${part}`;
351
+ }
352
+ }
353
+ return `require(${expr})`;
354
+ }
355
+
356
+ function emitLibraryLines(libraries) {
307
357
  const api = [];
308
358
  const types = [];
309
- if (!libraries.length) {
310
- return { api: "", types: "" };
311
- }
312
- api.push('const ReplicatedStorage = game:GetService("ReplicatedStorage")');
313
359
  for (const spec of libraries) {
314
360
  api.push(`const ${spec.bind} = ${requireCluauppLib(spec.file)}`);
315
361
  const exported = TYPE_EXPORTS[spec.bind];
@@ -323,6 +369,15 @@ function emitRequireParts(libraries) {
323
369
  }
324
370
  }
325
371
  }
372
+ return { api, types };
373
+ }
374
+
375
+ function emitRequireParts(libraries) {
376
+ if (!libraries.length) {
377
+ return { api: "", types: "" };
378
+ }
379
+ const { api, types } = emitLibraryLines(libraries);
380
+ api.unshift(SERVICE_GET.ReplicatedStorage);
326
381
  return {
327
382
  api: `${api.join("\n")}\n`,
328
383
  types: types.length ? `${types.join("\n")}\n` : "",
@@ -339,34 +394,7 @@ function insertRequires(luau, libraries) {
339
394
  if (!block) {
340
395
  return luau;
341
396
  }
342
- const match = String(luau).match(/^(?:--[^\n]*\n)+/);
343
- if (!match) {
344
- return `${block}\n${luau}`;
345
- }
346
- const insertAt = match[0].length;
347
- const rest = luau.slice(insertAt).replace(/^\n*/, "\n");
348
- return `${luau.slice(0, insertAt)}\n${block}\n${rest}`;
349
- }
350
-
351
- function robloxRequireFrom(fromOutRel, toOutRel) {
352
- const fromDir = path.posix.dirname(String(fromOutRel || "module.luau").replace(/\\/g, "/"));
353
- const toMod = String(toOutRel || "module.luau")
354
- .replace(/\\/g, "/")
355
- .replace(/\.luau$/i, "");
356
- let rel = path.posix.relative(fromDir, toMod);
357
- if (!rel || rel === ".") {
358
- rel = path.posix.basename(toMod);
359
- }
360
- const parts = rel.split("/");
361
- let expr = "script.Parent";
362
- for (const part of parts) {
363
- if (part === "..") {
364
- expr += ".Parent";
365
- } else if (part && part !== ".") {
366
- expr += `.${part}`;
367
- }
368
- }
369
- return `require(${expr})`;
397
+ return insertBlock(luau, block);
370
398
  }
371
399
 
372
400
  function moduleIsUsed(luau, spec) {
@@ -377,13 +405,11 @@ function moduleIsUsed(luau, spec) {
377
405
  return exported.some((name) => String(luau).includes(name));
378
406
  }
379
407
 
380
- function insertModuleRequires(luau, modules, fromOutRel) {
381
- if (!modules || modules.length === 0) {
382
- return luau;
383
- }
408
+ function moduleRequireLines(modules, fromOutRel, luau) {
384
409
  const lines = [];
410
+ const services = new Set();
385
411
  const seen = new Set();
386
- for (const spec of modules) {
412
+ for (const spec of modules || []) {
387
413
  if (!spec || !spec.name || seen.has(spec.name)) {
388
414
  continue;
389
415
  }
@@ -391,20 +417,65 @@ function insertModuleRequires(luau, modules, fromOutRel) {
391
417
  continue;
392
418
  }
393
419
  seen.add(spec.name);
420
+ const { root } = parseOutRel(spec.outRel);
421
+ if (root) {
422
+ services.add(root.service);
423
+ }
394
424
  lines.push(`const ${spec.name} = ${robloxRequireFrom(fromOutRel, spec.outRel)}`);
395
425
  for (const name of (spec.exports && spec.exports.consts) || []) {
396
426
  if (name !== spec.name) {
397
427
  lines.push(`const ${name} = ${spec.name}.${name}`);
398
428
  }
399
429
  }
400
- if ((spec.exports && spec.exports.structs || []).includes(spec.name) && !spec.impl) {
401
- lines.push(`type ${spec.name} = typeof(${spec.name}())`);
430
+ if (((spec.exports && spec.exports.structs) || []).includes(spec.name) || (spec.exports && spec.exports.hasProtos)) {
431
+ lines.push(`type ${spec.name} = ${spec.name}.${spec.name}`);
402
432
  }
403
433
  }
404
- if (lines.length === 0) {
434
+ return { lines, services };
435
+ }
436
+
437
+ function insertModuleRequires(luau, modules, fromOutRel) {
438
+ return insertPreamble(luau, modules, [], fromOutRel);
439
+ }
440
+
441
+ function stripServiceGets(luau, services) {
442
+ let next = String(luau);
443
+ for (const name of services) {
444
+ const line = SERVICE_GET[name];
445
+ if (!line) {
446
+ continue;
447
+ }
448
+ next = next.replace(new RegExp(`^${line.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "m"), "");
449
+ }
450
+ return next;
451
+ }
452
+
453
+ function insertPreamble(luau, modules, libraries, fromOutRel) {
454
+ const text = String(luau || "");
455
+ const trimmed = text.trim();
456
+ if (trimmed.startsWith("{") || trimmed.startsWith("[") || /\.json$/i.test(String(fromOutRel || ""))) {
405
457
  return luau;
406
458
  }
407
- return insertBlock(luau, lines.join("\n"));
459
+ const { lines: moduleLines, services } = moduleRequireLines(modules, fromOutRel, luau);
460
+ const libList = libraries || [];
461
+ if (libList.length) {
462
+ services.add("ReplicatedStorage");
463
+ }
464
+ if (moduleLines.length === 0 && libList.length === 0) {
465
+ return luau;
466
+ }
467
+ const { api: libApi, types: libTypes } = emitLibraryLines(libList);
468
+ const blockLines = [];
469
+ for (const name of SERVICE_ORDER) {
470
+ if (services.has(name)) {
471
+ blockLines.push(SERVICE_GET[name]);
472
+ }
473
+ }
474
+ blockLines.push(...moduleLines);
475
+ blockLines.push(...libApi);
476
+ blockLines.push(...libTypes);
477
+ const body = stripServiceGets(luau, services);
478
+ return insertBlock(body, blockLines.join("\n"));
408
479
  }
409
480
 
410
481
  function insertBlock(luau, block) {
@@ -417,8 +488,9 @@ function insertBlock(luau, block) {
417
488
  return `${luau.slice(0, insertAt)}\n${block}\n${rest}`;
418
489
  }
419
490
 
420
- module.exports = {
491
+ export {
421
492
  MODULES,
493
+ INCLUDE_TO_MODULE,
422
494
  TYPE_EXPORTS,
423
495
  EXTRA_TYPE_EXPORTS,
424
496
  LIBRARY_TYPES,
@@ -431,4 +503,5 @@ module.exports = {
431
503
  emitRequires,
432
504
  insertRequires,
433
505
  insertModuleRequires,
506
+ insertPreamble,
434
507
  };
package/src/lsp.ts ADDED
@@ -0,0 +1,60 @@
1
+ import path from "node:path";
2
+ import {
3
+ createConnection,
4
+ ProposedFeatures,
5
+ TextDocuments,
6
+ TextDocumentSyncKind,
7
+ } from "vscode-languageserver/node";
8
+ import { TextDocument } from "vscode-languageserver-textdocument";
9
+ import { diagnosticsFor } from "./intellisense.js";
10
+ import { pkg } from "./package-info.js";
11
+
12
+ function uriPath(uri: string): string {
13
+ let file = String(uri || "").replace(/^file:\/\//, "");
14
+ if (/^\/[A-Za-z]:/.test(file)) {
15
+ file = file.slice(1);
16
+ }
17
+ return decodeURIComponent(file);
18
+ }
19
+
20
+ export function start(options: { projectRoot?: string } = {}): void {
21
+ const projectRoot = options.projectRoot || process.cwd();
22
+ const connection = createConnection(ProposedFeatures.all);
23
+ const documents = new TextDocuments(TextDocument);
24
+
25
+ connection.onInitialize(() => ({
26
+ capabilities: {
27
+ textDocumentSync: TextDocumentSyncKind.Full,
28
+ },
29
+ serverInfo: { name: "cluaupp", version: pkg.version },
30
+ }));
31
+
32
+ const publish = (document: TextDocument) => {
33
+ const file = uriPath(document.uri);
34
+ const items = diagnosticsFor(document.getText(), file, {
35
+ filePath: file,
36
+ includeDirs: [path.dirname(file), path.join(projectRoot, "src"), path.join(projectRoot, "include")],
37
+ });
38
+ connection.sendDiagnostics({
39
+ uri: document.uri,
40
+ diagnostics: items.map((item) => ({
41
+ range: {
42
+ start: { line: Math.max(0, (item.line || 1) - 1), character: Math.max(0, (item.col || 1) - 1) },
43
+ end: { line: Math.max(0, (item.line || 1) - 1), character: Math.max(1, item.col || 1) },
44
+ },
45
+ severity: 1,
46
+ source: "cluaupp",
47
+ message: item.message,
48
+ })),
49
+ });
50
+ };
51
+
52
+ documents.onDidOpen((event) => publish(event.document));
53
+ documents.onDidChangeContent((event) => publish(event.document));
54
+ documents.onDidClose((event) => {
55
+ connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
56
+ });
57
+
58
+ documents.listen(connection);
59
+ connection.listen();
60
+ }
@@ -0,0 +1,7 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+
4
+ export const projectRoot = path.resolve(__dirname, "..");
5
+
6
+ const fromPkg = createRequire(path.join(projectRoot, "package.json"));
7
+ export const pkg = fromPkg("./package.json") as { name: string; version: string };
@@ -1,6 +1,5 @@
1
- "use strict";
2
-
3
- const { tokenize } = require("./lex");
1
+ // @ts-nocheck
2
+ import { tokenize } from "./lex.js";
4
3
 
5
4
  function parse(source, fileName) {
6
5
  const tokens = tokenize(source);
@@ -628,4 +627,4 @@ function parse(source, fileName) {
628
627
  return { type: "program", body: parseProgramInside(), fileName };
629
628
  }
630
629
 
631
- module.exports = { parse };
630
+ export { parse };