From 9a56823f20c1a3aa69ae34040aeefbc88aeb6e73 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 5 Nov 2018 19:00:17 +0100 Subject: [PATCH] Use stil4m/elm-syntax for parsing --- elm.json | 3 +- src/Lint.elm | 68 +++++++------------ src/Lint/Rules/NoDebug.elm | 28 ++++---- src/Lint/Types.elm | 43 ++++--------- src/Lint/Visitor.elm | 129 ++++++++++++++++++++----------------- 5 files changed, 123 insertions(+), 148 deletions(-) diff --git a/elm.json b/elm.json index ce4d176c..1026f96b 100644 --- a/elm.json +++ b/elm.json @@ -14,7 +14,8 @@ "elm/core": "1.0.0 <= v < 2.0.0", "elm/html": "1.0.0 <= v < 2.0.0", "elm/regex": "1.0.0 <= v < 2.0.0", - "elm-community/list-extra": "8.1.0 <= v < 9.0.0" + "elm-community/list-extra": "8.1.0 <= v < 9.0.0", + "stil4m/elm-syntax": "7.0.2 <= v < 8.0.0" }, "test-dependencies": {} } diff --git a/src/Lint.elm b/src/Lint.elm index d29feb28..bed46321 100644 --- a/src/Lint.elm +++ b/src/Lint.elm @@ -1,7 +1,7 @@ module Lint exposing ( lintSource , lint, doNothing, visitExpression - , countErrors, parseSource + , parseSource ) {-| A linter for Elm. @@ -41,15 +41,17 @@ To run the rules on a source code and get a list of errors: # Internal -@docs countErrors, parseSource +@docs parseSource -} -import Ast -import Ast.Expression exposing (Expression) -import Ast.Statement exposing (Statement) -import Lint.Types exposing (Direction, File, LintError, LintImplementation, LintRule, LintRuleImplementation, Severity(..), Visitor) -import Lint.Visitor exposing (expressionToVisitors, transformStatementsIntoVisitors) +import Elm.Parser as Parser +import Elm.Processing exposing (addFile, init, process) +import Elm.Syntax.Expression exposing (Expression) +import Elm.Syntax.File exposing (File) +import Elm.Syntax.Node exposing (Node) +import Lint.Types exposing (Direction, LintError, LintImplementation, LintRule, LintRuleImplementation, Severity(..), Visitor) +import Lint.Visitor exposing (expressionToVisitors, transformDeclarationsIntoVisitors) import Regex @@ -71,27 +73,22 @@ lintSource rules source = ) -lintSourceWithRule : List Statement -> ( Severity, LintRule ) -> List ( Severity, LintError ) -lintSourceWithRule statements ( severity, rule ) = - rule statements +lintSourceWithRule : File -> ( Severity, LintRule ) -> List ( Severity, LintError ) +lintSourceWithRule file ( severity, rule ) = + rule file |> List.map (\b -> ( severity, b )) {-| Parse source code into a AST -} -parseSource : String -> Result (List String) (List Ast.Statement.Statement) +parseSource : String -> Result (List String) File parseSource source = source - |> removeComments - |> Ast.parse - |> Result.mapError (\( _, _, errors ) -> errors) - |> Result.map (\( _, _, statements ) -> statements) - - -removeComments : String -> String -removeComments = - Regex.replace Regex.All (Regex.regex "--.$") (always "") - >> Regex.replace Regex.All (Regex.regex "\n +\\w+ : .*") (always "") + |> Parser.parse + -- TODO Improve parsing error handling + |> Result.mapError (\error -> [ "Parsing error" ]) + -- TODO Add all files to have more context https://package.elm-lang.org/packages/stil4m/elm-syntax/7.0.2/Elm-Processing + |> Result.map (process init) {-| Lints source code using a given rule implementation, and gives back a list of errors that were found. @@ -102,18 +99,17 @@ removeComments = implementation : LintRuleImplementation Context implementation = - { statementFn = doNothing - , typeFn = doNothing + { typeFn = doNothing , expressionFn = expressionFn , moduleEndFn = \ctx -> ( [], ctx ) , initialContext = Context } -} -lint : List Statement -> LintRuleImplementation context -> List LintError -lint statements rule = - statements - |> transformStatementsIntoVisitors +lint : File -> LintRuleImplementation context -> List LintError +lint file rule = + file.declarations + |> transformDeclarationsIntoVisitors |> lintWithVisitors rule @@ -139,7 +135,7 @@ part of the implementation of complex rules much easier. It gives back a list of } -} -visitExpression : LintRuleImplementation context -> Expression -> ( List LintError, context ) +visitExpression : LintRuleImplementation context -> Node Expression -> ( List LintError, context ) visitExpression rule expression = expressionToVisitors expression |> List.foldl (visitAndAccumulate rule) ( [], rule.initialContext ) @@ -174,19 +170,3 @@ context. This is used to avoid a bit of boilerplate for visitor functions whose doNothing : LintImplementation a context doNothing ctx _ = ( [], ctx ) - - -{-| Count the number of errors of a given Severity in the list of errors. --} -countErrors : Severity -> List ( File, List ( Severity, LintError ) ) -> Int -countErrors severity errors = - errors - |> List.map - (Tuple.second - >> List.filter - (Tuple.first - >> (==) severity - ) - >> List.length - ) - |> List.sum diff --git a/src/Lint/Rules/NoDebug.elm b/src/Lint/Rules/NoDebug.elm index a7719372..143d26e0 100644 --- a/src/Lint/Rules/NoDebug.elm +++ b/src/Lint/Rules/NoDebug.elm @@ -30,7 +30,8 @@ module Lint.Rules.NoDebug exposing (rule) -} -import Ast.Expression exposing (..) +import Elm.Syntax.Expression exposing (Expression(..)) +import Elm.Syntax.Node exposing (Node, value) import Lint exposing (doNothing, lint) import Lint.Types exposing (Direction(..), LintError, LintRule, LintRuleImplementation) @@ -53,9 +54,7 @@ rule input = implementation : LintRuleImplementation Context implementation = - { statementFn = doNothing - , typeFn = doNothing - , expressionFn = expressionFn + { expressionFn = expressionFn , moduleEndFn = \ctx -> ( [], ctx ) , initialContext = Context } @@ -66,15 +65,20 @@ error = LintError "NoDebug" "Forbidden use of Debug" -expressionFn : Context -> Direction Expression -> ( List LintError, Context ) -expressionFn ctx node = - case node of - Enter (Variable vars) -> - if List.member "Debug" vars then - ( [ error ], ctx ) +expressionFn : Context -> Direction (Node Expression) -> ( List LintError, Context ) +expressionFn ctx node_ = + case node_ of + Enter node -> + case value node of + FunctionOrValue moduleName fnName -> + if List.member "Debug" moduleName then + ( [ error ], ctx ) - else - ( [], ctx ) + else + ( [], ctx ) + + _ -> + ( [], ctx ) _ -> ( [], ctx ) diff --git a/src/Lint/Types.elm b/src/Lint/Types.elm index 26544287..9c18483d 100644 --- a/src/Lint/Types.elm +++ b/src/Lint/Types.elm @@ -1,8 +1,8 @@ module Lint.Types exposing ( LintError, Direction(..) - , LintRule, Severity(..), Reporter + , LintRule, Severity(..) , LintRuleImplementation, LintImplementation - , Visitor, LintResult, File + , Visitor, LintResult ) {-| This module contains types that are used for writing rules. @@ -15,7 +15,7 @@ module Lint.Types exposing # Configuration -@docs LintRule, Severity, Reporter +@docs LintRule, Severity # Writing rules @@ -25,12 +25,13 @@ module Lint.Types exposing # Internal types -@docs Visitor, LintResult, File +@docs Visitor, LintResult -} -import Ast.Expression exposing (..) -import Ast.Statement exposing (..) +import Elm.Syntax.Expression exposing (Expression) +import Elm.Syntax.File exposing (File) +import Elm.Syntax.Node exposing (Node) {-| Value that describes an error found by a given rule, that contains the name of the rule that raised the error, and @@ -71,7 +72,7 @@ enforce. -} type alias LintImplementation nodeType context = - context -> Direction nodeType -> ( List LintError, context ) + context -> Direction (Node nodeType) -> ( List LintError, context ) {-| When visiting the AST, nodes are visited twice: @@ -104,10 +105,6 @@ type Direction node - 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 @@ -119,18 +116,14 @@ type Direction node implementation : LintRuleImplementation Context implementation = - { statementFn = doNothing - , typeFn = doNothing - , expressionFn = expressionFn + { expressionFn = expressionFn , moduleEndFn = (\\ctx -> ( [], ctx )) , initialContext = Context } -} type alias LintRuleImplementation context = - { statementFn : LintImplementation Statement context - , typeFn : LintImplementation Type context - , expressionFn : LintImplementation Expression context + { expressionFn : LintImplementation Expression context , moduleEndFn : context -> ( List LintError, context ) , initialContext : context } @@ -145,7 +138,7 @@ type alias LintResult = {-| Shortcut to a lint rule -} type alias LintRule = - List Statement -> List LintError + File -> List LintError {-| Shorthand for a function that takes a rule's implementation, a context and returns ( List LintError, context ). @@ -169,17 +162,3 @@ type Severity = Disabled | Warning | Critical - - -{-| Description of an Elm file. --} -type alias File = - { filename : String - , source : String - } - - -{-| Function that summarizes the result of the linting process. --} -type alias Reporter a = - List ( File, List ( Severity, LintError ) ) -> a diff --git a/src/Lint/Visitor.elm b/src/Lint/Visitor.elm index 8ebba374..96136ec9 100644 --- a/src/Lint/Visitor.elm +++ b/src/Lint/Visitor.elm @@ -1,7 +1,9 @@ -module Lint.Visitor exposing (expressionToVisitors, transformStatementsIntoVisitors) +module Lint.Visitor exposing (expressionToVisitors, transformDeclarationsIntoVisitors) -import Ast.Expression exposing (..) -import Ast.Statement exposing (..) +import Elm.Syntax.Declaration exposing (Declaration(..)) +import Elm.Syntax.Expression exposing (Expression(..), Function, FunctionImplementation, LetDeclaration(..)) +import Elm.Syntax.Infix exposing (InfixDirection(..)) +import Elm.Syntax.Node exposing (Node, value) import Lint.Types exposing (Direction(..), LintRule, Visitor) @@ -19,74 +21,89 @@ moduleVisitor rule context = rule.moduleEndFn context -expressionVisitor : Direction Expression -> Visitor context +expressionVisitor : Direction (Node Expression) -> Visitor context expressionVisitor node rule context = rule.expressionFn context node -statementVisitor : Direction Statement -> Visitor context -statementVisitor node rule context = - rule.statementFn context node +functionToExpression : Function -> Node Expression +functionToExpression { documentation, signature, declaration } = + let + { name, arguments, expression } = + value declaration + in + expression -expressionToVisitors : Expression -> List (Visitor context) +expressionToVisitors : Node Expression -> List (Visitor context) expressionToVisitors node = let + children : List (Node Expression) children = - case node of - Application expression1 expression2 -> - [ expression1, expression2 ] + case value node of + Application expressions -> + expressions - Access expression names -> - [ expression ] - - Variable _ -> - [] - - String _ -> - [] - - Character _ -> + Literal _ -> [] Integer _ -> [] - Float _ -> + Floatable _ -> [] - List elements -> + UnitExpr -> + [] + + ListExpr elements -> elements - Record pairs -> - List.map Tuple.second pairs + FunctionOrValue _ _ -> + [] - RecordUpdate name updates -> - List.map Tuple.second updates + RecordUpdateExpression name setters -> + List.map (value >> (\( field, expr ) -> expr)) setters - BinOp operator left right -> - [ operator, left, right ] + OperatorApplication operator direction left right -> + case direction of + Left -> + [ left, right ] - If cond then_ else_ -> + Right -> + [ right, left ] + + Non -> + [ left, right ] + + IfBlock cond then_ else_ -> [ cond, then_, else_ ] - Let declarations body -> - List.append - (List.map Tuple.second declarations) - [ body ] + LetExpression { expression, declarations } -> + List.map + (\declaration -> + case value declaration of + LetFunction function -> + functionToExpression function - Case target cases -> - List.append - [ target ] - (List.concatMap (\( a, b ) -> [ a, b ]) cases) + LetDestructuring pattern expr -> + expr + ) + declarations + ++ [ expression ] - Lambda names expression -> + CaseExpression { expression, cases } -> + [ expression ] + ++ List.map (\( pattern, caseExpression ) -> caseExpression) cases + + LambdaExpression { args, expression } -> [ expression ] - Tuple expressions -> + TupledExpression expressions -> expressions - AccessFunction name -> + -- TODO Implement the rest + _ -> [] childrenVisitors = @@ -95,30 +112,24 @@ expressionToVisitors node = createExitAndEnterWithChildren expressionVisitor node childrenVisitors -typeToVisitors : Type -> List (Visitor context) -typeToVisitors node = - [] - - -statementToVisitors : Statement -> List (Visitor context) -statementToVisitors node = +declarationToVisitors : Declaration -> List (Visitor context) +declarationToVisitors declaration = let childrenVisitors = - case node of - FunctionTypeDeclaration name application -> - typeToVisitors application - - FunctionDeclaration name params body -> - expressionToVisitors body + case declaration of + FunctionDeclaration function -> + functionToExpression function |> expressionToVisitors + -- TODO Implement the rest _ -> [] in - createExitAndEnterWithChildren statementVisitor node childrenVisitors + -- createExitAndEnterWithChildren statementVisitor declaration childrenVisitors + childrenVisitors -transformStatementsIntoVisitors : List Statement -> List (Visitor context) -transformStatementsIntoVisitors statements = - statements - |> List.concatMap statementToVisitors +transformDeclarationsIntoVisitors : List (Node Declaration) -> List (Visitor context) +transformDeclarationsIntoVisitors declarations = + declarations + |> List.concatMap (value >> declarationToVisitors) |> (\allVisitors -> List.append allVisitors [ moduleVisitor ])