mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-08-16 19:40:22 +03:00
Backport rules from packages
This commit is contained in:
parent
0d55d0b858
commit
73262f6f47
@ -10,6 +10,7 @@ import Dict exposing (Dict)
|
||||
import Elm.License
|
||||
import Elm.Project
|
||||
import Elm.Syntax.Range exposing (Range)
|
||||
import Json.Encode as Encode
|
||||
import Review.Project.Dependency as Dependency exposing (Dependency)
|
||||
import Review.Rule as Rule exposing (Error, Rule)
|
||||
import Set exposing (Set)
|
||||
@ -25,24 +26,50 @@ import Set exposing (Set)
|
||||
]
|
||||
|
||||
If the license of a dependency is in the `allowed` list, the dependency will not be reported.
|
||||
If it's in the `forbidden`, the dependency will be reported as an error.
|
||||
If it's in the `forbidden` list, the dependency will be reported as an error.
|
||||
If it's in neither, the dependency will be reported but with a different message asking you
|
||||
to add the license to either list.
|
||||
|
||||
|
||||
## Usage as an insight rule
|
||||
|
||||
If instead of enforcing a restriction on the licenses, you wish to have an overview of the licenses used in your project,
|
||||
you can run the rule as an insight rule (using `elm-review --report=json --extract`), which would yield an output like
|
||||
the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"NoRedInk/elm-json-decode-pipeline": "BSD-3-Clause",
|
||||
"elm-explorations/markdown": "BSD-3-Clause",
|
||||
"elm-explorations/test": "BSD-3-Clause",
|
||||
"elm/browser": "BSD-3-Clause",
|
||||
"elm/core": "BSD-3-Clause",
|
||||
"elm/html": "BSD-3-Clause",
|
||||
"elm/http": "BSD-3-Clause",
|
||||
"elm/json": "BSD-3-Clause",
|
||||
"elm/parser": "BSD-3-Clause",
|
||||
"elm/random": "BSD-3-Clause",
|
||||
"elm/time": "BSD-3-Clause",
|
||||
"elm/url": "BSD-3-Clause",
|
||||
"elm/virtual-dom": "BSD-3-Clause",
|
||||
"rtfeldman/elm-iso8601-date-strings": "BSD-3-Clause"
|
||||
}
|
||||
```
|
||||
|
||||
-}
|
||||
rule : { allowed : List String, forbidden : List String } -> Rule
|
||||
rule configuration =
|
||||
Rule.newProjectRuleSchema "NoUnapprovedLicense" initialProjectContext
|
||||
|> Rule.withElmJsonProjectVisitor elmJsonVisitor
|
||||
|> Rule.withDependenciesProjectVisitor dependenciesVisitor
|
||||
|> Rule.withFinalProjectEvaluation (finalEvaluationForProject configuration)
|
||||
|> Rule.fromProjectRuleSchema
|
||||
|
||||
|
||||
type alias Configuration =
|
||||
{ allowed : List String
|
||||
, forbidden : List String
|
||||
|> Rule.withFinalProjectEvaluation
|
||||
(finalEvaluationForProject
|
||||
{ allowed = Set.fromList configuration.allowed
|
||||
, forbidden = Set.fromList configuration.forbidden
|
||||
}
|
||||
)
|
||||
|> Rule.withDataExtractor dataExtractor
|
||||
|> Rule.fromProjectRuleSchema
|
||||
|
||||
|
||||
dependenciesVisitor : Dict String Dependency -> ProjectContext -> ( List nothing, ProjectContext )
|
||||
@ -50,18 +77,17 @@ dependenciesVisitor dependencies projectContext =
|
||||
let
|
||||
licenses : Dict String String
|
||||
licenses =
|
||||
dependencies
|
||||
|> Dict.toList
|
||||
|> List.filterMap
|
||||
(\( packageName, dependency ) ->
|
||||
Dict.foldl
|
||||
(\packageName dependency acc ->
|
||||
case Dependency.elmJson dependency of
|
||||
Elm.Project.Package { license } ->
|
||||
Just ( packageName, Elm.License.toString license )
|
||||
Dict.insert packageName (Elm.License.toString license) acc
|
||||
|
||||
Elm.Project.Application _ ->
|
||||
Nothing
|
||||
acc
|
||||
)
|
||||
|> Dict.fromList
|
||||
Dict.empty
|
||||
dependencies
|
||||
in
|
||||
( [], { projectContext | licenses = licenses } )
|
||||
|
||||
@ -101,29 +127,16 @@ initialProjectContext =
|
||||
-- FINAL EVALUATION
|
||||
|
||||
|
||||
finalEvaluationForProject : Configuration -> ProjectContext -> List (Error { useErrorForModule : () })
|
||||
finalEvaluationForProject configuration projectContext =
|
||||
finalEvaluationForProject : { allowed : Set String, forbidden : Set String } -> ProjectContext -> List (Error { useErrorForModule : () })
|
||||
finalEvaluationForProject { allowed, forbidden } projectContext =
|
||||
case projectContext.elmJsonKey of
|
||||
Just elmJsonKey ->
|
||||
let
|
||||
allowed : Set String
|
||||
allowed =
|
||||
Set.fromList configuration.allowed
|
||||
Dict.foldl
|
||||
(\name license acc ->
|
||||
if Set.member license allowed then
|
||||
acc
|
||||
|
||||
forbidden : Set String
|
||||
forbidden =
|
||||
Set.fromList configuration.forbidden
|
||||
|
||||
unknownOrForbiddenLicenses : Dict String String
|
||||
unknownOrForbiddenLicenses =
|
||||
projectContext.licenses
|
||||
|> Dict.filter (\_ license -> not <| Set.member license allowed)
|
||||
in
|
||||
unknownOrForbiddenLicenses
|
||||
|> Dict.toList
|
||||
|> List.map
|
||||
(\( name, license ) ->
|
||||
if Set.member license forbidden then
|
||||
else if Set.member license forbidden then
|
||||
Rule.errorForElmJson elmJsonKey
|
||||
(\elmJson ->
|
||||
{ message = "Forbidden license `" ++ license ++ "` for dependency `" ++ name ++ "`"
|
||||
@ -131,6 +144,7 @@ finalEvaluationForProject configuration projectContext =
|
||||
, range = findPackageNameInElmJson name elmJson
|
||||
}
|
||||
)
|
||||
:: acc
|
||||
|
||||
else
|
||||
Rule.errorForElmJson elmJsonKey
|
||||
@ -143,25 +157,37 @@ finalEvaluationForProject configuration projectContext =
|
||||
, range = findPackageNameInElmJson name elmJson
|
||||
}
|
||||
)
|
||||
:: acc
|
||||
)
|
||||
[]
|
||||
projectContext.licenses
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
|
||||
|
||||
dataExtractor : ProjectContext -> Encode.Value
|
||||
dataExtractor projectContext =
|
||||
Encode.dict identity Encode.string projectContext.licenses
|
||||
|
||||
|
||||
findPackageNameInElmJson : String -> String -> Range
|
||||
findPackageNameInElmJson packageName elmJson =
|
||||
elmJson
|
||||
|> String.lines
|
||||
|> List.indexedMap Tuple.pair
|
||||
|> List.filterMap
|
||||
(\( row, line ) ->
|
||||
findPackageNameInElmJsonHelp packageName (String.lines elmJson) 0
|
||||
|
||||
|
||||
findPackageNameInElmJsonHelp : String -> List String -> Int -> Range
|
||||
findPackageNameInElmJsonHelp packageName lines row =
|
||||
case lines of
|
||||
[] ->
|
||||
{ start = { row = 1, column = 1 }, end = { row = 10000, column = 1 } }
|
||||
|
||||
line :: rest ->
|
||||
case String.indexes ("\"" ++ packageName ++ "\"") line of
|
||||
[] ->
|
||||
Nothing
|
||||
findPackageNameInElmJsonHelp packageName rest (row + 1)
|
||||
|
||||
column :: _ ->
|
||||
Just
|
||||
{ start =
|
||||
{ row = row + 1
|
||||
, column = column + 2
|
||||
@ -171,6 +197,3 @@ findPackageNameInElmJson packageName elmJson =
|
||||
, column = column + String.length packageName + 2
|
||||
}
|
||||
}
|
||||
)
|
||||
|> List.head
|
||||
|> Maybe.withDefault { start = { row = 1, column = 1 }, end = { row = 10000, column = 1 } }
|
||||
|
@ -113,17 +113,24 @@ all =
|
||||
\() ->
|
||||
sourceCode
|
||||
|> Review.Test.run (rule { allowed = [], forbidden = [] })
|
||||
|> Review.Test.expectNoErrors
|
||||
|> Review.Test.expectDataExtract """
|
||||
{
|
||||
"elm/core": "BSD-3-Clause"
|
||||
}"""
|
||||
, test "should not report anything if all dependencies have a license that is allowed" <|
|
||||
\() ->
|
||||
sourceCode
|
||||
|> Review.Test.runWithProjectData (createProject "MIT") (rule { allowed = [ "MIT" ], forbidden = [] })
|
||||
|> Review.Test.expectNoErrors
|
||||
|> Review.Test.expectDataExtract """
|
||||
{
|
||||
"author/dependency": "MIT"
|
||||
}"""
|
||||
, test "should report an error if a dependency has an unknown license" <|
|
||||
\() ->
|
||||
sourceCode
|
||||
|> Review.Test.runWithProjectData (createProject "BSD-3-Clause") (rule { allowed = [ "MIT" ], forbidden = [] })
|
||||
|> Review.Test.expectErrorsForElmJson
|
||||
|> Review.Test.expect
|
||||
[ Review.Test.elmJsonErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Unknown license `BSD-3-Clause` for dependency `author/dependency`"
|
||||
, details =
|
||||
@ -133,15 +140,26 @@ all =
|
||||
, under = "author/dependency"
|
||||
}
|
||||
]
|
||||
, Review.Test.dataExtract """
|
||||
{
|
||||
"author/dependency": "BSD-3-Clause"
|
||||
}"""
|
||||
]
|
||||
, test "should report an error if a dependency has a forbidden license" <|
|
||||
\() ->
|
||||
sourceCode
|
||||
|> Review.Test.runWithProjectData (createProject "BSD-3-Clause") (rule { allowed = [ "MIT" ], forbidden = [ "BSD-3-Clause" ] })
|
||||
|> Review.Test.expectErrorsForElmJson
|
||||
|> Review.Test.expect
|
||||
[ Review.Test.elmJsonErrors
|
||||
[ Review.Test.error
|
||||
{ message = "Forbidden license `BSD-3-Clause` for dependency `author/dependency`"
|
||||
, details = [ "This license has been marked as forbidden and you should therefore not use this package." ]
|
||||
, under = "author/dependency"
|
||||
}
|
||||
]
|
||||
, Review.Test.dataExtract """
|
||||
{
|
||||
"author/dependency": "BSD-3-Clause"
|
||||
}"""
|
||||
]
|
||||
]
|
||||
|
@ -66,7 +66,7 @@ This function won't be reported because it has not been tagged.
|
||||
|
||||
-- With opt-in configuration
|
||||
config =
|
||||
[ NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optInWithComment "CHECK TCO")
|
||||
[ NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optInWithComment "ENSURE TCO")
|
||||
]
|
||||
|
||||
fun n =
|
||||
@ -272,13 +272,13 @@ optOutWithComment comment =
|
||||
{-| Reports only the functions tagged with a comment.
|
||||
|
||||
config =
|
||||
[ NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optInWithComment "CHECK TCO")
|
||||
[ NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optInWithComment "ENSURE TCO")
|
||||
]
|
||||
|
||||
With the configuration above, the following function would be reported.
|
||||
|
||||
fun n =
|
||||
-- CHECK TCO
|
||||
-- ENSURE TCO
|
||||
if condition n then
|
||||
fun n * n
|
||||
|
||||
@ -291,8 +291,17 @@ optInWithComment comment =
|
||||
OptIn comment
|
||||
|
||||
|
||||
shouldReportFunction : Configuration -> Context -> Range -> Bool
|
||||
shouldReportFunction configuration context range =
|
||||
hasNoArguments : Expression.FunctionImplementation -> Bool
|
||||
hasNoArguments declaration =
|
||||
List.isEmpty declaration.arguments
|
||||
|
||||
|
||||
shouldReportFunction : Configuration -> Context -> Node Expression.FunctionImplementation -> Bool
|
||||
shouldReportFunction configuration context (Node range declaration) =
|
||||
if hasNoArguments declaration then
|
||||
False
|
||||
|
||||
else
|
||||
let
|
||||
foundComment : Bool
|
||||
foundComment =
|
||||
@ -361,10 +370,16 @@ commentsVisitor configuration comments context =
|
||||
( []
|
||||
, { context
|
||||
| comments =
|
||||
List.foldl
|
||||
(\(Node range value) acc ->
|
||||
if String.contains commentTag value then
|
||||
Set.insert range.start.row acc
|
||||
|
||||
else
|
||||
acc
|
||||
)
|
||||
Set.empty
|
||||
comments
|
||||
|> List.filter (Node.value >> String.contains commentTag)
|
||||
|> List.map (Node.range >> .start >> .row)
|
||||
|> Set.fromList
|
||||
}
|
||||
)
|
||||
|
||||
@ -375,16 +390,7 @@ declarationVisitor configuration node context =
|
||||
Declaration.FunctionDeclaration function ->
|
||||
( []
|
||||
, { currentFunctionName =
|
||||
let
|
||||
hasArguments : Bool
|
||||
hasArguments =
|
||||
function.declaration
|
||||
|> Node.value
|
||||
|> .arguments
|
||||
|> List.isEmpty
|
||||
|> not
|
||||
in
|
||||
if hasArguments && shouldReportFunction configuration context (Node.range function.declaration) then
|
||||
if shouldReportFunction configuration context function.declaration then
|
||||
function.declaration
|
||||
|> Node.value
|
||||
|> .name
|
||||
@ -503,53 +509,7 @@ addAllowedLocation configuration node context =
|
||||
}
|
||||
|
||||
Expression.LetExpression { declarations, expression } ->
|
||||
let
|
||||
newScopes : List ( Range, String )
|
||||
newScopes =
|
||||
List.filterMap
|
||||
(\decl ->
|
||||
case Node.value decl of
|
||||
Expression.LetFunction function ->
|
||||
let
|
||||
functionDeclaration : Expression.FunctionImplementation
|
||||
functionDeclaration =
|
||||
Node.value function.declaration
|
||||
|
||||
hasArguments : Bool
|
||||
hasArguments =
|
||||
function.declaration
|
||||
|> Node.value
|
||||
|> .arguments
|
||||
|> List.isEmpty
|
||||
|> not
|
||||
in
|
||||
Just
|
||||
( Node.range functionDeclaration.expression
|
||||
, if hasArguments && shouldReportFunction configuration context (Node.range function.declaration) then
|
||||
Node.value functionDeclaration.name
|
||||
|
||||
else
|
||||
""
|
||||
)
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
Nothing
|
||||
)
|
||||
declarations
|
||||
in
|
||||
{ context
|
||||
| newScopesForLet = newScopes
|
||||
|
||||
{- The following translates to TCO code
|
||||
|
||||
let
|
||||
fun x =
|
||||
fun x
|
||||
in
|
||||
fun 1
|
||||
-}
|
||||
, tcoLocations = Node.range expression :: context.tcoLocations
|
||||
}
|
||||
addAllowedLocationForLetExpression configuration context declarations expression
|
||||
|
||||
Expression.ParenthesizedExpression expr ->
|
||||
{- The following translates to TCO code
|
||||
@ -561,12 +521,12 @@ addAllowedLocation configuration node context =
|
||||
|
||||
Expression.CaseExpression { cases } ->
|
||||
let
|
||||
caseBodies : List Range
|
||||
caseBodies =
|
||||
List.map (Tuple.second >> Node.range) cases
|
||||
tcoLocations : List Range
|
||||
tcoLocations =
|
||||
List.foldl (\( _, Node range _ ) acc -> range :: acc) context.tcoLocations cases
|
||||
in
|
||||
{ context
|
||||
| tcoLocations = caseBodies ++ context.tcoLocations
|
||||
| tcoLocations = tcoLocations
|
||||
, deOptimizationRange = Just (Node.range node)
|
||||
, deOptimizationReason = [ "Among other possible reasons, the recursive call should not appear in the pattern to evaluate for a case expression." ]
|
||||
}
|
||||
@ -656,6 +616,49 @@ expressionExitVisitor node context =
|
||||
( [], removeDeOptimizationRangeIfNeeded node context )
|
||||
|
||||
|
||||
addAllowedLocationForLetExpression : Configuration -> Context -> List (Node Expression.LetDeclaration) -> Node a -> Context
|
||||
addAllowedLocationForLetExpression configuration context declarations expression =
|
||||
let
|
||||
newScopes : List ( Range, String )
|
||||
newScopes =
|
||||
List.filterMap
|
||||
(\decl ->
|
||||
case Node.value decl of
|
||||
Expression.LetFunction function ->
|
||||
let
|
||||
functionDeclaration : Expression.FunctionImplementation
|
||||
functionDeclaration =
|
||||
Node.value function.declaration
|
||||
in
|
||||
Just
|
||||
( Node.range functionDeclaration.expression
|
||||
, if shouldReportFunction configuration context function.declaration then
|
||||
Node.value functionDeclaration.name
|
||||
|
||||
else
|
||||
""
|
||||
)
|
||||
|
||||
Expression.LetDestructuring _ _ ->
|
||||
Nothing
|
||||
)
|
||||
declarations
|
||||
in
|
||||
{ context
|
||||
| newScopesForLet = newScopes
|
||||
|
||||
{- The following translates to TCO code
|
||||
|
||||
let
|
||||
fun x =
|
||||
fun x
|
||||
in
|
||||
fun 1
|
||||
-}
|
||||
, tcoLocations = Node.range expression :: context.tcoLocations
|
||||
}
|
||||
|
||||
|
||||
removeDeOptimizationRangeIfNeeded : Node Expression -> Context -> Context
|
||||
removeDeOptimizationRangeIfNeeded node context =
|
||||
if Just (Node.range node) == context.deOptimizationRange then
|
||||
|
@ -149,7 +149,6 @@ moduleVisitor schema =
|
||||
|> Rule.withExpressionEnterVisitor (\node context -> ( [], expressionVisitor node context ))
|
||||
|> Rule.withCaseBranchEnterVisitor (\caseBlock casePattern context -> ( [], caseBranchEnterVisitor caseBlock casePattern context ))
|
||||
|> Rule.withCaseBranchExitVisitor (\caseBlock casePattern context -> ( [], caseBranchExitVisitor caseBlock casePattern context ))
|
||||
|> Rule.withFinalModuleEvaluation finalModuleEvaluation
|
||||
|
||||
|
||||
|
||||
@ -197,7 +196,6 @@ type alias ProjectContext =
|
||||
type alias ModuleContext =
|
||||
{ lookupTable : ModuleNameLookupTable
|
||||
, exposedCustomTypesWithConstructors : Set CustomTypeName
|
||||
, exposedCustomTypesWithoutConstructors : Set CustomTypeName
|
||||
, isExposed : Bool
|
||||
, exposesEverything : Bool
|
||||
, exposedConstructors : Dict ModuleNameAsString ExposedConstructors
|
||||
@ -238,7 +236,6 @@ fromProjectToModule =
|
||||
(\lookupTable moduleName projectContext ->
|
||||
{ lookupTable = lookupTable
|
||||
, exposedCustomTypesWithConstructors = Set.empty
|
||||
, exposedCustomTypesWithoutConstructors = Set.empty
|
||||
, isExposed = Set.member (String.join "." moduleName) projectContext.exposedModules
|
||||
, exposedConstructors = projectContext.declaredConstructors
|
||||
, exposesEverything = False
|
||||
@ -291,11 +288,7 @@ fromModuleToProject =
|
||||
{ moduleKey = moduleKey
|
||||
, customTypes =
|
||||
moduleContext.declaredTypesWithConstructors
|
||||
|> Dict.filter
|
||||
(\typeName _ ->
|
||||
not (Set.member typeName moduleContext.exposedCustomTypesWithConstructors)
|
||||
&& not (Set.member typeName moduleContext.exposedCustomTypesWithoutConstructors)
|
||||
)
|
||||
|> Dict.filter (\typeName _ -> not <| Set.member typeName moduleContext.exposedCustomTypesWithConstructors)
|
||||
}
|
||||
)
|
||||
|
||||
@ -304,13 +297,7 @@ fromModuleToProject =
|
||||
moduleNameAsString
|
||||
(ExposedConstructors
|
||||
{ moduleKey = moduleKey
|
||||
, customTypes =
|
||||
moduleContext.declaredTypesWithConstructors
|
||||
|> Dict.filter
|
||||
(\typeName _ ->
|
||||
Set.member typeName moduleContext.exposedCustomTypesWithConstructors
|
||||
|| Set.member typeName moduleContext.exposedCustomTypesWithoutConstructors
|
||||
)
|
||||
, customTypes = moduleContext.declaredTypesWithConstructors
|
||||
}
|
||||
)
|
||||
, usedConstructors =
|
||||
@ -470,26 +457,21 @@ moduleDefinitionVisitor moduleNode context =
|
||||
|
||||
Exposing.Explicit list ->
|
||||
let
|
||||
( exposedCustomTypesWithConstructors, exposedCustomTypesWithoutConstructors ) =
|
||||
exposedCustomTypesWithConstructors : Set String
|
||||
exposedCustomTypesWithConstructors =
|
||||
List.foldl
|
||||
(\node (( withConstructors, withoutConstructors ) as acc) ->
|
||||
(\node acc ->
|
||||
case Node.value node of
|
||||
Exposing.TypeExpose { name } ->
|
||||
( Set.insert name withConstructors, withoutConstructors )
|
||||
|
||||
Exposing.TypeOrAliasExpose name ->
|
||||
( withConstructors, Set.insert name withoutConstructors )
|
||||
Set.insert name acc
|
||||
|
||||
_ ->
|
||||
acc
|
||||
)
|
||||
( context.exposedCustomTypesWithConstructors, context.exposedCustomTypesWithoutConstructors )
|
||||
context.exposedCustomTypesWithConstructors
|
||||
list
|
||||
in
|
||||
{ context
|
||||
| exposedCustomTypesWithConstructors = exposedCustomTypesWithConstructors
|
||||
, exposedCustomTypesWithoutConstructors = exposedCustomTypesWithoutConstructors
|
||||
}
|
||||
{ context | exposedCustomTypesWithConstructors = exposedCustomTypesWithConstructors }
|
||||
|
||||
|
||||
|
||||
@ -1090,44 +1072,6 @@ isCapitalized name =
|
||||
|
||||
|
||||
|
||||
-- FINAL MODULE EVALUATION
|
||||
|
||||
|
||||
finalModuleEvaluation : ModuleContext -> List (Error {})
|
||||
finalModuleEvaluation moduleContext =
|
||||
let
|
||||
customTypes : Dict CustomTypeName (Dict ConstructorName ConstructorInformation)
|
||||
customTypes =
|
||||
if moduleContext.isExposed then
|
||||
moduleContext.declaredTypesWithConstructors
|
||||
|> Dict.filter
|
||||
(\typeName _ ->
|
||||
Set.member typeName moduleContext.exposedCustomTypesWithoutConstructors
|
||||
)
|
||||
|
||||
else
|
||||
moduleContext.declaredTypesWithConstructors
|
||||
|> Dict.filter
|
||||
(\typeName _ ->
|
||||
not (Set.member typeName moduleContext.exposedCustomTypesWithConstructors)
|
||||
&& not (Set.member typeName moduleContext.exposedCustomTypesWithoutConstructors)
|
||||
)
|
||||
|
||||
getFixes : ConstructorName -> List Fix
|
||||
getFixes name =
|
||||
Dict.get name moduleContext.fixesForRemovingConstructor |> Maybe.withDefault []
|
||||
in
|
||||
errorsForCustomTypes
|
||||
moduleContext
|
||||
Rule.errorWithFix
|
||||
getFixes
|
||||
(Dict.get "" moduleContext.usedFunctionsOrValues |> Maybe.withDefault Set.empty)
|
||||
""
|
||||
customTypes
|
||||
[]
|
||||
|
||||
|
||||
|
||||
-- FINAL PROJECT EVALUATION
|
||||
|
||||
|
||||
@ -1136,76 +1080,41 @@ finalProjectEvaluation projectContext =
|
||||
Dict.foldl
|
||||
(\moduleName (ExposedConstructors { moduleKey, customTypes }) acc ->
|
||||
let
|
||||
getFixes : ConstructorName -> List Fix
|
||||
getFixes name =
|
||||
Dict.get ( moduleName, name ) projectContext.fixesForRemovingConstructor |> Maybe.withDefault []
|
||||
|
||||
usedConstructors : Set ConstructorName
|
||||
usedConstructors =
|
||||
Dict.get moduleName projectContext.usedConstructors
|
||||
|> Maybe.withDefault Set.empty
|
||||
in
|
||||
errorsForCustomTypes
|
||||
projectContext
|
||||
(Rule.errorForModuleWithFix moduleKey)
|
||||
getFixes
|
||||
usedConstructors
|
||||
moduleName
|
||||
customTypes
|
||||
acc
|
||||
errorsForCustomTypes projectContext usedConstructors moduleName moduleKey customTypes acc
|
||||
)
|
||||
[]
|
||||
projectContext.declaredConstructors
|
||||
|
||||
|
||||
type alias DataForReportingConstructors a =
|
||||
{ a
|
||||
| wasUsedInLocationThatNeedsItself : Set ( ModuleNameAsString, String )
|
||||
, wasUsedInComparisons : Set ( ModuleNameAsString, String )
|
||||
, wasUsedInOtherModules : Set ( ModuleNameAsString, String )
|
||||
}
|
||||
|
||||
|
||||
errorsForCustomTypes :
|
||||
DataForReportingConstructors a
|
||||
-> ({ message : String, details : List String } -> Range -> List Fix -> Error scope)
|
||||
-> (ConstructorName -> List Fix)
|
||||
-> Set String
|
||||
-> String
|
||||
-> Dict CustomTypeName (Dict ConstructorName ConstructorInformation)
|
||||
-> List (Error scope)
|
||||
-> List (Error scope)
|
||||
errorsForCustomTypes projectContext createError getFixes usedConstructors moduleName customTypes acc =
|
||||
errorsForCustomTypes : ProjectContext -> Set String -> String -> Rule.ModuleKey -> Dict CustomTypeName (Dict ConstructorName ConstructorInformation) -> List (Error scope) -> List (Error scope)
|
||||
errorsForCustomTypes projectContext usedConstructors moduleName moduleKey customTypes acc =
|
||||
Dict.foldl
|
||||
(\_ constructors subAcc ->
|
||||
errorsForConstructors projectContext createError getFixes usedConstructors moduleName constructors subAcc
|
||||
errorsForConstructors projectContext usedConstructors moduleName moduleKey constructors subAcc
|
||||
)
|
||||
acc
|
||||
customTypes
|
||||
|
||||
|
||||
errorsForConstructors :
|
||||
DataForReportingConstructors a
|
||||
-> ({ message : String, details : List String } -> Range -> List Fix -> Error scope)
|
||||
-> (ConstructorName -> List Fix)
|
||||
-> Set String
|
||||
-> String
|
||||
-> Dict ConstructorName ConstructorInformation
|
||||
-> List (Error scope)
|
||||
-> List (Error scope)
|
||||
errorsForConstructors projectContext createError getFixes usedConstructors moduleName constructors acc =
|
||||
errorsForConstructors : ProjectContext -> Set String -> String -> Rule.ModuleKey -> Dict ConstructorName ConstructorInformation -> List (Error scope) -> List (Error scope)
|
||||
errorsForConstructors projectContext usedConstructors moduleName moduleKey constructors acc =
|
||||
Dict.foldl
|
||||
(\constructorName constructorInformation subAcc ->
|
||||
if Set.member constructorName usedConstructors then
|
||||
subAcc
|
||||
|
||||
else
|
||||
reportError
|
||||
createError
|
||||
{ wasUsedInLocationThatNeedsItself = Set.member ( moduleName, constructorName ) projectContext.wasUsedInLocationThatNeedsItself
|
||||
, wasUsedInComparisons = Set.member ( moduleName, constructorName ) projectContext.wasUsedInComparisons
|
||||
, isUsedInOtherModules = Set.member ( moduleName, constructorName ) projectContext.wasUsedInOtherModules
|
||||
, fixesForRemovingConstructor = getFixes constructorName
|
||||
errorForModule
|
||||
moduleKey
|
||||
{ wasUsedInLocationThatNeedsItself = Set.member ( moduleName, constructorInformation.name ) projectContext.wasUsedInLocationThatNeedsItself
|
||||
, wasUsedInComparisons = Set.member ( moduleName, constructorInformation.name ) projectContext.wasUsedInComparisons
|
||||
, isUsedInOtherModules = Set.member ( moduleName, constructorInformation.name ) projectContext.wasUsedInOtherModules
|
||||
, fixesForRemovingConstructor = Dict.get ( moduleName, constructorInformation.name ) projectContext.fixesForRemovingConstructor |> Maybe.withDefault []
|
||||
}
|
||||
constructorInformation
|
||||
:: subAcc
|
||||
@ -1236,8 +1145,8 @@ defaultDetails =
|
||||
"This type constructor is never used. It might be handled everywhere it might appear, but there is no location where this value actually gets created."
|
||||
|
||||
|
||||
reportError :
|
||||
({ message : String, details : List String } -> Range -> List Fix -> Error scope)
|
||||
errorForModule :
|
||||
Rule.ModuleKey
|
||||
->
|
||||
{ wasUsedInLocationThatNeedsItself : Bool
|
||||
, wasUsedInComparisons : Bool
|
||||
@ -1246,8 +1155,9 @@ reportError :
|
||||
}
|
||||
-> ConstructorInformation
|
||||
-> Error scope
|
||||
reportError createError params constructorInformation =
|
||||
createError
|
||||
errorForModule moduleKey params constructorInformation =
|
||||
Rule.errorForModuleWithFix
|
||||
moduleKey
|
||||
(errorInformation
|
||||
{ wasUsedInLocationThatNeedsItself = params.wasUsedInLocationThatNeedsItself
|
||||
, wasUsedInComparisons = params.wasUsedInComparisons
|
||||
|
@ -924,6 +924,18 @@ module MyModule exposing (..)
|
||||
type Foo = Bar
|
||||
"""
|
||||
]
|
||||
, test "should not report used type constructors when an application module is exposing all but it's used elsewhere" <|
|
||||
\() ->
|
||||
[ """
|
||||
module MyModule exposing (..)
|
||||
type Foo = Bar | Baz
|
||||
""", """
|
||||
module Main exposing (main)
|
||||
import MyModule
|
||||
main = [MyModule.Bar, MyModule.Baz]
|
||||
""" ]
|
||||
|> Review.Test.runOnModulesWithProjectData applicationProject (rule [])
|
||||
|> Review.Test.expectNoErrors
|
||||
, test "should not report unused type constructors when package module is exposing the constructors of that type and module is exposed" <|
|
||||
\() ->
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user