diff --git a/README.md b/README.md index 5b72529c..2521a934 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # elm-lint -An [Elm](http://elm-lang.org/) linter written in Elm. +An [Elm](http://elm-lang.org/) linter written in Elm. Get your code from correct to better. ## Try it diff --git a/elm-package.json b/elm-package.json index 8c5b3e5a..81d384a4 100644 --- a/elm-package.json +++ b/elm-package.json @@ -1,12 +1,28 @@ { "version": "1.0.0", - "summary": "helpful summary of your project, less than 80 characters", - "repository": "https://github.com/user/project.git", + "summary": "A linter for Elm. Get your code from correct to better.", + "repository": "https://github.com/jfmengels/elm-lint.git", "license": "BSD3", "source-directories": [ "src" ], - "exposed-modules": [], + "exposed-modules": [ + "Lint", + "Lint.Types", + "Lint.Rules.DefaultPatternPosition", + "Lint.Rules.NoConstantCondition", + "Lint.Rules.NoDebug", + "Lint.Rules.NoDuplicateImports", + "Lint.Rules.NoExposingEverything", + "Lint.Rules.NoImportingEverything", + "Lint.Rules.NoNestedLet", + "Lint.Rules.NoUnannotatedFunction", + "Lint.Rules.NoUnusedVariables", + "Lint.Rules.NoUselessIf", + "Lint.Rules.NoUselessPatternMatching", + "Lint.Rules.NoWarningComments", + "Lint.Rules.SimplifyPiping" + ], "dependencies": { "elm-community/list-extra": "5.0.1 <= v < 6.0.0", "elm-lang/core": "5.0.0 <= v < 6.0.0", diff --git a/example/Main.elm b/example/Main.elm index 9fd4045b..bd82f7fe 100644 --- a/example/Main.elm +++ b/example/Main.elm @@ -14,40 +14,40 @@ import Regex exposing (regex, escape) -- Rules -import DefaultPatternPosition -import NoConstantCondition -import NoDebug -import NoDuplicateImports -import NoExposingEverything -import NoImportingEverything -import NoNestedLet -import NoUnannotatedFunction -import NoUnusedVariables -import NoUselessIf -import NoUselessPatternMatching -import NoWarningComments -import SimplifyPiping +import Lint.Rules.DefaultPatternPosition +import Lint.Rules.NoConstantCondition +import Lint.Rules.NoDebug +import Lint.Rules.NoDuplicateImports +import Lint.Rules.NoExposingEverything +import Lint.Rules.NoImportingEverything +import Lint.Rules.NoNestedLet +import Lint.Rules.NoUnannotatedFunction +import Lint.Rules.NoUnusedVariables +import Lint.Rules.NoUselessIf +import Lint.Rules.NoUselessPatternMatching +import Lint.Rules.NoWarningComments +import Lint.Rules.SimplifyPiping type Msg = Replace String -rules : List (String -> List Types.Error) +rules : List (String -> List Lint.Types.Error) rules = - [ DefaultPatternPosition.rule { position = DefaultPatternPosition.Last } - , NoConstantCondition.rule - , NoDebug.rule - , NoDuplicateImports.rule - , NoExposingEverything.rule - , NoImportingEverything.rule - , NoNestedLet.rule - , NoUnannotatedFunction.rule - , NoUnusedVariables.rule - , NoUselessIf.rule - , NoUselessPatternMatching.rule - , NoWarningComments.rule - , SimplifyPiping.rule + [ Lint.Rules.DefaultPatternPosition.rule { position = Lint.Rules.DefaultPatternPosition.Last } + , Lint.Rules.NoConstantCondition.rule + , Lint.Rules.NoDebug.rule + , Lint.Rules.NoDuplicateImports.rule + , Lint.Rules.NoExposingEverything.rule + , Lint.Rules.NoImportingEverything.rule + , Lint.Rules.NoNestedLet.rule + , Lint.Rules.NoUnannotatedFunction.rule + , Lint.Rules.NoUnusedVariables.rule + , Lint.Rules.NoUselessIf.rule + , Lint.Rules.NoUselessPatternMatching.rule + , Lint.Rules.NoWarningComments.rule + , Lint.Rules.SimplifyPiping.rule ] diff --git a/example/elm-package.json b/example/elm-package.json index 4d13b3bf..65ea3a5e 100644 --- a/example/elm-package.json +++ b/example/elm-package.json @@ -1,7 +1,7 @@ { "version": "1.0.0", - "summary": "helpful summary of your project, less than 80 characters", - "repository": "https://github.com/user/project.git", + "summary": "Online example for elm-lint", + "repository": "https://github.com/jfmengels/elm-lint.git", "license": "BSD3", "source-directories": [ ".", diff --git a/src/Lint.elm b/src/Lint.elm index 7bda83be..c7476ba4 100644 --- a/src/Lint.elm +++ b/src/Lint.elm @@ -1,14 +1,67 @@ module Lint exposing (lint, visitExpression, doNothing) +{-| A linter for Elm. + +See Lint.Rules for the implemented rules. + +@docs lint, doNothing, visitExpression +-} + import Ast import Ast.Expression exposing (Expression) import Lint.Types exposing (Error, LintImplementation, LintRule, Direction, Visitor) import Lint.Visitor exposing (transformStatementsIntoVisitors, expressionToVisitors) -doNothing : LintImplementation a context -doNothing ctx _ = - ( [], ctx ) +{-| Lints source code using a given rule implementation, and gives back a list of errors that were found. + + rule : String -> List Error + rule input = + lint input implementation + + implementation : LintRule Context + implementation = + { statementFn = doNothing + , typeFn = doNothing + , expressionFn = expressionFn + , moduleEndFn = (\ctx -> ( [], ctx )) + , initialContext = Context + } +-} +lint : String -> LintRule context -> List Error +lint source = + source + |> Ast.parse + |> Result.map (\( _, _, statements ) -> statements) + |> Result.withDefault [] + |> transformStatementsIntoVisitors + |> lintWithVisitors + + +{-| Visit an expression using a sub rule implementation. The use of this function is not encouraged, but it can make +part of the implementation of complex rules much easier. It gives back a list of errors and a context. + + expressionFn : Context -> Direction Expression -> ( List Error, Context ) + expressionFn ctx node = + case node of + Enter (Case expr patterns) -> + visitExpression subimplementation expr + _ -> + ( [], ctx ) + + subimplementation : LintRule Subcontext + subimplementation = + { statementFn = doNothing + , typeFn = doNothing + , expressionFn = subexpressionFn + , moduleEndFn = (\ctx -> ( [], ctx )) + , initialContext = Subcontext + } +-} +visitExpression : LintRule context -> Expression -> ( List Error, context ) +visitExpression rule expression = + expressionToVisitors expression + |> List.foldl (visitAndAccumulate rule) ( [], rule.initialContext ) visitAndAccumulate : LintRule context -> Visitor context -> ( List Error, context ) -> ( List Error, context ) @@ -24,17 +77,18 @@ lintWithVisitors visitors rule = |> Tuple.first -visitExpression : LintRule context -> Expression -> ( List Error, context ) -visitExpression rule expression = - expressionToVisitors expression - |> List.foldl (visitAndAccumulate rule) ( [], rule.initialContext ) +{-| Basic implementation of a visitor function that does nothing, i.e. return an empty list of errors and an untouched +context. This is used to avoid a bit of boilerplate for visitor functions whose node types we are not interested in. - -lint : String -> LintRule context -> List Error -lint source = - source - |> Ast.parse - |> Result.map (\( _, _, statements ) -> statements) - |> Result.withDefault [] - |> transformStatementsIntoVisitors - |> lintWithVisitors + implementation : LintRule Context + implementation = + { statementFn = doNothing + , typeFn = doNothing + , expressionFn = expressionFn + , moduleEndFn = (\ctx -> ( [], ctx )) + , initialContext = Context + } +-} +doNothing : LintImplementation a context +doNothing ctx _ = + ( [], ctx ) diff --git a/src/Lint/Types.elm b/src/Lint/Types.elm index afeba6c4..82639ee2 100644 --- a/src/Lint/Types.elm +++ b/src/Lint/Types.elm @@ -1,24 +1,104 @@ -module Lint.Types exposing (..) +module Lint.Types exposing (Error, LintImplementation, Direction(..), LintRule, Visitor) + +{-| This module contains types that are used for writing rules. + +# Elementary types +@docs Error, Direction + +# Writing rules +@docs LintRule, LintImplementation + +# Internal types +@docs Visitor +-} import Ast.Expression exposing (..) import Ast.Statement exposing (..) +{-| Value that describes an error found by a given rule, that contains the name of the rule that raised the error, and +a description of the error. + + error : Error + error = + Error "NoDebug" "Forbidden use of Debug" +-} type alias Error = { rule : String , message : String } +{-| A LintImplementation is a function that takes a given Context object, as defined by each rule, a node (with a +Direction) and returns a list of errors and an updated Context. +Every rule should implement three LintImplementation functions, one for every Node type (Statement, Type and +Expression). +The Context is there to accumulate information about the source code as the AST is being visited and is shared by all +the LintImplementation functions of your rule. +It must return a list of errors which will be reported to the user, that are violations of the thing the rule wants to +enforce. + + rule : String -> List Error + rule input = + lint input implementation + + implementation : LintRule Context + implementation = + { statementFn = doNothing + , typeFn = doNothing + , expressionFn = expressionFn + , moduleEndFn = (\ctx -> ( [], ctx )) + , initialContext = Context + } +-} +type alias LintImplementation nodeType context = + context -> Direction nodeType -> ( List Error, context ) + + +{-| When visiting the AST, nodes are visited twice: +- on Enter, before the children of the node will be visited +- on Exit, after the children of the node have been visited + + expressionFn : Context -> Direction Expression -> ( List Error, Context ) + expressionFn ctx node = + case node of + Enter (Variable names) -> + ( [], markVariableAsUsed ctx names ) + + -- Find variables declared in `let .. in ..` expression + Enter (Let declarations body) -> + ( [], registerVariables ctx declarations ) + + -- When exiting the `let .. in ..` expression, report the variables that were not used. + Exit (Let _ _) -> + ( unusedVariables ctx |> List.map createError, ctx ) +-} type Direction node = Enter node | Exit node -type alias LintImplementation nodeType context = - context -> Direction nodeType -> ( List Error, context ) +{-| A LintRule is the implementation of a rule. It is a record that contains: +- initialContext: An initial context +- statementFn: A LintImplementation for Statement nodes +- typeFn: A LintImplementation for Type nodes +- expressionFn: A LintImplementation for Expression nodes +- moduleEndFn: A function that takes a context and returns a list of error. Similar to a LintImplementation, but will +be called after visiting the whole AST. + rule : String -> List Error + rule input = + lint input implementation + implementation : LintRule Context + implementation = + { statementFn = doNothing + , typeFn = doNothing + , expressionFn = expressionFn + , moduleEndFn = (\ctx -> ( [], ctx )) + , initialContext = Context + } +-} type alias LintRule context = { statementFn : LintImplementation Statement context , typeFn : LintImplementation Type context @@ -28,5 +108,10 @@ type alias LintRule context = } +{-| Shorthand for a function that takes a rule's implementation, a context and returns ( List Error, context ). +A Visitor represents a node and calls the appropriate function for the given node type. + +Note: this is internal API, and will be removed in a future version. +-} type alias Visitor context = LintRule context -> context -> ( List Error, context ) diff --git a/tests/elm-package.json b/tests/elm-package.json index 9a99348f..c627007a 100644 --- a/tests/elm-package.json +++ b/tests/elm-package.json @@ -1,7 +1,7 @@ { "version": "1.0.0", - "summary": "helpful summary of your project, less than 80 characters", - "repository": "https://github.com/user/project.git", + "summary": "Tests for elm-lint", + "repository": "https://github.com/jfmengels/elm-lint.git", "license": "BSD3", "source-directories": [ ".",