module Dhall.LSP.Backend.Linting ( Suggestion(..) , suggest , Dhall.lint ) where import Dhall.Parser (Src) import Dhall.Core ( Expr(..), Binding(..), MultiLet(..), Import , subExpressions, multiLet, wrapInLets) import qualified Dhall.Lint as Dhall import Dhall.LSP.Backend.Diagnostics import qualified Data.List.NonEmpty as NonEmpty import Data.Maybe (maybeToList) import Data.Monoid ((<>)) import Data.Text (Text) import Control.Lens (universeOf) data Suggestion = Suggestion { range :: Range, suggestion :: Text } -- Diagnose nested let-blocks. -- -- Pattern matching on a 'Let' wrapped in a 'Note' prevents us from repeating -- the search beginning at different @let@s in the same let-block – only -- the outermost 'Let' of a let-block is wrapped in a 'Note'. diagLetInLet :: Expr Src a -> Maybe Suggestion diagLetInLet (Note _ (Let b e)) = case multiLet b e of MultiLet _ (Note src (Let {})) -> Just (Suggestion (rangeFromDhall src) "Superfluous 'in' before nested let binding") _ -> Nothing diagLetInLet _ = Nothing -- Given a let-block compute all unused variables in the block. unusedBindings :: Eq a => MultiLet s a -> [Text] unusedBindings (MultiLet bindings d) = let go bs@(Binding { variable = var} : _) | Just _ <- Dhall.removeUnusedBindings (wrapInLets bs d) = [var] go _ = [] in foldMap go (NonEmpty.tails bindings) -- Diagnose unused let bindings. diagUnusedBindings :: Eq a => Expr Src a -> [Suggestion] diagUnusedBindings (Note src (Let b e)) = map (\var -> Suggestion (rangeFromDhall src) ("Unused let binding '" <> var <> "'")) (unusedBindings (multiLet b e)) diagUnusedBindings _ = [] -- | Given an dhall expression suggest all the possible improvements that would -- be made by the linter. suggest :: Expr Src Import -> [Suggestion] suggest expr = concat [ maybeToList (diagLetInLet e) ++ diagUnusedBindings e | e <- universeOf subExpressions expr ]