mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-12-23 09:42:49 +03:00
Add rule NoUnusedTypeConstructors
This commit is contained in:
parent
f771ef1540
commit
3f6cc7c3e7
@ -46,6 +46,7 @@ These are the rules that are built-in and available for you to choose from.
|
||||
- **NoExtraBooleanComparison** - Forbid extraneous comparisons with booleans, like `(isAdmin user) == True`.
|
||||
- **NoImportingEverything** - Forbid importing everything from your module. This can especially be confusing to newcomers when the exposed functions and types are unknown to them.
|
||||
- **NoUnusedVariables** - Reports variables that are declared but never used.
|
||||
- **NoUnusedTypeConstructors** - Reports type constructors that are declared but never used.
|
||||
|
||||
The following is a list of rules that were temporarily removed when changing the AST implementation, and which can potentially be re-added later.
|
||||
- **NoExposingEverything** - Forbid exporting everything in your modules `module Main exposing (..)`, to make your module explicit in what it exposes.
|
||||
@ -70,10 +71,11 @@ module LintConfig exposing (config)
|
||||
import Lint exposing (Severity(..))
|
||||
import Lint.Rule exposing (Rule)
|
||||
import Lint.Rule.DefaultPatternPosition as DefaultPatternPosition
|
||||
import Lint.Rule.NoUnusedVariables
|
||||
import Lint.Rule.NoDebug
|
||||
import Lint.Rule.NoExtraBooleanComparison
|
||||
import Lint.Rule.NoImportingEverything
|
||||
import Lint.Rule.NoUnusedTypeConstructors
|
||||
import Lint.Rule.NoUnusedVariables
|
||||
|
||||
|
||||
config : List ( Severity, Rule )
|
||||
@ -81,6 +83,7 @@ config =
|
||||
[ ( Critical, DefaultPatternPosition.rule DefaultPatternPosition.ShouldBeLast )
|
||||
, ( Critical, Lint.Rule.NoExtraBooleanComparison.rule )
|
||||
, ( Critical, Lint.Rule.NoUnusedVariables.rule )
|
||||
, ( Critical, Lint.Rule.NoUnusedTypeConstructors.rule )
|
||||
, ( Warning, Lint.Rule.NoDebug.rule )
|
||||
, ( Critical, Lint.Rule.NoDuplicateImports.rule )
|
||||
, ( Critical, Lint.Rule.NoImportingEverything.rule { exceptions = [ "Html" ] } )
|
||||
|
1
elm.json
1
elm.json
@ -11,6 +11,7 @@
|
||||
"Lint.Rule.NoDebug",
|
||||
"Lint.Rule.NoExtraBooleanComparison",
|
||||
"Lint.Rule.NoImportingEverything",
|
||||
"Lint.Rule.NoUnusedTypeConstructors",
|
||||
"Lint.Rule.NoUnusedVariables",
|
||||
"Lint.Test"
|
||||
],
|
||||
|
@ -10,6 +10,7 @@ import Lint.Rule.DefaultPatternPosition as DefaultPatternPosition exposing (Patt
|
||||
import Lint.Rule.NoDebug
|
||||
import Lint.Rule.NoExtraBooleanComparison
|
||||
import Lint.Rule.NoImportingEverything
|
||||
import Lint.Rule.NoUnusedTypeConstructors
|
||||
import Lint.Rule.NoUnusedVariables
|
||||
|
||||
|
||||
@ -24,6 +25,7 @@ config model =
|
||||
, ( model.noImportingEverythingEnabled, ( Critical, Lint.Rule.NoImportingEverything.rule { exceptions = [ "Html" ] } ) )
|
||||
, ( model.defaultPatternPositionEnabled, ( Critical, DefaultPatternPosition.rule model.defaultPatternPositionPattern ) )
|
||||
, ( model.noExtraBooleanComparisonEnabled, ( Critical, Lint.Rule.NoExtraBooleanComparison.rule ) )
|
||||
, ( model.noUnusedTypeConstructorsEnabled, ( Critical, Lint.Rule.NoUnusedTypeConstructors.rule ) )
|
||||
|
||||
-- , ( Critical, Lint.Rule.NoConstantCondition.rule )
|
||||
-- , ( Critical, Lint.Rule.NoDuplicateImports.rule )
|
||||
@ -55,6 +57,7 @@ type alias Model =
|
||||
, defaultPatternPositionEnabled : Bool
|
||||
, defaultPatternPositionPattern : PatternPosition
|
||||
, noExtraBooleanComparisonEnabled : Bool
|
||||
, noUnusedTypeConstructorsEnabled : Bool
|
||||
, showConfigurationAsText : Bool
|
||||
}
|
||||
|
||||
@ -88,6 +91,7 @@ g n = n + 1
|
||||
, defaultPatternPositionEnabled = True
|
||||
, defaultPatternPositionPattern = DefaultPatternPosition.ShouldBeLast
|
||||
, noExtraBooleanComparisonEnabled = True
|
||||
, noUnusedTypeConstructorsEnabled = True
|
||||
, showConfigurationAsText = False
|
||||
}
|
||||
in
|
||||
@ -105,6 +109,7 @@ type Msg
|
||||
| UserToggledNoImportingEverythingRule
|
||||
| UserToggledDefaultPatternPositionRule
|
||||
| UserToggledNoExtraBooleanComparisonRule
|
||||
| UserToggledNoUnusedTypeConstructorsRule
|
||||
| UserChangedDefaultPatternSetting PatternPosition
|
||||
| UserToggledConfigurationAsText
|
||||
|
||||
@ -142,6 +147,10 @@ update action model =
|
||||
{ model | noExtraBooleanComparisonEnabled = not model.noExtraBooleanComparisonEnabled }
|
||||
|> rerunLinting
|
||||
|
||||
UserToggledNoUnusedTypeConstructorsRule ->
|
||||
{ model | noUnusedTypeConstructorsEnabled = not model.noUnusedTypeConstructorsEnabled }
|
||||
|> rerunLinting
|
||||
|
||||
UserToggledConfigurationAsText ->
|
||||
{ model | showConfigurationAsText = not model.showConfigurationAsText }
|
||||
|
||||
@ -210,6 +219,7 @@ viewConfigurationPanel model =
|
||||
model.defaultPatternPositionPattern
|
||||
]
|
||||
, viewCheckbox UserToggledNoExtraBooleanComparisonRule "NoExtraBooleanComparison" model.noExtraBooleanComparisonEnabled
|
||||
, viewCheckbox UserToggledNoUnusedTypeConstructorsRule "NoUnusedTypeConstructors" model.noUnusedTypeConstructorsEnabled
|
||||
]
|
||||
]
|
||||
|
||||
@ -280,6 +290,11 @@ configurationAsText model =
|
||||
, configExpression = "Lint.Rule.NoExtraBooleanComparison.rule"
|
||||
}
|
||||
)
|
||||
, ( model.noUnusedTypeConstructorsEnabled
|
||||
, { import_ = "Lint.Rule.NoUnusedTypeConstructors"
|
||||
, configExpression = "Lint.Rule.NoUnusedTypeConstructors.rule"
|
||||
}
|
||||
)
|
||||
]
|
||||
|> List.filter Tuple.first
|
||||
|> List.map Tuple.second
|
||||
|
210
src/Lint/Rule/NoUnusedTypeConstructors.elm
Normal file
210
src/Lint/Rule/NoUnusedTypeConstructors.elm
Normal file
@ -0,0 +1,210 @@
|
||||
module Lint.Rule.NoUnusedTypeConstructors exposing (rule)
|
||||
|
||||
{-| Forbid having unused custom type constructors in a file.
|
||||
|
||||
Note that this does not report a constructor if it's exposed in the module, even
|
||||
if it is not used anywhere in the project.
|
||||
|
||||
|
||||
## Fail
|
||||
|
||||
-- module A exposing (a)
|
||||
type MyType
|
||||
= UsedType
|
||||
| UnusedType -- Will get reported
|
||||
|
||||
a =
|
||||
UsedType
|
||||
|
||||
|
||||
## Success
|
||||
|
||||
-- module A exposing (ExposedType(..))
|
||||
type MyType
|
||||
= UsedType
|
||||
|
||||
a =
|
||||
UsedType
|
||||
|
||||
type ExposedType
|
||||
= A
|
||||
| B
|
||||
| C
|
||||
|
||||
-----------------------
|
||||
-- module A exposing (..)
|
||||
type ExposedType
|
||||
= A
|
||||
| B
|
||||
| C
|
||||
|
||||
|
||||
# Rule
|
||||
|
||||
@docs rule
|
||||
|
||||
-}
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Elm.Syntax.Declaration exposing (Declaration(..))
|
||||
import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..))
|
||||
import Elm.Syntax.Expression exposing (Expression(..), Function, FunctionImplementation, LetDeclaration(..))
|
||||
import Elm.Syntax.Import exposing (Import)
|
||||
import Elm.Syntax.Module as Module exposing (Module(..))
|
||||
import Elm.Syntax.Node as Node exposing (Node)
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation(..))
|
||||
import Lint.Rule as Rule exposing (Direction, Error, Rule)
|
||||
import List.Nonempty as Nonempty exposing (Nonempty)
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
{-| Forbid having unused custom type constructors in a file.
|
||||
|
||||
Note that this does not report a constructor if it's exposed in the module, even
|
||||
if it is not used anywhere in the project.
|
||||
|
||||
config =
|
||||
[ ( Critical, NoUnusedTypeConstructors.rule )
|
||||
]
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule =
|
||||
Rule.newSchema "NoUnusedTypeConstructors"
|
||||
|> Rule.withInitialContext initialContext
|
||||
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|
||||
|> Rule.withDeclarationVisitor declarationVisitor
|
||||
|> Rule.withExpressionVisitor expressionVisitor
|
||||
|> Rule.withFinalEvaluation finalEvaluation
|
||||
|> Rule.fromSchema
|
||||
|
||||
|
||||
type alias Context =
|
||||
{ exposedCustomTypesWithConstructors : Set String
|
||||
, exposesEverything : Bool
|
||||
, declaredTypesWithConstructors : Dict String (Node String)
|
||||
, usedFunctionOrValues : Set String
|
||||
}
|
||||
|
||||
|
||||
initialContext : Context
|
||||
initialContext =
|
||||
{ exposedCustomTypesWithConstructors = Set.empty
|
||||
, exposesEverything = False
|
||||
, declaredTypesWithConstructors = Dict.empty
|
||||
, usedFunctionOrValues = Set.empty
|
||||
}
|
||||
|
||||
|
||||
error : Node String -> Error
|
||||
error node =
|
||||
Rule.error
|
||||
("Type constructor `" ++ Node.value node ++ "` is not used.")
|
||||
(Node.range node)
|
||||
|
||||
|
||||
moduleDefinitionVisitor : Node Module -> Context -> ( List Error, Context )
|
||||
moduleDefinitionVisitor moduleNode context =
|
||||
case Module.exposingList (Node.value moduleNode) of
|
||||
All _ ->
|
||||
( [], { context | exposesEverything = True } )
|
||||
|
||||
Explicit list ->
|
||||
let
|
||||
names : List String
|
||||
names =
|
||||
List.filterMap
|
||||
(\node ->
|
||||
case Node.value node of
|
||||
TypeExpose { name, open } ->
|
||||
case open of
|
||||
Just _ ->
|
||||
Just name
|
||||
|
||||
Nothing ->
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
list
|
||||
in
|
||||
( []
|
||||
, { context
|
||||
| exposedCustomTypesWithConstructors =
|
||||
Set.union (Set.fromList names) context.exposedCustomTypesWithConstructors
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
declarationVisitor : Node Declaration -> Direction -> Context -> ( List Error, Context )
|
||||
declarationVisitor node direction context =
|
||||
case ( direction, Node.value node ) of
|
||||
( Rule.OnEnter, CustomTypeDeclaration { name, constructors } ) ->
|
||||
if Set.member (Node.value name) context.exposedCustomTypesWithConstructors then
|
||||
( [], context )
|
||||
|
||||
else
|
||||
let
|
||||
newContext : Context
|
||||
newContext =
|
||||
List.foldl
|
||||
(\constructor ctx ->
|
||||
let
|
||||
nameNode : Node String
|
||||
nameNode =
|
||||
(Node.value constructor).name
|
||||
in
|
||||
{ ctx
|
||||
| declaredTypesWithConstructors =
|
||||
Dict.insert
|
||||
(Node.value nameNode)
|
||||
nameNode
|
||||
ctx.declaredTypesWithConstructors
|
||||
}
|
||||
)
|
||||
context
|
||||
constructors
|
||||
in
|
||||
( [], newContext )
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
expressionVisitor : Node Expression -> Direction -> Context -> ( List Error, Context )
|
||||
expressionVisitor node direction context =
|
||||
if context.exposesEverything then
|
||||
( [], context )
|
||||
|
||||
else
|
||||
case ( direction, Node.value node ) of
|
||||
( Rule.OnEnter, FunctionOrValue [] name ) ->
|
||||
( [], { context | usedFunctionOrValues = Set.insert name context.usedFunctionOrValues } )
|
||||
|
||||
_ ->
|
||||
( [], context )
|
||||
|
||||
|
||||
finalEvaluation : Context -> List Error
|
||||
finalEvaluation context =
|
||||
if context.exposesEverything then
|
||||
[]
|
||||
|
||||
else
|
||||
context.declaredTypesWithConstructors
|
||||
|> Dict.filter (\name node -> not <| Set.member name context.usedFunctionOrValues)
|
||||
|> Dict.toList
|
||||
|> List.map (\( _, node ) -> error node)
|
||||
|
||||
|
||||
|
||||
-- if context.exposesEverything then
|
||||
-- []
|
||||
--
|
||||
-- else
|
||||
-- context.scopes
|
||||
-- |> Nonempty.head
|
||||
-- |> makeReport
|
||||
-- |> Tuple.first
|
72
tests/NoUnusedTypeConstructorsTest.elm
Normal file
72
tests/NoUnusedTypeConstructorsTest.elm
Normal file
@ -0,0 +1,72 @@
|
||||
module NoUnusedTypeConstructorsTest exposing (all)
|
||||
|
||||
import Lint.Rule.NoUnusedTypeConstructors exposing (rule)
|
||||
import Lint.Test exposing (LintResult)
|
||||
import Test exposing (Test, describe, test)
|
||||
|
||||
|
||||
testRule : String -> LintResult
|
||||
testRule =
|
||||
Lint.Test.run rule
|
||||
|
||||
|
||||
tests : List Test
|
||||
tests =
|
||||
[ test "should not report non-exposed variables" <|
|
||||
\() ->
|
||||
testRule """module A exposing (b)
|
||||
a = 1"""
|
||||
|> Lint.Test.expectNoErrors
|
||||
, test "should not report used type constructors" <|
|
||||
\() ->
|
||||
testRule """module A exposing (b)
|
||||
type Foo = Bar | Baz
|
||||
a = Bar
|
||||
b = Baz"""
|
||||
|> Lint.Test.expectNoErrors
|
||||
, test "should not report unused type constructors when module is exposing all" <|
|
||||
\() ->
|
||||
testRule """module A exposing (..)
|
||||
type Foo = Bar | Baz
|
||||
"""
|
||||
|> Lint.Test.expectNoErrors
|
||||
, test "should not report unused type constructors when module is exposing the constructors of that type" <|
|
||||
\() ->
|
||||
testRule """module A exposing (Foo(..))
|
||||
type Foo = Bar | Baz
|
||||
"""
|
||||
|> Lint.Test.expectNoErrors
|
||||
, test "should report unused type constructors" <|
|
||||
\() ->
|
||||
testRule """module A exposing (b)
|
||||
type Foo = Bar | Baz"""
|
||||
|> Lint.Test.expectErrors
|
||||
[ Lint.Test.error
|
||||
{ message = "Type constructor `Bar` is not used."
|
||||
, under = "Bar"
|
||||
}
|
||||
, Lint.Test.error
|
||||
{ message = "Type constructor `Baz` is not used."
|
||||
, under = "Baz"
|
||||
}
|
||||
]
|
||||
, test "should report unused type constructors, even if the type is exposed" <|
|
||||
\() ->
|
||||
testRule """module A exposing (Foo)
|
||||
type Foo = Bar | Baz"""
|
||||
|> Lint.Test.expectErrors
|
||||
[ Lint.Test.error
|
||||
{ message = "Type constructor `Bar` is not used."
|
||||
, under = "Bar"
|
||||
}
|
||||
, Lint.Test.error
|
||||
{ message = "Type constructor `Baz` is not used."
|
||||
, under = "Baz"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoUnusedTypeConstructors" tests
|
Loading…
Reference in New Issue
Block a user