Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .changes/68-feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make compatible w/ lute to 0.1.0-nightly.20260312
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ Use these formats for automatic changelog creation:
## 📋 Requirements

- **VSCode** 1.74.0 or higher
- **Lute** (>= [0.1.0-nightly.20260130](https://github.com/luau-lang/lute/releases/tag/0.1.0-nightly.20260130))
- **Lute** (>= [0.1.0-nightly.20260312](https://github.com/luau-lang/lute/releases/tag/0.1.0-nightly.20260312))
- **Foreman** or **Rokit** (See installation instructions linked above)
- **Node.js** 16+ (for development)

Expand Down
4 changes: 2 additions & 2 deletions foreman.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[tools]
lute = { source = "luau-lang/lute", version = "=0.1.0-nightly.20260130" }
stylua = { source = "JohnnyMorganz/StyLua", version = "2.0.2" }
lute = { source = "luau-lang/lute", version = "=0.1.0-nightly.20260312" }
stylua = { source = "JohnnyMorganz/StyLua", version = "2.0.2" }
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"compile": "npm run build",
"test": "react-scripts test --watchAll=false",
"test:watch": "react-scripts test",
"eject": "react-scripts eject"
Expand Down
10 changes: 4 additions & 6 deletions frontend/src/tests/TreeNode.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,9 @@ describe("TreeNode", () => {
test("fallback chain: _astType -> parentInferred -> array inference", () => {
// Test actual fallback: parent type defines array property, mixed content with/without _astType
const tableWithEntries = {
_astType: "AstExprTable", // Parent type that has `entries: { AstExprTableItem }` property
_astType: "AstExprTable", // Parent type that has `entries: { AstTableExprItem }` property
entries: [
{ text: "filler" }, // No _astType - should infer AstExprTableItem from parent definition
{ text: "filler" }, // No _astType - should infer AstTableExprItem from parent definition
{ _astType: "MySpecialType" }, // Has _astType - should use that directly
],
};
Expand All @@ -275,14 +275,12 @@ describe("TreeNode", () => {
// The entries array should get inferred type from AstExprTable definition
const entriesQuery = getQueryableNode("root.entries", "nodeHeader");
expect(
entriesQuery.getByText(/type: { AstExprTableItem }/)
entriesQuery.getByText(/type: { AstTableExprItem }/)
).toBeInTheDocument();

// First item should infer type from parent array definition
const item0Query = getQueryableNode("root.entries.0", "nodeHeader");
expect(
item0Query.getByText(/type: AstExprTableItem/)
).toBeInTheDocument();
expect(item0Query.getByText(/type: AstTableExprItem/)).toBeInTheDocument();

// Second item should use its explicit _astType
const item1Query = getQueryableNode("root.entries.1", "nodeHeader");
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/tests/astTypeHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ describe("astTypeHelpers", () => {
expect(getArrayType("statements")).toEqual(["{ AstStat }", ""]);

expect(getArrayType("entries", [{ colon: ":", kind: "record" }])).toEqual([
"{ AstTypeTableItem }",
"{ AstTableTypeItem }",
"record",
]);

expect(getArrayType("entries", [{ kind: "general" }])).toEqual([
"{ AstExprTableItem }",
"{ AstTableExprItem }",
"general",
]);

Expand Down Expand Up @@ -105,14 +105,14 @@ describe("astTypeHelpers", () => {

// leverages getArrayType
expect(getTypeString([{}], "entries")).toEqual([
"{ AstExprTableItem }",
"{ AstTableExprItem }",
"",
]);

// falls back on parentInferredType
expect(
getTypeString({ kind: "record" }, "[0]", "AstExprTableItem")
).toEqual(["AstExprTableItem", "record"]);
getTypeString({ kind: "record" }, "[0]", "AstTableExprItem")
).toEqual(["AstTableExprItem", "record"]);
});

test("getType", () => {
Expand Down
55 changes: 37 additions & 18 deletions frontend/src/utils/astTypeDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,24 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstExprInstantiate: {
properties: [
{ name: "location", type: "span" },
{ name: "kind", type: '"expr"' },
{ name: "tag", type: '"instantiate"' },
{ name: "expr", type: "AstExpr" },
{ name: "leftarrow1", type: "Token", generic: 'Token<"<">' },
{ name: "leftarrow2", type: "Token", generic: 'Token<"<">' },
{
name: "typearguments",
type: "Punctuated",
generic: "Punctuated<AstType | AstTypePack>",
},
{ name: "rightarrow1", type: "Token", generic: 'Token<">">' },
{ name: "rightarrow2", type: "Token", generic: 'Token<">">' },
],
},

AstExprIndexName: {
properties: [
{ name: "location", type: "span" },
Expand Down Expand Up @@ -304,7 +322,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstExprTableItemList: {
AstTableExprListItem: {
properties: [
{ name: "location", type: "span" },
{ name: "kind", type: '"list"' },
Expand All @@ -319,7 +337,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstExprTableItemRecord: {
AstTableExprRecordItem: {
properties: [
{ name: "location", type: "span" },
{ name: "kind", type: '"record"' },
Expand All @@ -336,7 +354,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstExprTableItemGeneral: {
AstTableExprGeneralItem: {
properties: [
{ name: "location", type: "span" },
{ name: "kind", type: '"general"' },
Expand All @@ -355,11 +373,11 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstExprTableItem: {
AstTableExprItem: {
unionMembers: [
"AstExprTableItemList",
"AstExprTableItemRecord",
"AstExprTableItemGeneral",
"AstTableExprListItem",
"AstTableExprRecordItem",
"AstTableExprGeneralItem",
],
},

Expand All @@ -369,7 +387,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
{ name: "kind", type: '"expr"' },
{ name: "tag", type: '"table"' },
{ name: "openbrace", type: "Token", generic: 'Token<"{">' },
{ name: "entries", type: "{ AstExprTableItem }" },
{ name: "entries", type: "{ AstTableExprItem }" },
{ name: "closebrace", type: "Token", generic: 'Token<"}">' },
],
},
Expand Down Expand Up @@ -451,6 +469,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
"AstExprGlobal",
"AstExprVarargs",
"AstExprCall",
"AstExprInstantiate",
"AstExprIndexName",
"AstExprIndexExpr",
"AstExprFunction",
Expand Down Expand Up @@ -935,7 +954,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstTypeTableItemIndexer: {
AstTableTypeItemIndexer: {
properties: [
{ name: "kind", type: '"indexer"' },
{
Expand All @@ -958,7 +977,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstTypeTableItemStringProperty: {
AstTableTypeItemStringProperty: {
properties: [
{ name: "kind", type: '"stringproperty"' },
{
Expand All @@ -981,7 +1000,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstTypeTableItemProperty: {
AstTableTypeItemProperty: {
properties: [
{ name: "kind", type: '"property"' },
{
Expand All @@ -1002,11 +1021,11 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
],
},

AstTypeTableItem: {
AstTableTypeItem: {
unionMembers: [
"AstTypeTableItemIndexer",
"AstTypeTableItemStringProperty",
"AstTypeTableItemProperty",
"AstTableTypeItemIndexer",
"AstTableTypeItemStringProperty",
"AstTableTypeItemProperty",
],
},

Expand All @@ -1016,12 +1035,12 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
{ name: "kind", type: '"type"' },
{ name: "tag", type: '"table"' },
{ name: "openbrace", type: "Token", generic: 'Token<"{">' },
{ name: "entries", type: "{ AstTypeTableItem }" },
{ name: "entries", type: "{ AstTableTypeItem }" },
{ name: "closebrace", type: "Token", generic: 'Token<"}">' },
],
},

AstTypeFunctionParameter: {
AstFunctionTypeParameter: {
properties: [
{ name: "location", type: "span" },
{ name: "name", type: "Token", optional: true },
Expand Down Expand Up @@ -1063,7 +1082,7 @@ export const astTypeDefinitions: Record<string, ASTTypeDefinition> = {
{
name: "parameters",
type: "Punctuated",
generic: "Punctuated<AstTypeFunctionParameter>",
generic: "Punctuated<AstFunctionTypeParameter>",
},
{ name: "vararg", type: "AstTypePack", optional: true },
{ name: "closeparens", type: "Token", generic: 'Token<")">' },
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/utils/astTypeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ export function getAllNodeKeys(): string[] {

const resolveEntriesType = (value: any[]): string => {
if (value[0] && value[0].colon) {
return "{ AstTypeTableItem }";
return "{ AstTableTypeItem }";
}

return "{ AstExprTableItem }";
return "{ AstTableExprItem }";
};

const resolveEntriesKind = (value: any[]): string => {
Expand Down Expand Up @@ -187,4 +187,4 @@ export const getChildPropertyDefinition = (
(prop) => prop.name === childKey
);
}
};
};
66 changes: 36 additions & 30 deletions lua_helpers/typeAnnotations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ local typeDefinitions = {
["global"] = "AstExprGlobal",
["vararg"] = "AstExprVarargs",
["call"] = "AstExprCall",
["instantiate"] = "AstExprInstantiate",
["indexname"] = "AstExprIndexName",
["index"] = "AstExprIndexExpr",
["unary"] = "AstExprUnary",
Expand Down Expand Up @@ -171,14 +172,14 @@ local typeDefinitions = {
-- Kind-based mappings for table items and other kinded structures
kinds = {
-- Expression table items (istableitem: true)
["record"] = "AstExprTableItemRecord",
["general"] = "AstExprTableItemGeneral",
["list"] = "AstExprTableItemList",
["record"] = "AstTableExprRecordItem",
["general"] = "AstTableExprGeneralItem",
["list"] = "AstTableExprListItem",

-- Type table items
["property"] = "AstTypeTableItemProperty",
["indexer"] = "AstTypeTableItemIndexer",
["stringproperty"] = "AstTypeTableItemStringProperty",
["property"] = "AstTableTypeItemProperty",
["indexer"] = "AstTableTypeItemIndexer",
["stringproperty"] = "AstTableTypeItemStringProperty",

-- AstLocal
["local"] = "AstLocal",
Expand All @@ -189,7 +190,7 @@ local typeDefinitions = {
}

-- Context-aware type resolution for ambiguous tags
local function resolveAmbiguousTags(node)
local function resolveAmbiguousTags(node): string?
local tag = node.tag
if not tag then
return nil
Expand Down Expand Up @@ -279,7 +280,7 @@ local function resolveAmbiguousTags(node)
return typeDefinitions.tags[tag]
end

local function resolveAmbiguousKeys(nodeKey, node, parentNode, parentKey)
local function resolveAmbiguousKeys(nodeKey, node, parentNode: any?): string?
-- Handle 'kind' field specially - it's a discriminator, not a type
if nodeKey == "kind" then
return nil -- Don't annotate the kind field itself
Expand Down Expand Up @@ -379,39 +380,43 @@ local function resolveAmbiguousKeys(nodeKey, node, parentNode, parentKey)
return typeDefinitions.keys[nodeKey]
end

local function annotateWithType(node, nodeKey, parent, parentKey)
local function annotateWithType(node, nodeKey: (string | number)?, parent: any?, parentKey: (string | number)?)
if type(node) ~= "table" then
return node
end

local astType = nil
-- Lute AST nodes can be readonly in newer versions; avoid in-place mutation by
-- copying before annotating.
local annotatedNode = table.clone(node)

local astType: string?

-- Priority 1: Check for 'kind' field for direct type mapping (local, attribute)
if node.kind == "local" and not node.tag then
if annotatedNode.kind == "local" and not annotatedNode.tag then
astType = "AstLocal"
elseif node.kind == "attribute" then
elseif annotatedNode.kind == "attribute" then
astType = "AstAttribute"

-- Priority 2: Context-aware tag-based type resolution
elseif node.tag then
astType = resolveAmbiguousTags(node)
-- Priority 2: Context-aware tag-based type resolution
elseif annotatedNode.tag then
astType = resolveAmbiguousTags(annotatedNode)

-- Priority 3: Check for 'kind' field for table items
elseif node.kind and node.istableitem then
astType = typeDefinitions.kinds[node.kind]
elseif node.kind and (node.key or node.indexeropen) then
-- Priority 3: Check for 'kind' field for table items
elseif annotatedNode.kind and annotatedNode.istableitem then
astType = typeDefinitions.kinds[annotatedNode.kind]
elseif annotatedNode.kind and (annotatedNode.key or annotatedNode.indexeropen) then
-- Type table items
astType = typeDefinitions.kinds[node.kind]
astType = typeDefinitions.kinds[annotatedNode.kind]

-- Priority 4: Check for istoken marker
elseif node.istoken then
-- Priority 4: Check for istoken marker
elseif annotatedNode.istoken then
astType = "Token"

-- Priority 5: Key-based type resolution (fallback)
-- Priority 5: Key-based type resolution (fallback)
elseif nodeKey then
astType = resolveAmbiguousKeys(nodeKey, node, parent, parentKey)
astType = resolveAmbiguousKeys(nodeKey, annotatedNode, parent)

-- Priority 6: Generic token detection
-- Priority 6: Generic token detection
elseif nodeKey and nodeKey:match("keyword$") then
-- Any property ending in "keyword" is probably a token
astType = "Token"
Expand All @@ -421,17 +426,18 @@ local function annotateWithType(node, nodeKey, parent, parentKey)
end

-- Add type annotation (skip arrays to avoid JSON encoding issues)
if astType and not (#node > 0) then
node._astType = astType
if astType and not (#annotatedNode > 0) then
annotatedNode._astType = astType
end

-- Recursively annotate children, passing key context
for key, value in pairs(node) do
for key, value in annotatedNode do
if key ~= "_astType" then
node[key] = annotateWithType(value, if typeof(key) == "string" then key else tostring(key), node, nodeKey)
annotatedNode[key] =
annotateWithType(value, if typeof(key) == "string" then key else tostring(key), annotatedNode, nodeKey)
end
end
return node
return annotatedNode
end

return {
Expand Down
1 change: 1 addition & 0 deletions lua_tests/helpers/astJsonToCodeHelpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ local cases = {
"type Generic<T> = { data: T }",
"type Props = { titleText: string, avatarThumbnail: string, avatarThumbnailDisplayHeight: number, bodyText: string?, primaryButtonText: string, onPrimaryButtonActivated: () -> ()?, secondaryButtonText: string?, onSecondaryButtonActivated: () -> ()?, closeCentralOverlayThunk: () -> any? }",
"local nil_val = nil",
"local b = a<<string>>",
-- Edge cases that test printASTNode fallback behavior
"local x",
"local y: number",
Expand Down
Loading
Loading