From 82b805a3ece0beb7b5a8628410ae8e0b1e2c282e Mon Sep 17 00:00:00 2001 From: Marin Minnerly <85896114+ghostnaps@users.noreply.github.com> Date: Fri, 10 Jun 2022 14:48:54 -0700 Subject: [PATCH] Setup basic Luau analysis (#372) This sets up the repo with Luau analysis and fixes the initial type errors. --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ .luaurc | 7 +++++++ CHANGELOG.md | 1 + foreman.toml | 6 ++++-- src/Binding.lua | 2 +- src/Component.lua | 1 + src/ElementUtils.lua | 9 ++++++--- src/Symbol.lua | 2 +- src/Symbol.spec.lua | 3 ++- src/assertDeepEqual.lua | 13 +++++++------ src/assign.spec.lua | 2 +- src/createReconciler.lua | 2 +- src/createReconciler.spec.lua | 4 +++- src/init.spec.lua | 2 +- src/strict.lua | 12 +++++++----- testez.d.lua | 24 ++++++++++++++++++++++++ 16 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 .luaurc create mode 100644 testez.d.lua diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 652b4fcd..3e976b5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,24 @@ on: - master jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: Roblox/setup-foreman@v1 + with: + version: "^1.0.1" + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download global Roblox types + shell: bash + run: curl -O https://raw.githubusercontent.com/JohnnyMorganz/luau-analyze-rojo/master/globalTypes.d.lua + + - name: Analyze + shell: bash + run: luau-analyze --project=default.project.json --defs=globalTypes.d.lua --defs=testez.d.lua src/ + test: runs-on: ubuntu-latest @@ -52,6 +70,9 @@ jobs: luarocks install luacov luarocks install luacov-reporter-lcov + - name: Install and run darklua + run: darklua process src/ src/ --format retain-lines + - name: Test run: | lua -lluacov bin/spec.lua diff --git a/.luaurc b/.luaurc new file mode 100644 index 00000000..fb9a3221 --- /dev/null +++ b/.luaurc @@ -0,0 +1,7 @@ +{ + "languageMode": "nonstrict", + "lint": { + "*": true + }, + "lintErrors": true +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f532b9..81f6b9ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased Changes * Removed the warning for `setState` on unmounted components to eliminate false positive warnings, matching upstream React ([#323](https://github.com/Roblox/roact/pull/323)). +* Added Luau analysis to the repository ([#372](https://github.com/Roblox/roact/pull/372)) ## [1.4.2](https://github.com/Roblox/roact/releases/tag/v1.4.2) (October 6th, 2021) * Fixed forwardRef doc code referencing React instead of Roact ([#310](https://github.com/Roblox/roact/pull/310)). diff --git a/foreman.toml b/foreman.toml index dedd8e87..5714bf4b 100644 --- a/foreman.toml +++ b/foreman.toml @@ -1,4 +1,6 @@ [tools] rojo = { source = "rojo-rbx/rojo", version = "6.2.0" } -selene = { source = "Kampfkarren/selene", version = "0.14" } -stylua = { source = "JohnnyMorganz/StyLua", version = "0.11" } +selene = { source = "Kampfkarren/selene", version = "0.18" } +stylua = { source = "JohnnyMorganz/StyLua", version = "0.13" } +luau-analyze = { source = "JohnnyMorganz/luau-analyze-rojo", version = "0.527" } +darklua = { gitlab = "seaofvoices/darklua", version = "0.7.0" } diff --git a/src/Binding.lua b/src/Binding.lua index 96987abf..8c748c8e 100644 --- a/src/Binding.lua +++ b/src/Binding.lua @@ -134,7 +134,7 @@ function BindingInternalApi.join(upstreamBindings) disconnect() end - disconnects = nil + disconnects = nil :: any end end diff --git a/src/Component.lua b/src/Component.lua index 2227892e..eaa91a41 100644 --- a/src/Component.lua +++ b/src/Component.lua @@ -296,6 +296,7 @@ function Component:__mount(reconciler, virtualNode) virtualNode = virtualNode, componentClass = self, lifecyclePhase = ComponentLifecyclePhase.Init, + pendingState = nil, } local instance = { diff --git a/src/ElementUtils.lua b/src/ElementUtils.lua index 01f2b2e3..8c258591 100644 --- a/src/ElementUtils.lua +++ b/src/ElementUtils.lua @@ -1,3 +1,4 @@ +--!strict local Type = require(script.Parent.Type) local Symbol = require(script.Parent.Symbol) @@ -16,6 +17,8 @@ local ElementUtils = {} ]] ElementUtils.UseParentKey = Symbol.named("UseParentKey") +type Iterator = ({ [K]: V }, K?) -> (K?, V?) +type Element = { [any]: any } --[[ Returns an iterator over the children of an element. `elementOrElements` may be one of: @@ -37,14 +40,14 @@ ElementUtils.UseParentKey = Symbol.named("UseParentKey") If `elementOrElements` is none of the above, this function will throw. ]] -function ElementUtils.iterateElements(elementOrElements) +function ElementUtils.iterateElements(elementOrElements): (Iterator, any, nil) local richType = Type.of(elementOrElements) -- Single child if richType == Type.Element then local called = false - return function() + return function(_, _) if called then return nil else @@ -57,7 +60,7 @@ function ElementUtils.iterateElements(elementOrElements) local regularType = typeof(elementOrElements) if elementOrElements == nil or regularType == "boolean" then - return noop + return (noop :: any) :: Iterator end if regularType == "table" then diff --git a/src/Symbol.lua b/src/Symbol.lua index 3e9b951e..b54560b9 100644 --- a/src/Symbol.lua +++ b/src/Symbol.lua @@ -1,4 +1,4 @@ ---!nonstrict +--!strict --[[ A 'Symbol' is an opaque marker type. diff --git a/src/Symbol.spec.lua b/src/Symbol.spec.lua index c9be1503..117ace11 100644 --- a/src/Symbol.spec.lua +++ b/src/Symbol.spec.lua @@ -11,7 +11,8 @@ return function() it("should coerce to the given name", function() local symbol = Symbol.named("foo") - expect(tostring(symbol):find("foo")).to.be.ok() + local index = tostring(symbol):find("foo") + expect(index).to.be.ok() end) it("should be unique when constructed", function() diff --git a/src/assertDeepEqual.lua b/src/assertDeepEqual.lua index c43dd26a..7e3bf505 100644 --- a/src/assertDeepEqual.lua +++ b/src/assertDeepEqual.lua @@ -1,3 +1,4 @@ +--!strict --[[ A utility used to assert that two objects are value-equal recursively. It outputs fairly nicely formatted messages to help diagnose why two objects @@ -6,7 +7,7 @@ This should only be used in tests. ]] -local function deepEqual(a, b) +local function deepEqual(a: any, b: any): (boolean, string?) if typeof(a) ~= typeof(b) then local message = ("{1} is of type %s, but {2} is of type %s"):format(typeof(a), typeof(b)) return false, message @@ -19,7 +20,7 @@ local function deepEqual(a, b) visitedKeys[key] = true local success, innerMessage = deepEqual(value, b[key]) - if not success then + if not success and innerMessage then local message = innerMessage :gsub("{1}", ("{1}[%s]"):format(tostring(key))) :gsub("{2}", ("{2}[%s]"):format(tostring(key))) @@ -32,7 +33,7 @@ local function deepEqual(a, b) if not visitedKeys[key] then local success, innerMessage = deepEqual(value, a[key]) - if not success then + if not success and innerMessage then local message = innerMessage :gsub("{1}", ("{1}[%s]"):format(tostring(key))) :gsub("{2}", ("{2}[%s]"):format(tostring(key))) @@ -42,11 +43,11 @@ local function deepEqual(a, b) end end - return true + return true, nil end if a == b then - return true + return true, nil end local message = "{1} ~= {2}" @@ -56,7 +57,7 @@ end local function assertDeepEqual(a, b) local success, innerMessageTemplate = deepEqual(a, b) - if not success then + if not success and innerMessageTemplate then local innerMessage = innerMessageTemplate:gsub("{1}", "first"):gsub("{2}", "second") local message = ("Values were not deep-equal.\n%s"):format(innerMessage) diff --git a/src/assign.spec.lua b/src/assign.spec.lua index 6ebe5b75..f41a21b5 100644 --- a/src/assign.spec.lua +++ b/src/assign.spec.lua @@ -11,7 +11,7 @@ return function() end) it("should merge multiple tables onto the given target table", function() - local target = { + local target: { a: number, b: number, c: number? } = { a = 5, b = 6, } diff --git a/src/createReconciler.lua b/src/createReconciler.lua index 92e22346..76345846 100644 --- a/src/createReconciler.lua +++ b/src/createReconciler.lua @@ -237,7 +237,7 @@ local function createReconciler(renderer) mount a new virtual node, and return it in this case, while also issuing a warning to the user. ]] - function updateVirtualNode(virtualNode, newElement, newState) + function updateVirtualNode(virtualNode, newElement, newState: { [any]: any }?): { [any]: any }? if config.internalTypeChecks then internalAssert(Type.of(virtualNode) == Type.VirtualNode, "Expected arg #1 to be of type VirtualNode") end diff --git a/src/createReconciler.spec.lua b/src/createReconciler.spec.lua index e82a68aa..96cd59fe 100644 --- a/src/createReconciler.spec.lua +++ b/src/createReconciler.spec.lua @@ -302,7 +302,9 @@ return function() local node = noopReconciler.mountVirtualNode(emptyFragment, nil, "test") expect(node).to.be.ok() - expect(next(node.children)).to.never.be.ok() + + local nextNode = next(node.children) + expect(nextNode).to.never.be.ok() end) it("should mount all fragment's children", function() diff --git a/src/init.spec.lua b/src/init.spec.lua index 4d003350..340e109c 100644 --- a/src/init.spec.lua +++ b/src/init.spec.lua @@ -43,7 +43,7 @@ return function() end if not success then - local existence = typeof(valueType) == "boolean" and "present" or "of type " .. valueType + local existence = typeof(valueType) == "boolean" and "present" or "of type " .. tostring(valueType) local message = ("Expected public API member %q to be %s, but instead it was of type %s"):format( tostring(key), existence, diff --git a/src/strict.lua b/src/strict.lua index 0f32dedf..763fe53f 100644 --- a/src/strict.lua +++ b/src/strict.lua @@ -1,16 +1,18 @@ ---!nonstrict -local function strict(t, name) - name = name or tostring(t) +--!strict +local function strict(t: { [any]: any }, name: string?) + -- FIXME Luau: Need to define a new variable since reassigning `name = ...` + -- doesn't narrow the type + local newName = name or tostring(t) return setmetatable(t, { __index = function(_self, key) - local message = ("%q (%s) is not a valid member of %s"):format(tostring(key), typeof(key), name) + local message = ("%q (%s) is not a valid member of %s"):format(tostring(key), typeof(key), newName) error(message, 2) end, __newindex = function(_self, key, _value) - local message = ("%q (%s) is not a valid member of %s"):format(tostring(key), typeof(key), name) + local message = ("%q (%s) is not a valid member of %s"):format(tostring(key), typeof(key), newName) error(message, 2) end, diff --git a/testez.d.lua b/testez.d.lua new file mode 100644 index 00000000..28bef921 --- /dev/null +++ b/testez.d.lua @@ -0,0 +1,24 @@ +declare function afterAll(callback: () -> ()): () +declare function afterEach(callback: () -> ()): () + +declare function beforeAll(callback: () -> ()): () +declare function beforeEach(callback: () -> ()): () + +declare function describe(phrase: string, callback: () -> ()): () +declare function describeFOCUS(phrase: string, callback: () -> ()): () +declare function fdescribe(phrase: string, callback: () -> ()): () +declare function describeSKIP(phrase: string, callback: () -> ()): () +declare function xdescribe(phrase: string, callback: () -> ()): () + +declare function expect(value: any): any + +declare function FIXME(optionalMessage: string?): () +declare function FOCUS(): () +declare function SKIP(): () + +declare function it(phrase: string, callback: () -> ()): () +declare function itFOCUS(phrase: string, callback: () -> ()): () +declare function fit(phrase: string, callback: () -> ()): () +declare function itSKIP(phrase: string, callback: () -> ()): () +declare function xit(phrase: string, callback: () -> ()): () +declare function itFIXME(phrase: string, callback: () -> ()): ()