Compare commits

...

4 Commits

Author SHA1 Message Date
Jeroen Engels
88e7f8b375 Disable SImplify for some files 2023-08-10 18:25:06 +02:00
Jeroen Engels
1db1a05c69 Apply simplifications 2023-06-26 22:06:21 +02:00
Jeroen Engels
fceaaf33e2 Enable Simplify 2023-06-25 22:44:55 +02:00
Jeroen Engels
efd3fc151a Backport rules from packages 2023-06-25 22:44:48 +02:00
17 changed files with 11841 additions and 1881 deletions

View File

@ -13,6 +13,7 @@
"elm/project-metadata-utils": "1.0.2",
"elm/regex": "1.0.0",
"jfmengels/elm-review": "2.11.0",
"pzp1997/assoc-list": "1.0.0",
"stil4m/elm-syntax": "7.2.9"
},
"indirect": {

View File

@ -34,6 +34,7 @@ import NoUnused.Patterns
import NoUnused.Variables
import Review.Rule as Rule exposing (Rule)
import NoUnused.CustomTypeConstructorArgs
import Simplify
config : List Rule
config =
@ -69,6 +70,11 @@ config =
, NoUnused.Variables.rule
, NoSimpleLetBody.rule
, NoPrematureLetComputation.rule
, Simplify.rule Simplify.defaults
|> Rule.ignoreErrorsForFiles
[ "src/Review/Test/Dependencies/Unsafe.elm"
, "src/Review/Logger.elm"
]
, NoForbiddenWords.rule [ "REPLACEME" ]
]
|> List.map (Rule.ignoreErrorsForDirectories [ "src/Vendor/", "tests/Vendor/" ])

View File

@ -83,9 +83,11 @@ rememberBadAlias { lookupAlias, canMissAliases } (Node moduleNameRange moduleNam
moduleCallVisitor : Node ( ModuleName, String ) -> Context.Module -> ( List (Error {}), Context.Module )
moduleCallVisitor node context =
case Node.value node of
( moduleName, function ) ->
( [], Context.addModuleCall moduleName function (Node.range node) context )
let
( moduleName, function ) =
Node.value node
in
( [], Context.addModuleCall moduleName function (Node.range node) context )
finalEvaluation : Options.AliasLookup -> Context.Module -> List (Error {})

View File

@ -124,28 +124,32 @@ rememberExposedNames { moduleName, moduleAlias, exposingList } context =
valueVisitor : Node ( ModuleName, String ) -> Context.Module -> ( List (Error {}), Context.Module )
valueVisitor node context =
case Node.value node of
( moduleName, name ) ->
if Context.isFunctionExposed context moduleName name then
( [ moduleOnExposedValueError name (Node.range node) ]
, context
)
let
( moduleName, name ) =
Node.value node
in
if Context.isFunctionExposed context moduleName name then
( [ moduleOnExposedValueError name (Node.range node) ]
, context
)
else
( [], context )
else
( [], context )
typeVisitor : Node ( ModuleName, String ) -> Context.Module -> ( List (Error {}), Context.Module )
typeVisitor node context =
case Node.value node of
( moduleName, name ) ->
if Context.isTypeExposed context moduleName name then
( [ moduleOnExposedTypeError name (Node.range node) ]
, context
)
let
( moduleName, name ) =
Node.value node
in
if Context.isTypeExposed context moduleName name then
( [ moduleOnExposedTypeError name (Node.range node) ]
, context
)
else
( [], context )
else
( [], context )
moduleOnExposedValueError : String -> Range -> Error {}

View File

@ -22,6 +22,7 @@ import List.Extra
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
import Review.Rule as Rule exposing (Error, Rule)
import Set exposing (Set)
import String.Extra
{-| Reports arguments of custom type constructors that are never used.
@ -194,13 +195,13 @@ fromModuleToProject =
replaceLocalModuleNameForSet : ModuleName -> Set ( ModuleName, comparable ) -> Set ( ModuleName, comparable )
replaceLocalModuleNameForSet moduleName set =
Set.map
(\( moduleNameForType, name ) ->
(\(( moduleNameForType, name ) as untouched) ->
case moduleNameForType of
[] ->
( moduleName, name )
_ ->
( moduleNameForType, name )
untouched
)
set
@ -208,13 +209,18 @@ replaceLocalModuleNameForSet moduleName set =
replaceLocalModuleNameForDict : ModuleName -> Dict ( ModuleName, comparable ) b -> Dict ( ModuleName, comparable ) b
replaceLocalModuleNameForDict moduleName dict =
Dict.foldl
(\( moduleNameForType, name ) value acc ->
case moduleNameForType of
[] ->
Dict.insert ( moduleName, name ) value acc
(\(( moduleNameForType, name ) as key) value acc ->
let
newKey : ( ModuleName, comparable )
newKey =
case moduleNameForType of
[] ->
( moduleName, name )
_ ->
Dict.insert ( moduleNameForType, name ) value acc
_ ->
key
in
Dict.insert newKey value acc
)
Dict.empty
dict
@ -231,22 +237,22 @@ getNonExposedCustomTypes moduleContext =
let
exposedCustomTypes : Set String
exposedCustomTypes =
list
|> List.filterMap
(\exposed ->
case Node.value exposed of
Exposing.TypeExpose { name, open } ->
case open of
Just _ ->
Just name
List.foldl
(\exposed acc ->
case Node.value exposed of
Exposing.TypeExpose { name, open } ->
case open of
Just _ ->
Set.insert name acc
Nothing ->
Nothing
Nothing ->
acc
_ ->
Nothing
)
|> Set.fromList
_ ->
acc
)
Set.empty
list
in
List.foldl
(\( typeName, args ) acc ->
@ -451,7 +457,7 @@ findCustomTypesHelp lookupTable nodes acc =
node :: restOfNodes ->
case Node.value node of
Expression.FunctionOrValue rawModuleName functionName ->
if isCustomTypeConstructor functionName then
if String.Extra.isCapitalized functionName then
case ModuleNameLookupTable.moduleNameFor lookupTable node of
Just moduleName ->
findCustomTypesHelp lookupTable restOfNodes (( moduleName, functionName ) :: acc)
@ -469,7 +475,7 @@ findCustomTypesHelp lookupTable nodes acc =
findCustomTypesHelp lookupTable (expression :: restOfNodes) acc
Expression.Application (((Node _ (Expression.FunctionOrValue _ functionName)) as first) :: expressions) ->
if isCustomTypeConstructor functionName then
if String.Extra.isCapitalized functionName then
findCustomTypesHelp lookupTable (first :: (expressions ++ restOfNodes)) acc
else
@ -488,12 +494,6 @@ findCustomTypesHelp lookupTable nodes acc =
findCustomTypesHelp lookupTable restOfNodes acc
isCustomTypeConstructor : String -> Bool
isCustomTypeConstructor functionName =
String.slice 0 1 functionName
|> String.all Char.isUpper
registerUsedPatterns : List ( ( ModuleName, String ), Set Int ) -> Dict ( ModuleName, String ) (Set Int) -> Dict ( ModuleName, String ) (Set Int)
registerUsedPatterns newUsedArguments previouslyUsedArguments =
List.foldl

View File

@ -431,6 +431,16 @@ module MyModule exposing (a, b)
type Foo = Unused Int | B
a = Unused 0 == b
b = B
"""
|> Review.Test.runWithProjectData packageProject rule
|> Review.Test.expectNoErrors
, test "should not report args for type constructors starting with a non-ASCII letter used in an equality expression (==)" <|
\() ->
"""
module MyModule exposing (a, b)
type Foo = Ö_Unused Int | Ö_B
a = Ö_Unused 0 == b
b = Ö_B
"""
|> Review.Test.runWithProjectData packageProject rule
|> Review.Test.expectNoErrors
@ -451,6 +461,22 @@ module MyModule exposing (a, b)
type Foo = Unused Int | B
a = Unused <| b
b = B
"""
|> Review.Test.runWithProjectData packageProject rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = message
, details = details
, under = "Int"
}
]
, test "should report args for type constructors starting with a non-ASCII letter used in non-equality operator expressions" <|
\() ->
"""
module MyModule exposing (a, b)
type Foo = Ö_Unused Int | Ö_B
a = Ö_Unused <| b
b = Ö_B
"""
|> Review.Test.runWithProjectData packageProject rule
|> Review.Test.expectErrors

View File

@ -24,6 +24,7 @@ import Review.Fix as Fix exposing (Fix)
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
import Review.Rule as Rule exposing (Error, Rule)
import Set exposing (Set)
import String.Extra
{-| Forbid having unused custom type constructors.
@ -326,15 +327,17 @@ fromModuleToProject =
)
moduleContext.wasUsedInComparisons
, wasUsedInOtherModules =
List.foldl
(\( moduleName_, constructors ) acc ->
Set.union
(Set.map (Tuple.pair moduleName_) constructors)
acc
)
moduleContext.wasUsedInOtherModules
-- TODO add test to make sure we don't fix something that is pattern matched in other modules
(Dict.toList <| Dict.remove "" moduleContext.usedFunctionsOrValues)
-- TODO add test to make sure we don't fix something that is pattern matched in other modules
moduleContext.usedFunctionsOrValues
|> Dict.remove ""
|> Dict.foldl
(\moduleName_ constructors acc ->
Set.foldl
(\constructor subAcc -> Set.insert ( moduleName_, constructor ) subAcc)
acc
constructors
)
moduleContext.wasUsedInOtherModules
, fixesForRemovingConstructor =
mapDictKeys
(\constructorName ->
@ -771,7 +774,7 @@ caseBranchEnterVisitor caseExpression ( casePattern, body ) moduleContext =
fixes : Dict ConstructorName (List Fix)
fixes =
List.foldl
Set.foldl
(\constructorName acc ->
let
fix : Fix
@ -784,7 +787,7 @@ caseBranchEnterVisitor caseExpression ( casePattern, body ) moduleContext =
updateToAdd constructorName fix acc
)
moduleContext.fixesForRemovingConstructor
(Set.toList constructors.fromThisModule)
constructors.fromThisModule
constructorsToIgnore : Set ( ModuleName, ConstructorName )
constructorsToIgnore =
@ -830,7 +833,7 @@ staticRanges nodes acc =
staticRanges restOfNodes (Node.range node :: acc)
Expression.Application ((Node _ (Expression.FunctionOrValue _ name)) :: restOfArgs) ->
if isCapitalized name then
if String.Extra.isCapitalized name then
staticRanges (restOfArgs ++ restOfNodes) (Node.range node :: acc)
else
@ -900,7 +903,7 @@ findConstructorsHelp lookupTable nodes acc =
node :: restOfNodes ->
case Node.value node of
Expression.FunctionOrValue _ name ->
if isCapitalized name then
if String.Extra.isCapitalized name then
findConstructorsHelp
lookupTable
restOfNodes
@ -910,7 +913,7 @@ findConstructorsHelp lookupTable nodes acc =
findConstructorsHelp lookupTable restOfNodes acc
Expression.Application ((Node _ (Expression.FunctionOrValue _ name)) :: restOfArgs) ->
if isCapitalized name then
if String.Extra.isCapitalized name then
findConstructorsHelp
lookupTable
(restOfArgs ++ restOfNodes)
@ -1043,7 +1046,7 @@ constructorsInPattern lookupTable nodes acc =
registerUsedFunctionOrValue : Range -> ModuleName -> ConstructorName -> ModuleContext -> ModuleContext
registerUsedFunctionOrValue range moduleName name moduleContext =
if not (isCapitalized name) then
if not (String.Extra.isCapitalized name) then
moduleContext
else if List.member range moduleContext.ignoredComparisonRanges then
@ -1061,16 +1064,6 @@ registerUsedFunctionOrValue range moduleName name moduleContext =
{ moduleContext | usedFunctionsOrValues = updateToInsert (String.join "." moduleName) name moduleContext.usedFunctionsOrValues }
isCapitalized : String -> Bool
isCapitalized name =
case String.uncons name of
Just ( char, _ ) ->
Char.isUpper char
Nothing ->
False
-- FINAL PROJECT EVALUATION

View File

@ -152,6 +152,25 @@ type Foo = Baz"""
module MyModule exposing (b)
type Foo = Bar"""
]
, test "should report unused and not report used type constructors starting with a non-ASCII letter" <|
\() ->
"""
module MyModule exposing (b)
type Foo = Ö_Used | Ö_NotUsed
b = Ö_Used"""
|> Review.Test.runWithProjectData project (rule [])
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Type constructor `Ö_NotUsed` is not used."
, details = [ defaultDetails ]
, under = "Ö_NotUsed"
}
|> Review.Test.whenFixed
"""
module MyModule exposing (b)
type Foo = Ö_Used
b = Ö_Used"""
]
, test "should report unused type constructors, even if the type is exposed" <|
\() ->
"""
@ -222,12 +241,12 @@ a = case () of
}
|> Review.Test.atExactly { start = { row = 3, column = 19 }, end = { row = 3, column = 25 } }
|> Review.Test.whenFixed
("""
"""
module MyModule exposing (a)
type Foo = Used
a = case () of
Used -> Used
""" |> String.replace "$" " ")
"""
]
, test "should report type constructors that are only used inside deep pattern matches that require themselves" <|
\() ->

View File

@ -292,23 +292,27 @@ finalEvaluationForProject projectContext =
projectContext.used
projectContext.used
( usedModules, unusedModules ) =
projectContext.modules
|> removeExposedPackages projectContext
|> Dict.partition (\moduleName _ -> Set.member moduleName projectContext.usedModules)
filterExposedPackage_ : ModuleName -> Bool
filterExposedPackage_ =
filterExposedPackage projectContext
in
List.concat
[ usedModules
|> Dict.toList
|> List.concatMap (errorsForModule projectContext used)
, unusedModules
|> Dict.toList
|> List.map unusedModuleError
]
Dict.foldl
(\moduleName module_ acc ->
if not (filterExposedPackage_ moduleName) then
acc
else if Set.member moduleName projectContext.usedModules then
errorsForModule projectContext used moduleName module_ acc
else
unusedModuleError moduleName module_ :: acc
)
[]
projectContext.modules
unusedModuleError : ( ModuleName, { a | moduleKey : Rule.ModuleKey, moduleNameLocation : Range } ) -> Error scope
unusedModuleError ( moduleName, { moduleKey, moduleNameLocation } ) =
unusedModuleError : ModuleName -> { a | moduleKey : Rule.ModuleKey, moduleNameLocation : Range } -> Error scope
unusedModuleError moduleName { moduleKey, moduleNameLocation } =
Rule.errorForModule moduleKey
{ message = "Module `" ++ String.join "." moduleName ++ "` is never used."
, details = [ "This module is never used. You may want to remove it to keep your project clean, and maybe detect some unused code in your project." ]
@ -316,15 +320,14 @@ unusedModuleError ( moduleName, { moduleKey, moduleNameLocation } ) =
moduleNameLocation
errorsForModule : ProjectContext -> Set ( ModuleName, String ) -> ( ModuleName, { a | moduleKey : Rule.ModuleKey, exposed : Dict String ExposedElement } ) -> List (Error scope)
errorsForModule projectContext used ( moduleName, { moduleKey, exposed } ) =
exposed
|> removeApplicationExceptions projectContext
|> removeReviewConfig moduleName
|> Dict.filter (\name _ -> not <| Set.member ( moduleName, name ) used)
|> Dict.toList
|> List.concatMap
(\( name, element ) ->
errorsForModule : ProjectContext -> Set ( ModuleName, String ) -> ModuleName -> { a | moduleKey : Rule.ModuleKey, exposed : Dict String ExposedElement } -> List (Error scope) -> List (Error scope)
errorsForModule projectContext used moduleName { moduleKey, exposed } acc =
Dict.foldl
(\name element subAcc ->
if isUsedOrException projectContext used moduleName name then
subAcc
else
let
what : String
what =
@ -338,48 +341,46 @@ errorsForModule projectContext used ( moduleName, { moduleKey, exposed } ) =
ExposedType _ ->
"Exposed type"
in
[ Rule.errorForModuleWithFix moduleKey
Rule.errorForModuleWithFix moduleKey
{ message = what ++ " `" ++ name ++ "` is never used outside this module."
, details = [ "This exposed element is never used. You may want to remove it to keep your project clean, and maybe detect some unused code in your project." ]
}
element.range
(List.map Fix.removeRange element.rangesToRemove)
]
)
:: subAcc
)
acc
exposed
removeExposedPackages : ProjectContext -> Dict ModuleName a -> Dict ModuleName a
removeExposedPackages projectContext dict =
filterExposedPackage : ProjectContext -> ModuleName -> Bool
filterExposedPackage projectContext =
case projectContext.projectType of
IsApplication _ ->
dict
always True
IsPackage exposedModuleNames ->
Dict.filter (\name _ -> not <| Set.member name exposedModuleNames) dict
\moduleName -> not <| Set.member moduleName exposedModuleNames
removeApplicationExceptions : ProjectContext -> Dict String a -> Dict String a
removeApplicationExceptions projectContext dict =
isUsedOrException : ProjectContext -> Set ( ModuleName, String ) -> List String -> String -> Bool
isUsedOrException projectContext used moduleName name =
Set.member ( moduleName, name ) used
|| isApplicationException projectContext name
|| (moduleName == [ "ReviewConfig" ])
isApplicationException : ProjectContext -> String -> Bool
isApplicationException projectContext name =
case projectContext.projectType of
IsPackage _ ->
dict
False
IsApplication ElmApplication ->
Dict.remove "main" dict
name == "main"
IsApplication LamderaApplication ->
dict
|> Dict.remove "main"
|> Dict.remove "app"
removeReviewConfig : ModuleName -> Dict String a -> Dict String a
removeReviewConfig moduleName dict =
if moduleName == [ "ReviewConfig" ] then
Dict.remove "config" dict
else
dict
name == "main" || name == "app"
getRangesToRemove : List ( Int, String ) -> Bool -> String -> Int -> Maybe Range -> Range -> Range -> List Range

View File

@ -225,10 +225,10 @@ report context =
)
in
( errors
, List.foldl
, Set.foldl
useValue
restOfScopes
(Set.toList nonUsedVars)
nonUsedVars
)
_ ->

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ module Simplify.Normalize exposing (Comparison(..), areAllTheSame, compare, comp
import Dict
import Elm.Syntax.Expression as Expression exposing (Expression)
import Elm.Syntax.Infix as Infix
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
import Elm.Syntax.Range as Range
@ -68,6 +69,9 @@ normalize resources node =
(normalize resources function)
(normalize resources extraArgument)
Expression.OperatorApplication "<<" _ left right ->
toNode (Expression.OperatorApplication ">>" Infix.Right (normalize resources right) (normalize resources left))
Expression.OperatorApplication "::" infixDirection element list ->
let
normalizedElement : Node Expression

View File

@ -8,7 +8,7 @@ import Elm.Processing
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Expression exposing (Expression(..), LetDeclaration(..))
import Elm.Syntax.File exposing (File)
import Elm.Syntax.Infix as Infix
import Elm.Syntax.Infix as Infix exposing (InfixDirection(..))
import Elm.Syntax.ModuleName exposing (ModuleName)
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern exposing (Pattern(..))
@ -488,6 +488,31 @@ simpleNormalizationTests =
, expression = n (FunctionOrValue [] "x")
}
)
, test "should not change anything about >>" <|
\() ->
"a >> b >> c >> d"
|> normalizeAndExpect
(OperatorApplication ">>"
Right
(n (FunctionOrValue [] "a"))
(n
(OperatorApplication ">>"
Right
(n (FunctionOrValue [] "b"))
(n
(OperatorApplication ">>"
Right
(n (FunctionOrValue [] "c"))
(n (FunctionOrValue [] "d"))
)
)
)
)
)
, test "should switch << to use >>" <|
\() ->
normalizeSourceCode [] Infer.empty "d << c << b << a"
|> Expect.equal (normalizeSourceCode [] Infer.empty "a >> b >> c >> d")
]
@ -621,6 +646,12 @@ normalizeWithInferredAndExpect moduleNames inferredList expected source =
normalizeBase : List ( Range, ModuleName ) -> Infer.Inferred -> Expression -> String -> Expect.Expectation
normalizeBase moduleNames inferred expected source =
normalizeSourceCode moduleNames inferred source
|> Expect.equal expected
normalizeSourceCode : List ( Range, ModuleName ) -> Infer.Inferred -> String -> Expression
normalizeSourceCode moduleNames inferred source =
("module A exposing (..)\nvalue = " ++ source)
|> parse
|> getValue
@ -629,7 +660,6 @@ normalizeBase moduleNames inferred expected source =
, inferredConstants = ( inferred, [] )
}
|> Node.value
|> Expect.equal expected
{-| Parse source code into a AST.

View File

@ -1,31 +1,75 @@
module Simplify.RangeDict exposing (RangeDict, empty, get, insert, member)
module Simplify.RangeDict exposing (RangeDict, any, empty, get, insert, mapFromList, member, remove, singleton, union)
import Dict exposing (Dict)
import Elm.Syntax.Range exposing (Range)
type alias RangeDict v =
Dict String v
type RangeDict v
= RangeDict (Dict String v)
empty : RangeDict v
empty =
Dict.empty
RangeDict Dict.empty
singleton : Range -> v -> RangeDict v
singleton range value =
RangeDict (Dict.singleton (rangeAsString range) value)
{-| Indirect conversion from a list to key-value pairs to avoid successive List.map calls.
-}
mapFromList : (a -> ( Range, v )) -> List a -> RangeDict v
mapFromList toAssociation list =
List.foldl
(\element acc ->
let
( range, v ) =
toAssociation element
in
Dict.insert (rangeAsString range) v acc
)
Dict.empty
list
|> RangeDict
insert : Range -> v -> RangeDict v -> RangeDict v
insert range =
Dict.insert (rangeAsString range)
insert range value (RangeDict rangeDict) =
RangeDict (Dict.insert (rangeAsString range) value rangeDict)
remove : Range -> RangeDict v -> RangeDict v
remove range (RangeDict rangeDict) =
RangeDict (Dict.remove (rangeAsString range) rangeDict)
get : Range -> RangeDict v -> Maybe v
get range =
Dict.get (rangeAsString range)
get range (RangeDict rangeDict) =
Dict.get (rangeAsString range) rangeDict
member : Range -> RangeDict v -> Bool
member range =
Dict.member (rangeAsString range)
member range (RangeDict rangeDict) =
Dict.member (rangeAsString range) rangeDict
foldl : (v -> folded -> folded) -> folded -> RangeDict v -> folded
foldl reduce initialFolded (RangeDict rangeDict) =
Dict.foldl (\_ -> reduce) initialFolded rangeDict
any : (v -> Bool) -> RangeDict v -> Bool
any isFound rangeDict =
foldl (\value soFar -> soFar || isFound value)
False
rangeDict
union : RangeDict v -> RangeDict v -> RangeDict v
union (RangeDict aRangeDict) (RangeDict bRangeDict) =
RangeDict (Dict.union aRangeDict bRangeDict)
rangeAsString : Range -> String

File diff suppressed because it is too large Load Diff

23
tests/String/Extra.elm Normal file
View File

@ -0,0 +1,23 @@
module String.Extra exposing (isCapitalized)
{-| Some utilities.
-}
{-| Check if the first character of a string is upper case, unicode aware.
Note that `Char.isUpper` returns the correct result only for ASCII characters, even though it can be called with any Unicode character.
See <https://github.com/elm/core/pull/1138>
-}
isCapitalized : String -> Bool
isCapitalized string =
case String.uncons string of
Just ( char, _ ) ->
-- 1. The character is its own upper variant.
(char == Char.toUpper char)
-- 2. The character is not its own lower variant.
&& (char /= Char.toLower char)
Nothing ->
False