Skip to content

Instantly share code, notes, and snippets.

@pete-murphy
Last active March 27, 2024 13:50
Show Gist options
  • Save pete-murphy/bc9949ac37775aa94a795775c47f258f to your computer and use it in GitHub Desktop.
Save pete-murphy/bc9949ac37775aa94a795775c47f258f to your computer and use it in GitHub Desktop.
elm-review: ModuleUsage

Can run this with

❯  elm-review --extract --report=json --rules ModuleUsage | jq -r '.extracts.ModuleUsage'
module ModuleUsage exposing (..)
import Dict exposing (Dict)
import Elm.Syntax.Expression as Expression exposing (Expression)
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.ModuleName exposing (ModuleName)
import Elm.Syntax.Node as Node exposing (Node)
import Json.Encode as Encode
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
import Review.Project.Dependency as Dependency exposing (Dependency)
import Review.Rule as Rule exposing (Rule)
import Set exposing (Set)
rule : Rule
rule =
Rule.newProjectRuleSchema "ModuleUsage" initContext
|> Rule.withDependenciesProjectVisitor dependencyVisitor
|> Rule.withModuleVisitor
(moduleVisitor
>> Rule.withExpressionEnterVisitor z
)
|> Rule.withModuleContextUsingContextCreator
{ fromProjectToModule = fromProjectToModule
, fromModuleToProject = fromModuleToProject
, foldProjectContexts = foldProjectContexts
}
|> Rule.withDataExtractor dataExtractor
|> Rule.fromProjectRuleSchema
z : Node Expression -> ModuleContext -> ( List (Rule.Error {}), ModuleContext )
z node context =
case Node.value node of
Expression.FunctionOrValue _ identifier ->
case ModuleNameLookupTable.moduleNameFor context.lookupTable node of
Just m ->
( []
, { context
| counts =
unionWith (\x y -> x + y)
context.counts
(Dict.singleton m 1)
, functionsOrValues =
unionWith (\x y -> x ++ y)
context.functionsOrValues
(Dict.singleton context.moduleName [ ( m, identifier ) ])
}
)
Nothing ->
( []
, { context
| counts =
unionWith (\x y -> x + y)
context.counts
(Dict.singleton [ "<MISSED>" ] 1)
}
)
_ ->
( [], context )
type alias ProjectContext =
{ imports : Dict ModuleName (List ModuleName)
, dependencyModules : Set ModuleName
, counts : Dict ModuleName Int
, functionsOrValues : Dict ModuleName (List ( ModuleName, String ))
}
type alias ModuleContext =
{ imports : List ModuleName
, dependencyModules : Set ModuleName
, lookupTable : ModuleNameLookupTable
, counts : Dict ModuleName Int
, functionsOrValues : Dict ModuleName (List ( ModuleName, String ))
, moduleName : ModuleName
}
initContext : ProjectContext
initContext =
{ imports = Dict.empty
, dependencyModules = Set.empty
, counts = Dict.empty
, functionsOrValues = Dict.empty
}
dependencyVisitor : Dict String Dependency -> ProjectContext -> ( List never, ProjectContext )
dependencyVisitor ds context =
Dict.values ds
|> List.concatMap Dependency.modules
|> List.map (String.split "." << .name)
|> Set.fromList
|> (\dModules -> ( [], { context | dependencyModules = dModules } ))
fromProjectToModule : Rule.ContextCreator ProjectContext ModuleContext
fromProjectToModule =
Rule.initContextCreator
(\moduleName lookupTable x projectContext ->
{ imports = []
, dependencyModules = projectContext.dependencyModules
, lookupTable = lookupTable
, counts = projectContext.counts
, functionsOrValues = projectContext.functionsOrValues
, moduleName = moduleName
}
)
|> Rule.withModuleName
|> Rule.withModuleNameLookupTable
|> Rule.withFullAst
fromModuleToProject : Rule.ContextCreator ModuleContext ProjectContext
fromModuleToProject =
Rule.initContextCreator
(\moduleName moduleContext ->
{ imports =
if not (List.isEmpty moduleContext.imports) then
moduleContext.imports
|> List.sort
|> Dict.singleton moduleName
else
Dict.empty
, dependencyModules = moduleContext.dependencyModules
, counts = moduleContext.counts
, functionsOrValues = moduleContext.functionsOrValues
}
)
|> Rule.withModuleName
unionWith : (b -> b -> b) -> Dict comparable b -> Dict comparable b -> Dict comparable b
unionWith f a b =
-- (k ->a -> Dict k a -> Dict k a) -> (k -> a -> a -> Dict k a -> Dict k a) -> (k -> a -> Dict k a -> Dict k a)
-- -> Dict k a -> Dict k a -> Dict k a -> Dict k a
Dict.merge
(\k v d -> Dict.insert k v d)
(\k v1 v2 d -> Dict.insert k (f v1 v2) d)
(\k v d -> Dict.insert k v d)
a
b
Dict.empty
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
foldProjectContexts newContext previousContext =
{ imports = Dict.union newContext.imports previousContext.imports
, dependencyModules = previousContext.dependencyModules
, counts = unionWith (\x y -> x + y) newContext.counts previousContext.counts
, functionsOrValues = unionWith (\x y -> x ++ y) newContext.functionsOrValues previousContext.functionsOrValues
}
moduleVisitor :
Rule.ModuleRuleSchema {} ModuleContext
-> Rule.ModuleRuleSchema { hasAtLeastOneVisitor : () } ModuleContext
moduleVisitor schema =
schema
|> Rule.withImportVisitor importVisitor
dataExtractor : ProjectContext -> Encode.Value
dataExtractor projectContext =
-- projectContext.counts
-- |> Encode.dict unwrapModuleName Encode.int
projectContext.functionsOrValues
|> Encode.dict unwrapModuleName
(Encode.list
(\( m, s ) ->
Encode.object
[ ( "module", Encode.string (unwrapModuleName m) )
, ( "functionOrValue", Encode.string s )
]
)
)
-- |> Encode.int
-- (Encode.list (unwrapModuleName >> Encode.string))
-- -- Encode.object
-- [ ( "json",
-- ]
-- [ ( "json", Encode.dict unwrapModuleName (Encode.list (unwrapModuleName >> Encode.string)) projectContext.imports )
-- ]
unwrapModuleName : ModuleName -> String
unwrapModuleName =
String.join "."
importVisitor : Node Import -> ModuleContext -> ( List never, ModuleContext )
importVisitor imp context =
let
moduleName : ModuleName
moduleName =
Node.value imp
|> .moduleName
|> Node.value
in
( []
, { context
| imports = moduleName :: context.imports
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment