Skip to content
Open
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4351412
add merge and foreach to tableext
wmccrthy Nov 14, 2025
0d47b03
nit
wmccrthy Nov 14, 2025
e9c9f01
clean up
wmccrthy Nov 14, 2025
6fa7a7b
add support for replacing tokens
wmccrthy Nov 14, 2025
ea6f9e5
e2e cases boiler plate
wmccrthy Nov 14, 2025
009e6a1
query wrapper; overrides selectFromRoot so replacements are passed ar…
wmccrthy Nov 14, 2025
39272ed
stylua
wmccrthy Nov 14, 2025
33c3bd3
add apiRenameMigration case + repeated transform utils
wmccrthy Nov 15, 2025
42f1814
func rename
wmccrthy Nov 15, 2025
51d953e
use table.unpack
wmccrthy Nov 17, 2025
3cbd48a
test case name change
wmccrthy Nov 17, 2025
cee4ee4
boilerplate for roact->react test case
wmccrthy Nov 17, 2025
72959d5
Merge branch 'primary' into e2e-query-tests
wmccrthy Nov 17, 2025
1cdcea9
update to work w/ primary branch changes to parser/printer/visitor
wmccrthy Nov 17, 2025
ab0ae3b
fully working roact->react transform + test case(s)
wmccrthy Nov 17, 2025
2386e6f
Merge branch 'primary' into e2e-query-tests
wmccrthy Nov 17, 2025
2846da7
Discard changes to lute/std/libs/syntax/printer.luau
wmccrthy Nov 17, 2025
2fd76ca
add token printing back
wmccrthy Nov 17, 2025
fa1d1fb
remove comments + unused; nit cleanups
wmccrthy Nov 17, 2025
dcd144a
Merge branch 'primary' into e2e-query-tests
wmccrthy Nov 20, 2025
a5223cc
update to account for breaking changes
wmccrthy Nov 20, 2025
3b17c80
slightly closer to snapshot tests
wmccrthy Nov 20, 2025
ca49ddc
slightly more organized transform output
wmccrthy Nov 20, 2025
5231fba
Merge branch 'primary' into e2e-query-tests
wmccrthy Nov 20, 2025
9999b21
restructure tests s.t cases are stored in snapshot files rather than …
wmccrthy Nov 20, 2025
34aded2
restructure test to support file-based snapshots; gitignore transform…
wmccrthy Nov 20, 2025
82b71f2
Discard changes to lute/std/libs/syntax/printer.luau
wmccrthy Nov 20, 2025
ac5da79
Delete tests/std/syntax/e2eCases/minimal_repro.luau
wmccrthy Nov 20, 2025
96da218
remove api transform and use functional query style
wmccrthy Nov 21, 2025
1eef702
fix some disgusting type errs
wmccrthy Nov 21, 2025
041ebed
fix more type errs; remove comment
wmccrthy Nov 21, 2025
18f73e7
add exists helper on query and update tests accordingly
wmccrthy Nov 21, 2025
5b072d2
add tests for exists helper
wmccrthy Nov 21, 2025
f5499b1
use luau files for snapshots
wmccrthy Nov 21, 2025
8076ed2
remove accidental print
wmccrthy Nov 21, 2025
d6d76b1
remove redundancies
wmccrthy Nov 21, 2025
8dda54a
reorganize cases output according to feedbck
wmccrthy Nov 22, 2025
3ed9211
add missing type
wmccrthy Nov 22, 2025
56bc199
output git diff on test failure
wmccrthy Nov 23, 2025
49a7e71
Merge branch 'primary' into e2e-query-tests
wmccrthy Nov 24, 2025
3a7b52f
clean up nits from review
wmccrthy Nov 25, 2025
7b72cce
Merge branch 'primary' into e2e-query-tests
wmccrthy Dec 1, 2025
24fca16
Merge branch 'primary' into e2e-query-tests
wmccrthy Dec 8, 2025
3746aa6
Merge branch 'e2e-query-tests' of https://github.com/wmccrthy/lute in…
wmccrthy Dec 8, 2025
53c0989
use difftext in test
wmccrthy Dec 8, 2025
c16a9c1
use transform's output arg
wmccrthy Dec 9, 2025
bfbf400
apply first round of feedback
wmccrthy Dec 10, 2025
9229b97
reformat to use fs.walk
wmccrthy Dec 10, 2025
87c10c9
Merge branch 'primary' into e2e-query-tests
wmccrthy Dec 10, 2025
f12ba96
return type annotation instead of any annotation
wmccrthy Dec 10, 2025
19f1a90
Merge branch 'primary' into e2e-query-tests
wmccrthy Dec 12, 2025
523c56f
use line numbers in diff output
wmccrthy Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ extern/*/
generated
**/generated-types.luau
WARP.md

**/result.luau
15 changes: 15 additions & 0 deletions lute/std/libs/syntax/query.luau
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type query<T = node> = {
findall: <U>(self: query<T>, fn: (node) -> U?) -> query<U>,
flatmap: <U>(self: query<T>, fn: (T) -> { U }) -> query<U>,
maptoarray: <U>(self: query<T>, fn: (T) -> U?) -> { U },
exists: (self: query<T>, pred: ((T) -> boolean)?) -> boolean,
}

local queryLib = {}
Expand Down Expand Up @@ -64,6 +65,19 @@ function queryLib.foreach<T>(self: query<T>, callback: (T) -> ()): query<T>
return self
end

function queryLib.exists<T>(self: query<T>, predicate: ((T) -> boolean)?): boolean
if not predicate then
return #self.nodes > 0
end

for _, node in self.nodes do
if predicate(node) then
return true
end
end
return false
end

local function newSelectVisitor<T>(nodes: { T }, fn: (node) -> T?): visitor.Visitor
local function visit(n: node) -- LUAUFIX: type checker doesn't like assigning visit in the visitor fields with n: node
local selected = fn(n)
Expand Down Expand Up @@ -145,6 +159,7 @@ function queryLib.findallfromroot<T>(ast: types.ParseResult | node, fn: (node) -
findall = queryLib.findall,
flatmap = queryLib.flatmap,
maptoarray = queryLib.maptoarray,
exists = queryLib.exists,
} -- LUAUFIX: queryLib.map has generics quantified at a different level than expected
end

Expand Down
4 changes: 2 additions & 2 deletions lute/std/libs/syntax/types.luau
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export type AstNode = luau.AstNode

export type ParseResult = luau.ParseResult

export type replacement = string | AstNode
export type replacements = { [AstNode]: replacement }
export type replacement = string | AstNode | Token
export type replacements = { [AstNode | Token]: replacement }

return {}
3 changes: 3 additions & 0 deletions tests/std/syntax/e2eCases/apiSurfaceChange/cases/.luaurc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"languageMode": "nocheck"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local sameLibrary = require(packagesRoot.sameLibrary)

local result = sameLibrary.processNumber(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local sameLibrary = require(packagesRoot.sameLibrary)

local result = sameLibrary.numFunc(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local sameLibrary = require(packagesRoot.sameLibrary)

local result = sameLibrary.processArray(table.unpack({ 1, 2, 3 }))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local sameLibrary = require(packagesRoot.sameLibrary)

local result = sameLibrary.arrayFunc({ 1, 2, 3 })
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
local sameLibrary = require(packagesRoot.sameLibrary)

local a = sameLibrary.processNumber(42)
local b = sameLibrary.processArray(table.unpack({ 10, 20, 30 }))
local c = sameLibrary.handleText("hello")
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
local sameLibrary = require(packagesRoot.sameLibrary)

local a = sameLibrary.numFunc(42)
local b = sameLibrary.arrayFunc({ 10, 20, 30 })
local c = sameLibrary.stringFunc("hello")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local sameLibrary = require(packagesRoot.sameLibrary)

local result = sameLibrary.processNumber(sameLibrary.retrieveCount())
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local sameLibrary = require(packagesRoot.sameLibrary)

local result = sameLibrary.numFunc(sameLibrary.getCount())
49 changes: 49 additions & 0 deletions tests/std/syntax/e2eCases/apiSurfaceChange/init.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
local luau = require("@lute/luau")
local utils = require("@std/syntax/utils")
local printer = require("@std/syntax/printer")
local qUtils = require("./utils")
local tableext = require("@std/tableext")
local query = require("@std/syntax/query")

local apiMap = {
numFunc = "processNumber",
stringFunc = "handleText",
arrayFunc = "processArray",
processData = "handleData",
getCount = "retrieveCount",
}

local argReplacements = {
arrayFunc = function(arg: luau.AstExpr)
return `table.unpack({printer.printnode(arg :: any)})`
end,
}

local function transformCallArgs(block: luau.AstStatBlock)
local argNodeToFuncName = {} :: { [luau.AstNode]: string }
return query
.findallfromroot(block, utils.isExprCall)
:filter(function(node: luau.AstExprCall)
return node.func.index and argReplacements[node.func.index.text] ~= nil
end)
:map(function(node: luau.AstExprCall)
local arg = node.arguments[1].node
argNodeToFuncName[arg] = (node.func :: luau.AstExprIndexName).index.text
return arg
end)
:replace(function(node: luau.AstExpr)
return (argReplacements[argNodeToFuncName[node]] :: (luau.AstExpr) -> string)(node)
end)
end

local function transform(ctx)
local parseresult = ctx.parseresult
local block = parseresult.root
local libAccessReplacements = qUtils.transformLibraryAccess(block, "sameLibrary", function(expr, index: luau.Token)
return `{printer.printnode(expr :: any)}.{apiMap[index.text]}`
end)
local argReplacements = transformCallArgs(block)
return tableext.combine(libAccessReplacements, argReplacements)
end

return transform
7 changes: 7 additions & 0 deletions tests/std/syntax/e2eCases/init.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
local types = require("./e2eCases/types")

return {
libraryMigration = require("./e2eCases/libraryMigration"),
apiSurfaceChange = require("./e2eCases/apiSurfaceChange"),
roactToReact = require("./e2eCases/roactToReact"),
} :: { [string]: types.e2e }
3 changes: 3 additions & 0 deletions tests/std/syntax/e2eCases/libraryMigration/cases/.luaurc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"languageMode": "nocheck"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
local newLibrary = require(packagesRoot.new)

local function myDependentFunction(input: { string }): boolean
for _, s in input do
if newLibrary.newFunction(input) then
return true
end
end
return false
end

local function myIndexUsingDependentFunction(input: string): boolean
return newLibrary.anotherNewFunction(input)
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
local oldLibrary = require(packagesRoot.old)

local function myDependentFunction(input: { string }): boolean
for _, s in input do
if oldLibrary.oldFunction(input) then
return true
end
end
return false
end

local function myIndexUsingDependentFunction(input: string): boolean
return oldLibrary.anotherFunction(input)
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
local newLibrary = require(root.nested.packages.new)

local myEl = newLibrary.newFunction("lol")

return newLibrary.anotherNewFunction(myEl)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
local oldLibraryDiffAlias = require(root.nested.packages.old)

local myEl = oldLibraryDiffAlias.oldFunction("lol")

return oldLibraryDiffAlias.anotherFunction(myEl)
51 changes: 51 additions & 0 deletions tests/std/syntax/e2eCases/libraryMigration/init.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
local types = require("@std/syntax/types")
local utils = require("@std/syntax/utils")
local qUtils = require("./utils")
local query = require("@std/syntax/query")
local tableext = require("@std/tableext")
local getRequireStatements = qUtils.getRequireStatements
local transformLibraryAccess = qUtils.transformLibraryAccess

local functionMap = {
["oldFunction"] = "newFunction",
["anotherFunction"] = "anotherNewFunction",
}

local function transformRequires(block: types.AstStatBlock): (string, types.replacements)
local oldLibRequires = getRequireStatements(block, "old")

local requireArgRepl = query
.findallfromroot(oldLibRequires.nodes[1] :: types.AstNode, utils.isExprCall)
:map(function(node: types.AstExprCall): types.Token?
if utils.isExprIndexName(node.arguments[1].node) ~= nil then
return node.arguments[1].node.index
end
return nil
end)
:replace(function(node)
return "new"
end)

local oldLibVar = oldLibRequires.nodes[1].variables[1].node
local oldLibAlias = ""
local requiredVarAliasRepl = query
.findallfromroot(oldLibVar :: any, utils.isToken)
:replace(function(node: types.Token)
oldLibAlias = node.text
return "newLibrary"
end)
return oldLibAlias, tableext.combine(requireArgRepl, requiredVarAliasRepl)
end

local function transform(ctx): types.replacements
local parseresult = ctx.parseresult
local block = parseresult.root
local oldLibAlias, requireRepl = transformRequires(block)
local libAccessRepl = transformLibraryAccess(block, oldLibAlias, function(_, index)
local oldFunction = index.text
return `newLibrary.{functionMap[oldFunction]}`
end)
return tableext.combine(requireRepl, libAccessRepl)
end

return transform
3 changes: 3 additions & 0 deletions tests/std/syntax/e2eCases/roactToReact/cases/.luaurc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"languageMode": "nocheck"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local React = require(PackagesRoot.React)

return React.createElement(React.Fragment, nil, arbitraryElement)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local Roact = require(PackagesRoot.Roact)

return Roact.createFragment(arbitraryElement)
11 changes: 11 additions & 0 deletions tests/std/syntax/e2eCases/roactToReact/cases/case2/expected.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
local React = require(PackagesRoot.React)

return React.createElement(
arbitraryElement,
nil,
if not otherArbitraryElement
or not select(2, next(otherArbitraryElement))
or next(otherArbitraryElement, table.pack(select(1, next(otherArbitraryElement) :: string))[1])
then nil
else React.Children.only(select(2, next(otherArbitraryElement) :: any))
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local Roact = require(PackagesRoot.Roact)

return Roact.createElement(arbitraryElement, nil, Roact.oneChild(otherArbitraryElement))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local ReactRoblox = require(PackagesRoot.ReactRoblox)

return ReactRoblox.createPortal(children, arbitraryRoot)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
local Roact = require(PackagesRoot.Roact)

return Roact.createElement(Roact.Portal, {
target = arbitraryRoot,
}, children)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
local React = require(PackagesRoot.React)

return React.createElement(abitraryElement, {
ref = arbitraryRef,
children = arbitraryChildren,
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
local Roact = require(PackagesRoot.Roact)

return Roact.createElement(abitraryElement, {
[Roact.Ref] = arbitraryRef,
[Roact.Children] = arbitraryChildren,
})
10 changes: 10 additions & 0 deletions tests/std/syntax/e2eCases/roactToReact/cases/case5/expected.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
local ReactRoblox = require(PackagesRoot.ReactRoblox)

local root = ReactRoblox.createRoot(Instance.new("Folder"))
local actualParent = Instance.new("Folder")
local actualKey = "Root"
ReactRoblox.act(function()
root:render(ReactRoblox.createPortal({ [actualKey] = element } :: any, actualParent))
end)
local instance = { root = root, parent = actualParent, key = actualKey }
instance.root:unmount()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
local Roact = require(PackagesRoot.Roact)

local instance = Roact.mount(element, nil, "Root")
Roact.unmount(instance)
13 changes: 13 additions & 0 deletions tests/std/syntax/e2eCases/roactToReact/cases/case6/expected.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
local ReactRoblox = require(PackagesRoot.ReactRoblox)

local container = Instance.new("Folder")
local root = ReactRoblox.createRoot(Instance.new("Folder"))
local actualParent = container
local actualKey = "ReactRoot"
ReactRoblox.act(function()
root:render(ReactRoblox.createPortal({ [actualKey] = arbitraryElement } :: any, actualParent))
end)
local instance = { root = root, parent = actualParent, key = actualKey }
ReactRoblox.act(function()
instance.root:render(ReactRoblox.createPortal({ [instance.key] = otherElement } :: any, instance.parent))
end)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
local Roact = require(PackagesRoot.Roact)

local container = Instance.new("Folder")
local instance = Roact.mount(arbitraryElement, container)
Roact.update(instance, otherElement)
Loading