Last active
July 21, 2021 11:19
-
-
Save mikaxyz/f7e2ff3e0745a29cf0c0f85c42c67366 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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