cluaupp 1.5.2 → 2.0.0-beta.1

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 (195) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +27 -40
  3. package/api/generated/bench-latest.json +43 -21
  4. package/api/generated/lsp-index.json +1 -1
  5. package/api/manifests/roblox-target.manifest.json +5 -5
  6. package/api/normalized/roblox-target.profile.json +8729 -267
  7. package/api/roblox/luau-builtins.d.luau +185 -0
  8. package/api/roblox/surface.json +1 -0
  9. package/api/roblox-api.lock.json +11 -2
  10. package/bin/cluaupp.js +38 -0
  11. package/editors/vscode/syntaxes/clpp.tmLanguage.json +211 -0
  12. package/examples/README.md +9 -9
  13. package/examples/game/cluaupp.config.json +2 -2
  14. package/examples/game/default.project.json +4 -15
  15. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
  16. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
  17. package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
  18. package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
  19. package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
  20. package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
  21. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
  22. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
  23. package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  24. package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
  25. package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
  26. package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  27. package/generated/api/build-profile.js +1 -1
  28. package/generated/api/loader.js +2 -2
  29. package/generated/api/surface/build.js +328 -0
  30. package/generated/api/surface/luau-defs.js +595 -0
  31. package/generated/api/surface/model.js +8 -0
  32. package/generated/clpp/contract.js +6 -3
  33. package/generated/clpp/handshake.js +36 -0
  34. package/generated/clpp/modules.js +20 -9
  35. package/generated/clpp/runner.js +62 -31
  36. package/generated/compile.js +18 -1
  37. package/generated/compiler/ast.js +3 -0
  38. package/generated/compiler/checker/calls.js +999 -0
  39. package/generated/compiler/checker/checker.js +1215 -0
  40. package/generated/compiler/checker/exprs.js +791 -0
  41. package/generated/compiler/checker/flow.js +168 -0
  42. package/generated/compiler/checker/members.js +763 -0
  43. package/generated/compiler/checker/statements.js +792 -0
  44. package/generated/compiler/checker/symbols.js +90 -0
  45. package/generated/compiler/checker/typeops.js +348 -0
  46. package/generated/compiler/checker/yields.js +115 -0
  47. package/generated/compiler/diagnostics.js +138 -0
  48. package/generated/compiler/emit/emitter.js +2356 -0
  49. package/generated/compiler/emit/luau-types.js +137 -0
  50. package/generated/compiler/emit/names.js +130 -0
  51. package/generated/compiler/emit/runtime.js +432 -0
  52. package/generated/compiler/lexer.js +403 -0
  53. package/generated/compiler/parser.js +1881 -0
  54. package/generated/compiler/project.js +332 -0
  55. package/generated/compiler/roblox/api.js +388 -0
  56. package/generated/compiler/roblox/context.js +100 -0
  57. package/generated/compiler/source.js +59 -0
  58. package/generated/compiler/stdlib.js +232 -0
  59. package/generated/compiler/types.js +250 -0
  60. package/generated/target/build-cache.js +4 -4
  61. package/generated/target/gen-rojo-tree.js +15 -0
  62. package/generated/target/source-map.js +61 -8
  63. package/generated/target/studio-bridge.js +1 -0
  64. package/generated/utils/project.js +83 -25
  65. package/native/bin/.gitkeep +1 -0
  66. package/package.json +17 -6
  67. package/plugins/CluauppNav/Plugin.luau +28 -4
  68. package/rust/cluaupp/Cargo.lock +795 -0
  69. package/rust/cluaupp/Cargo.toml +17 -0
  70. package/rust/cluaupp/src/main.rs +717 -0
  71. package/scripts/postinstall-native.js +56 -0
  72. package/skills/README.md +3 -3
  73. package/skills/clpp-hive-flare/SKILL.md +30 -26
  74. package/skills/clpp-language/SKILL.md +67 -74
  75. package/skills/clpp-language/compiler.md +24 -36
  76. package/skills/clpp-style/SKILL.md +31 -44
  77. package/src/api/build-profile.ts +1 -1
  78. package/src/api/loader.ts +2 -2
  79. package/src/api/surface/build.ts +374 -0
  80. package/src/api/surface/luau-defs.ts +635 -0
  81. package/src/api/surface/model.ts +96 -0
  82. package/src/clpp/contract.ts +20 -3
  83. package/src/clpp/handshake.ts +25 -0
  84. package/src/clpp/modules.ts +17 -10
  85. package/src/clpp/runner.ts +52 -34
  86. package/src/compile.ts +20 -2
  87. package/src/compiler/ast.ts +336 -0
  88. package/src/compiler/checker/calls.ts +1025 -0
  89. package/src/compiler/checker/checker.ts +1352 -0
  90. package/src/compiler/checker/exprs.ts +805 -0
  91. package/src/compiler/checker/flow.ts +174 -0
  92. package/src/compiler/checker/members.ts +793 -0
  93. package/src/compiler/checker/statements.ts +802 -0
  94. package/src/compiler/checker/symbols.ts +219 -0
  95. package/src/compiler/checker/typeops.ts +355 -0
  96. package/src/compiler/checker/yields.ts +120 -0
  97. package/src/compiler/diagnostics.ts +163 -0
  98. package/src/compiler/emit/emitter.ts +2486 -0
  99. package/src/compiler/emit/luau-types.ts +143 -0
  100. package/src/compiler/emit/names.ts +130 -0
  101. package/src/compiler/emit/runtime.ts +438 -0
  102. package/src/compiler/lexer.ts +444 -0
  103. package/src/compiler/parser.ts +1943 -0
  104. package/src/compiler/project.ts +354 -0
  105. package/src/compiler/roblox/api.ts +408 -0
  106. package/src/compiler/roblox/context.ts +108 -0
  107. package/src/compiler/source.ts +67 -0
  108. package/src/compiler/stdlib.ts +256 -0
  109. package/src/compiler/types.ts +313 -0
  110. package/src/target/build-cache.ts +5 -8
  111. package/src/target/gen-rojo-tree.ts +15 -0
  112. package/src/target/source-map.ts +65 -27
  113. package/src/target/studio-bridge.ts +3 -0
  114. package/src/types.ts +3 -0
  115. package/src/utils/project.ts +79 -31
  116. package/templates/README.md +7 -0
  117. package/templates/{game → game-modern}/cluaupp.config.json +2 -2
  118. package/templates/{game → game-modern}/package.json +1 -2
  119. package/templates/game-modern/rokit.toml +2 -0
  120. package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  121. package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
  122. package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
  123. package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  124. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
  125. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
  126. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
  127. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
  128. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
  129. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
  130. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
  131. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
  132. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
  133. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
  134. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
  135. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
  136. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
  137. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
  138. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
  139. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
  140. package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
  141. package/examples/game/src/Client/Main.client.clpp +0 -4
  142. package/examples/game/src/Declarations/Net.flare +0 -5
  143. package/examples/game/src/Include/GameTypes.clh +0 -40
  144. package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
  145. package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
  146. package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  147. package/examples/game/src/Server/Main.server.clpp +0 -4
  148. package/templates/game/.clangd +0 -25
  149. package/templates/game/.vscode/extensions.json +0 -8
  150. package/templates/game/.vscode/settings.json +0 -17
  151. package/templates/game/Packages/.gitkeep +0 -0
  152. package/templates/game/compile_flags.txt +0 -6
  153. package/templates/game/default.project.json +0 -41
  154. package/templates/game/include/clpp/datatypes.clh +0 -575
  155. package/templates/game/include/clpp/generated/enums.clh +0 -4920
  156. package/templates/game/include/clpp/generated/instances.clh +0 -87182
  157. package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
  158. package/templates/game/include/clpp/libs/axiom.clh +0 -154
  159. package/templates/game/include/clpp/libs/bloom.clh +0 -58
  160. package/templates/game/include/clpp/libs/coil.clh +0 -37
  161. package/templates/game/include/clpp/libs/crest.clh +0 -22
  162. package/templates/game/include/clpp/libs/echo.clh +0 -52
  163. package/templates/game/include/clpp/libs/ember.clh +0 -17
  164. package/templates/game/include/clpp/libs/flare.clh +0 -65
  165. package/templates/game/include/clpp/libs/gleam.clh +0 -111
  166. package/templates/game/include/clpp/libs/guide.clh +0 -42
  167. package/templates/game/include/clpp/libs/helm.clh +0 -31
  168. package/templates/game/include/clpp/libs/hive.clh +0 -20
  169. package/templates/game/include/clpp/libs/keep.clh +0 -158
  170. package/templates/game/include/clpp/libs/lens.clh +0 -24
  171. package/templates/game/include/clpp/libs/mint.clh +0 -28
  172. package/templates/game/include/clpp/libs/net.clh +0 -31
  173. package/templates/game/include/clpp/libs/occlude.clh +0 -15
  174. package/templates/game/include/clpp/libs/pin.clh +0 -28
  175. package/templates/game/include/clpp/libs/promise.clh +0 -24
  176. package/templates/game/include/clpp/libs/roster.clh +0 -123
  177. package/templates/game/include/clpp/libs/shift.clh +0 -22
  178. package/templates/game/include/clpp/libs/spark.clh +0 -28
  179. package/templates/game/include/clpp/libs/stage.clh +0 -23
  180. package/templates/game/include/clpp/libs/sweep.clh +0 -23
  181. package/templates/game/include/clpp/libs/trace.clh +0 -18
  182. package/templates/game/include/clpp/libs/ward.clh +0 -19
  183. package/templates/game/include/clpp/libs.clh +0 -31
  184. package/templates/game/include/clpp/roblox.clh +0 -41
  185. package/templates/game/rokit.toml +0 -5
  186. package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
  187. package/templates/game/src/Client/Main.client.clpp +0 -4
  188. package/templates/game/src/Declarations/Net.flare +0 -5
  189. package/templates/game/src/Include/GameTypes.clh +0 -40
  190. package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
  191. package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
  192. package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  193. package/templates/game/src/Server/Main.server.clpp +0 -4
  194. package/templates/game/stylua.toml +0 -9
  195. package/templates/game/wally.toml +0 -12
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Builds `api/roblox/surface.json` from:
3
+ * 1. luau-lsp Roblox definitions (`globalTypes.*.d.luau`) — types, nullability, overloads, operators;
4
+ * 2. `api/roblox/luau-builtins.d.luau` — math, string, table, coroutine, bit32, buffer, os, vector, globals;
5
+ * 3. the Roblox API dump profile (`api/normalized/roblox-target.profile.json`) — security, read-only,
6
+ * creatable/service, deprecation tags and integer-typed members.
7
+ *
8
+ * Run with `cluaupp api surface --defs <globalTypes.d.luau>`.
9
+ */
10
+
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ import { parseDefinitions, type DefinitionFile, type ExternType } from "./luau-defs.js";
14
+ import type { LFn, LType, RobloxSurface, SurfaceClass, SurfaceDatatype, SurfaceMember, SurfaceStatic } from "./model.js";
15
+ import { projectRoot } from "../../package-info.js";
16
+
17
+ interface ProfileMember {
18
+ name: string;
19
+ writable?: boolean;
20
+ readable?: boolean;
21
+ readSecurity?: string;
22
+ writeSecurity?: string;
23
+ security?: string;
24
+ tags?: Array<string | Record<string, unknown>>;
25
+ valueType?: { kind: string; name?: string };
26
+ returnTypes?: Array<{ kind: string; name?: string }>;
27
+ canYield?: boolean;
28
+ deprecated?: { deprecated: boolean; replacement?: string };
29
+ }
30
+
31
+ interface ProfileClass {
32
+ name: string;
33
+ superclass: string | null;
34
+ tags: string[];
35
+ creatable: boolean;
36
+ service: boolean;
37
+ creation?: { kind: string; abstract?: boolean; pluginOnly?: boolean };
38
+ properties: ProfileMember[];
39
+ methods: ProfileMember[];
40
+ events: ProfileMember[];
41
+ callbacks: ProfileMember[];
42
+ }
43
+
44
+ interface Profile {
45
+ classes: Record<string, ProfileClass>;
46
+ enums: Record<string, { items: Array<{ name: string; tags?: string[] }> }>;
47
+ }
48
+
49
+ const INT_PRIMITIVES = new Set(["int", "int64", "int32", "int16", "int8"]);
50
+
51
+ function hasTag(m: ProfileMember | ProfileClass, tag: string): boolean {
52
+ return (m.tags || []).some((t) => t === tag);
53
+ }
54
+
55
+ function isSignal(t: LType | undefined): t is Extract<LType, { k: "name" }> {
56
+ return !!t && t.k === "name" && t.name === "RBXScriptSignal";
57
+ }
58
+
59
+ function intify(t: LType): LType {
60
+ // keep the union wide: callers store the result back into an LType slot
61
+ if (t.k === "name" && t.name === "number") {
62
+ return { k: "name", name: "int" };
63
+ }
64
+ if (t.k === "opt") {
65
+ return { k: "opt", t: intify(t.t) };
66
+ }
67
+ return t;
68
+ }
69
+
70
+ function security(level: string | undefined): string | undefined {
71
+ return level && level !== "None" ? level : undefined;
72
+ }
73
+
74
+ function convertMembers(ext: ExternType, profileClass: ProfileClass | undefined): Record<string, SurfaceMember> {
75
+ const members: Record<string, SurfaceMember> = {};
76
+ const pProps = new Map((profileClass?.properties || []).map((p) => [p.name, p]));
77
+ const pMethods = new Map<string, ProfileMember[]>();
78
+ for (const m of profileClass?.methods || []) {
79
+ const list = pMethods.get(m.name) || [];
80
+ list.push(m);
81
+ pMethods.set(m.name, list);
82
+ }
83
+ const pEvents = new Map((profileClass?.events || []).map((p) => [p.name, p]));
84
+ const pCallbacks = new Map((profileClass?.callbacks || []).map((p) => [p.name, p]));
85
+
86
+ for (const m of ext.members) {
87
+ if (m.fn) {
88
+ if (m.name.startsWith("__")) {
89
+ continue;
90
+ }
91
+ const existing = members[m.name];
92
+ if (existing && existing.kind === "method") {
93
+ existing.overloads!.push(m.fn);
94
+ continue;
95
+ }
96
+ const pm = pMethods.get(m.name)?.[0];
97
+ const member: SurfaceMember = { kind: "method", overloads: [m.fn] };
98
+ if (pm) {
99
+ const ret = pm.returnTypes?.[0];
100
+ if (ret && ret.kind === "Primitive" && INT_PRIMITIVES.has(ret.name || "") && m.fn.returns.length === 1) {
101
+ m.fn.returns[0] = intify(m.fn.returns[0]) as (typeof m.fn.returns)[number];
102
+ }
103
+ const sec = security(pm.security);
104
+ if (sec) {
105
+ member.security = sec;
106
+ }
107
+ if (pm.canYield || hasTag(pm, "Yields") || hasTag(pm, "CanYield")) {
108
+ member.yields = true;
109
+ }
110
+ if (hasTag(pm, "NotScriptable")) {
111
+ member.notScriptable = true;
112
+ }
113
+ } else if (/Async$/.test(m.name)) {
114
+ member.yields = true;
115
+ }
116
+ if (m.deprecated) {
117
+ member.deprecated = m.deprecated;
118
+ }
119
+ members[m.name] = member;
120
+ continue;
121
+ }
122
+ const type = m.type!;
123
+ if (isSignal(type)) {
124
+ const args = (type.args || []).filter((a) => !(a.k === "name" && a.name === "()"));
125
+ const member: SurfaceMember = { kind: "event", args };
126
+ const pe = pEvents.get(m.name);
127
+ const sec = security(pe?.security);
128
+ if (sec) {
129
+ member.security = sec;
130
+ }
131
+ if (m.deprecated) {
132
+ member.deprecated = m.deprecated;
133
+ }
134
+ members[m.name] = member;
135
+ continue;
136
+ }
137
+ const pc = pCallbacks.get(m.name);
138
+ if (pc || (type.k === "fn" && !pProps.has(m.name))) {
139
+ const member: SurfaceMember = { kind: "callback", type };
140
+ const sec = security(pc?.security);
141
+ if (sec) {
142
+ member.security = sec;
143
+ }
144
+ members[m.name] = member;
145
+ continue;
146
+ }
147
+ const pp = pProps.get(m.name);
148
+ let propType: LType = type;
149
+ if (pp?.valueType?.kind === "Primitive" && INT_PRIMITIVES.has(pp.valueType.name || "")) {
150
+ propType = intify(propType);
151
+ }
152
+ const member: SurfaceMember = { kind: "property", type: propType };
153
+ if (pp) {
154
+ if (pp.writable === false || hasTag(pp, "ReadOnly")) {
155
+ member.readonly = true;
156
+ }
157
+ const rs = security(pp.readSecurity);
158
+ const ws = security(pp.writeSecurity);
159
+ if (rs) {
160
+ member.security = rs;
161
+ }
162
+ if (ws) {
163
+ member.writeSecurity = ws;
164
+ }
165
+ if (hasTag(pp, "NotScriptable")) {
166
+ member.notScriptable = true;
167
+ }
168
+ if (hasTag(pp, "NotReplicated")) {
169
+ member.notReplicated = true;
170
+ }
171
+ }
172
+ if (m.deprecated) {
173
+ member.deprecated = m.deprecated;
174
+ }
175
+ members[m.name] = member;
176
+ }
177
+ return members;
178
+ }
179
+
180
+ function staticsFrom(type: LType): Record<string, SurfaceStatic> {
181
+ const out: Record<string, SurfaceStatic> = {};
182
+ const tables: LType[] = type.k === "inter" ? type.ts : [type];
183
+ for (const t of tables) {
184
+ if (t.k !== "table") {
185
+ continue;
186
+ }
187
+ for (const [name, value] of Object.entries(t.fields)) {
188
+ if (value.k === "fn") {
189
+ out[name] = { kind: "function", overloads: [value] };
190
+ } else if (value.k === "inter" && value.ts.every((x) => x.k === "fn")) {
191
+ out[name] = { kind: "function", overloads: value.ts as LFn[] };
192
+ } else {
193
+ out[name] = { kind: "value", type: value };
194
+ }
195
+ }
196
+ }
197
+ return out;
198
+ }
199
+
200
+ function datatypeFrom(ext: ExternType, statics: LType | undefined): SurfaceDatatype {
201
+ const operators: Record<string, LFn[]> = {};
202
+ for (const m of ext.members) {
203
+ if (m.fn && m.name.startsWith("__")) {
204
+ (operators[m.name] ||= []).push(m.fn);
205
+ }
206
+ }
207
+ return {
208
+ name: ext.name,
209
+ members: convertMembers(ext, undefined),
210
+ operators,
211
+ statics: statics ? staticsFrom(statics) : {},
212
+ };
213
+ }
214
+
215
+ export interface BuildSurfaceOptions {
216
+ definitions: string;
217
+ builtins?: string;
218
+ profile?: string;
219
+ }
220
+
221
+ export function buildSurface(options: BuildSurfaceOptions): RobloxSurface {
222
+ const defsText = fs.readFileSync(options.definitions, "utf8");
223
+ const builtinsPath = options.builtins || path.join(projectRoot, "api", "roblox", "luau-builtins.d.luau");
224
+ const profilePath = options.profile || path.join(projectRoot, "api", "normalized", "roblox-target.profile.json");
225
+ const roblox = parseDefinitions(defsText);
226
+ const builtins = parseDefinitions(fs.readFileSync(builtinsPath, "utf8"));
227
+ const profile: Profile = fs.existsSync(profilePath) ? JSON.parse(fs.readFileSync(profilePath, "utf8")) : { classes: {}, enums: {} };
228
+ return assemble(roblox, builtins, profile, {
229
+ definitions: path.basename(options.definitions),
230
+ builtins: path.basename(builtinsPath),
231
+ profile: fs.existsSync(profilePath) ? path.basename(profilePath) : "none",
232
+ });
233
+ }
234
+
235
+ function assemble(roblox: DefinitionFile, builtins: DefinitionFile, profile: Profile, sources: Record<string, string>): RobloxSurface {
236
+ const surface: RobloxSurface = {
237
+ schema: 1,
238
+ generatedAt: new Date().toISOString().slice(0, 10),
239
+ sources,
240
+ classes: {},
241
+ datatypes: {},
242
+ enums: {},
243
+ globals: {},
244
+ libraries: {},
245
+ aliases: {},
246
+ };
247
+ const declared = new Map<string, { type: LType; deprecated?: string | true }>();
248
+ for (const d of [...roblox.declares, ...builtins.declares]) {
249
+ declared.set(d.name, d);
250
+ }
251
+ const externByName = new Map<string, ExternType>();
252
+ for (const ext of roblox.externs) {
253
+ externByName.set(ext.name, ext);
254
+ }
255
+ const isClass = (name: string | null): boolean => {
256
+ let cur = name;
257
+ let guard = 0;
258
+ while (cur && guard++ < 64) {
259
+ if (cur === "Object" || cur === "Instance") {
260
+ return true;
261
+ }
262
+ cur = externByName.get(cur)?.extends ?? null;
263
+ }
264
+ return false;
265
+ };
266
+ const services = new Set<string>((roblox.metadata.SERVICES as string[]) || []);
267
+ const creatable = new Set<string>((roblox.metadata.CREATABLE_INSTANCES as string[]) || []);
268
+
269
+ for (const ext of roblox.externs) {
270
+ if (ext.name.startsWith("Enum") && ext.name.endsWith("_INTERNAL") && ext.extends === "Enum") {
271
+ const enumName = ext.name.slice(4, -"_INTERNAL".length);
272
+ const items: string[] = [];
273
+ const deprecated: string[] = [];
274
+ for (const m of ext.members) {
275
+ if (m.type) {
276
+ items.push(m.name);
277
+ if (m.deprecated) {
278
+ deprecated.push(m.name);
279
+ }
280
+ }
281
+ }
282
+ surface.enums[enumName] = { name: enumName, items, ...(deprecated.length ? { deprecated } : {}) };
283
+ continue;
284
+ }
285
+ if (ext.extends === "EnumItem" || ext.name === "Enum" || ext.name === "EnumItem") {
286
+ continue;
287
+ }
288
+ if (ext.name === "Object" || isClass(ext.extends) || ext.name === "Instance") {
289
+ const pc = profile.classes[ext.name];
290
+ const cls: SurfaceClass = {
291
+ name: ext.name,
292
+ superclass: ext.extends,
293
+ members: convertMembers(ext, pc),
294
+ creatable: creatable.has(ext.name),
295
+ service: services.has(ext.name),
296
+ };
297
+ if (pc?.creation?.abstract) {
298
+ cls.abstract = true;
299
+ }
300
+ if (pc && hasTag(pc, "Deprecated")) {
301
+ cls.deprecated = true;
302
+ }
303
+ if (pc && hasTag(pc, "NotCreatable")) {
304
+ cls.notCreatable = true;
305
+ }
306
+ surface.classes[ext.name] = cls;
307
+ continue;
308
+ }
309
+ surface.datatypes[ext.name] = datatypeFrom(ext, declared.get(ext.name)?.type);
310
+ }
311
+
312
+ // Datatype-like statics without an extern body (e.g. `declare Instance: {…}` is handled by the checker).
313
+ for (const [name, d] of declared) {
314
+ if (surface.datatypes[name]) {
315
+ continue;
316
+ }
317
+ if (["Instance", "Enum", "game", "workspace", "script", "plugin", "shared", "task", "debug", "utf8"].includes(name)) {
318
+ continue;
319
+ }
320
+ if (/^[A-Z]/.test(name) && d.type.k === "table" && !surface.classes[name]) {
321
+ surface.datatypes[name] = { name, members: {}, operators: {}, statics: staticsFrom(d.type) };
322
+ }
323
+ }
324
+
325
+ for (const [name, d] of declared) {
326
+ if (d.type.k === "table" && /^[a-z]/.test(name)) {
327
+ surface.libraries[name] = staticsFrom(d.type);
328
+ continue;
329
+ }
330
+ if (d.type.k === "fn") {
331
+ surface.globals[name] = { overloads: [d.type], ...(d.deprecated ? { deprecated: d.deprecated } : {}) };
332
+ continue;
333
+ }
334
+ if (["game", "workspace", "script", "plugin", "shared", "Enum"].includes(name)) {
335
+ surface.globals[name] = { type: d.type };
336
+ }
337
+ }
338
+ surface.globals.Instance = { type: declared.get("Instance")!.type };
339
+
340
+ for (const [name, t] of Object.entries(roblox.aliases)) {
341
+ if (name === "ENUM_LIST" || name.startsWith("Enum")) {
342
+ continue;
343
+ }
344
+ surface.aliases[name] = t;
345
+ }
346
+ return surface;
347
+ }
348
+
349
+ export function writeSurface(surface: RobloxSurface, dest = path.join(projectRoot, "api", "roblox", "surface.json")): string {
350
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
351
+ fs.writeFileSync(dest, `${JSON.stringify(surface)}\n`, "utf8");
352
+ return dest;
353
+ }
354
+
355
+ /** Default location of the luau-lsp definitions downloaded by the VS Code extension. */
356
+ export function findLuauLspDefinitions(): string | null {
357
+ const roots = [
358
+ process.env.APPDATA && path.join(process.env.APPDATA, "Code", "User", "globalStorage", "johnnymorganz.luau-lsp"),
359
+ process.env.APPDATA && path.join(process.env.APPDATA, "Cursor", "User", "globalStorage", "johnnymorganz.luau-lsp"),
360
+ process.env.HOME && path.join(process.env.HOME, ".config", "Code", "User", "globalStorage", "johnnymorganz.luau-lsp"),
361
+ process.env.HOME && path.join(process.env.HOME, "Library", "Application Support", "Code", "User", "globalStorage", "johnnymorganz.luau-lsp"),
362
+ ].filter(Boolean) as string[];
363
+ for (const root of roots) {
364
+ if (!fs.existsSync(root)) {
365
+ continue;
366
+ }
367
+ const files = fs.readdirSync(root).filter((f) => /^globalTypes.*\.d\.luau$/.test(f));
368
+ const preferred = files.find((f) => /PluginSecurity/.test(f)) || files[0];
369
+ if (preferred) {
370
+ return path.join(root, preferred);
371
+ }
372
+ }
373
+ return null;
374
+ }