diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b362ad..f560425 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,32 @@ on: - master jobs: + analyze: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Install foreman tools + uses: Roblox/setup-foreman@v1 + with: + version: "^1.0.1" + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download global Roblox types + shell: bash + run: curl -s -O https://raw.githubusercontent.com/JohnnyMorganz/luau-lsp/master/scripts/globalTypes.d.lua + + - name: Generate sourcemap for LSP + shell: bash + run: rojo sourcemap default.project.json -o sourcemap.json + + - name: Analyze + shell: bash + run: luau-lsp analyze --sourcemap=sourcemap.json --defs=globalTypes.d.lua --defs=testez.d.lua src/ + test: runs-on: ubuntu-latest @@ -30,11 +56,6 @@ jobs: luarocks install luacov luarocks install luacov-reporter-lcov - - name: Test - run: | - lua -lluacov test/lemur.lua - luacov -r lcov - - name: install code quality tools uses: Roblox/setup-foreman@v1 with: @@ -47,6 +68,16 @@ jobs: selene src stylua -c src/ + - name: install and run darklua + run: | + cargo install --git https://gitlab.com/seaofvoices/darklua.git#v0.6.0 + darklua process src/ src/ --format retain-lines + + - name: Test + run: | + lua -lluacov test/lemur.lua + luacov -r lcov + - name: Report to Coveralls uses: coverallsapp/github-action@v1.1.2 with: diff --git a/.gitignore b/.gitignore index 51e5571..ee0359b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Rotriever rotriever.lock Packages + +# Misc OS +.DS_Store diff --git a/default.project.json b/default.project.json index 3706102..b763aa2 100644 --- a/default.project.json +++ b/default.project.json @@ -1,6 +1,23 @@ { "name": "RoactRodux", "tree": { - "$path": "src" + "$className": "Folder", + "Roact": { + "$path": "modules/roact/src", + ".luaurc": { + "$className": "StringValue", + "$path": "modules/.luaurc" + } + }, + "Rodux": { + "$path": "modules/rodux/src", + ".luaurc": { + "$className": "StringValue", + "$path": "modules/.luaurc" + } + }, + "RoactRodux": { + "$path": "src" + } } -} \ No newline at end of file +} diff --git a/foreman.toml b/foreman.toml index 0bafa32..014faff 100644 --- a/foreman.toml +++ b/foreman.toml @@ -2,3 +2,4 @@ rojo = { source = "rojo-rbx/rojo", version = "=7.2.1" } selene = { source = "Kampfkarren/selene", version = "=0.21.1" } stylua = { source = "JohnnyMorganz/StyLua", version = "=0.15.1" } +luau-lsp = { source = "JohnnyMorganz/luau-lsp", version = "=1.8.1" } diff --git a/modules/.luaurc b/modules/.luaurc new file mode 100644 index 0000000..b8b04d1 --- /dev/null +++ b/modules/.luaurc @@ -0,0 +1,5 @@ +{ + "languageMode": "nocheck", + "lint": { "*": false }, + "lintErrors": false +} diff --git a/modules/lemur b/modules/lemur index f7ad797..3e4ff7d 160000 --- a/modules/lemur +++ b/modules/lemur @@ -1 +1 @@ -Subproject commit f7ad797a70940f937eb16a98f6b6dc6e539fc3ba +Subproject commit 3e4ff7d8f09e57164ad0614c1b81cc9338caf006 diff --git a/modules/rodux b/modules/rodux index 45c106f..6758f69 160000 --- a/modules/rodux +++ b/modules/rodux @@ -1 +1 @@ -Subproject commit 45c106f09c58f706a7ea458c6ff17914dd9a22c6 +Subproject commit 6758f690a697be68d13c206ae950712bcc935735 diff --git a/rotriever.toml b/rotriever.toml index 15423a4..f6bcc9d 100644 --- a/rotriever.toml +++ b/rotriever.toml @@ -8,4 +8,4 @@ files = ["*", "!*.spec.lua"] [dependencies] Roact = "github.com/roblox/roact@1.4" -Rodux = "github.com/roblox/rodux@3.0" +Rodux = "github.com/roblox/rodux@4.0.0-rc.0" diff --git a/src/Symbol.spec.lua b/src/Symbol.spec.lua index 070e74c..aa5ad93 100644 --- a/src/Symbol.spec.lua +++ b/src/Symbol.spec.lua @@ -11,7 +11,7 @@ return function() it("should coerce to the given name", function() local symbol = Symbol.named("foo") - expect(tostring(symbol):find("foo")).to.be.ok() + expect((tostring(symbol):find("foo"))).to.be.ok() end) it("should be unique when constructed", function() diff --git a/src/connect.lua b/src/connect.lua index 35c8352..1b467b8 100644 --- a/src/connect.lua +++ b/src/connect.lua @@ -1,8 +1,26 @@ +--!strict local Roact = require(script.Parent.Parent.Roact) + local shallowEqual = require(script.Parent.shallowEqual) local join = require(script.Parent.join) local StoreContext = require(script.Parent.StoreContext) +local types = require(script.Parent.types) + +type ThunkfulDispatchProp = types.ThunkfulDispatchProp +type MapStateToProps = types.MapStateToProps +type MapStateToPropsOrThunk = types.MapStateToPropsOrThunk< + StoreState, + Props, + PartialProps +> +type ActionCreatorMap = types.ActionCreatorMap +type MapDispatchToProps = types.MapDispatchToProps +type MapDispatchToPropsOrActionCreator = types.MapDispatchToPropsOrActionCreator< + StoreState, + PartialProps +> + --[[ Formats a multi-line message with printf-style placeholders. ]] @@ -47,7 +65,17 @@ end () -> (storeState, props) -> partialProps mapDispatchToProps: (dispatch) -> partialProps ]] -local function connect(mapStateToPropsOrThunk, mapDispatchToProps) +local function connect( + mapStateToPropsOrThunk: MapStateToPropsOrThunk< + StoreState, + Props, + MappedStatePartialProps + >?, + mapDispatchToProps: MapDispatchToPropsOrActionCreator< + StoreState, + MappedDispatchPartialProps + >? +) if mapStateToPropsOrThunk ~= nil then assert(typeof(mapStateToPropsOrThunk) == "function", "mapStateToProps must be a function or nil!") else @@ -86,6 +114,7 @@ local function connect(mapStateToPropsOrThunk, mapDispatchToProps) if prevState.stateUpdater ~= nil then return prevState.stateUpdater(nextProps.innerProps, prevState) end + return nil end function Connection:init(props) @@ -105,7 +134,8 @@ local function connect(mapStateToPropsOrThunk, mapDispatchToProps) local storeState = self.store:getState() - local mapStateToProps = mapStateToPropsOrThunk + local mapStateToProps = + mapStateToPropsOrThunk :: MapStateToProps local mappedStoreState = mapStateToProps(storeState, self.props.innerProps) -- mapStateToPropsOrThunk can return a function instead of a state @@ -133,11 +163,11 @@ local function connect(mapStateToPropsOrThunk, mapDispatchToProps) return self.store:dispatch(...) end - local mappedStoreDispatch + local mappedStoreDispatch: any if mapDispatchType == "table" then mappedStoreDispatch = {} - for key, actionCreator in pairs(mapDispatchToProps) do + for key, actionCreator in pairs(mapDispatchToProps :: ActionCreatorMap) do assert(typeof(actionCreator) == "function", "mapDispatchToProps must contain function values") mappedStoreDispatch[key] = function(...) @@ -145,7 +175,9 @@ local function connect(mapStateToPropsOrThunk, mapDispatchToProps) end end elseif mapDispatchType == "function" then - mappedStoreDispatch = mapDispatchToProps(dispatch) + mappedStoreDispatch = (mapDispatchToProps :: MapDispatchToProps)( + (dispatch :: any) :: ThunkfulDispatchProp + ) end local stateUpdater = makeStateUpdater(self.store) diff --git a/src/connect.spec.lua b/src/connect.spec.lua index 94a1724..e0c1628 100644 --- a/src/connect.spec.lua +++ b/src/connect.spec.lua @@ -6,6 +6,8 @@ return function() local Roact = require(script.Parent.Parent.Roact) local Rodux = require(script.Parent.Parent.Rodux) + type AnyActionCreator = Rodux.ActionCreator + local function noop() return nil end @@ -47,7 +49,7 @@ return function() it("should accept one table of action creators", function() connect(nil, { - foo = function() end, + foo = (function() end :: any) :: AnyActionCreator, }) end) @@ -219,11 +221,11 @@ return function() it("should dispatch the action using a table of action creators", function() local mapDispatchToProps = { - increment = function() + increment = (function() return { type = "increment", } - end, + end :: any) :: AnyActionCreator, } local function SomeComponent(props) diff --git a/src/init.lua b/src/init.lua index 387d5df..07dcbce 100644 --- a/src/init.lua +++ b/src/init.lua @@ -1,6 +1,11 @@ +--!strict local StoreProvider = require(script.StoreProvider) local StoreContext = require(script.StoreContext) local connect = require(script.connect) +local types = require(script.types) + +export type DispatchProp = types.DispatchProp +export type ThunkfulDispatchProp = types.ThunkfulDispatchProp return { StoreProvider = StoreProvider, diff --git a/src/types.lua b/src/types.lua new file mode 100644 index 0000000..f90721e --- /dev/null +++ b/src/types.lua @@ -0,0 +1,30 @@ +--!strict +local Rodux = require(script.Parent.Parent.Rodux) + +type Action = Rodux.Action +type ThunkAction = Rodux.ThunkAction +type ActionCreator = Rodux.ActionCreator + +export type DispatchProp = (action: Payload & Action) -> () + +export type ThunkfulDispatchProp = + DispatchProp + & (thunkAction: ThunkAction) -> ReturnType + +export type MapStateToProps = (StoreState, Props) -> PartialProps? + +export type MapStateToPropsOrThunk = + MapStateToProps + | () -> MapStateToProps + +export type ActionCreatorMap = { + [string]: ActionCreator, +} + +export type MapDispatchToProps = (ThunkfulDispatchProp) -> PartialProps? + +export type MapDispatchToPropsOrActionCreator = + MapDispatchToProps + | ActionCreatorMap + +return nil diff --git a/testez.d.lua b/testez.d.lua new file mode 100644 index 0000000..349ea08 --- /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: () -> ()): () \ No newline at end of file