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,354 @@
1
+ /**
2
+ * Project compilation: maps `Src/` onto the Roblox DataModel, builds the typed `@game` tree,
3
+ * resolves `link`s into a module graph (each module checked once, cycles reported) and emits Luau.
4
+ *
5
+ * Layout — folders named like a service map onto that service:
6
+ * Src/ServerScriptService/… → game.ServerScriptService
7
+ * Src/ReplicatedStorage/Shared/… → game.ReplicatedStorage.Shared
8
+ * Src/StarterPlayer/StarterPlayerScripts → game.StarterPlayer.StarterPlayerScripts
9
+ * Legacy domain folders still work: Server → ServerScriptService, Client → StarterPlayerScripts,
10
+ * anything else → ReplicatedStorage/<folder>.
11
+ */
12
+
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import type { LinkDecl } from "./ast.js";
16
+ import { Checker, type FileInfo, type LinkInfo } from "./checker/checker.js";
17
+ import type { LinkTarget, ModuleExports } from "./checker/symbols.js";
18
+ import { DiagnosticBag, type Diagnostic } from "./diagnostics.js";
19
+ import { emitLuau } from "./emit/emitter.js";
20
+ import { isIdentifier, luauString } from "./emit/names.js";
21
+ import { parse } from "./parser.js";
22
+ import { robloxApi, type RobloxApi } from "./roblox/api.js";
23
+ import { runContextOf, type RunContext } from "./roblox/context.js";
24
+ import { SourceFile } from "./source.js";
25
+ import { DESKTOP_ONLY, STD_PATHS } from "./stdlib.js";
26
+ import type { DataModelNode } from "./types.js";
27
+
28
+ export const SOURCE_EXT = ".clp";
29
+ export const LIBS_FOLDER = ["ReplicatedStorage", "CluauppLibs"];
30
+
31
+ const LEGACY_FOLDERS: Record<string, string[]> = {
32
+ Server: ["ServerScriptService"],
33
+ Client: ["StarterPlayer", "StarterPlayerScripts"],
34
+ Character: ["StarterPlayer", "StarterCharacterScripts"],
35
+ Gui: ["StarterGui"],
36
+ };
37
+
38
+ const NESTED_SERVICES: Record<string, string[]> = {
39
+ StarterPlayerScripts: ["StarterPlayer", "StarterPlayerScripts"],
40
+ StarterCharacterScripts: ["StarterPlayer", "StarterCharacterScripts"],
41
+ };
42
+
43
+ export interface ProjectOptions {
44
+ root: string;
45
+ srcDir: string;
46
+ strict: boolean;
47
+ version: string;
48
+ api?: RobloxApi;
49
+ }
50
+
51
+ export interface CompiledFile {
52
+ file: string;
53
+ rel: string;
54
+ outRel: string;
55
+ luau: string | null;
56
+ sourceMap?: import("../clpp/contract.js").SourceMapLine[];
57
+ diagnostics: Diagnostic[];
58
+ source: SourceFile;
59
+ info: FileInfo | null;
60
+ }
61
+
62
+ export function scriptStem(fileName: string): string {
63
+ return path.basename(fileName).replace(/\.(server|client|plugin)?\.?clp$/i, "").replace(/\.clp$/i, "");
64
+ }
65
+
66
+ export function outputName(rel: string): string {
67
+ return rel.replace(/\.clp$/i, ".luau");
68
+ }
69
+
70
+ /** DataModel path of a source file, e.g. `ReplicatedStorage/Shared/Combat.clp` → ["ReplicatedStorage", "Shared", "Combat"]. */
71
+ export function dataModelPath(api: RobloxApi, rel: string): string[] {
72
+ const parts = rel.replace(/\\/g, "/").split("/");
73
+ const file = parts.pop()!;
74
+ const stem = scriptStem(file);
75
+ const first = parts[0];
76
+ let base: string[];
77
+ let rest: string[];
78
+ if (!first) {
79
+ base = ["ReplicatedStorage"];
80
+ rest = [];
81
+ } else if (NESTED_SERVICES[first]) {
82
+ base = NESTED_SERVICES[first];
83
+ rest = parts.slice(1);
84
+ } else if (first === "StarterPlayer" && NESTED_SERVICES[parts[1]]) {
85
+ base = NESTED_SERVICES[parts[1]];
86
+ rest = parts.slice(2);
87
+ } else if (api.isService(first)) {
88
+ base = [first];
89
+ rest = parts.slice(1);
90
+ } else if (LEGACY_FOLDERS[first]) {
91
+ base = LEGACY_FOLDERS[first];
92
+ rest = parts.slice(1);
93
+ } else {
94
+ base = ["ReplicatedStorage"];
95
+ rest = parts;
96
+ }
97
+ return [...base, ...rest, stem];
98
+ }
99
+
100
+ export function classForFile(fileName: string): string {
101
+ const ctx = runContextOf(fileName);
102
+ return ctx === "server" ? "Script" : ctx === "client" ? "LocalScript" : ctx === "plugin" ? "Script" : "ModuleScript";
103
+ }
104
+
105
+ export function collectClp(dir: string, out: string[] = []): string[] {
106
+ if (!fs.existsSync(dir)) {
107
+ return out;
108
+ }
109
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
110
+ const full = path.join(dir, entry.name);
111
+ if (entry.isDirectory()) {
112
+ collectClp(full, out);
113
+ } else if (entry.name.toLowerCase().endsWith(SOURCE_EXT)) {
114
+ out.push(full);
115
+ }
116
+ }
117
+ return out;
118
+ }
119
+
120
+ /** Typed `game` tree: services → folders → scripts compiled from Src, plus CluauppLibs. */
121
+ export function buildDataModel(api: RobloxApi, srcDir: string, files: string[], libs: string[] = []): DataModelNode {
122
+ const root: DataModelNode = { name: "game", className: "DataModel", path: [], children: new Map() };
123
+ const ensure = (segments: string[], leafClass: string, sourceFile?: string): void => {
124
+ let node = root;
125
+ segments.forEach((seg, i) => {
126
+ let child = node.children.get(seg);
127
+ const isLeaf = i === segments.length - 1;
128
+ if (!child) {
129
+ const cls = i === 0 && api.isService(seg) ? seg : isLeaf ? leafClass : i === 1 && segments[0] === "StarterPlayer" ? seg : "Folder";
130
+ child = { name: seg, className: cls, path: segments.slice(0, i + 1), children: new Map() };
131
+ node.children.set(seg, child);
132
+ }
133
+ if (isLeaf && sourceFile) {
134
+ child.className = leafClass;
135
+ child.sourceFile = sourceFile;
136
+ }
137
+ node = child;
138
+ });
139
+ };
140
+ for (const svc of ["Workspace", "Players", "ReplicatedStorage", "ReplicatedFirst", "ServerScriptService", "ServerStorage", "StarterGui", "StarterPack", "StarterPlayer", "Lighting", "SoundService", "Teams"]) {
141
+ ensure([svc], svc);
142
+ }
143
+ ensure(["StarterPlayer", "StarterPlayerScripts"], "StarterPlayerScripts");
144
+ ensure(["StarterPlayer", "StarterCharacterScripts"], "StarterCharacterScripts");
145
+ for (const file of files) {
146
+ const rel = path.relative(srcDir, file).replace(/\\/g, "/");
147
+ ensure(dataModelPath(api, rel), classForFile(file), path.resolve(file));
148
+ }
149
+ if (libs.length) {
150
+ ensure(LIBS_FOLDER, "Folder");
151
+ for (const lib of libs) {
152
+ ensure([...LIBS_FOLDER, lib], "ModuleScript");
153
+ }
154
+ }
155
+ return root;
156
+ }
157
+
158
+ /** Luau expression that reaches an Instance by its DataModel path, robust to replication order. */
159
+ export function instancePath(dm: string[], service: (name: string) => string, fromDm: string[] | null): string {
160
+ if (fromDm && fromDm.length > 1 && dm.length > 1) {
161
+ const fromParent = fromDm.slice(0, -1);
162
+ const targetParent = dm.slice(0, -1);
163
+ if (fromParent.join("/") === targetParent.join("/")) {
164
+ return `script.Parent${childAccess(dm[dm.length - 1])}`;
165
+ }
166
+ }
167
+ const [first, ...rest] = dm;
168
+ let expr = first === "Workspace" ? "workspace" : service(first);
169
+ for (const seg of rest) {
170
+ expr += childAccess(seg);
171
+ }
172
+ return expr;
173
+ }
174
+
175
+ function childAccess(name: string): string {
176
+ return `:WaitForChild(${luauString(name)})`;
177
+ }
178
+
179
+ export class ProjectCompiler {
180
+ readonly api: RobloxApi;
181
+ readonly files: string[];
182
+ readonly dataModel: DataModelNode;
183
+ private readonly results = new Map<string, CompiledFile>();
184
+ private readonly inProgress: string[] = [];
185
+ private readonly byDmPath = new Map<string, string>();
186
+ private readonly libs: string[];
187
+
188
+ constructor(private readonly options: ProjectOptions, libs: string[] = []) {
189
+ this.api = options.api ?? robloxApi();
190
+ this.files = collectClp(options.srcDir).map((f) => path.resolve(f));
191
+ this.libs = libs;
192
+ this.dataModel = buildDataModel(this.api, options.srcDir, this.files, libs);
193
+ for (const f of this.files) {
194
+ this.byDmPath.set(this.dmOf(f).join("/"), f);
195
+ }
196
+ }
197
+
198
+ rel(file: string): string {
199
+ return path.relative(this.options.srcDir, file).replace(/\\/g, "/");
200
+ }
201
+
202
+ dmOf(file: string): string[] {
203
+ return dataModelPath(this.api, this.rel(file));
204
+ }
205
+
206
+ compileAll(): CompiledFile[] {
207
+ return this.files.map((f) => this.compile(f));
208
+ }
209
+
210
+ compile(file: string, text?: string): CompiledFile {
211
+ const key = path.resolve(file);
212
+ const cached = this.results.get(key);
213
+ if (cached && text === undefined) {
214
+ return cached;
215
+ }
216
+ const source = new SourceFile(this.rel(key), text ?? fs.readFileSync(key, "utf8"));
217
+ const diags = new DiagnosticBag(source);
218
+ const rel = this.rel(key);
219
+ const context = runContextOf(key);
220
+ const kind = context === "shared" ? "module" : "script";
221
+ this.inProgress.push(key);
222
+ const program = parse(source, diags);
223
+ const dmPath = this.dmOf(key);
224
+ let info: FileInfo | null = null;
225
+ try {
226
+ info = new Checker({
227
+ program,
228
+ source,
229
+ diags,
230
+ api: this.api,
231
+ moduleId: key,
232
+ kind,
233
+ context,
234
+ strict: this.options.strict,
235
+ resolveLink: (link) => this.resolveLink(link, key, diags),
236
+ dataModel: this.dataModel,
237
+ dmPath,
238
+ }).run();
239
+ } finally {
240
+ this.inProgress.pop();
241
+ }
242
+ let luau: string | null = null;
243
+ let sourceMap: import("../clpp/contract.js").SourceMapLine[] = [];
244
+ if (diags.errorCount === 0) {
245
+ luau = emitLuau(program, info, this.api, {
246
+ sourceLabel: rel,
247
+ sourcePosition: offset => source.position(offset),
248
+ onSourceMap: lines => {sourceMap = lines;},
249
+ version: this.options.version,
250
+ requireTarget: (l, service) => this.requireTarget(l, dmPath, service),
251
+ libsRoot: (service) => instancePath(LIBS_FOLDER, service, null),
252
+ });
253
+ }
254
+ const result: CompiledFile = { file: key, rel, outRel: outputName(rel), luau, sourceMap, diagnostics: diags.items, source, info };
255
+ this.results.set(key, result);
256
+ return result;
257
+ }
258
+
259
+ private resolveLink(link: LinkDecl, fromFile: string, diags: DiagnosticBag): LinkTarget {
260
+ const p = link.path;
261
+ if (link.isPackage) {
262
+ if (p === "@clpp.roblox" || p === "@roblox") {
263
+ return { kind: "roblox" };
264
+ }
265
+ const std = STD_PATHS[p];
266
+ if (std && std !== "unsupported") {
267
+ return { kind: "std", module: std };
268
+ }
269
+ if (DESKTOP_ONLY[p]) {
270
+ return { kind: "error", message: `${p} is a desktop module of the clpp VM and does not exist on Roblox`, notes: [DESKTOP_ONLY[p]] };
271
+ }
272
+ const lib = /^@(?:clpp\.libs|cluaupp)\.([A-Za-z0-9_]+)$/.exec(p);
273
+ if (lib) {
274
+ const name = this.libs.find((l) => l.toLowerCase() === lib[1].toLowerCase());
275
+ if (!name && this.libs.length) {
276
+ return { kind: "error", message: `CluauppLibs has no library '${lib[1]}'`, notes: [`available: ${this.libs.join(", ")}`] };
277
+ }
278
+ const libName = name ?? lib[1][0].toUpperCase() + lib[1].slice(1);
279
+ return { kind: "luau", dmPath: [...LIBS_FOLDER, libName], label: libName };
280
+ }
281
+ if (p.startsWith("@game.") || /^@[A-Z]/.test(p)) {
282
+ const shorthand = !p.startsWith("@game.");
283
+ const segments = p.slice(shorthand ? 1 : "@game.".length).split(".");
284
+ if (!this.api.isService(segments[0])) {
285
+ return { kind: "error", message: `'${segments[0]}' is not a Roblox service`, notes: ["link @ReplicatedStorage.Shared.Module as Module;"] };
286
+ }
287
+ const fileHit = this.byDmPath.get(segments.join("/"));
288
+ if (fileHit) {
289
+ return this.linkFile(fileHit, fromFile);
290
+ }
291
+ if (shorthand) {
292
+ return { kind: "error", message: `no .clp module at ${p}`, notes: [`create src/${segments.join("/")}.clp or use a relative link to the file`] };
293
+ }
294
+ return { kind: "luau", dmPath: segments, label: segments.join(".") };
295
+ }
296
+ return { kind: "error", message: `unknown module ${p}`, notes: ["@clpp.roblox, @clpp.axiom, @clpp.libs.<Lib>, @ReplicatedStorage.Shared.<Module>, @game.<Service>.<…>"] };
297
+ }
298
+ let target = path.resolve(path.dirname(fromFile), p);
299
+ if (!fs.existsSync(target) && fs.existsSync(`${target}.clp`)) {
300
+ target = `${target}.clp`;
301
+ }
302
+ if (!target.toLowerCase().endsWith(SOURCE_EXT)) {
303
+ return { kind: "error", message: `cannot link '${p}': CL++ modules are .clp files`, notes: [/\.(clh|clpp)$/i.test(p) ? "CL++ 0.9 has no headers: rename the module to .clp" : "link \"./file.clp\" as Name;"] };
304
+ }
305
+ if (!fs.existsSync(target)) {
306
+ return { kind: "error", message: `cannot open module '${p}'` };
307
+ }
308
+ void diags;
309
+ return this.linkFile(target, fromFile);
310
+ }
311
+
312
+ private linkFile(target: string, fromFile: string): LinkTarget {
313
+ const key = path.resolve(target);
314
+ if (this.inProgress.includes(key)) {
315
+ const cycle = [...this.inProgress.slice(this.inProgress.indexOf(key)), key].map((f) => this.rel(f));
316
+ return { kind: "error", message: "circular link", notes: [cycle.join(" → ")] };
317
+ }
318
+ if (key === path.resolve(fromFile)) {
319
+ return { kind: "error", message: "a module cannot link itself" };
320
+ }
321
+ const compiled = this.compile(key);
322
+ if (!compiled.info) {
323
+ return { kind: "error", message: `module ${this.rel(key)} failed to compile` };
324
+ }
325
+ if (compiled.diagnostics.some((d) => d.severity === "error")) {
326
+ const first = compiled.diagnostics.find((d) => d.severity === "error")!;
327
+ return { kind: "error", message: `module ${this.rel(key)} has errors`, notes: [`${first.file}:${first.line}:${first.column}: ${first.message}`] };
328
+ }
329
+ const exports: ModuleExports = compiled.info.exports;
330
+ return { kind: "clp", exports, dmPath: this.dmOf(key), file: key };
331
+ }
332
+
333
+ private requireTarget(link: LinkInfo, fromDm: string[], service: (name: string) => string): string | null {
334
+ const t = link.target;
335
+ if (t.kind === "clp" && t.dmPath) {
336
+ return instancePath(t.dmPath, service, fromDm);
337
+ }
338
+ if (t.kind === "luau") {
339
+ return instancePath(t.dmPath, service, fromDm);
340
+ }
341
+ return null;
342
+ }
343
+ }
344
+
345
+ /** Compile one file with no project around it (tests, playground, `cluaupp check <file>`). */
346
+ export function compileSource(text: string, fileName = "Main.server.clp", options: Partial<ProjectOptions> = {}): CompiledFile {
347
+ const root = options.root ?? process.cwd();
348
+ const srcDir = options.srcDir ?? root;
349
+ const compiler = new ProjectCompiler({ root, srcDir, strict: options.strict ?? false, version: options.version ?? "dev", api: options.api });
350
+ return compiler.compile(path.resolve(srcDir, fileName), text);
351
+ }
352
+
353
+ export { isIdentifier };
354
+ export type { RunContext };