diff --git a/demo/elm.json b/demo/elm.json index 23f9e1e9..e2d8ae45 100644 --- a/demo/elm.json +++ b/demo/elm.json @@ -13,12 +13,12 @@ "elm/browser": "1.0.1", "elm/core": "1.0.2", "elm/html": "1.0.0", + "elm/json": "1.1.3", "elm/project-metadata-utils": "1.0.0", "jinjor/elm-diff": "1.0.6", "stil4m/elm-syntax": "7.1.0" }, "indirect": { - "elm/json": "1.1.3", "elm/parser": "1.1.0", "elm/random": "1.0.0", "elm/time": "1.0.0", diff --git a/elm.json b/elm.json index 78b3c258..44f69da4 100644 --- a/elm.json +++ b/elm.json @@ -15,6 +15,7 @@ "elm-version": "0.19.0 <= v < 0.20.0", "dependencies": { "elm/core": "1.0.2 <= v < 2.0.0", + "elm/json": "1.0.0 <= v < 2.0.0", "elm/project-metadata-utils": "1.0.0 <= v < 2.0.0", "elm-explorations/test": "1.2.2 <= v < 2.0.0", "stil4m/elm-syntax": "7.1.0 <= v < 8.0.0" diff --git a/src/Review/ModuleInterface.elm b/src/Review/ModuleInterface.elm new file mode 100644 index 00000000..5aae7392 --- /dev/null +++ b/src/Review/ModuleInterface.elm @@ -0,0 +1,179 @@ +module Review.ModuleInterface exposing (Exposed(..), fromDocsJson) + +import Elm.Interface as Interface +import Elm.Syntax.Infix as Infix exposing (InfixDirection) +import Elm.Syntax.Node as Node exposing (Node) +import Elm.Syntax.Range as Range +import Json.Decode as Decode + + + +-- DEFINITION + + +type Exposed + = CustomType + { name : String + , arguments : List String + , cases : List Case + } + | TypeAlias + { name : String + , arguments : List String + , type_ : String + } + | Value + { name : String + , type_ : String + } + | Operator + { name : String + , type_ : String + , precedence : Int + , associativity : InfixDirection + } + + +type alias Case = + { name : String + , arguments : List String + } + + + +-- DECODE + + +fromDocsJson : Decode.Decoder (List ( String, List Exposed )) +fromDocsJson = + Decode.list decodeItem + + +decodeItem : Decode.Decoder ( String, List Exposed ) +decodeItem = + Decode.map5 + (\name customTypes aliases values operators -> + ( name, List.concat [ customTypes, aliases, values, operators ] ) + ) + (Decode.field "name" Decode.string) + (Decode.field "unions" (Decode.list decodeCustomType)) + (Decode.field "aliases" (Decode.list decodeTypeAlias)) + (Decode.field "values" (Decode.list decodeValue)) + (Decode.field "binops" (Decode.list decodeOperator)) + + +decodeCustomType : Decode.Decoder Exposed +decodeCustomType = + Decode.map3 + (\name arguments cases -> + CustomType + { name = name + , arguments = arguments + , cases = cases + } + ) + (Decode.field "name" Decode.string) + (Decode.field "args" (Decode.list Decode.string)) + (Decode.field "cases" (Decode.list decodeCase)) + + +decodeCase : Decode.Decoder Case +decodeCase = + Decode.map2 Case + (Decode.field "0" Decode.string) + (Decode.field "1" (Decode.list Decode.string)) + + +decodeTypeAlias : Decode.Decoder Exposed +decodeTypeAlias = + Decode.map3 + (\name arguments type_ -> + TypeAlias + { name = name + , arguments = arguments + , type_ = type_ + } + ) + (Decode.field "name" Decode.string) + (Decode.field "args" (Decode.list Decode.string)) + (Decode.field "type" Decode.string) + + +decodeValue : Decode.Decoder Exposed +decodeValue = + Decode.map2 + (\name type_ -> + Value + { name = name + , type_ = type_ + } + ) + (Decode.field "name" Decode.string) + (Decode.field "type" Decode.string) + + +decodeOperator : Decode.Decoder Exposed +decodeOperator = + Decode.map4 + (\name type_ precedence associativity -> + Operator + { name = name + , type_ = type_ + , precedence = precedence + , associativity = associativity + } + ) + (Decode.field "name" Decode.string) + (Decode.field "type" Decode.string) + (Decode.field "precedence" Decode.int) + (Decode.field "associativity" decodeAssociativity) + + +decodeAssociativity : Decode.Decoder InfixDirection +decodeAssociativity = + Decode.string + |> Decode.andThen + (\infix -> + case infix of + "left" -> + Decode.succeed Infix.Left + + "non" -> + Decode.succeed Infix.Non + + "right" -> + Decode.succeed Infix.Right + + _ -> + Decode.fail "Unknown associativity for operator" + ) + + + +-- ELM-SYNTAX + + +toElmSyntaxInterface : List Exposed -> Interface.Interface +toElmSyntaxInterface exposedList = + List.map toElmSyntaxExposed exposedList + + +toElmSyntaxExposed : Exposed -> Interface.Exposed +toElmSyntaxExposed exposed = + case exposed of + CustomType { name, cases } -> + Interface.CustomType ( name, List.map .name cases ) + + TypeAlias { name } -> + Interface.Alias name + + Value { name } -> + Interface.Function name + + Operator { name, associativity, precedence } -> + Interface.Operator + { operator = Node.Node Range.emptyRange name + , direction = Node.Node Range.emptyRange associativity + , precedence = Node.Node Range.emptyRange precedence + , function = Node.Node Range.emptyRange name + } diff --git a/src/Review/Project.elm b/src/Review/Project.elm index 63cc5b47..92244896 100644 --- a/src/Review/Project.elm +++ b/src/Review/Project.elm @@ -1,7 +1,7 @@ module Review.Project exposing ( Project, ElmJson , elmJson, interfaces - , new, withElmJson + , new, withElmJson, withDependency ) {-| Represents project-related data, that a rule can access to get more information. @@ -23,7 +23,7 @@ rules to have access to, to later pass it to the [`Review.review`](./Review#revi # Build -@docs new, withElmJson +@docs new, withElmJson, withDependency -} @@ -31,6 +31,7 @@ import Dict exposing (Dict) import Elm.Interface exposing (Interface) import Elm.Project import Elm.Syntax.ModuleName exposing (ModuleName) +import Review.ModuleInterface as ModuleInterface @@ -43,12 +44,13 @@ the `elm.json` file. type Project = Project { elmJson : Maybe ElmJson - , interfaces : Dict ModuleName Interface + , interfaces : Dict ModuleName (List ModuleInterface.Exposed) + , moduleToDependency : Dict ModuleName String } {-| Contents of the `elm.json` file. Alias to -[`elm/project-metadata-utils`'s Project data structure](https://package.elm-lang.org/packages/elm/project-metadata-utils/latest/Elm-Project). +[`elm/project-metadata-utils`'s Project project structure](https://package.elm-lang.org/packages/elm/project-metadata-utils/latest/Elm-Project). -} type alias ElmJson = Elm.Project.Project @@ -67,8 +69,8 @@ information inside the `elm.json` file. -} elmJson : Project -> Maybe ElmJson -elmJson (Project data) = - data.elmJson +elmJson (Project project) = + project.elmJson {-| Get the interfaces for every dependency in the project. @@ -79,9 +81,9 @@ package, so you will need to install and use it to gain access to the information inside the `elm.json` file. -} -interfaces : Project -> Dict ModuleName Interface -interfaces (Project data) = - data.interfaces +interfaces : Project -> Dict ModuleName (List ModuleInterface.Exposed) +interfaces (Project project) = + project.interfaces @@ -95,11 +97,35 @@ new = Project { elmJson = Nothing , interfaces = Dict.empty + , moduleToDependency = Dict.empty } {-| Add the contents of the `elm.json` file to the project details. -} withElmJson : ElmJson -> Project -> Project -withElmJson elmJson_ (Project data) = - Project { data | elmJson = Just elmJson_ } +withElmJson elmJson_ (Project project) = + Project { project | elmJson = Just elmJson_ } + + +{-| Add a dependency to the project +-} +withDependency : { packageName : String, interfaces : List ( String, List ModuleInterface.Exposed ) } -> Project -> Project +withDependency dependency (Project project) = + Project + { project + | interfaces = + dependency.interfaces + |> List.map (Tuple.mapFirst (String.split ".")) + |> Dict.fromList + |> Dict.union project.interfaces + , moduleToDependency = + dependency.interfaces + |> List.map + (Tuple.mapBoth + (String.split ".") + (always dependency.packageName) + ) + |> Dict.fromList + |> Dict.union project.moduleToDependency + }