From e2a8067855865c19bd663826b293ca75ec6e836d Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 6 May 2024 11:51:44 +0200 Subject: [PATCH] Add docs+example for withExtraFilesModuleVisitor --- src/Review/Rule.elm | 93 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/Review/Rule.elm b/src/Review/Rule.elm index 16259e4a..2e4566f4 100644 --- a/src/Review/Rule.elm +++ b/src/Review/Rule.elm @@ -1903,6 +1903,8 @@ doesn't analyze by default. The visitor function will be called with all the files matching the file patterns. +REPLACEME + The following example rule reads a project's `.css` files to extract all the mentioned CSS classes, then finds calls to `Html.Attributes.class` in the Elm code (such as `Html.Attributes.class "big-red-button"`) and reports errors when the classes given as argument are unknown. @@ -2497,7 +2499,96 @@ withElmJsonModuleVisitor visitor (ModuleRuleSchema schema) = ModuleRuleSchema { schema | elmJsonVisitor = Just (combineContextOnlyVisitor visitor schema.elmJsonVisitor) } -{-| REPLACEME +{-| Add a visitor to the [`ModuleRuleSchema`](#ModuleRuleSchema) to visit files that `elm-review` +doesn't analyze by default. + +The visitor function will be called with all the files matching the file patterns. + +The following example rule reads a project's `.css` files to extract all the mentioned CSS classes, +then finds calls to `Html.Attributes.class` in the Elm code (such as `Html.Attributes.class "big-red-button"`) +and reports errors when the classes given as argument are unknown. + + import Dict exposing (Dict) + import Elm.Syntax.Expression as Expression exposing (Expression) + import Elm.Syntax.Node as Node exposing (Node) + import Elm.Syntax.Range exposing (Range) + import Regex exposing (Regex) + import Review.FilePattern as FilePattern + import Review.Rule as Rule exposing (Rule) + import Set exposing (Set) + + rule : Rule + rule = + Rule.newModuleRuleSchema "Css.NoUnknownCssClasses" initialContext + |> Rule.withExtraFilesModuleVisitor cssFilesVisitor + [ FilePattern.include "**/*.css" ] + |> Rule.withExpressionEnterVisitor expressionVisitor + |> Rule.fromModuleRuleSchema + + type alias Context = + { knownCssClasses : Set String + } + + initialContext : Context + initialContext = + { knownCssClasses = Set.empty + } + + cssClassRegex : Regex + cssClassRegex = + Regex.fromString "\\.([\\w-_]+)" + |> Maybe.withDefault Regex.never + + cssFilesVisitor : Dict String String -> Context -> Context + cssFilesVisitor files context = + { knownCssClasses = + files + |> Dict.values + |> List.concatMap (\cssSource -> Regex.find cssClassRegex cssSource) + |> List.map (\m -> String.dropLeft 1 m.match) + |> Set.fromList + } + + expressionVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context ) + expressionVisitor node context = + case Node.value node of + Expression.Application [ function, firstArg ] -> + case Node.value function of + Expression.FunctionOrValue [ "Html", "Attributes" ] "class" -> + case Node.value firstArg of + Expression.Literal stringLiteral -> + ( stringLiteral + |> String.split " " + |> List.filterMap (checkForUnknownCssClass context.knownCssClasses (Node.range firstArg)) + , context + ) + + _ -> + ( [], context ) + + _ -> + ( [], context ) + + _ -> + ( [], context ) + + checkForUnknownCssClass : Set String -> Range -> String -> Maybe (Rule.Error {}) + checkForUnknownCssClass knownCssClasses range class = + if Set.member class knownCssClasses then + Nothing + + else + Just + (Rule.error + { message = "Unknown CSS class " ++ class + , details = + [ "This CSS class does not appear in the project's `.css` files." + , "Could it be that you misspelled the name of the class, or that the class recently got removed?" + ] + } + range + ) + -} withExtraFilesModuleVisitor : (Dict String String -> moduleContext -> moduleContext)