From 02fc2b140be1f706f94f7450cc2c996a446f1ddc Mon Sep 17 00:00:00 2001 From: techs-sus <92276908+techs-sus@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:36:34 -0400 Subject: [PATCH 1/7] add worker manager contract validator two modes: - graph: display require graph (pass option `--graph` - check: check for unsafe usage of globals, do not pass `--graph` example usage: `lute run .\checks\checkAll.luau` `lute run .\checks\checkAll.luau --graph` --- .luaurc | 25 +-- aftman.toml | 1 + checks/checkAll.luau | 245 ++++++++++++++++++++++++++++++ checks/exampleUnsafeUsage.luau | 22 +++ checks/workerManagerContract.luau | 98 ++++++++++++ 5 files changed, 380 insertions(+), 11 deletions(-) create mode 100644 checks/checkAll.luau create mode 100644 checks/exampleUnsafeUsage.luau create mode 100644 checks/workerManagerContract.luau diff --git a/.luaurc b/.luaurc index d60d4c0..9a92598 100644 --- a/.luaurc +++ b/.luaurc @@ -1,13 +1,16 @@ { - "languageMode": "nonstrict", - "lint": { - "*": true - }, - "lintErrors": false, - "typeErrors": true, - "aliases": { - "client": "./modules/client", - "server": "./modules/server", - "shared": "./modules/shared" - } + "lint": { + "*": true + }, + "languageMode": "nonstrict", + "typeErrors": true, + "lintErrors": false, + "aliases": { + "std": "~/.lute/typedefs/0.1.0/std", + "shared": "./modules/shared", + "lint": "~/.lute/typedefs/0.1.0/lint", + "lute": "~/.lute/typedefs/0.1.0/lute", + "client": "./modules/client", + "server": "./modules/server" + } } diff --git a/aftman.toml b/aftman.toml index bac4749..231136b 100755 --- a/aftman.toml +++ b/aftman.toml @@ -8,3 +8,4 @@ darklua = "seaofvoices/darklua@0.17.3" selene = "Kampfkarren/selene@0.29.0" StyLua = "JohnnyMorganz/StyLua@2.3.1" lune = "lune-org/lune@0.10.4" +lute = "luau-lang/lute@0.1.0-nightly.20260408" diff --git a/checks/checkAll.luau b/checks/checkAll.luau new file mode 100644 index 0000000..640061a --- /dev/null +++ b/checks/checkAll.luau @@ -0,0 +1,245 @@ +--!strict +--!native + +local json = require("@std/json") +local fs = require("@lute/fs") + +local luaurc +do + local handle = fs.open("./.luaurc", "r") + luaurc = assert(json.asObject(json.deserialize(fs.read(handle))), "expected object in luaurc") + fs.close(handle) + handle = nil +end + +local aliases: { [string]: string } = assert(luaurc["aliases"], "expected aliases key in luaurc") :: any + +local Path = require("@std/path") +local AstParser = require("@std/syntax/parser") +local Query = require("@std/syntax/query") +local SyntaxUtils = require("@std/syntax/utils") +local AstTypes = require("@std/syntax/types") + +local ENTRYPOINTS = table.freeze({ + "src/server/workerManager.server.luau", + "src/client/localWorkerManager.luau", +}) + +local DEFAULT_OUTPUT_PATH = "checks/checkAll.arrowcrab.json" + +local COLORS = { + entrypoint = "#F2C166", + server = "#F28B82", + client = "#8AB4F8", + shared = "#81C995", + other = "#C58AF9", +} + +type ModuleInfo = { + path: string, + dependencies: { string }, + isEntrypoint: boolean, +} + +type GraphData = { + subgraphs: { number }, + nodes: { number }, + edges: { number }, + strings: { string }, +} + +local function resolveRequirePath(path: string, callerPath: string): string? + local pieces = string.split(path, "/") + + if string.sub(pieces[1], 1, 1) == "@" then + pieces[1] = string.sub(pieces[1], 2) + + if pieces[1] == "self" then + pieces[1] = Path.dirname(callerPath) + else + pieces[1] = assert(aliases[pieces[1]], `failed finding alias {pieces[1]}`) + end + end + + local final = Path.format(Path.join(unpack(pieces))) + + local stat + pcall(function(...) + stat = fs.stat(final) + end) + + if not stat then + return final .. ".luau" + elseif stat.type == "dir" then + return final .. "/init.luau" + end + + return nil -- yaml, toml, etc. +end + +local function readFile(file: string): string + local handle = fs.open(file) + local source = fs.read(handle) + fs.close(handle) + return source +end + +local function collectDependencies(file: string): { string } + local dependencies: { string } = {} + local seenDependencies: { [string]: boolean } = {} + + Query.findAllFromRoot(AstParser.parse(readFile(file)), SyntaxUtils.isExprCall :: any) + :forEach(function(expr: AstTypes.AstExprCall) + if + SyntaxUtils.isExprGlobal(expr.func :: any) + and (expr.func :: AstTypes.AstExprGlobal).name.text == "require" + and #expr.arguments == 1 + and SyntaxUtils.isExprConstantString(expr.arguments[1].node :: any) + then + local path = (expr.arguments[1].node :: AstTypes.AstExprConstantString).token.text + local resolved = resolveRequirePath(path, file) + if resolved and not seenDependencies[resolved] then + seenDependencies[resolved] = true + table.insert(dependencies, resolved) + end + end + end) + + return dependencies +end + +local function getNodeColor(file: string, isEntrypoint: boolean): string + if isEntrypoint then + return COLORS.entrypoint + elseif string.find(file, "src/server/", 1, true) or string.find(file, "modules/server/", 1, true) then + return COLORS.server + elseif string.find(file, "src/client/", 1, true) or string.find(file, "modules/client/", 1, true) then + return COLORS.client + elseif string.find(file, "src/shared/", 1, true) or string.find(file, "modules/shared/", 1, true) then + return COLORS.shared + else + return COLORS.other + end +end + +local function buildArrowCrabGraph(entrypoints: { string }): GraphData + local visitState: { [string]: "visiting" | "done" } = {} + local modulesByPath: { [string]: ModuleInfo } = {} + local orderedPaths: { string } = {} + + local function visit(file: string, isEntrypoint: boolean) + local state = visitState[file] + if state == "done" then + if isEntrypoint then + modulesByPath[file].isEntrypoint = true + end + + return + elseif state == "visiting" then + error(`cyclic require graph detected involving {file}`) + end + + visitState[file] = "visiting" + + local dependencies = collectDependencies(file) + for _, dependency in dependencies do + visit(dependency, false) + end + + modulesByPath[file] = { + path = file, + dependencies = dependencies, + isEntrypoint = isEntrypoint, + } + + visitState[file] = "done" + table.insert(orderedPaths, file) + end + + for _, entrypoint in entrypoints do + visit(entrypoint, true) + end + + local strings: { string } = {} + local stringToIndex: { [string]: number } = {} + local nodeIds: { [string]: number } = {} + local nodes: { number } = {} + local edges: { number } = {} + + local function internString(value: string): number + local existing = stringToIndex[value] + if existing ~= nil then + return existing + end + + local index = #strings + table.insert(strings, value) + stringToIndex[value] = index + return index + end + + for id, path in orderedPaths do + local nodeId = id - 1 + local moduleInfo = modulesByPath[path] + nodeIds[path] = nodeId + + table.insert(nodes, nodeId) + table.insert(nodes, internString(moduleInfo.path)) + table.insert(nodes, internString(getNodeColor(path, moduleInfo.isEntrypoint))) + end + + for _, path in orderedPaths do + local moduleInfo = modulesByPath[path] + local targetId = nodeIds[path] + for _, dependency in moduleInfo.dependencies do + table.insert(edges, nodeIds[dependency]) + table.insert(edges, 0) + table.insert(edges, targetId) + table.insert(edges, 0) + end + end + + return { + subgraphs = {}, + nodes = nodes, + edges = edges, + strings = strings, + } +end + +local function writeArrowCrabGraph(outputPath: string, graphData: GraphData) + local handle = fs.open(outputPath, "w") + fs.write(handle, json.serialize(graphData :: any)) + fs.close(handle) +end + +local argv = { ... } + +if argv[2] == "--graph" then + local providedOutputPath = argv[3] + local outputPath = (providedOutputPath :: string?) or DEFAULT_OUTPUT_PATH + + writeArrowCrabGraph(outputPath, buildArrowCrabGraph(ENTRYPOINTS)) + print(`wrote ArrowCrab (https://hemileia.club/arrowcrab/) graph to {outputPath}`) +else + local process = require("@lute/process") + local allPaths = {} + + for _, entrypoint in ENTRYPOINTS do + local function visit(path: string) + allPaths[path] = true -- mark + + for _, dependency in collectDependencies(path) do + visit(dependency) + end + end + + visit(entrypoint) + end + + for path in allPaths do + process.run({ "lute", "run", "checks/workerManagerContract.luau", path }, { + stdio = "inherit", + }) + end +end diff --git a/checks/exampleUnsafeUsage.luau b/checks/exampleUnsafeUsage.luau new file mode 100644 index 0000000..e90e3df --- /dev/null +++ b/checks/exampleUnsafeUsage.luau @@ -0,0 +1,22 @@ +-- pretend this is running in a worker manager + +local os = os + +do + local a = bit32 + local b = buffer +end + +local function a() + buffer.create(0) -- unsafe +end + +function b() + function c() + buffer.create(0) -- unsafe + end +end + +local c = function() + buffer.create(0) -- unsafe +end diff --git a/checks/workerManagerContract.luau b/checks/workerManagerContract.luau new file mode 100644 index 0000000..b21b92c --- /dev/null +++ b/checks/workerManagerContract.luau @@ -0,0 +1,98 @@ +--!native +--!strict + +local Parser = require("@std/syntax/parser") +local AstVisitor = require("@std/syntax/visitor") +local utils = require("@std/syntax/utils") +local types = require("@std/syntax/types") +local tableext = require("@std/tableext") +local fs = require("@lute/fs") +local pathlib = require("@std/path") + +local filePath = assert(({ ... })[2], "no file given") +filePath = pathlib.normalize(filePath) + +local fileHandle = fs.open(pathlib.format(filePath), "r") +local root = Parser.parseBlock(fs.read(fileHandle)) +fs.close(fileHandle) + +local visitor = AstVisitor.create(nil) +local functionScopes: { types.AstExprFunction } = {} +local functionNameMap: { [types.AstExprFunction]: { + name: string?, + location: types.Span, +} } = {} + +local function getScopePath() + return table.concat( + tableext.map(functionScopes, function(expr: types.AstExprFunction) + return if functionNameMap[expr] then functionNameMap[expr].name else "" + end), + "->" + ) +end + +local function formatSpan(span: types.Span, filePath: string) + return `{filePath}:{span.beginLine}:{span.beginColumn}-{span.endColumn}` +end + +visitor.visitExprGlobal = function(expr: types.AstExprGlobal) + if #functionScopes > 0 then + -- not in root scope, in a function + print( + `usage of global '{expr.name.text}' @ {formatSpan(expr.location, filePath)} in scope {getScopePath()} is unsafe` + ) + end + + return true +end + +local disable = function() + return false +end + +visitor.visitStatTypeFunction = disable +visitor.visitTypeFunction = disable + +visitor.visitStatFunction = function(stat: types.AstStatFunction) + functionNameMap[stat.func] = { + name = utils.isExprGlobal(stat.name :: any) and (stat.name :: types.AstExprGlobal).name.text or nil, + location = stat.location, + } + + AstVisitor.visit(stat.func, visitor) + + -- skip the AstExprGlobalin this stat function + return false +end + +visitor.visitStatLocalFunction = function(stat: types.AstStatLocalFunction) + functionNameMap[stat.func] = { + name = stat.name.name.text, + location = stat.location, + } + + return true +end + +visitor.visitStatLocalDeclaration = function(stat: types.AstStatLocal) + for index, value in stat.values do + functionNameMap[value.node :: types.AstExprFunction] = { + name = utils.isExprFunction(value.node :: any) and stat.variables[index].node.name.text or nil, + location = value.node.location, + } + end + return true +end + +visitor.visitExprFunction = function(expr: types.AstExprFunction) + table.insert(functionScopes, expr) + + return true +end + +visitor.visitExprFunctionEnd = function(expr: types.AstExprFunction) + table.remove(functionScopes) +end + +AstVisitor.visit(root, visitor) From 5069bff103dc3d3a08c88f00c79078d7df08bf2a Mon Sep 17 00:00:00 2001 From: techs-sus <92276908+techs-sus@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:18:58 -0400 Subject: [PATCH 2/7] add auto patcher to checkAll script --- checks/checkAll.luau | 68 +++++++++----- checks/workerManagerContract.luau | 146 +++++++++++++++--------------- 2 files changed, 116 insertions(+), 98 deletions(-) diff --git a/checks/checkAll.luau b/checks/checkAll.luau index 640061a..3203c35 100644 --- a/checks/checkAll.luau +++ b/checks/checkAll.luau @@ -19,6 +19,7 @@ local AstParser = require("@std/syntax/parser") local Query = require("@std/syntax/query") local SyntaxUtils = require("@std/syntax/utils") local AstTypes = require("@std/syntax/types") +local checkWorkerManagerContract = require("./workerManagerContract") local ENTRYPOINTS = table.freeze({ "src/server/workerManager.server.luau", @@ -77,6 +78,12 @@ local function resolveRequirePath(path: string, callerPath: string): string? return nil -- yaml, toml, etc. end +local function writeFile(file: string, contents: string) + local handle = fs.open(file, "w") + fs.write(handle, contents) + fs.close(handle) +end + local function readFile(file: string): string local handle = fs.open(file) local source = fs.read(handle) @@ -84,26 +91,25 @@ local function readFile(file: string): string return source end -local function collectDependencies(file: string): { string } +local function collectDependencies(file: string, ast: AstTypes.ParseResult): { string } local dependencies: { string } = {} local seenDependencies: { [string]: boolean } = {} - Query.findAllFromRoot(AstParser.parse(readFile(file)), SyntaxUtils.isExprCall :: any) - :forEach(function(expr: AstTypes.AstExprCall) - if - SyntaxUtils.isExprGlobal(expr.func :: any) - and (expr.func :: AstTypes.AstExprGlobal).name.text == "require" - and #expr.arguments == 1 - and SyntaxUtils.isExprConstantString(expr.arguments[1].node :: any) - then - local path = (expr.arguments[1].node :: AstTypes.AstExprConstantString).token.text - local resolved = resolveRequirePath(path, file) - if resolved and not seenDependencies[resolved] then - seenDependencies[resolved] = true - table.insert(dependencies, resolved) - end + Query.findAllFromRoot(ast, SyntaxUtils.isExprCall :: any):forEach(function(expr: AstTypes.AstExprCall) + if + SyntaxUtils.isExprGlobal(expr.func :: any) + and (expr.func :: AstTypes.AstExprGlobal).name.text == "require" + and #expr.arguments == 1 + and SyntaxUtils.isExprConstantString(expr.arguments[1].node :: any) + then + local path = (expr.arguments[1].node :: AstTypes.AstExprConstantString).token.text + local resolved = resolveRequirePath(path, file) + if resolved and not seenDependencies[resolved] then + seenDependencies[resolved] = true + table.insert(dependencies, resolved) end - end) + end + end) return dependencies end @@ -141,7 +147,7 @@ local function buildArrowCrabGraph(entrypoints: { string }): GraphData visitState[file] = "visiting" - local dependencies = collectDependencies(file) + local dependencies = collectDependencies(file, AstParser.parse(readFile(file))) for _, dependency in dependencies do visit(dependency, false) end @@ -222,24 +228,36 @@ if argv[2] == "--graph" then writeArrowCrabGraph(outputPath, buildArrowCrabGraph(ENTRYPOINTS)) print(`wrote ArrowCrab (https://hemileia.club/arrowcrab/) graph to {outputPath}`) else - local process = require("@lute/process") + local patch = argv[2] == "--patch" local allPaths = {} for _, entrypoint in ENTRYPOINTS do local function visit(path: string) allPaths[path] = true -- mark - for _, dependency in collectDependencies(path) do + local source = readFile(path) + local ast = AstParser.parse(source) + local globalsMap = checkWorkerManagerContract(ast.root, path) + + if patch then + local globalsArray = {} + for global in globalsMap do + table.insert(globalsArray, global) + end + + if #globalsArray > 0 then + local pretext = `local {table.concat(globalsArray, ", ")} = {table.concat(globalsArray, ", ")}\n\n` + source = pretext .. source + + writeFile(path, source) + end + end + + for _, dependency in collectDependencies(path, ast) do visit(dependency) end end visit(entrypoint) end - - for path in allPaths do - process.run({ "lute", "run", "checks/workerManagerContract.luau", path }, { - stdio = "inherit", - }) - end end diff --git a/checks/workerManagerContract.luau b/checks/workerManagerContract.luau index b21b92c..13b8871 100644 --- a/checks/workerManagerContract.luau +++ b/checks/workerManagerContract.luau @@ -1,98 +1,98 @@ --!native --!strict -local Parser = require("@std/syntax/parser") local AstVisitor = require("@std/syntax/visitor") -local utils = require("@std/syntax/utils") +local SyntaxUtils = require("@std/syntax/utils") local types = require("@std/syntax/types") local tableext = require("@std/tableext") -local fs = require("@lute/fs") -local pathlib = require("@std/path") - -local filePath = assert(({ ... })[2], "no file given") -filePath = pathlib.normalize(filePath) - -local fileHandle = fs.open(pathlib.format(filePath), "r") -local root = Parser.parseBlock(fs.read(fileHandle)) -fs.close(fileHandle) - -local visitor = AstVisitor.create(nil) -local functionScopes: { types.AstExprFunction } = {} -local functionNameMap: { [types.AstExprFunction]: { - name: string?, - location: types.Span, -} } = {} - -local function getScopePath() - return table.concat( - tableext.map(functionScopes, function(expr: types.AstExprFunction) - return if functionNameMap[expr] then functionNameMap[expr].name else "" - end), - "->" - ) -end - -local function formatSpan(span: types.Span, filePath: string) - return `{filePath}:{span.beginLine}:{span.beginColumn}-{span.endColumn}` -end -visitor.visitExprGlobal = function(expr: types.AstExprGlobal) - if #functionScopes > 0 then - -- not in root scope, in a function - print( - `usage of global '{expr.name.text}' @ {formatSpan(expr.location, filePath)} in scope {getScopePath()} is unsafe` +local function check(root: types.AstStatBlock, filePath: string): { [string]: true } + local visitor = AstVisitor.create(nil) + local functionScopes: { types.AstExprFunction } = {} + local functionNameMap: { [types.AstExprFunction]: { + name: string?, + location: types.Span, + } } = {} + + local globals: { [string]: true } = {} + + local function getScopePath() + return table.concat( + tableext.map(functionScopes, function(expr: types.AstExprFunction) + return if functionNameMap[expr] then functionNameMap[expr].name else "" + end), + "->" ) end - return true -end + local function formatSpan(span: types.Span, filePath: string) + return `{filePath}:{span.beginLine}:{span.beginColumn}-{span.endColumn}` + end -local disable = function() - return false -end + visitor.visitExprGlobal = function(expr: types.AstExprGlobal) + if #functionScopes > 0 then + -- not in root scope, in a function + print( + `usage of global '{expr.name.text}' @ {formatSpan(expr.location, filePath)} in scope {getScopePath()} is unsafe` + ) -visitor.visitStatTypeFunction = disable -visitor.visitTypeFunction = disable + globals[expr.name.text] = true + end -visitor.visitStatFunction = function(stat: types.AstStatFunction) - functionNameMap[stat.func] = { - name = utils.isExprGlobal(stat.name :: any) and (stat.name :: types.AstExprGlobal).name.text or nil, - location = stat.location, - } + return true + end - AstVisitor.visit(stat.func, visitor) + local disable = function() + return false + end - -- skip the AstExprGlobalin this stat function - return false -end + visitor.visitStatTypeFunction = disable + visitor.visitTypeFunction = disable -visitor.visitStatLocalFunction = function(stat: types.AstStatLocalFunction) - functionNameMap[stat.func] = { - name = stat.name.name.text, - location = stat.location, - } + visitor.visitStatFunction = function(stat: types.AstStatFunction) + functionNameMap[stat.func] = { + name = SyntaxUtils.isExprGlobal(stat.name :: any) and (stat.name :: types.AstExprGlobal).name.text or nil, + location = stat.location, + } - return true -end + AstVisitor.visit(stat.func, visitor) -visitor.visitStatLocalDeclaration = function(stat: types.AstStatLocal) - for index, value in stat.values do - functionNameMap[value.node :: types.AstExprFunction] = { - name = utils.isExprFunction(value.node :: any) and stat.variables[index].node.name.text or nil, - location = value.node.location, + -- skip the AstExprGlobalin this stat function + return false + end + + visitor.visitStatLocalFunction = function(stat: types.AstStatLocalFunction) + functionNameMap[stat.func] = { + name = stat.name.name.text, + location = stat.location, } + + return true end - return true -end -visitor.visitExprFunction = function(expr: types.AstExprFunction) - table.insert(functionScopes, expr) + visitor.visitStatLocalDeclaration = function(stat: types.AstStatLocal) + for index, value in stat.values do + functionNameMap[value.node :: types.AstExprFunction] = { + name = SyntaxUtils.isExprFunction(value.node :: any) and stat.variables[index].node.name.text or nil, + location = value.node.location, + } + end + return true + end - return true -end + visitor.visitExprFunction = function(expr: types.AstExprFunction) + table.insert(functionScopes, expr) + + return true + end + + visitor.visitExprFunctionEnd = function(expr: types.AstExprFunction) + table.remove(functionScopes) + end + + AstVisitor.visit(root, visitor) -visitor.visitExprFunctionEnd = function(expr: types.AstExprFunction) - table.remove(functionScopes) + return globals end -AstVisitor.visit(root, visitor) +return check From d173caf80feebbe1b00d546be56cf2cb32d36da4 Mon Sep 17 00:00:00 2001 From: techs-sus <92276908+techs-sus@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:20:27 -0400 Subject: [PATCH 3/7] fix typo --- checks/workerManagerContract.luau | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/workerManagerContract.luau b/checks/workerManagerContract.luau index 13b8871..a4b118f 100644 --- a/checks/workerManagerContract.luau +++ b/checks/workerManagerContract.luau @@ -57,7 +57,7 @@ local function check(root: types.AstStatBlock, filePath: string): { [string]: tr AstVisitor.visit(stat.func, visitor) - -- skip the AstExprGlobalin this stat function + -- skip the AstExprGlobal in this stat function return false end From 9581e75064938c458289327bca5c83b02524b2fd Mon Sep 17 00:00:00 2001 From: techs-sus <92276908+techs-sus@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:24:23 -0400 Subject: [PATCH 4/7] do not produce long lists and make emitted code more readable --- checks/checkAll.luau | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/checks/checkAll.luau b/checks/checkAll.luau index 3203c35..f94188b 100644 --- a/checks/checkAll.luau +++ b/checks/checkAll.luau @@ -19,6 +19,7 @@ local AstParser = require("@std/syntax/parser") local Query = require("@std/syntax/query") local SyntaxUtils = require("@std/syntax/utils") local AstTypes = require("@std/syntax/types") +local tableext = require("@std/tableext") local checkWorkerManagerContract = require("./workerManagerContract") local ENTRYPOINTS = table.freeze({ @@ -240,13 +241,17 @@ else local globalsMap = checkWorkerManagerContract(ast.root, path) if patch then - local globalsArray = {} - for global in globalsMap do - table.insert(globalsArray, global) - end + local globalsArray = tableext.keys(globalsMap) if #globalsArray > 0 then - local pretext = `local {table.concat(globalsArray, ", ")} = {table.concat(globalsArray, ", ")}\n\n` + local pretext = table.concat( + tableext.map(globalsArray, function(global: string) + return `local {global} = {global}` + end), + "\n" + ) + -- local pretext = `local {table.concat(globalsArray, ", ")} = {table.concat(globalsArray, ", ")}\n\n` + source = pretext .. source writeFile(path, source) From 623f92c3bd8fe03391063b3dd5317ea5956b3304 Mon Sep 17 00:00:00 2001 From: techs-sus <92276908+techs-sus@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:25:07 -0400 Subject: [PATCH 5/7] always emit newline or else bugs happen --- checks/checkAll.luau | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checks/checkAll.luau b/checks/checkAll.luau index f94188b..8b8898d 100644 --- a/checks/checkAll.luau +++ b/checks/checkAll.luau @@ -246,9 +246,9 @@ else if #globalsArray > 0 then local pretext = table.concat( tableext.map(globalsArray, function(global: string) - return `local {global} = {global}` + return `local {global} = {global}\n` end), - "\n" + "" ) -- local pretext = `local {table.concat(globalsArray, ", ")} = {table.concat(globalsArray, ", ")}\n\n` From 5b544141e0127f1a260f27b5e36360a9e154c52e Mon Sep 17 00:00:00 2001 From: techs-sus <92276908+techs-sus@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:39:30 -0400 Subject: [PATCH 6/7] skip leading trivia (comments) --- checks/checkAll.luau | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/checks/checkAll.luau b/checks/checkAll.luau index 8b8898d..702e294 100644 --- a/checks/checkAll.luau +++ b/checks/checkAll.luau @@ -229,13 +229,31 @@ if argv[2] == "--graph" then writeArrowCrabGraph(outputPath, buildArrowCrabGraph(ENTRYPOINTS)) print(`wrote ArrowCrab (https://hemileia.club/arrowcrab/) graph to {outputPath}`) else + local TriviaUtils = require("@std/syntax/utils/trivia") + local patch = argv[2] == "--patch" - local allPaths = {} + + local function insertAfterLeadingTrivia(source: string, ast: AstTypes.ParseResult, text: string): string + local firstStatement = ast.root.statements[1] + if firstStatement == nil then + return text .. source + end + + local leadingTrivia = TriviaUtils.leftmostTrivia(firstStatement) + if #leadingTrivia == 0 then + return text .. source + end + + local prefixLength = 0 + for _, trivia in leadingTrivia do + prefixLength += #trivia.text + end + + return string.sub(source, 1, prefixLength) .. text .. string.sub(source, prefixLength + 1) + end for _, entrypoint in ENTRYPOINTS do local function visit(path: string) - allPaths[path] = true -- mark - local source = readFile(path) local ast = AstParser.parse(source) local globalsMap = checkWorkerManagerContract(ast.root, path) @@ -250,10 +268,8 @@ else end), "" ) - -- local pretext = `local {table.concat(globalsArray, ", ")} = {table.concat(globalsArray, ", ")}\n\n` - - source = pretext .. source + source = insertAfterLeadingTrivia(source, ast, pretext) writeFile(path, source) end end From 7d593700f760db028d1fd5be27dabbf0004741ca Mon Sep 17 00:00:00 2001 From: techs-sus <92276908+techs-sus@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:52:19 -0400 Subject: [PATCH 7/7] run `lute run ./checks/checkAll.luau --patch` --- modules/client/wm/sandbox/init.luau | 6 ++++++ modules/client/wm/sandbox/rules.luau | 4 ++++ modules/client/wm/scriptManager.luau | 7 +++++++ modules/client/wm/stackTrace.luau | 1 + modules/server/compile.luau | 4 ++++ modules/server/wm/sandbox/environment.luau | 2 ++ modules/server/wm/sandbox/init.luau | 2 ++ modules/server/wm/sandbox/rules.luau | 3 +++ modules/server/wm/sandbox/wrapper/init.luau | 3 +++ .../server/wm/sandbox/wrapper/reflection.luau | 1 + modules/server/wm/scriptManager.luau | 4 ++++ modules/shared/crypto/CSPRNG/Blake3.luau | 3 +++ modules/shared/crypto/CSPRNG/ChaCha20.luau | 5 +++++ modules/shared/crypto/CSPRNG/Conversions.luau | 4 ++++ modules/shared/crypto/CSPRNG/init.luau | 18 ++++++++++++++++++ modules/shared/enum.luau | 3 +++ modules/shared/errors.luau | 1 + modules/shared/functions.luau | 3 +++ modules/shared/log.luau | 1 + modules/shared/wm/communication.luau | 6 ++++++ modules/shared/wm/protection/init.luau | 3 +++ 21 files changed, 84 insertions(+) diff --git a/modules/client/wm/sandbox/init.luau b/modules/client/wm/sandbox/init.luau index 4e9a8ff..b75829e 100644 --- a/modules/client/wm/sandbox/init.luau +++ b/modules/client/wm/sandbox/init.luau @@ -1,3 +1,9 @@ +local task = task +local table = table +local pcall = pcall +local rawset = rawset +local error = error +local getfenv = getfenv local game = game local setmetatable = setmetatable local os = os diff --git a/modules/client/wm/sandbox/rules.luau b/modules/client/wm/sandbox/rules.luau index f41c23e..0a85681 100644 --- a/modules/client/wm/sandbox/rules.luau +++ b/modules/client/wm/sandbox/rules.luau @@ -1,3 +1,7 @@ +local Instance = Instance +local game = game +local table = table +local ipairs = ipairs local tonumber = tonumber local error = error local typeof = typeof diff --git a/modules/client/wm/scriptManager.luau b/modules/client/wm/scriptManager.luau index 2c0fb10..b7508d6 100644 --- a/modules/client/wm/scriptManager.luau +++ b/modules/client/wm/scriptManager.luau @@ -1,3 +1,10 @@ +local coroutine = coroutine +local pcall = pcall +local unpack = unpack +local typeof = typeof +local _G = _G +local require = require +local ipairs = ipairs local setmetatable = setmetatable local shared = shared local rawequal = rawequal diff --git a/modules/client/wm/stackTrace.luau b/modules/client/wm/stackTrace.luau index 32ac147..0a78066 100644 --- a/modules/client/wm/stackTrace.luau +++ b/modules/client/wm/stackTrace.luau @@ -1,3 +1,4 @@ +local tonumber = tonumber local table = table local ipairs = ipairs diff --git a/modules/server/compile.luau b/modules/server/compile.luau index d62611e..c0bb363 100644 --- a/modules/server/compile.luau +++ b/modules/server/compile.luau @@ -1,3 +1,7 @@ +local table = table +local _G = _G +local type = type +local ipairs = ipairs local buffer = buffer local string = string local pcall = pcall diff --git a/modules/server/wm/sandbox/environment.luau b/modules/server/wm/sandbox/environment.luau index 547c260..e001175 100644 --- a/modules/server/wm/sandbox/environment.luau +++ b/modules/server/wm/sandbox/environment.luau @@ -1,3 +1,5 @@ +local unpack = unpack +local tostring = tostring local setmetatable = setmetatable local error = error local table = table diff --git a/modules/server/wm/sandbox/init.luau b/modules/server/wm/sandbox/init.luau index 1036345..c2548a6 100644 --- a/modules/server/wm/sandbox/init.luau +++ b/modules/server/wm/sandbox/init.luau @@ -1,3 +1,5 @@ +local require = require +local setfenv = setfenv local table = table local task = task local pcall = pcall diff --git a/modules/server/wm/sandbox/rules.luau b/modules/server/wm/sandbox/rules.luau index 760b429..baccfa9 100644 --- a/modules/server/wm/sandbox/rules.luau +++ b/modules/server/wm/sandbox/rules.luau @@ -1,3 +1,6 @@ +local game = game +local warn = warn +local ipairs = ipairs local table = table local typeof = typeof local error = error diff --git a/modules/server/wm/sandbox/wrapper/init.luau b/modules/server/wm/sandbox/wrapper/init.luau index c97ca4e..fcd8a20 100644 --- a/modules/server/wm/sandbox/wrapper/init.luau +++ b/modules/server/wm/sandbox/wrapper/init.luau @@ -1,3 +1,6 @@ +local getfenv = getfenv +local newproxy = newproxy +local setfenv = setfenv local table = table local setmetatable = setmetatable local typeof = typeof diff --git a/modules/server/wm/sandbox/wrapper/reflection.luau b/modules/server/wm/sandbox/wrapper/reflection.luau index 04df82b..c94f087 100644 --- a/modules/server/wm/sandbox/wrapper/reflection.luau +++ b/modules/server/wm/sandbox/wrapper/reflection.luau @@ -1,3 +1,4 @@ +local tostring = tostring local table = table local setmetatable = setmetatable local error = error diff --git a/modules/server/wm/scriptManager.luau b/modules/server/wm/scriptManager.luau index 8b18e7c..112bb47 100644 --- a/modules/server/wm/scriptManager.luau +++ b/modules/server/wm/scriptManager.luau @@ -1,3 +1,7 @@ +local Instance = Instance +local _G = _G +local require = require +local unpack = unpack local setmetatable = setmetatable local shared = shared local typeof = typeof diff --git a/modules/shared/crypto/CSPRNG/Blake3.luau b/modules/shared/crypto/CSPRNG/Blake3.luau index 0b64a79..b3b010f 100644 --- a/modules/shared/crypto/CSPRNG/Blake3.luau +++ b/modules/shared/crypto/CSPRNG/Blake3.luau @@ -26,6 +26,9 @@ --!optimize 2 --!native +local math = math +local buffer = buffer +local bit32 = bit32 local BLOCK_SIZE = 64 local CV_SIZE = 32 local EXTENDED_CV_SIZE = 64 diff --git a/modules/shared/crypto/CSPRNG/ChaCha20.luau b/modules/shared/crypto/CSPRNG/ChaCha20.luau index f0ac3f2..f8e3cd7 100644 --- a/modules/shared/crypto/CSPRNG/ChaCha20.luau +++ b/modules/shared/crypto/CSPRNG/ChaCha20.luau @@ -22,6 +22,11 @@ --!native --!optimize 2 +local buffer = buffer +local typeof = typeof +local math = math +local error = error +local bit32 = bit32 local DWORD = 4 local BLOCK_SIZE = 64 local STATE_SIZE = 16 diff --git a/modules/shared/crypto/CSPRNG/Conversions.luau b/modules/shared/crypto/CSPRNG/Conversions.luau index 7eeabf8..28fe39a 100644 --- a/modules/shared/crypto/CSPRNG/Conversions.luau +++ b/modules/shared/crypto/CSPRNG/Conversions.luau @@ -11,6 +11,10 @@ --!optimize 2 --!native +local type = type +local buffer = buffer +local error = error +local bit32 = bit32 local ENCODE_LOOKUP = buffer.create(256 * 2) do local HexChars = "0123456789abcdef" diff --git a/modules/shared/crypto/CSPRNG/init.luau b/modules/shared/crypto/CSPRNG/init.luau index 5b66de5..8ace3b4 100644 --- a/modules/shared/crypto/CSPRNG/init.luau +++ b/modules/shared/crypto/CSPRNG/init.luau @@ -24,6 +24,24 @@ --!optimize 2 --!strict +local string = string +local warn = warn +local tostring = tostring +local DateTime = DateTime +local tick = tick +local math = math +local coroutine = coroutine +local bit32 = bit32 +local buffer = buffer +local typeof = typeof +local error = error +local type = type +local table = table +local pcall = pcall +local os = os +local game = game +local newproxy = newproxy +local workspace = workspace local Conversions = require("@self/Conversions") local ChaCha20 = require("@self/ChaCha20") local Blake3 = require("@self/Blake3") diff --git a/modules/shared/enum.luau b/modules/shared/enum.luau index c3d617e..e82382c 100644 --- a/modules/shared/enum.luau +++ b/modules/shared/enum.luau @@ -1,3 +1,6 @@ +local _G = _G +local setmetatable = setmetatable +local error = error local ipairs = ipairs local table = table diff --git a/modules/shared/errors.luau b/modules/shared/errors.luau index 5926bd0..335383b 100644 --- a/modules/shared/errors.luau +++ b/modules/shared/errors.luau @@ -1,3 +1,4 @@ +local type = type local Functions = require("@shared/functions") local getInstanceName = Functions.getInstanceName diff --git a/modules/shared/functions.luau b/modules/shared/functions.luau index e0b6a73..24b4935 100644 --- a/modules/shared/functions.luau +++ b/modules/shared/functions.luau @@ -1,4 +1,7 @@ -- Maybe this should be rewritten to have each function in it's own file? +local tostring = tostring +local type = type +local tonumber = tonumber local string = string local game = game local coroutine = coroutine diff --git a/modules/shared/log.luau b/modules/shared/log.luau index 281f4d4..49f484c 100644 --- a/modules/shared/log.luau +++ b/modules/shared/log.luau @@ -1,3 +1,4 @@ +local _G = _G local table = table local print = print local warn = warn diff --git a/modules/shared/wm/communication.luau b/modules/shared/wm/communication.luau index 0ecd125..e177030 100644 --- a/modules/shared/wm/communication.luau +++ b/modules/shared/wm/communication.luau @@ -1,3 +1,9 @@ +local debug = debug +local unpack = unpack +local coroutine = coroutine +local _G = _G +local pcall = pcall +local ipairs = ipairs local setmetatable = setmetatable local table = table local error = error diff --git a/modules/shared/wm/protection/init.luau b/modules/shared/wm/protection/init.luau index 02eaba8..12c7209 100644 --- a/modules/shared/wm/protection/init.luau +++ b/modules/shared/wm/protection/init.luau @@ -10,6 +10,9 @@ each further level implies the last -- local ManagerCommunication = require("@shared/wm/communication") +local require = require +local error = error +local ipairs = ipairs local protectedInstances: { [Instance]: number } = {} local protectedClasses: { [string]: number } = {}