diff --git a/README.md b/README.md index e68f99d..d6fa57d 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ update msg model = - **Install.FieldInTypeAlias**: Add a field to a specified type alias in a specified module. For more details, see the [docs](https://package.elm-lang.org/packages/jxxcarlson/elm-review-codeinstaller/latest/Install-FieldInTypeAlias). - **Install.Initializer**: Add a field to the body of an initializer function where the return value is of the form `( Model, Cmd msg )`. For more details, see the [docs](https://package.elm-lang.org/packages/jxxcarlson/elm-review-codeinstaller/latest/Install-Initializer). - **Install.TypeVariant**: Add a variant to a specified type in a specified module. For more details, see the [docs](https://package.elm-lang.org/packages/jxxcarlson/elm-review-codeinstaller/latest/Install-TypeVariant). -- **Install.Function**: Replace a function in a given module with a new implementation or add that function definition if it is not present in the module. For more details, see the [docs](https://package.elm-lang.org/packages/jxxcarlson/elm-review-codeinstaller/latest/Install-Function). +- **Install.Function.ReplaceFunction**: Replace a function in a given module with a new implementation. For more details, see the [docs](https://package.elm-lang.org/packages/jxxcarlson/elm-review-codeinstaller/latest/Install-Function-ReplaceFunction). +- **Install.Function.InsertFunction**: Add a function in a given module if it is not present. For more details, see the [docs](https://package.elm-lang.org/packages/jxxcarlson/elm-review-codeinstaller/latest/Install-Function-InsertFunction). - **Install.Import**: Add import statements to a given module. For more details, see the [docs](https://package.elm-lang.org/packages/jxxcarlson/elm-review-codeinstaller/latest/Install-Import). ## Try it out diff --git a/elm.json b/elm.json index 0fb1aa1..9578612 100644 --- a/elm.json +++ b/elm.json @@ -9,7 +9,8 @@ "Install.FieldInTypeAlias", "Install.Initializer", "Install.TypeVariant", - "Install.Function", + "Install.Function.InsertFunction", + "Install.Function.ReplaceFunction", "Install.Import", "Install.Type" ], diff --git a/preview/src/ReviewConfig.elm b/preview/src/ReviewConfig.elm index e4a66c8..8e6c2c1 100644 --- a/preview/src/ReviewConfig.elm +++ b/preview/src/ReviewConfig.elm @@ -11,34 +11,36 @@ when inside the directory containing this file. -} -import Install.Import -import Install.TypeVariant +import Install.ClauseInCase import Install.FieldInTypeAlias -import Install.Type +import Install.Function.InsertFunction +import Install.Function.ReplaceFunction +import Install.Import import Install.Initializer -import Install.ClauseInCase -import Install.Function +import Install.Type +import Install.TypeVariant import Review.Rule exposing (Rule) -config = config1 + +config = + config1 config1 : List Rule config1 = - [ - Install.TypeVariant.makeRule "Types" "ToBackend" "CounterReset" - , Install.TypeVariant.makeRule "Types" "FrontendMsg" "Reset" - , Install.ClauseInCase.init "Frontend" "update" "Reset" "( { model | counter = 0 }, sendToBackend CounterReset )" + [ Install.TypeVariant.makeRule "Types" "ToBackend" "CounterReset" + , Install.TypeVariant.makeRule "Types" "FrontendMsg" "Reset" + , Install.ClauseInCase.init "Frontend" "update" "Reset" "( { model | counter = 0 }, sendToBackend CounterReset )" |> Install.ClauseInCase.withInsertAfter "Increment" |> Install.ClauseInCase.makeRule - , Install.ClauseInCase.init "Backend" "updateFromFrontend" "CounterReset" "( { model | counter = 0 }, broadcast (CounterNewValue 0 clientId) )" + , Install.ClauseInCase.init "Backend" "updateFromFrontend" "CounterReset" "( { model | counter = 0 }, broadcast (CounterNewValue 0 clientId) )" |> Install.ClauseInCase.makeRule - , Install.Function.init "Frontend" "view" viewFunction |>Install.Function.makeRule - + , Install.Function.ReplaceFunction.init "Frontend" "view" viewFunction |> Install.Function.ReplaceFunction.makeRule ] -viewFunction = """view model = +viewFunction = + """view model = Html.div [ style "padding" "50px" ] [ Html.button [ onClick Increment ] [ text "+" ] , Html.div [ style "padding" "10px" ] [ Html.text (String.fromInt model.counter) ] @@ -47,7 +49,9 @@ viewFunction = """view model = , Html.button [ onClick Reset ] [ text "Reset" ] ]""" -viewFunction2 = """view model = + +viewFunction2 = + """view model = Html.div [ style "padding" "50px" ] [ Html.button [ onClick Increment ] [ text "+" ] , Html.div [ style "padding" "10px" ] [ Html.text (String.fromInt model.counter) ] @@ -56,7 +60,9 @@ Html.div [ style "padding" "50px" ] , Html.button [ onClick Reset ] [ text "Reset" ] ]""" -viewFunction3 = """view model = + +viewFunction3 = + """view model = Html.div [ style "padding" "50px" ] [ Html.button [ onClick Increment ] [ text "+" ] , Html.div [ style "padding" "10px" ] [ Html.text (String.fromInt model.counter) ] @@ -66,142 +72,160 @@ viewFunction3 = """view model = ]""" - config2 : List Rule config2 = - [ - -- TYPES - Install.Type.makeRule "Types" "SignInState" [ "SignedOut", "SignUp", "SignedIn" ] - , Install.Type.makeRule "Types" "BackendDataStatus" [ "Sunny", "LoadedBackendData" ] - -- TYPES IMPORTS - , Install.Import.initSimple "Types" ["Auth.Common", "MagicLink.Types", "User", "Session", "Dict", "AssocList"] |>Install.Import.makeRule - , Install.Import.init "Types" [{moduleToImport = "Url", alias_ = Nothing, exposedValues = Just ["Url"] }] |>Install.Import.makeRule - -- Type Frontend, MagicLink - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "authFlow : Auth.Common.Flow" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "authRedirectBaseUrl : Url" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "signinForm : MagicLink.Types.SigninForm" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "loginErrorMessage : Maybe String" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "signInStatus : MagicLink.Types.SignInStatus" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "currentUserData : Maybe User.LoginData" - -- Type Frontend, User - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "currentUser : Maybe User.User" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "signInState : SignInState" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "realname : String" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "username : String" - , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "email : String" - -- Type BackendModel - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "pendingAuths : Dict Lamdera.SessionId Auth.Common.PendingAuth" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "pendingEmailAuths : Dict Lamdera.SessionId Auth.Common.PendingEmailAuth" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "sessions : Dict SessionId Auth.Common.UserInfo" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "secretCounter : Int" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "sessionDict : AssocList.Dict SessionId String" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "pendingLogins: MagicLink.Types.PendingLogins" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "log : MagicLink.Types.Log" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "users: Dict.Dict User.EmailString User.User" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "userNameToEmailString : Dict.Dict User.Username User.EmailString" - , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "sessionInfo : Session.SessionInfo" - -- Type ToBackend - , Install.TypeVariant.makeRule "Types" "ToBackend" "AuthToBackend Auth.Common.ToBackend" - , Install.TypeVariant.makeRule "Types" "ToBackend" "AddUser String String String" - , Install.TypeVariant.makeRule "Types" "ToBackend" "RequestSignup String String String" - -- Type BackendMsg - , Install.TypeVariant.makeRule "Types" "BackendMsg" "AuthBackendMsg Auth.Common.BackendMsg" - , Install.TypeVariant.makeRule "Types" "BackendMsg" "AutoLogin SessionId User.LoginData" - -- Type ToFrontend - , Install.TypeVariant.makeRule "Types" "ToFrontend" "AuthToFrontend Auth.Common.ToFrontend" - , Install.TypeVariant.makeRule "Types" "ToFrontend" "AuthSuccess Auth.Common.UserInfo" - , Install.TypeVariant.makeRule "Types" "ToFrontend" "UserInfoMsg (Maybe Auth.Common.UserInfo)" - , Install.TypeVariant.makeRule "Types" "ToFrontend" "CheckSignInResponse (Result BackendDataStatus User.LoginData)" - , Install.TypeVariant.makeRule "Types" "ToFrontend" "GetLoginTokenRateLimited" - , Install.TypeVariant.makeRule "Types" "ToFrontend" "RegistrationError String" - , Install.TypeVariant.makeRule "Types" "ToFrontend" "SignInError String" - , Install.TypeVariant.makeRule "Types" "ToFrontend" "UserSignedIn (Maybe User.User)" - -- Initialize BackendModel - , Install.Initializer.makeRule "Backend" "init" "users" "Dict.empty" - , Install.Initializer.makeRule "Backend" "init" "sessions" "Dict.empty" - , Install.Initializer.makeRule "Backend" "init" "time" "Time.millisToPosix 0" - , Install.Initializer.makeRule "Backend" "init" "time" "Time.millisToPosix 0" - , Install.Initializer.makeRule "Backend" "init" "randomAtmosphericNumbers" "Nothing" - , Install.Initializer.makeRule "Backend" "init" "localUuidData" "Dict.empty" - , Install.Initializer.makeRule "Backend" "init" "pendingAuths" "Nothing" - , Install.Initializer.makeRule "Backend" "init" "localUuidData" "Nothing" - , Install.Initializer.makeRule "Backend" "init" "secretCounter" "0" - , Install.Initializer.makeRule "Backend" "init" "pendingAuths" "Dict.empty" - , Install.Initializer.makeRule "Backend" "init" "pendingEmailAuths" "Dict.empty" - , Install.Initializer.makeRule "Backend" "init" "sessionDict" "AssocList.empty" - , Install.Initializer.makeRule "Backend" "init" "sessionDict" "AssocList.empty" - , Install.Initializer.makeRule "Backend" "init" "log" "[]" - -- Backend import - , Install.Import.initSimple "Backend" - ["Auth.Common", "AssocList", "Auth.Flow" , "Dict", "Helper", "LocalUUID", - "MagicLink.Auth", "Process", "Task", "Time", "User"] - |>Install.Import.makeRule - , Install.Import.init "Backend" [{moduleToImport = "Lamdera", alias_ = Nothing, exposedValues = Just ["ClientId", "SessionId"]}] |>Install.Import.makeRule - --- - , Install.ClauseInCase.init - "Frontend" "updateFromBacked" - "AuthToFrontend authToFrontendMsg" - "MagicLink.Auth.updateFromBackend authToFrontendMsg model" - |> Install.ClauseInCase.makeRule - + [ -- TYPES + Install.Type.makeRule "Types" "SignInState" [ "SignedOut", "SignUp", "SignedIn" ] + , Install.Type.makeRule "Types" "BackendDataStatus" [ "Sunny", "LoadedBackendData" ] + + -- TYPES IMPORTS + , Install.Import.initSimple "Types" [ "Auth.Common", "MagicLink.Types", "User", "Session", "Dict", "AssocList" ] |> Install.Import.makeRule + , Install.Import.init "Types" [ { moduleToImport = "Url", alias_ = Nothing, exposedValues = Just [ "Url" ] } ] |> Install.Import.makeRule + + -- Type Frontend, MagicLink + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "authFlow : Auth.Common.Flow" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "authRedirectBaseUrl : Url" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "signinForm : MagicLink.Types.SigninForm" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "loginErrorMessage : Maybe String" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "signInStatus : MagicLink.Types.SignInStatus" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "currentUserData : Maybe User.LoginData" + + -- Type Frontend, User + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "currentUser : Maybe User.User" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "signInState : SignInState" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "realname : String" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "username : String" + , Install.FieldInTypeAlias.makeRule "Types" "FrontendModel" "email : String" + + -- Type BackendModel + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "pendingAuths : Dict Lamdera.SessionId Auth.Common.PendingAuth" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "pendingEmailAuths : Dict Lamdera.SessionId Auth.Common.PendingEmailAuth" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "sessions : Dict SessionId Auth.Common.UserInfo" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "secretCounter : Int" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "sessionDict : AssocList.Dict SessionId String" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "pendingLogins: MagicLink.Types.PendingLogins" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "log : MagicLink.Types.Log" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "users: Dict.Dict User.EmailString User.User" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "userNameToEmailString : Dict.Dict User.Username User.EmailString" + , Install.FieldInTypeAlias.makeRule "Types" "BackendModel" "sessionInfo : Session.SessionInfo" + + -- Type ToBackend + , Install.TypeVariant.makeRule "Types" "ToBackend" "AuthToBackend Auth.Common.ToBackend" + , Install.TypeVariant.makeRule "Types" "ToBackend" "AddUser String String String" + , Install.TypeVariant.makeRule "Types" "ToBackend" "RequestSignup String String String" + + -- Type BackendMsg + , Install.TypeVariant.makeRule "Types" "BackendMsg" "AuthBackendMsg Auth.Common.BackendMsg" + , Install.TypeVariant.makeRule "Types" "BackendMsg" "AutoLogin SessionId User.LoginData" + + -- Type ToFrontend + , Install.TypeVariant.makeRule "Types" "ToFrontend" "AuthToFrontend Auth.Common.ToFrontend" + , Install.TypeVariant.makeRule "Types" "ToFrontend" "AuthSuccess Auth.Common.UserInfo" + , Install.TypeVariant.makeRule "Types" "ToFrontend" "UserInfoMsg (Maybe Auth.Common.UserInfo)" + , Install.TypeVariant.makeRule "Types" "ToFrontend" "CheckSignInResponse (Result BackendDataStatus User.LoginData)" + , Install.TypeVariant.makeRule "Types" "ToFrontend" "GetLoginTokenRateLimited" + , Install.TypeVariant.makeRule "Types" "ToFrontend" "RegistrationError String" + , Install.TypeVariant.makeRule "Types" "ToFrontend" "SignInError String" + , Install.TypeVariant.makeRule "Types" "ToFrontend" "UserSignedIn (Maybe User.User)" + + -- Initialize BackendModel + , Install.Initializer.makeRule "Backend" "init" "users" "Dict.empty" + , Install.Initializer.makeRule "Backend" "init" "sessions" "Dict.empty" + , Install.Initializer.makeRule "Backend" "init" "time" "Time.millisToPosix 0" + , Install.Initializer.makeRule "Backend" "init" "time" "Time.millisToPosix 0" + , Install.Initializer.makeRule "Backend" "init" "randomAtmosphericNumbers" "Nothing" + , Install.Initializer.makeRule "Backend" "init" "localUuidData" "Dict.empty" + , Install.Initializer.makeRule "Backend" "init" "pendingAuths" "Nothing" + , Install.Initializer.makeRule "Backend" "init" "localUuidData" "Nothing" + , Install.Initializer.makeRule "Backend" "init" "secretCounter" "0" + , Install.Initializer.makeRule "Backend" "init" "pendingAuths" "Dict.empty" + , Install.Initializer.makeRule "Backend" "init" "pendingEmailAuths" "Dict.empty" + , Install.Initializer.makeRule "Backend" "init" "sessionDict" "AssocList.empty" + , Install.Initializer.makeRule "Backend" "init" "sessionDict" "AssocList.empty" + , Install.Initializer.makeRule "Backend" "init" "log" "[]" + + -- Backend import + , Install.Import.initSimple "Backend" + [ "Auth.Common" + , "AssocList" + , "Auth.Flow" + , "Dict" + , "Helper" + , "LocalUUID" + , "MagicLink.Auth" + , "Process" + , "Task" + , "Time" + , "User" + ] + |> Install.Import.makeRule + , Install.Import.init "Backend" [ { moduleToImport = "Lamdera", alias_ = Nothing, exposedValues = Just [ "ClientId", "SessionId" ] } ] |> Install.Import.makeRule + + --- + , Install.ClauseInCase.init + "Frontend" + "updateFromBacked" + "AuthToFrontend authToFrontendMsg" + "MagicLink.Auth.updateFromBackend authToFrontendMsg model" + |> Install.ClauseInCase.makeRule ] -{- - updateFromBackendLoaded : ToFrontend -> LoadedModel -> ( LoadedModel, Cmd msg ) - updateFromBackendLoaded msg model = - case msg of - AuthToFrontend authToFrontendMsg -> - MagicLink.Auth.updateFromBackend authToFrontendMsg model +{- - GotBackendModel beModel -> - ( { model | backendModel = Just beModel }, Cmd.none ) - -- MAGICLINK - AuthSuccess userInfo -> - -- TODO (placholder) - case userInfo.username of - Just username -> - ( { model | authFlow = Auth.Common.Authorized userInfo.email username }, Cmd.none ) + updateFromBackendLoaded : ToFrontend -> LoadedModel -> ( LoadedModel, Cmd msg ) + updateFromBackendLoaded msg model = + case msg of + AuthToFrontend authToFrontendMsg -> + MagicLink.Auth.updateFromBackend authToFrontendMsg model - Nothing -> - ( model, Cmd.none ) + GotBackendModel beModel -> + ( { model | backendModel = Just beModel }, Cmd.none ) - UserInfoMsg _ -> - -- TODO (placholder) - ( model, Cmd.none ) + -- MAGICLINK + AuthSuccess userInfo -> + -- TODO (placholder) + case userInfo.username of + Just username -> + ( { model | authFlow = Auth.Common.Authorized userInfo.email username }, Cmd.none ) - SignInError message -> - MagicLink.Frontend.handleSignInError model message + Nothing -> + ( model, Cmd.none ) - RegistrationError str -> - MagicLink.Frontend.handleRegistrationError model str + UserInfoMsg _ -> + -- TODO (placholder) + ( model, Cmd.none ) - CheckSignInResponse _ -> - ( model, Cmd.none ) + SignInError message -> + MagicLink.Frontend.handleSignInError model message - GetLoginTokenRateLimited -> - ( model, Cmd.none ) + RegistrationError str -> + MagicLink.Frontend.handleRegistrationError model str - UserRegistered user -> - MagicLink.Frontend.userRegistered model user + CheckSignInResponse _ -> + ( model, Cmd.none ) - UserSignedIn maybeUser -> - ( { model | signInStatus = MagicLink.Types.NotSignedIn }, Cmd.none ) + GetLoginTokenRateLimited -> + ( model, Cmd.none ) - GotMessage message -> - ( { model | message = message }, Cmd.none ) + UserRegistered user -> + MagicLink.Frontend.userRegistered model user - AdminInspectResponse backendModel -> - ( { model | backendModel = Just backendModel }, Cmd.none ) + UserSignedIn maybeUser -> + ( { model | signInStatus = MagicLink.Types.NotSignedIn }, Cmd.none ) + GotMessage message -> + ( { model | message = message }, Cmd.none ) - , Cmd.batch - [ Time.now |> Task.perform GotFastTick - , Helper.getAtmosphericRandomNumbers - ] - ) --} + AdminInspectResponse backendModel -> + ( { model | backendModel = Just backendModel }, Cmd.none ) + , Cmd.batch + [ Time.now |> Task.perform GotFastTick + , Helper.getAtmosphericRandomNumbers + ] + ) +-} diff --git a/review/elm.json b/review/elm.json index 364253d..7fd1552 100644 --- a/review/elm.json +++ b/review/elm.json @@ -9,7 +9,7 @@ "elm/core": "1.0.5", "elm/json": "1.1.3", "elm/project-metadata-utils": "1.0.2", - "jfmengels/elm-review": "2.13.2", + "jfmengels/elm-review": "2.14.0", "jfmengels/elm-review-debug": "1.0.8", "jfmengels/elm-review-unused": "1.2.3", "jxxcarlson/elm-review-codeinstaller": "1.0.0", @@ -20,11 +20,11 @@ "elm/html": "1.0.0", "elm/parser": "1.1.0", "elm/random": "1.0.0", + "elm/regex": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.3", "elm-explorations/test": "2.2.0", "miniBill/elm-unicode": "1.1.1", - "pzp1997/assoc-list": "1.0.0", "rtfeldman/elm-hex": "1.0.0", "stil4m/structured-writer": "1.0.3" } @@ -33,9 +33,6 @@ "direct": { "elm-explorations/test": "2.2.0" }, - "indirect": { - "elm/regex": "1.0.0", - "pzp1997/assoc-list": "1.0.0" - } + "indirect": {} } } diff --git a/src/Install/Function.elm b/src/Install/Function/InsertFunction.elm similarity index 53% rename from src/Install/Function.elm rename to src/Install/Function/InsertFunction.elm index 3692c57..3f669ce 100644 --- a/src/Install/Function.elm +++ b/src/Install/Function/InsertFunction.elm @@ -1,45 +1,43 @@ -module Install.Function exposing (makeRule, init, Config, CustomError, withInsertAfter) +module Install.Function.InsertFunction exposing (makeRule, init, Config, CustomError, withInsertAfter) -{-| Replace a function in a given module with a new implementation or -add that function definition if it is not present in the module. +{-| Add a function in a given module if it is not present. -- code for ReviewConfig.elm: rule = - Install.Function.init + Install.Function.InsertFunction.init "Frontend" "view" """view model = - Html.text "This is a test\"""" - |> Install.Function.makeRule + Html.text "This is a test\"""" + |> Install.Function.InsertFunction.makeRule + +Running this rule will insert the function `view` in the module `Frontend` with the provided implementation. -Running this rule will insert or replace the function `view` in the module `Frontend` with the new implementation. The form of the rule is the same for nested modules: rule = - Install.Function.init + Install.Function.InsertFunction.init "Foo.Bar" "earnInterest" "hoho model = { model | interest = 1.03 * model.interest }" - |> Install.Function.makeRule + |> Install.Function.InsertFunction.makeRule @docs makeRule, init, Config, CustomError, withInsertAfter -} -import Elm.Syntax.Declaration exposing (Declaration(..)) -import Elm.Syntax.Expression exposing (Expression, Function) +import Elm.Syntax.Declaration exposing (Declaration) +import Elm.Syntax.Expression exposing (Expression) import Elm.Syntax.ModuleName exposing (ModuleName) import Elm.Syntax.Node as Node exposing (Node) import Elm.Syntax.Range as Range exposing (Range) -import Install.Infer as Infer import Install.Library -import Install.Normalize as Normalize import Review.Fix as Fix import Review.ModuleNameLookupTable exposing (ModuleNameLookupTable) import Review.Rule as Rule exposing (Error, Rule) -{-| Configuration for makeRule: add (or replace if function already exists) a function in a specified module. +{-| Configuration for makeRule: add a function in a specified module if it does not already exist. -} type Config = Config @@ -79,13 +77,12 @@ init moduleNaeme functionName functionImplementation = , functionName = functionName , functionImplementation = functionImplementation , theFunctionNodeExpression = Install.Library.maybeNodeExpressionFromString { moduleName = String.split "." moduleNaeme } functionImplementation - , customErrorMessage = CustomError { message = "Replace function \"" ++ functionName ++ "\" with new code.", details = [ "" ] } + , customErrorMessage = CustomError { message = "Add function \"" ++ functionName ++ "\".", details = [ "" ] } , insertAt = AtEnd } -{-| Create a rule that replaces a function in a given module with a new implementation or -creates it if it is not present. +{-| Create a rule that adds a function in a given module if it is not present. -} makeRule : Config -> Rule makeRule config = @@ -94,7 +91,7 @@ makeRule config = visitor declaration context = declarationVisitor context config declaration in - Rule.newModuleRuleSchemaUsingContextCreator "Install.Function" initialContext + Rule.newModuleRuleSchemaUsingContextCreator "Install.Function.InsertFunction" initialContext |> Rule.withDeclarationEnterVisitor visitor |> Rule.withFinalModuleEvaluation (finalEvaluation config) |> Rule.providesFixesForModuleRule @@ -120,10 +117,13 @@ initialContext = declarationVisitor : Context -> Config -> Node Declaration -> ( List (Rule.Error {}), Context ) declarationVisitor context (Config config) declaration = let + declarationName = + Install.Library.getDeclarationName declaration + contextWithLastDeclarationRange = case config.insertAt of After previousDeclaration -> - if getDeclarationName declaration == previousDeclaration then + if Install.Library.getDeclarationName declaration == previousDeclaration then { context | lastDeclarationRange = Node.range declaration } else @@ -132,49 +132,11 @@ declarationVisitor context (Config config) declaration = AtEnd -> { context | lastDeclarationRange = Node.range declaration } in - case Node.value declaration of - FunctionDeclaration function -> - let - name : String - name = - getDeclarationName declaration - - isInCorrectModule = - Install.Library.isInCorrectModule config.moduleName context - - resources = - { lookupTable = context.lookupTable, inferredConstants = ( Infer.empty, [] ) } - - -- isNotImplemented returns True if the values of the current function expression and the replacement expression are different - isNotImplemented : Function -> { a | functionImplementation : String } -> Bool - isNotImplemented f confg = - Maybe.map2 (Normalize.compare resources) - (f.declaration |> Node.value |> .expression |> Just) - (Install.Library.getExpressionFromString context confg.functionImplementation) - == Just Normalize.ConfirmedEquality - |> not - - isImplemented = - not (isNotImplemented function config) - in - if isImplemented then - ( [], { contextWithLastDeclarationRange | appliedFix = True } ) - - else if name == config.functionName && isInCorrectModule && isNotImplemented function config then - replaceFunction { range = Node.range declaration, functionName = config.functionName, functionImplementation = config.functionImplementation } context - - else - ( [], contextWithLastDeclarationRange ) + if declarationName == config.functionName then + ( [], { context | appliedFix = True } ) - _ -> - ( [], contextWithLastDeclarationRange ) - - -type alias FixConfig = - { range : Range - , functionName : String - , functionImplementation : String - } + else + ( [], contextWithLastDeclarationRange ) finalEvaluation : Config -> Context -> List (Rule.Error {}) @@ -186,15 +148,11 @@ finalEvaluation (Config config) context = [] -replaceFunction : FixConfig -> Context -> ( List (Error {}), Context ) -replaceFunction fixConfig context = - ( [ Rule.errorWithFix - { message = "Replace function \"" ++ fixConfig.functionName ++ "\"", details = [ "" ] } - fixConfig.range - [ Fix.replaceRangeBy fixConfig.range fixConfig.functionImplementation ] - ] - , { context | appliedFix = True } - ) +type alias FixConfig = + { range : Range + , functionName : String + , functionImplementation : String + } addFunction : FixConfig -> List (Error {}) @@ -204,26 +162,3 @@ addFunction fixConfig = fixConfig.range [ Fix.insertAt { row = fixConfig.range.end.row + 1, column = 0 } fixConfig.functionImplementation ] ] - - -getDeclarationName : Node Declaration -> String -getDeclarationName declaration = - let - getName declaration_ = - declaration_ |> .name >> Node.value - in - case Node.value declaration of - FunctionDeclaration function -> - getName (Node.value function.declaration) - - AliasDeclaration alias_ -> - getName alias_ - - CustomTypeDeclaration customType -> - getName customType - - PortDeclaration port_ -> - getName port_ - - _ -> - "" diff --git a/src/Install/Function/ReplaceFunction.elm b/src/Install/Function/ReplaceFunction.elm new file mode 100644 index 0000000..c7087e2 --- /dev/null +++ b/src/Install/Function/ReplaceFunction.elm @@ -0,0 +1,155 @@ +module Install.Function.ReplaceFunction exposing (makeRule, init, Config, CustomError) + +{-| Replace a function in a given module with a new implementation. + + -- code for ReviewConfig.elm: + rule = + Install.Function.InsertFunction.init + "Frontend" + "view" + """view model = + Html.text "This is a test\"""" + |> Install.Function.InsertFunction.makeRule + +Running this rule will replace the function `view` in the module `Frontend` with the provided implementation. + +The form of the rule is the same for nested modules: + + rule = + Install.Function.ReplaceFunction.init + "Foo.Bar" + "earnInterest" + "hoho model = { model | interest = 1.03 * model.interest }" + |> Install.Function.ReplaceFunction.makeRule + +@docs makeRule, init, Config, CustomError + +-} + +import Elm.Syntax.Declaration exposing (Declaration(..)) +import Elm.Syntax.Expression exposing (Expression, Function) +import Elm.Syntax.ModuleName exposing (ModuleName) +import Elm.Syntax.Node as Node exposing (Node) +import Elm.Syntax.Range exposing (Range) +import Install.Infer as Infer +import Install.Library +import Install.Normalize as Normalize +import Review.Fix as Fix +import Review.ModuleNameLookupTable exposing (ModuleNameLookupTable) +import Review.Rule as Rule exposing (Error, Rule) + + +{-| Configuration for makeRule: replace a function in a specified module with a new implementation. +-} +type Config + = Config + { moduleName : String + , functionName : String + , functionImplementation : String + , theFunctionNodeExpression : Maybe (Node Expression) + , customErrorMessage : CustomError + } + + +{-| Custom error message to be displayed when running `elm-review --fix` or `elm-review --fix-all` +-} +type CustomError + = CustomError { message : String, details : List String } + + +{-| Initialize the configuration for the rule. +-} +init : String -> String -> String -> Config +init moduleNaeme functionName functionImplementation = + Config + { moduleName = moduleNaeme + , functionName = functionName + , functionImplementation = functionImplementation + , theFunctionNodeExpression = Install.Library.maybeNodeExpressionFromString { moduleName = String.split "." moduleNaeme } functionImplementation + , customErrorMessage = CustomError { message = "Replace function \"" ++ functionName ++ "\" with new code.", details = [ "" ] } + } + + +{-| Create a rule that replaces a function in a given module with a new implementation. +-} +makeRule : Config -> Rule +makeRule config = + let + visitor : Node Declaration -> Context -> ( List (Error {}), Context ) + visitor declaration context = + declarationVisitor context config declaration + in + Rule.newModuleRuleSchemaUsingContextCreator "Install.Function.ReplaceFunction" initialContext + |> Rule.withDeclarationEnterVisitor visitor + |> Rule.providesFixesForModuleRule + |> Rule.fromModuleRuleSchema + + +type alias Context = + { moduleName : ModuleName + , lookupTable : ModuleNameLookupTable + } + + +initialContext : Rule.ContextCreator () Context +initialContext = + Rule.initContextCreator + (\lookupTable moduleName () -> { lookupTable = lookupTable, moduleName = moduleName }) + |> Rule.withModuleNameLookupTable + |> Rule.withModuleName + + +declarationVisitor : Context -> Config -> Node Declaration -> ( List (Rule.Error {}), Context ) +declarationVisitor context (Config config) declaration = + case Node.value declaration of + FunctionDeclaration function -> + let + name : String + name = + Install.Library.getDeclarationName declaration + + isInCorrectModule = + Install.Library.isInCorrectModule config.moduleName context + + resources = + { lookupTable = context.lookupTable, inferredConstants = ( Infer.empty, [] ) } + + isInCorrectFunction = + isInCorrectModule && name == config.functionName + + isNotImplemented : Function -> { a | functionImplementation : String } -> Bool + isNotImplemented f confg = + isInCorrectFunction + && (Maybe.map2 (Normalize.compare resources) + (f.declaration |> Node.value |> .expression |> Just) + (Install.Library.getExpressionFromString context confg.functionImplementation) + == Just Normalize.ConfirmedEquality + |> not + ) + in + if isNotImplemented function config then + replaceFunction { range = Node.range declaration, functionName = config.functionName, functionImplementation = config.functionImplementation } context + + else + ( [], context ) + + _ -> + ( [], context ) + + +replaceFunction : FixConfig -> Context -> ( List (Error {}), Context ) +replaceFunction fixConfig context = + ( [ Rule.errorWithFix + { message = "Replace function \"" ++ fixConfig.functionName ++ "\"", details = [ "" ] } + fixConfig.range + [ Fix.replaceRangeBy fixConfig.range fixConfig.functionImplementation ] + ] + , context + ) + + +type alias FixConfig = + { range : Range + , functionName : String + , functionImplementation : String + } diff --git a/src/Install/Library.elm b/src/Install/Library.elm index 8878dcd..d5ac5d6 100644 --- a/src/Install/Library.elm +++ b/src/Install/Library.elm @@ -254,3 +254,26 @@ isInCorrectModule moduleName context = context.moduleName |> String.join "." |> (==) moduleName + + +getDeclarationName : Node Declaration -> String +getDeclarationName declaration = + let + getName declaration_ = + declaration_ |> .name >> Node.value + in + case Node.value declaration of + FunctionDeclaration function -> + getName (Node.value function.declaration) + + AliasDeclaration alias_ -> + getName alias_ + + CustomTypeDeclaration customType -> + getName customType + + PortDeclaration port_ -> + getName port_ + + _ -> + "" diff --git a/tests/Install/FunctionTest.elm b/tests/Install/FunctionTest.elm index a48a61d..5707981 100644 --- a/tests/Install/FunctionTest.elm +++ b/tests/Install/FunctionTest.elm @@ -1,6 +1,7 @@ module Install.FunctionTest exposing (all) -import Install.Function +import Install.Function.InsertFunction as InsertFunction +import Install.Function.ReplaceFunction as ReplaceFunction import Review.Rule exposing (Rule) import Run import Test exposing (Test, describe) @@ -36,12 +37,12 @@ test1 = rule1 : Rule rule1 = - Install.Function.init + ReplaceFunction.init "Frontend" "view" """view model = Html.text "This is a test\"""" - |> Install.Function.makeRule + |> ReplaceFunction.makeRule src1 : String @@ -103,12 +104,12 @@ test2 = rule2 : Rule rule2 = - Install.Function.init + InsertFunction.init "Frontend" "newFunction" """newFunction model = Html.text "This is a test\"""" - |> Install.Function.makeRule + |> InsertFunction.makeRule under2 : String @@ -168,12 +169,12 @@ type alias Model = rule3 : Rule rule3 = - Install.Function.init + InsertFunction.init "Frontend" "newFunction" """newFunction model = Html.text "This is a test\"""" - |> Install.Function.makeRule + |> InsertFunction.makeRule under3 : String @@ -212,13 +213,13 @@ test4 = rule4 : Rule rule4 = - Install.Function.init + InsertFunction.init "Frontend" "newFunction" """newFunction model = Html.text "This is a test\"""" - |> Install.Function.withInsertAfter "view" - |> Install.Function.makeRule + |> InsertFunction.withInsertAfter "view" + |> InsertFunction.makeRule under4 : String @@ -259,13 +260,13 @@ test4a = rule4a : Rule rule4a = - Install.Function.init + InsertFunction.init "Frontend" "newFunction" """newFunction model = Html.text "This is a test\"""" - |> Install.Function.withInsertAfter "Model" - |> Install.Function.makeRule + |> InsertFunction.withInsertAfter "Model" + |> InsertFunction.makeRule under4a : String @@ -305,13 +306,13 @@ test4b = rule4b : Rule rule4b = - Install.Function.init + InsertFunction.init "Frontend" "newFunction" """newFunction model = Html.text "This is a test\"""" - |> Install.Function.withInsertAfter "Model" - |> Install.Function.makeRule + |> InsertFunction.withInsertAfter "Model" + |> InsertFunction.makeRule under4b : String