Skip to content

Instantly share code, notes, and snippets.

@mikaxyz
Last active July 21, 2021 11:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikaxyz/f7e2ff3e0745a29cf0c0f85c42c67366 to your computer and use it in GitHub Desktop.
Save mikaxyz/f7e2ff3e0745a29cf0c0f85c42c67366 to your computer and use it in GitHub Desktop.
module NoFunctionEquality exposing (rule)
import Dict exposing (Dict)
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Expression as Expression exposing (Expression)
import Elm.Syntax.Module as Module exposing (Module)
import Elm.Syntax.ModuleName exposing (ModuleName)
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Range exposing (Range)
import Review.Rule as Rule exposing (Direction, Error, ModuleKey, Rule)
import Set exposing (Set)
rule : Rule
rule =
Rule.newProjectRuleSchema "NoUnusedExportedFunctions" initialProjectContext
-- Omitted, but this will collect the list of exposed modules for packages.
-- We don't want to report functions that are exposed
--|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|> Rule.withModuleVisitor moduleVisitor
|> Rule.withModuleContext
{ fromProjectToModule = fromProjectToModule
, fromModuleToProject = fromModuleToProject
, foldProjectContexts = foldProjectContexts
}
|> Rule.withFinalProjectEvaluation finalEvaluationForProject
|> Rule.fromProjectRuleSchema
--
type alias ProjectContext =
--{ -- Modules exposed by the package, that we should not report
-- exposedModules : Set ModuleName
--, exposedFunctions :
-- -- An entry for each module
-- Dict
-- ModuleName
-- { -- To report errors in this module
-- moduleKey : Rule.ModuleKey
--
-- -- An entry for each function with its location
-- , exposed : Dict String Range
-- }
--, used : Set ( ModuleName, String )
{ equatedFunctionsOrValues : Set ( ModuleName, List String )
, functions : Set String
--, equatedFunctions : Dict String { moduleKey : Rule.ModuleKey, functionName : String }
}
type alias ModuleContext =
--{ isExposed : Bool
--, exposed : Dict String Range
--
----, used : Set ( ModuleName, String )
{ equatedFunctionsOrValues : Set ( ModuleName, List String )
, functions : Set String
}
initialProjectContext : ProjectContext
initialProjectContext =
--{ exposedModules = Set.empty
--, exposedFunctions = Dict.empty
--, modules = Dict.empty
--, used = Set.empty
{ equatedFunctionsOrValues = Set.empty
, functions = Set.empty
--, equatedFunctions = Dict.empty
}
--
moduleVisitor :
Rule.ModuleRuleSchema {} ModuleContext
-> Rule.ModuleRuleSchema { hasAtLeastOneVisitor : () } ModuleContext
moduleVisitor schema =
schema
-- Omitted, but this will collect the exposed functions
--|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
--|> Rule.withSimpleExpressionVisitor expressionVisitor
|> Rule.withDeclarationVisitor collectFunctionDefinitionsWithArguments
|> Rule.withExpressionVisitor collectEqualityChecks
collectFunctionDefinitionsWithArguments : Node Declaration -> Direction -> ModuleContext -> ( List (Error {}), ModuleContext )
collectFunctionDefinitionsWithArguments node dir ctx =
--declarationVisitor : Node Declaration -> List (Error {})
--declarationVisitor node =
case Node.value node of
--Declaration.FunctionDeclaration (Expression.Function _ _ (Node _ functionImplementation)) ->
Declaration.FunctionDeclaration { declaration } ->
let
functionImplementation =
Node.value declaration
in
if List.isEmpty functionImplementation.arguments then
( [], ctx )
else
( []
, { ctx
| functions =
ctx.functions
|> Set.insert (Node.value functionImplementation.name)
}
)
--case declaration of
-- Just _ ->
-- []
--
-- Nothing ->
-- --let
-- -- functionName : String
-- -- functionName =
-- -- declaration |> Node.value |> .name |> Node.value
-- --in
-- --[ Rule.error
-- -- { message = "Missing type annotation for `" ++ functionName ++ "`"
-- -- , details =
-- -- --[ "Type annotations are very helpful for people who read your code. It can give a lot of information without having to read the contents of the function. When encountering problems, the compiler will also give much more precise and helpful information to help you solve the problem."
-- -- --, "To add a type annotation, add a line like `" functionName ++ " : ()`, and replace the `()` by the type of the function. If you don't replace `()`, the compiler should give you a suggestion of what the type should be."
-- -- --]
-- -- []
-- -- }
-- -- (Node.range node)
-- --]
--[]
_ ->
( [], ctx )
--|> Rule.withExpressionVisitor collectFunctionNames
--moduleDefinitionVisitor : Node Module -> ModuleContext -> ( List (Error {}), ModuleContext )
--moduleDefinitionVisitor node ctx =
-- if List.any (String.contains "") (Node.value node |> Module.moduleName) then
-- ( [ Rule.error
-- { message = "Do not use `_` in a module name"
-- , details = [ "By convention, Elm modules names use Pascal case (like `MyModuleName`). Please rename your module using this format." ]
-- }
-- (Node.range node)
-- ]
-- , ctx
-- )
--
-- else
-- ( [], ctx )
collectEqualityChecks : Node Expression -> Direction -> ModuleContext -> ( List (Error {}), ModuleContext )
collectEqualityChecks node dir ctx =
case Node.value node of
-- It will look at string literals (like "a", """a""")
--Expression.Literal str ->
-- if String.contains "pinmeto.com" str then
-- -- Return a single error, describing the problem
-- [ Rule.error
-- { message = "Replace `frits.com` by `fruits.com`"
-- , details = [ "This typo has been made and noticed by users too many times. Our company is `fruits.com`, not `frits.com`." ]
-- }
-- -- This is the location of the problem in the source code
-- (Node.range node)
-- ]
--
-- else
-- []
--Expression.PrefixOperator operator ->
-- if operator == "==" then
-- let
-- _ =
-- Debug.log "x" node
-- in
-- []
--
-- else
-- []
Expression.OperatorApplication str _ node1 node2 ->
if str == "==" then
case ( Node.value node1, Node.value node2 ) of
( Expression.FunctionOrValue moduleName1 name1, Expression.FunctionOrValue moduleName2 name2 ) ->
( []
, { ctx
| equatedFunctionsOrValues =
ctx.equatedFunctionsOrValues
|> Set.insert ( moduleName1, [ name1, name2 ] )
}
)
_ ->
( [], ctx )
else
( [], ctx )
_ ->
( [], ctx )
-- Omitted, but this will collect uses of exported functions
--|> Rule.withExpressionEnterVisitor expressionVisitor
moduleDefinitionVisitor : Node Module -> ModuleContext -> ( List (Error {}), ModuleContext )
moduleDefinitionVisitor node ctx =
if List.any (String.contains "") (Node.value node |> Module.moduleName) then
( [ Rule.error
{ message = "Do not use `_` in a module name"
, details = [ "By convention, Elm modules names use Pascal case (like `MyModuleName`). Please rename your module using this format." ]
}
(Node.range node)
]
, ctx
)
else
( [], ctx )
fromProjectToModule : Rule.ModuleKey -> Node ModuleName -> ProjectContext -> ModuleContext
fromProjectToModule moduleKey moduleName projectContext =
--{ isExposed = Set.member (Node.value moduleName) projectContext.exposedModules
--, exposed = Dict.empty
--, used = Set.empty
{ equatedFunctionsOrValues = Set.empty
, functions = Set.empty
}
fromModuleToProject : Rule.ModuleKey -> Node ModuleName -> ModuleContext -> ProjectContext
fromModuleToProject moduleKey moduleName moduleContext =
-- { -- We don't care about this value, we'll take
-- the one from the initial context when folding
-- exposedModules = Set.empty
--, exposedFunctions =
-- if moduleContext.isExposed then
-- -- If the module is exposed, don't collect the exported functions
-- Dict.empty
--
-- else
-- -- Create a dictionary with all the exposed functions, associated to
-- -- the module that was just visited
-- Dict.singleton
-- (Node.value moduleName)
-- { moduleKey = moduleKey
-- , exposed = moduleContext.exposed
-- }
--, used = moduleContext.used
{ equatedFunctionsOrValues = moduleContext.equatedFunctionsOrValues
, functions = moduleContext.functions
}
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
foldProjectContexts newContext previousContext =
--{ -- Always take the one from the "initial" context,
-- -- which is always the second argument
-- exposedModules = previousContext.exposedModules
-- Collect the exposed functions from the new context and the previous one.
-- We could use `Dict.merge`, but in this case, that doesn't change anything
--, exposedFunctions = Dict.union previousContext.modules newContext.modules
--, exposedFunctions = Dict.empty
-- Collect the used functions from the new context and the previous one
--, used = Set.union newContext.used previousContext.used
{ equatedFunctionsOrValues = Set.union newContext.equatedFunctionsOrValues previousContext.equatedFunctionsOrValues
, functions = Set.union newContext.functions previousContext.functions
}
finalEvaluationForProject : ProjectContext -> List (Error { useErrorForModule : () })
finalEvaluationForProject projectContext =
projectContext
|> Debug.log "projectContext"
|> unusedFunctions
|> List.map
(\{ moduleKey, functionName, range } ->
Rule.errorForModule moduleKey
{ message = "Function `" ++ functionName ++ "` is never used"
, details = [ "<Omitted>" ]
}
range
)
unusedFunctions : ProjectContext -> List { moduleKey : ModuleKey, functionName : String, range : Range }
unusedFunctions ctx =
--ctx.equatedFunctionsOrValues
--|> Set.foldl (\((moduleName, name), i) -> if Set.member name ctx.functions then
-- {moduleKey = Review.Rule.ModuleKey "", functionName =name, range =Range.emptyRange} :: i
--)
[]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment