diff --git a/CHANGELOG.md b/CHANGELOG.md index ef8580972..7db996291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,9 @@ Bug fixes: - Fixed an issue where `Dict.intersect Dict.empty` would be fixed to `Dict.empty` - Fixed an issue where `Set.intersect Set.empty` would be fixed to `Set.empty` +- Fixed an issue where `(a |> not) == (b |> not)` would be fixed to `(a |> ) == (b |> )` +- Fixed an issue where `List.intersperse << List.singleton` would be fixed to `List.singleton` +- Fixed an issue where e.g. `List.sortBy f << g` would be fixed to `g` ## [2.1.2] - 2023-09-28 diff --git a/src/Simplify.elm b/src/Simplify.elm index 751445685..20a3aee3a 100644 --- a/src/Simplify.elm +++ b/src/Simplify.elm @@ -2786,10 +2786,10 @@ operatorApplicationChecks = , ( "&&", andChecks ) , ( "==", equalityChecks True ) , ( "/=", equalityChecks False ) - , ( "<", comparisonChecks (<) ) - , ( ">", comparisonChecks (>) ) - , ( "<=", comparisonChecks (<=) ) - , ( ">=", comparisonChecks (>=) ) + , ( "<", numberComparisonChecks (<) ) + , ( ">", numberComparisonChecks (>) ) + , ( "<=", numberComparisonChecks (<=) ) + , ( ">=", numberComparisonChecks (>=) ) ] @@ -2817,8 +2817,8 @@ compositionChecks = [ basicsIdentityCompositionChecks , \checkInfo -> case - ( AstHelpers.getValueOrFunctionOrFunctionCall checkInfo.earlier.node - , AstHelpers.getValueOrFunctionOrFunctionCall checkInfo.later.node + ( AstHelpers.getValueOrFnOrFnCall checkInfo.earlier.node + , AstHelpers.getValueOrFnOrFnCall checkInfo.later.node ) of ( Just earlierFnOrCall, Just laterFnOrCall ) -> @@ -2829,7 +2829,7 @@ compositionChecks = of ( Just earlierFnModuleName, Just laterFnModuleName ) -> case Dict.get ( laterFnModuleName, laterFnOrCall.fnName ) compositionIntoChecks of - Just compositionIntoChecksForSpecificLater -> + Just ( laterArgCount, compositionIntoChecksForSpecificLater ) -> compositionIntoChecksForSpecificLater { lookupTable = checkInfo.lookupTable , importLookup = checkInfo.importLookup @@ -2842,6 +2842,7 @@ compositionChecks = , fn = ( laterFnModuleName, laterFnOrCall.fnName ) , fnRange = laterFnOrCall.fnRange , args = laterFnOrCall.args + , argCount = laterArgCount , removeRange = checkInfo.later.removeRange } , earlier = @@ -2884,6 +2885,8 @@ type alias CompositionIntoCheckInfo = , fn : ( ModuleName, String ) , fnRange : Range , args : List (Node Expression) + , -- how many arguments a fully applied call would have + argCount : Int , removeRange : Range } , earlier : @@ -2897,124 +2900,52 @@ type alias CompositionIntoCheckInfo = } -compositionIntoChecks : Dict ( ModuleName, String ) (CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix) +compositionIntoChecks : Dict ( ModuleName, String ) ( Int, CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix ) compositionIntoChecks = Dict.fromList - [ ( ( [ "Basics" ], "always" ), basicsAlwaysCompositionChecks ) - , ( ( [ "Basics" ], "not" ), toggleCompositionChecks ) - , ( ( [ "Basics" ], "negate" ), toggleCompositionChecks ) - , ( ( [ "String" ], "reverse" ), stringReverseCompositionChecks ) - , ( ( [ "String" ], "fromList" ), stringFromListCompositionChecks ) - , ( ( [ "String" ], "toList" ), stringToListCompositionChecks ) - , ( ( [ "String" ], "concat" ), stringConcatCompositionChecks ) - , ( ( [ "Tuple" ], "first" ), tupleFirstCompositionChecks ) - , ( ( [ "Tuple" ], "second" ), tupleSecondCompositionChecks ) - , ( ( [ "Maybe" ], "map" ), maybeMapCompositionChecks ) - , ( ( [ "Maybe" ], "withDefault" ), wrapperWithDefaultChecks maybeWithJustAsWrap ) - , ( ( [ "Result" ], "map" ), resultMapCompositionChecks ) - , ( ( [ "Result" ], "mapError" ), resultMapErrorCompositionChecks ) - , ( ( [ "Result" ], "toMaybe" ), resultToMaybeCompositionChecks ) - , ( ( [ "Result" ], "fromMaybe" ), wrapperFromMaybeCompositionChecks resultWithOkAsWrap ) - , ( ( [ "Result" ], "withDefault" ), wrapperWithDefaultChecks resultWithOkAsWrap ) - , ( ( [ "List" ], "reverse" ), listReverseCompositionChecks ) - , ( ( [ "List" ], "sort" ), listSortCompositionChecks ) - , ( ( [ "List" ], "sortBy" ), listSortByCompositionChecks ) - , ( ( [ "List" ], "map" ), listMapCompositionChecks ) - , ( ( [ "List" ], "filterMap" ), listFilterMapCompositionChecks ) - , ( ( [ "List" ], "intersperse" ), listIntersperseCompositionChecks ) - , ( ( [ "List" ], "concat" ), listConcatCompositionChecks ) - , ( ( [ "List" ], "sum" ), sumCompositionChecks listCollection ) - , ( ( [ "List" ], "product" ), productCompositionChecks listCollection ) - , ( ( [ "List" ], "minimum" ), minimumCompositionChecks listCollection ) - , ( ( [ "List" ], "maximum" ), maximumCompositionChecks listCollection ) - , ( ( [ "List" ], "foldl" ), listFoldlCompositionChecks ) - , ( ( [ "List" ], "foldr" ), listFoldrCompositionChecks ) - , ( ( [ "Set" ], "fromList" ), setFromListCompositionChecks ) - , ( ( [ "Dict" ], "fromList" ), dictFromListCompositionChecks ) - , ( ( [ "Array" ], "toList" ), arrayToListCompositionChecks ) - , ( ( [ "Array" ], "fromList" ), arrayFromListCompositionChecks ) - , ( ( [ "Task" ], "map" ), taskMapCompositionChecks ) - , ( ( [ "Task" ], "mapError" ), taskMapErrorCompositionChecks ) - , ( ( [ "Task" ], "sequence" ), taskSequenceCompositionChecks ) - , ( ( [ "Platform", "Cmd" ], "batch" ), batchCompositionChecks ) - , ( ( [ "Platform", "Sub" ], "batch" ), batchCompositionChecks ) - , ( ( [ "Json", "Decode" ], "map" ), jsonDecodeMapCompositionChecks ) - , ( ( [ "Random" ], "map" ), randomMapCompositionChecks ) + [ ( ( [ "Basics" ], "always" ), ( 2, basicsAlwaysCompositionChecks ) ) + , ( ( [ "Basics" ], "not" ), ( 1, toggleCompositionChecks ) ) + , ( ( [ "Basics" ], "negate" ), ( 1, toggleCompositionChecks ) ) + , ( ( [ "String" ], "reverse" ), ( 1, stringReverseCompositionChecks ) ) + , ( ( [ "String" ], "fromList" ), ( 1, stringFromListCompositionChecks ) ) + , ( ( [ "String" ], "toList" ), ( 1, stringToListCompositionChecks ) ) + , ( ( [ "String" ], "concat" ), ( 1, stringConcatCompositionChecks ) ) + , ( ( [ "Tuple" ], "first" ), ( 1, tupleFirstCompositionChecks ) ) + , ( ( [ "Tuple" ], "second" ), ( 1, tupleSecondCompositionChecks ) ) + , ( ( [ "Maybe" ], "map" ), ( 2, maybeMapCompositionChecks ) ) + , ( ( [ "Maybe" ], "withDefault" ), ( 2, wrapperWithDefaultChecks maybeWithJustAsWrap ) ) + , ( ( [ "Result" ], "map" ), ( 2, resultMapCompositionChecks ) ) + , ( ( [ "Result" ], "mapError" ), ( 2, resultMapErrorCompositionChecks ) ) + , ( ( [ "Result" ], "toMaybe" ), ( 1, resultToMaybeCompositionChecks ) ) + , ( ( [ "Result" ], "fromMaybe" ), ( 3, wrapperFromMaybeCompositionChecks resultWithOkAsWrap ) ) + , ( ( [ "Result" ], "withDefault" ), ( 2, wrapperWithDefaultChecks resultWithOkAsWrap ) ) + , ( ( [ "List" ], "reverse" ), ( 1, listReverseCompositionChecks ) ) + , ( ( [ "List" ], "sort" ), ( 1, listSortCompositionChecks ) ) + , ( ( [ "List" ], "sortBy" ), ( 2, listSortByCompositionChecks ) ) + , ( ( [ "List" ], "map" ), ( 2, listMapCompositionChecks ) ) + , ( ( [ "List" ], "filterMap" ), ( 2, listFilterMapCompositionChecks ) ) + , ( ( [ "List" ], "intersperse" ), ( 2, listIntersperseCompositionChecks ) ) + , ( ( [ "List" ], "concat" ), ( 1, listConcatCompositionChecks ) ) + , ( ( [ "List" ], "sum" ), ( 1, sumCompositionChecks listCollection ) ) + , ( ( [ "List" ], "product" ), ( 1, productCompositionChecks listCollection ) ) + , ( ( [ "List" ], "minimum" ), ( 1, minimumCompositionChecks listCollection ) ) + , ( ( [ "List" ], "maximum" ), ( 1, maximumCompositionChecks listCollection ) ) + , ( ( [ "List" ], "foldl" ), ( 3, listFoldlCompositionChecks ) ) + , ( ( [ "List" ], "foldr" ), ( 3, listFoldrCompositionChecks ) ) + , ( ( [ "Set" ], "fromList" ), ( 1, setFromListCompositionChecks ) ) + , ( ( [ "Dict" ], "fromList" ), ( 1, dictFromListCompositionChecks ) ) + , ( ( [ "Array" ], "toList" ), ( 1, arrayToListCompositionChecks ) ) + , ( ( [ "Array" ], "fromList" ), ( 1, arrayFromListCompositionChecks ) ) + , ( ( [ "Task" ], "map" ), ( 2, taskMapCompositionChecks ) ) + , ( ( [ "Task" ], "mapError" ), ( 2, taskMapErrorCompositionChecks ) ) + , ( ( [ "Task" ], "sequence" ), ( 1, taskSequenceCompositionChecks ) ) + , ( ( [ "Platform", "Cmd" ], "batch" ), ( 1, batchCompositionChecks ) ) + , ( ( [ "Platform", "Sub" ], "batch" ), ( 1, batchCompositionChecks ) ) + , ( ( [ "Json", "Decode" ], "map" ), ( 2, jsonDecodeMapCompositionChecks ) ) + , ( ( [ "Random" ], "map" ), ( 2, randomMapCompositionChecks ) ) ] -removeAlongWithOtherFunctionCheck : CheckInfo -> Maybe (Error {}) -removeAlongWithOtherFunctionCheck checkInfo = - let - fnToFind : ( ModuleName, String ) - fnToFind = - checkInfo.fn - in - case Node.value (AstHelpers.removeParens checkInfo.firstArg) of - Expression.Application (secondFn :: firstArgOfSecondCall :: _) -> - case AstHelpers.getSpecificValueOrFunction fnToFind checkInfo.lookupTable secondFn of - Just secondRange -> - Just - (Rule.errorWithFix - (doubleToggleErrorInfo fnToFind) - (Range.combine [ checkInfo.fnRange, secondRange ]) - (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg } - ++ replaceBySubExpressionFix (Node.range checkInfo.firstArg) - firstArgOfSecondCall - ) - ) - - Nothing -> - Nothing - - Expression.OperatorApplication "|>" _ firstArgOfSecondCall secondFn -> - case AstHelpers.getSpecificValueOrFunction fnToFind checkInfo.lookupTable secondFn of - Just secondRange -> - Just - (Rule.errorWithFix - (doubleToggleErrorInfo fnToFind) - (Range.combine [ checkInfo.fnRange, secondRange ]) - (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg } - ++ replaceBySubExpressionFix (Node.range checkInfo.firstArg) - firstArgOfSecondCall - ) - ) - - Nothing -> - Nothing - - Expression.OperatorApplication "<|" _ secondFn firstArgOfSecondCall -> - case AstHelpers.getSpecificValueOrFunction fnToFind checkInfo.lookupTable secondFn of - Just secondRange -> - Just - (Rule.errorWithFix - (doubleToggleErrorInfo fnToFind) - (Range.combine [ checkInfo.fnRange, secondRange ]) - (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg } - ++ replaceBySubExpressionFix (Node.range checkInfo.firstArg) - firstArgOfSecondCall - ) - ) - - Nothing -> - Nothing - - _ -> - Nothing - - -doubleToggleErrorInfo : ( ModuleName, String ) -> { message : String, details : List String } -doubleToggleErrorInfo toggle = - let - toggleFullyQualifiedAsString : String - toggleFullyQualifiedAsString = - qualifiedToString toggle - in - { message = "Unnecessary double " ++ toggleFullyQualifiedAsString - , details = [ "Chaining " ++ toggleFullyQualifiedAsString ++ " with " ++ toggleFullyQualifiedAsString ++ " makes both functions cancel each other out." ] - } - - findOperatorRange : { extractSourceCode : Range -> String , commentRanges : List Range @@ -3091,12 +3022,11 @@ offsetInStringToLocation config = plusChecks : OperatorCheckInfo -> Maybe (Error {}) -plusChecks checkInfo = +plusChecks = firstThatConstructsJust [ addingZeroCheck , addingOppositesCheck ] - checkInfo addingZeroCheck : OperatorCheckInfo -> Maybe (Error {}) @@ -3106,17 +3036,17 @@ addingZeroCheck checkInfo = if AstHelpers.getUncomputedNumberValue side.node == Just 0 then Just (Rule.errorWithFix - { message = "Unnecessary addition with 0" - , details = [ "Adding 0 does not change the value of the number." ] + { message = "Unnecessary adding 0" + , details = [ "You can replace this operation by the " ++ side.otherDescription ++ " number you added 0 to." ] } - side.errorRange - [ Fix.removeRange side.removeRange ] + (Range.combine [ checkInfo.operatorRange, Node.range side.node ]) + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range side.otherNode }) ) else Nothing ) - (operationToSides checkInfo) + (operationSides checkInfo) addingOppositesCheck : OperatorCheckInfo -> Maybe (Error {}) @@ -3129,8 +3059,8 @@ addingOppositesCheck checkInfo = Normalize.ConfirmedEquality -> Just (Rule.errorWithFix - { message = "Addition always results in 0" - , details = [ "These two expressions have an equal absolute value but an opposite sign. This means adding them they will cancel out to 0." ] + { message = "Adding opposite numbers will result in 0" + , details = [ "Adding two numbers with an equal absolute value and an opposite sign will cancel each other out. You can replace this operation by 0." ] } checkInfo.parentRange [ Fix.replaceRangeBy checkInfo.parentRange "0" ] @@ -3148,58 +3078,51 @@ minusChecks checkInfo = if AstHelpers.getUncomputedNumberValue checkInfo.right == Just 0 then Just (Rule.errorWithFix - { message = "Unnecessary subtraction with 0" - , details = [ "Subtracting 0 does not change the value of the number." ] + { message = "Unnecessary subtracting 0" + , details = [ "You can replace this operation by the left number you subtracted 0 from." ] } - (errorToRightRange checkInfo) - [ Fix.removeRange (fixToRightRange checkInfo) ] + checkInfo.operatorRange + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.left }) ) else if AstHelpers.getUncomputedNumberValue checkInfo.left == Just 0 then - let - replacedRange : Range - replacedRange = - fixToLeftRange checkInfo - in Just (Rule.errorWithFix - { message = "Unnecessary subtracting from 0" - , details = [ "You can negate the expression on the right like `-n`." ] + { message = "Subtracting from 0 is the same as negating" + , details = [ "You can replace this operation by the negated right number you subtracted from 0, like `-n`." ] } - (errorToLeftRange checkInfo) - (if needsParens (Node.value checkInfo.right) then - [ Fix.replaceRangeBy replacedRange "-(", Fix.insertAt checkInfo.rightRange.end ")" ] - - else - [ Fix.replaceRangeBy replacedRange "-" ] + checkInfo.operatorRange + (replaceBySubExpressionFix checkInfo.parentRange checkInfo.right + ++ [ Fix.insertAt checkInfo.parentRange.start "-" ] ) ) - else if checkInfo.expectNaN then - Nothing - else checkIfMinusResultsInZero checkInfo checkIfMinusResultsInZero : OperatorCheckInfo -> Maybe (Error {}) checkIfMinusResultsInZero checkInfo = - case Normalize.compare checkInfo checkInfo.left checkInfo.right of - Normalize.ConfirmedEquality -> - Just - (Rule.errorWithFix - { message = "Subtraction always results in 0" - , details = [ "These two expressions have the same value, which means they will cancel add when subtracting one by the other." ] - } - checkInfo.parentRange - [ Fix.replaceRangeBy checkInfo.parentRange "0" ] - ) + if checkInfo.expectNaN then + Nothing - Normalize.ConfirmedInequality -> - Nothing + else + case Normalize.compare checkInfo checkInfo.left checkInfo.right of + Normalize.ConfirmedEquality -> + Just + (Rule.errorWithFix + { message = "Subtracting equal numbers will result in 0" + , details = [ "You can replace this operation by 0." ] + } + checkInfo.parentRange + [ Fix.replaceRangeBy checkInfo.parentRange "0" ] + ) - Normalize.Unconfirmed -> - Nothing + Normalize.ConfirmedInequality -> + Nothing + + Normalize.Unconfirmed -> + Nothing multiplyChecks : OperatorCheckInfo -> Maybe (Error {}) @@ -3211,11 +3134,11 @@ multiplyChecks checkInfo = if number == 1 then Just (Rule.errorWithFix - { message = "Unnecessary multiplication by 1" - , details = [ "Multiplying by 1 does not change the value of the number." ] + { message = "Unnecessary multiplying by 1" + , details = [ "You can replace this operation by the " ++ side.otherDescription ++ " number you multiplied by 1." ] } - side.errorRange - [ Fix.removeRange side.removeRange ] + (Range.combine [ checkInfo.operatorRange, Node.range side.node ]) + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range side.otherNode }) ) else if number == 0 then @@ -3231,7 +3154,7 @@ by explicitly checking for `Basics.isNaN` and `Basics.isInfinite`.""" Basics.isInfinite: https://package.elm-lang.org/packages/elm/core/latest/Basics#isInfinite""" ] } - side.errorRange + (Range.combine [ checkInfo.operatorRange, Node.range side.node ]) (if checkInfo.expectNaN then [] @@ -3246,69 +3169,16 @@ Basics.isInfinite: https://package.elm-lang.org/packages/elm/core/latest/Basics# Nothing -> Nothing ) - (operationToSides checkInfo) + (operationSides checkInfo) -operationToSides : OperatorCheckInfo -> List { node : Node Expression, removeRange : Range, errorRange : Range } -operationToSides checkInfo = - [ { node = checkInfo.right - , removeRange = fixToRightRange checkInfo - , errorRange = errorToRightRange checkInfo - } - , { node = checkInfo.left - , removeRange = fixToLeftRange checkInfo - , errorRange = errorToLeftRange checkInfo - } +operationSides : OperatorCheckInfo -> List { node : Node Expression, otherNode : Node Expression, otherDescription : String } +operationSides checkInfo = + [ { node = checkInfo.left, otherNode = checkInfo.right, otherDescription = "right" } + , { node = checkInfo.right, otherNode = checkInfo.left, otherDescription = "left" } ] -{-| Takes the ranges of two neighboring elements and -returns a range that includes the specified element and everything between them. - -This is useful when you can't use `replaceBySubExpressionFix` and `keepOnlyFix` because there is no -existing node that could be kept. - -For example, you might want to remove `|> identity` in `f |> g |> identity`. `elm-syntax` might represent this as (simplified) - - Op (Var "f") "|>" (Op (Var "g") "|>" (Var "identity")) - -In practice, you will check this syntax tree recursively, leading to situations where we only know - - - the previous/next element which we want to keep - - and the current element which we want to remove - --} -andBetweenRange : { excluded : Range, included : Range } -> Range -andBetweenRange ranges = - case Range.compare ranges.excluded ranges.included of - LT -> - { start = ranges.excluded.end, end = ranges.included.end } - - -- GT | EQ -> - _ -> - { start = ranges.included.start, end = ranges.excluded.start } - - -fixToLeftRange : { checkInfo | leftRange : Range, rightRange : Range } -> Range -fixToLeftRange checkInfo = - { start = checkInfo.leftRange.start, end = checkInfo.rightRange.start } - - -errorToLeftRange : { checkInfo | leftRange : Range, operatorRange : Range } -> Range -errorToLeftRange checkInfo = - { start = checkInfo.leftRange.start, end = checkInfo.operatorRange.end } - - -fixToRightRange : { checkInfo | leftRange : Range, rightRange : Range } -> Range -fixToRightRange checkInfo = - { start = checkInfo.leftRange.end, end = checkInfo.rightRange.end } - - -errorToRightRange : { checkInfo | rightRange : Range, operatorRange : Range } -> Range -errorToRightRange checkInfo = - { start = checkInfo.operatorRange.start, end = checkInfo.rightRange.end } - - divisionChecks : OperatorCheckInfo -> Maybe (Error {}) divisionChecks checkInfo = let @@ -3319,11 +3189,11 @@ divisionChecks checkInfo = if maybeDivisorNumber == Just 1 then Just (Rule.errorWithFix - { message = "Unnecessary division by 1" - , details = [ "Dividing by 1 does not change the value of the number." ] + { message = "Unnecessary dividing by 1" + , details = [ "You can replace this operation by the left number you divided by 1." ] } - (errorToRightRange checkInfo) - [ Fix.removeRange (fixToRightRange checkInfo) ] + checkInfo.operatorRange + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.left }) ) else if not checkInfo.expectNaN && (AstHelpers.getUncomputedNumberValue checkInfo.left == Just 0) then @@ -3344,13 +3214,13 @@ divisionChecks checkInfo = else Just (Rule.errorWithFix - { message = "Dividing 0 always returns 0" + { message = "Dividing 0 will result in 0" , details = [ "Dividing 0 by anything, even infinite numbers, gives 0 which means you can replace the whole division operation by 0." , "Most likely, dividing 0 was unintentional and you had a different number in mind." ] } - (errorToLeftRange checkInfo) + checkInfo.operatorRange (keepOnlyFix { parentRange = checkInfo.parentRange, keep = checkInfo.leftRange }) ) @@ -3367,8 +3237,8 @@ intDivideChecks = if rightNumber == 1 then Just (Rule.errorWithFix - { message = "Unnecessary division by 1" - , details = [ "Dividing by 1 using (//) does not change the value of the number." ] + { message = "Unnecessary dividing by 1" + , details = [ "You can replace this operation by the left integer you divided by 1." ] } checkInfo.operatorRange (keepOnlyFix { parentRange = checkInfo.parentRange, keep = checkInfo.leftRange }) @@ -3377,7 +3247,7 @@ intDivideChecks = else if rightNumber == 0 then Just (Rule.errorWithFix - { message = "Dividing by 0 always returns 0" + { message = "Dividing by 0 will result in 0" , details = [ "Dividing anything by 0 using (//) gives 0 which means you can replace the whole division operation by 0." , "Most likely, dividing by 0 was unintentional and you had a different number in mind." @@ -3396,7 +3266,7 @@ intDivideChecks = if AstHelpers.getUncomputedNumberValue checkInfo.left == Just 0 then Just (Rule.errorWithFix - { message = "Dividing 0 always returns 0" + { message = "Dividing 0 will result in 0" , details = [ "Dividing 0 by anything using (//), even 0, gives 0 which means you can replace the whole division operation by 0." , "Most likely, dividing 0 was unintentional and you had a different number in mind." @@ -3415,85 +3285,36 @@ plusplusChecks : OperatorCheckInfo -> Maybe (Error {}) plusplusChecks = firstThatConstructsJust [ \checkInfo -> - case ( Node.value checkInfo.left, Node.value checkInfo.right ) of - ( Expression.Literal "", Expression.Literal _ ) -> - Just - (Rule.errorWithFix - (concatenateEmptyErrorInfo { represents = "string", emptyDescription = emptyStringAsString }) - checkInfo.operatorRange - (keepOnlyFix - { keep = checkInfo.rightRange - , parentRange = checkInfo.parentRange - } - ) - ) - - ( Expression.Literal _, Expression.Literal "" ) -> - Just - (Rule.errorWithFix - (concatenateEmptyErrorInfo { represents = "string", emptyDescription = emptyStringAsString }) - checkInfo.operatorRange - (keepOnlyFix - { keep = checkInfo.leftRange - , parentRange = checkInfo.parentRange - } - ) - ) - - _ -> - Nothing - , \checkInfo -> - case AstHelpers.getListLiteral checkInfo.left of - Just [] -> - Just - (Rule.errorWithFix - (concatenateEmptyErrorInfo { represents = "list", emptyDescription = "[]" }) - checkInfo.operatorRange - (keepOnlyFix - { keep = checkInfo.rightRange - , parentRange = checkInfo.parentRange - } - ) - ) + findMap + (\side -> + case Node.value side.otherNode of + Expression.Literal _ -> + appendEmptyCheck side stringCollection checkInfo - _ -> - Nothing + _ -> + Nothing + ) + (operationSides checkInfo) , \checkInfo -> - case AstHelpers.getListLiteral checkInfo.right of - Just [] -> - Just - (Rule.errorWithFix - (concatenateEmptyErrorInfo { represents = "list", emptyDescription = "[]" }) - checkInfo.operatorRange - (keepOnlyFix - { keep = checkInfo.leftRange - , parentRange = checkInfo.parentRange - } - ) - ) - - _ -> - Nothing + findMap (\side -> appendEmptyCheck side listCollection checkInfo) (operationSides checkInfo) , \checkInfo -> - collectionUnionWithLiteralsChecks listCollection - { lookupTable = checkInfo.lookupTable - , extractSourceCode = checkInfo.extractSourceCode - , parentRange = checkInfo.parentRange - , first = checkInfo.left + collectionUnionWithLiteralsChecks + { first = checkInfo.left , second = checkInfo.right , operationRange = checkInfo.operatorRange , operation = "++" } + listCollection + checkInfo , \checkInfo -> - collectionUnionWithLiteralsChecks stringCollection - { lookupTable = checkInfo.lookupTable - , extractSourceCode = checkInfo.extractSourceCode - , parentRange = checkInfo.parentRange - , first = checkInfo.left + collectionUnionWithLiteralsChecks + { first = checkInfo.left , second = checkInfo.right , operationRange = checkInfo.operatorRange , operation = "++" } + stringCollection + checkInfo , \checkInfo -> case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.left of Just leftListSingleton -> @@ -3518,11 +3339,28 @@ plusplusChecks = ] -concatenateEmptyErrorInfo : { represents : String, emptyDescription : String } -> { message : String, details : List String } -concatenateEmptyErrorInfo config = - { message = "Unnecessary concatenation with " ++ config.emptyDescription - , details = [ "You should remove the concatenation with the empty " ++ config.represents ++ "." ] - } +appendEmptyCheck : + { side | node : Node Expression, otherNode : Node Expression, otherDescription : String } + -> TypeProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) + -> OperatorCheckInfo + -> Maybe (Error {}) +appendEmptyCheck side collection checkInfo = + if collection.empty.is checkInfo.lookupTable side.node then + Just + (Rule.errorWithFix + { message = "Unnecessary appending " ++ descriptionForIndefinite collection.empty.description + , details = [ "You can replace this operation by the " ++ side.otherDescription ++ " " ++ collection.represents ++ "." ] + } + checkInfo.operatorRange + (keepOnlyFix + { keep = Node.range side.otherNode + , parentRange = checkInfo.parentRange + } + ) + ) + + else + Nothing consChecks : OperatorCheckInfo -> Maybe (Error {}) @@ -3589,6 +3427,16 @@ consChecks = ] +toggleCallChecks : CheckInfo -> Maybe (Error {}) +toggleCallChecks checkInfo = + onCallToInverseReturnsItsArgumentCheck checkInfo.fn checkInfo + + +toggleCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix +toggleCompositionChecks checkInfo = + inversesCompositionCheck checkInfo.later.fn checkInfo + + {-| Chaining two operations that are inverses of each other and therefore cancel each other out. For example @@ -3619,7 +3467,7 @@ would be an incorrect fix. See for example -} onCallToInverseReturnsItsArgumentCheck : ( ModuleName, String ) -> CheckInfo -> Maybe (Error {}) onCallToInverseReturnsItsArgumentCheck inverseFn checkInfo = - case AstHelpers.getSpecificFunctionCall inverseFn checkInfo.lookupTable checkInfo.firstArg of + case AstHelpers.getSpecificFnCall inverseFn checkInfo.lookupTable checkInfo.firstArg of Just call -> Just (Rule.errorWithFix @@ -3843,67 +3691,261 @@ getFullComposition expressionNode = Nothing -toggleCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -toggleCompositionChecks checkInfo = - inversesCompositionCheck checkInfo.later.fn checkInfo +-- EQUALITY + + +equalityChecks : Bool -> OperatorCheckInfo -> Maybe (Error {}) +equalityChecks isEqual = + firstThatConstructsJust + [ \checkInfo -> + findMap + (\side -> + if Evaluate.getBoolean checkInfo side.node == Determined isEqual then + Just + (Rule.errorWithFix + { message = "Unnecessary comparison with boolean" + , details = [ "The result of the expression will be the same with or without the comparison." ] + } + (Range.combine [ checkInfo.operatorRange, Node.range side.node ]) + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range side.otherNode }) + ) + else + Nothing + ) + (operationSides checkInfo) + , \checkInfo -> + case + ( AstHelpers.getSpecificFnCall ( [ "Basics" ], "not" ) checkInfo.lookupTable checkInfo.left + , AstHelpers.getSpecificFnCall ( [ "Basics" ], "not" ) checkInfo.lookupTable checkInfo.right + ) + of + ( Just leftNotCall, Just rightNotCall ) -> + Just + (Rule.errorWithFix + { message = "Unnecessary `not` on both sides of (" ++ checkInfo.operator ++ ")" + , details = [ "You can replace the bool on each side by the value given to `not`." ] + } + checkInfo.operatorRange + (replaceBySubExpressionFix leftNotCall.nodeRange leftNotCall.firstArg + ++ replaceBySubExpressionFix rightNotCall.nodeRange rightNotCall.firstArg + ) + ) --- NEGATE + _ -> + Nothing + , \checkInfo -> + let + inferred : Infer.Inferred + inferred = + Tuple.first checkInfo.inferredConstants + normalizeAndInfer : Node Expression -> Node Expression + normalizeAndInfer expressionNode = + let + normalizedExpressionNode : Node Expression + normalizedExpressionNode = + Normalize.normalize checkInfo expressionNode + in + case Infer.get (Node.value normalizedExpressionNode) inferred of + Just expr -> + Node Range.emptyRange expr -basicsNegateChecks : CheckInfo -> Maybe (Error {}) -basicsNegateChecks checkInfo = - removeAlongWithOtherFunctionCheck checkInfo + Nothing -> + normalizedExpressionNode + normalizedLeft : Node Expression + normalizedLeft = + normalizeAndInfer checkInfo.left + normalizedRight : Node Expression + normalizedRight = + normalizeAndInfer checkInfo.right + in + case Normalize.compareWithoutNormalization normalizedLeft normalizedRight of + Normalize.ConfirmedEquality -> + if checkInfo.expectNaN then + Nothing --- BOOLEAN + else + Just (comparisonError isEqual checkInfo) + Normalize.ConfirmedInequality -> + Just (comparisonError (not isEqual) checkInfo) -basicsNotChecks : CheckInfo -> Maybe (Error {}) -basicsNotChecks = - firstThatConstructsJust - [ notOnKnownBoolCheck - , removeAlongWithOtherFunctionCheck - , isNotOnBooleanOperatorCheck + Normalize.Unconfirmed -> + Nothing ] -notOnKnownBoolCheck : CheckInfo -> Maybe (Error {}) -notOnKnownBoolCheck checkInfo = - case Evaluate.getBoolean checkInfo checkInfo.firstArg of - Determined bool -> - let - notBoolAsString : String - notBoolAsString = - AstHelpers.boolToString (not bool) - in - Just - (Rule.errorWithFix - { message = wrapInBackticks "not" ++ " on a bool known to be " ++ AstHelpers.boolToString bool ++ " can be replaced by " ++ notBoolAsString - , details = [ "You can replace this call by " ++ notBoolAsString ++ "." ] - } - checkInfo.fnRange - [ Fix.replaceRangeBy checkInfo.parentRange - (qualifiedToString (qualify ( [ "Basics" ], notBoolAsString ) checkInfo)) - ] - ) - Undetermined -> +-- COMPARISONS + + +numberComparisonChecks : (Float -> Float -> Bool) -> OperatorCheckInfo -> Maybe (Error {}) +numberComparisonChecks operatorFunction operatorCheckInfo = + case + Maybe.map2 operatorFunction + (Normalize.getNumberValue operatorCheckInfo.left) + (Normalize.getNumberValue operatorCheckInfo.right) + of + Just bool -> + Just (comparisonError bool operatorCheckInfo) + + Nothing -> Nothing -isNotOnBooleanOperatorCheck : CheckInfo -> Maybe (Error {}) -isNotOnBooleanOperatorCheck checkInfo = - case Node.value checkInfo.firstArg of - Expression.ParenthesizedExpression (Node _ (Expression.OperatorApplication operator _ (Node leftRange _) (Node rightRange _))) -> - case isNegatableOperator operator of - Just replacement -> - let - operatorRange : Range - operatorRange = - findOperatorRange +comparisonError : Bool -> QualifyResources { a | parentRange : Range, operator : String } -> Error {} +comparisonError bool checkInfo = + let + boolAsString : String + boolAsString = + AstHelpers.boolToString bool + in + Rule.errorWithFix + { message = "(" ++ checkInfo.operator ++ ") comparison will result in " ++ boolAsString + , details = + [ "Based on the values and/or the context, we can determine the result. You can replace this operation by " ++ boolAsString ++ "." + ] + } + checkInfo.parentRange + [ Fix.replaceRangeBy checkInfo.parentRange + (qualifiedToString (qualify ( [ "Basics" ], boolAsString ) checkInfo)) + ] + + + +-- BASICS + + +basicsIdentityChecks : CheckInfo -> Maybe (Error {}) +basicsIdentityChecks checkInfo = + Just + (Rule.errorWithFix + { message = "`identity` should be removed" + , details = [ "`identity` can be a useful function to be passed as arguments to other functions, but calling it manually with an argument is the same thing as writing the argument on its own." ] + } + checkInfo.fnRange + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg }) + ) + + +basicsIdentityCompositionErrorMessage : { message : String, details : List String } +basicsIdentityCompositionErrorMessage = + { message = "`identity` should be removed" + , details = [ "Composing a function with `identity` is the same as simplify referencing the function." ] + } + + +basicsIdentityCompositionChecks : CompositionCheckInfo -> Maybe (Error {}) +basicsIdentityCompositionChecks checkInfo = + if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.later.node then + Just + (Rule.errorWithFix + basicsIdentityCompositionErrorMessage + (Node.range checkInfo.later.node) + [ Fix.removeRange checkInfo.later.removeRange ] + ) + + else if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.earlier.node then + Just + (Rule.errorWithFix + basicsIdentityCompositionErrorMessage + (Node.range checkInfo.earlier.node) + [ Fix.removeRange checkInfo.earlier.removeRange ] + ) + + else + Nothing + + +basicsAlwaysChecks : CheckInfo -> Maybe (Error {}) +basicsAlwaysChecks checkInfo = + case secondArg checkInfo of + Just (Node secondArgRange _) -> + Just + (Rule.errorWithFix + { message = "Expression can be replaced by the first argument given to `always`" + , details = [ "The second argument will be ignored because of the `always` call." ] + } + checkInfo.fnRange + (replaceBySubExpressionFix + (Range.combine [ checkInfo.fnRange, Node.range checkInfo.firstArg, secondArgRange ]) + checkInfo.firstArg + ) + ) + + Nothing -> + Nothing + + +basicsAlwaysCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix +basicsAlwaysCompositionChecks checkInfo = + case checkInfo.later.args of + _ :: [] -> + Just + { info = + { message = "Function composed with always will be ignored" + , details = [ "`always` will swallow the function composed into it." ] + } + , fix = + [ Fix.removeRange checkInfo.earlier.removeRange ] + } + + _ -> + Nothing + + +basicsNegateChecks : CheckInfo -> Maybe (Error {}) +basicsNegateChecks = + toggleCallChecks + + +basicsNotChecks : CheckInfo -> Maybe (Error {}) +basicsNotChecks = + firstThatConstructsJust + [ notOnKnownBoolCheck + , toggleCallChecks + , isNotOnBooleanOperatorCheck + ] + + +notOnKnownBoolCheck : CheckInfo -> Maybe (Error {}) +notOnKnownBoolCheck checkInfo = + case Evaluate.getBoolean checkInfo checkInfo.firstArg of + Determined bool -> + let + notBoolAsString : String + notBoolAsString = + AstHelpers.boolToString (not bool) + in + Just + (Rule.errorWithFix + { message = wrapInBackticks "not" ++ " on a bool known to be " ++ AstHelpers.boolToString bool ++ " can be replaced by " ++ notBoolAsString + , details = [ "You can replace this call by " ++ notBoolAsString ++ "." ] + } + checkInfo.fnRange + [ Fix.replaceRangeBy checkInfo.parentRange + (qualifiedToString (qualify ( [ "Basics" ], notBoolAsString ) checkInfo)) + ] + ) + + Undetermined -> + Nothing + + +isNotOnBooleanOperatorCheck : CheckInfo -> Maybe (Error {}) +isNotOnBooleanOperatorCheck checkInfo = + case Node.value checkInfo.firstArg of + Expression.ParenthesizedExpression (Node _ (Expression.OperatorApplication operator _ (Node leftRange _) (Node rightRange _))) -> + case isNegatableOperator operator of + Just replacement -> + let + operatorRange : Range + operatorRange = + findOperatorRange { operator = operator , commentRanges = checkInfo.commentRanges , extractSourceCode = checkInfo.extractSourceCode @@ -3957,8 +3999,7 @@ isNegatableOperator op = orChecks : OperatorCheckInfo -> Maybe (Error {}) orChecks = firstThatConstructsJust - [ or_isLeftSimplifiableError - , or_isRightSimplifiableError + [ \checkInfo -> findMap (\side -> orSideChecks side checkInfo) (operationSides checkInfo) , findSimilarConditionsError ] @@ -4073,70 +4114,30 @@ listConditions operatorToLookFor redundantConditionResolution expressionNode = [ ( redundantConditionResolution, expressionNode ) ] -or_isLeftSimplifiableError : OperatorCheckInfo -> Maybe (Error {}) -or_isLeftSimplifiableError checkInfo = - case Evaluate.getBoolean checkInfo checkInfo.left of +orSideChecks : { side | node : Node Expression, otherNode : Node Expression, otherDescription : String } -> OperatorCheckInfo -> Maybe (Error {}) +orSideChecks side checkInfo = + case Evaluate.getBoolean checkInfo side.node of Determined True -> Just (Rule.errorWithFix - { message = "Comparison is always True" - , details = alwaysSameDetails - } - checkInfo.parentRange - [ Fix.removeRange - { start = checkInfo.leftRange.end - , end = checkInfo.rightRange.end - } - ] - ) - - Determined False -> - Just - (Rule.errorWithFix - { message = unnecessaryMessage - , details = unnecessaryDetails - } - checkInfo.parentRange - [ Fix.removeRange - { start = checkInfo.leftRange.start - , end = checkInfo.rightRange.start - } - ] - ) - - Undetermined -> - Nothing - - -or_isRightSimplifiableError : OperatorCheckInfo -> Maybe (Error {}) -or_isRightSimplifiableError checkInfo = - case Evaluate.getBoolean checkInfo checkInfo.right of - Determined True -> - Just - (Rule.errorWithFix - { message = unnecessaryMessage - , details = unnecessaryDetails + { message = "(||) with any side being True will result in True" + , details = + [ "You can replace this operation by True." + , "Maybe you have hardcoded a value or mistyped a condition?" + ] } checkInfo.parentRange - [ Fix.removeRange - { start = checkInfo.leftRange.start - , end = checkInfo.rightRange.start - } - ] + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range side.node }) ) Determined False -> Just (Rule.errorWithFix - { message = unnecessaryMessage - , details = unnecessaryDetails + { message = "Unnecessary check for || False" + , details = [ "You can replace this operation by the " ++ side.otherDescription ++ " bool." ] } - checkInfo.parentRange - [ Fix.removeRange - { start = checkInfo.leftRange.end - , end = checkInfo.rightRange.end - } - ] + (Range.combine [ checkInfo.operatorRange, Node.range side.node ]) + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range side.otherNode }) ) Undetermined -> @@ -4146,300 +4147,38 @@ or_isRightSimplifiableError checkInfo = andChecks : OperatorCheckInfo -> Maybe (Error {}) andChecks = firstThatConstructsJust - [ and_isLeftSimplifiableError - , and_isRightSimplifiableError + [ \checkInfo -> findMap (\side -> andSideChecks side checkInfo) (operationSides checkInfo) , findSimilarConditionsError ] -and_isLeftSimplifiableError : OperatorCheckInfo -> Maybe (Error {}) -and_isLeftSimplifiableError checkInfo = - case Evaluate.getBoolean checkInfo checkInfo.left of +andSideChecks : { side | node : Node Expression, otherNode : Node Expression, otherDescription : String } -> OperatorCheckInfo -> Maybe (Error {}) +andSideChecks side checkInfo = + case Evaluate.getBoolean checkInfo side.node of Determined True -> Just (Rule.errorWithFix - { message = unnecessaryMessage - , details = unnecessaryDetails + { message = "Unnecessary check for && True" + , details = [ "You can replace this operation by the " ++ side.otherDescription ++ " bool." ] } - checkInfo.parentRange - [ Fix.removeRange - { start = checkInfo.leftRange.start - , end = checkInfo.rightRange.start - } - ] + (Range.combine [ checkInfo.operatorRange, Node.range side.node ]) + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range side.otherNode }) ) - - Determined False -> - Just - (Rule.errorWithFix - { message = "Comparison is always False" - , details = alwaysSameDetails - } - checkInfo.parentRange - [ Fix.removeRange - { start = checkInfo.leftRange.end - , end = checkInfo.rightRange.end - } - ] - ) - - Undetermined -> - Nothing - - -and_isRightSimplifiableError : OperatorCheckInfo -> Maybe (Error {}) -and_isRightSimplifiableError checkInfo = - case Evaluate.getBoolean checkInfo checkInfo.right of - Determined True -> - Just - (Rule.errorWithFix - { message = unnecessaryMessage - , details = unnecessaryDetails - } - checkInfo.parentRange - [ Fix.removeRange - { start = checkInfo.leftRange.end - , end = checkInfo.rightRange.end - } - ] - ) - - Determined False -> - Just - (Rule.errorWithFix - { message = "Comparison is always False" - , details = alwaysSameDetails - } - checkInfo.parentRange - [ Fix.removeRange - { start = checkInfo.leftRange.start - , end = checkInfo.rightRange.start - } - ] - ) - - Undetermined -> - Nothing - - - --- EQUALITY - - -equalityChecks : Bool -> OperatorCheckInfo -> Maybe (Error {}) -equalityChecks isEqual checkInfo = - if Evaluate.getBoolean checkInfo checkInfo.right == Determined isEqual then - Just - (Rule.errorWithFix - { message = "Unnecessary comparison with boolean" - , details = [ "The result of the expression will be the same with or without the comparison." ] - } - (errorToRightRange checkInfo) - [ Fix.removeRange (fixToRightRange checkInfo) ] - ) - - else if Evaluate.getBoolean checkInfo checkInfo.left == Determined isEqual then - Just - (Rule.errorWithFix - { message = "Unnecessary comparison with boolean" - , details = [ "The result of the expression will be the same with or without the comparison." ] - } - (errorToLeftRange checkInfo) - [ Fix.removeRange (fixToLeftRange checkInfo) ] - ) - - else - case - Maybe.map2 Tuple.pair - (AstHelpers.getSpecificFunctionCall ( [ "Basics" ], "not" ) checkInfo.lookupTable checkInfo.left) - (AstHelpers.getSpecificFunctionCall ( [ "Basics" ], "not" ) checkInfo.lookupTable checkInfo.right) - of - Just ( leftNot, rightNot ) -> - Just - (Rule.errorWithFix - { message = "Unnecessary negation on both sides" - , details = [ "Since both sides are negated using `not`, they are redundant and can be removed." ] - } - checkInfo.parentRange - [ Fix.removeRange leftNot.fnRange, Fix.removeRange rightNot.fnRange ] - ) - - _ -> - let - inferred : Infer.Inferred - inferred = - Tuple.first checkInfo.inferredConstants - - normalizeAndInfer : Node Expression -> Node Expression - normalizeAndInfer expressionNode = - let - normalizedExpressionNode : Node Expression - normalizedExpressionNode = - Normalize.normalize checkInfo expressionNode - in - case Infer.get (Node.value normalizedExpressionNode) inferred of - Just expr -> - Node Range.emptyRange expr - - Nothing -> - normalizedExpressionNode - - normalizedLeft : Node Expression - normalizedLeft = - normalizeAndInfer checkInfo.left - - normalizedRight : Node Expression - normalizedRight = - normalizeAndInfer checkInfo.right - in - case Normalize.compareWithoutNormalization normalizedLeft normalizedRight of - Normalize.ConfirmedEquality -> - if checkInfo.expectNaN then - Nothing - - else - Just (comparisonError isEqual checkInfo) - - Normalize.ConfirmedInequality -> - Just (comparisonError (not isEqual) checkInfo) - - Normalize.Unconfirmed -> - Nothing - - -alwaysSameDetails : List String -alwaysSameDetails = - [ "This condition will always result in the same value. You may have hardcoded a value or mistyped a condition." - ] - - -unnecessaryMessage : String -unnecessaryMessage = - "Part of the expression is unnecessary" - - -unnecessaryDetails : List String -unnecessaryDetails = - [ "A part of this condition is unnecessary. You can remove it and it would not impact the behavior of the program." - ] - - - --- COMPARISONS - - -comparisonChecks : (Float -> Float -> Bool) -> OperatorCheckInfo -> Maybe (Error {}) -comparisonChecks operatorFunction operatorCheckInfo = - case - Maybe.map2 operatorFunction - (Normalize.getNumberValue operatorCheckInfo.left) - (Normalize.getNumberValue operatorCheckInfo.right) - of - Just bool -> - Just (comparisonError bool operatorCheckInfo) - - Nothing -> - Nothing - - -comparisonError : Bool -> QualifyResources { a | parentRange : Range } -> Error {} -comparisonError bool checkInfo = - let - boolAsString : String - boolAsString = - AstHelpers.boolToString bool - in - Rule.errorWithFix - { message = "Comparison is always " ++ boolAsString - , details = - [ "Based on the values and/or the context, we can determine that the value of this operation will always be " ++ boolAsString ++ "." - ] - } - checkInfo.parentRange - [ Fix.replaceRangeBy checkInfo.parentRange - (qualifiedToString (qualify ( [ "Basics" ], boolAsString ) checkInfo)) - ] - - - --- BASICS - - -basicsIdentityChecks : CheckInfo -> Maybe (Error {}) -basicsIdentityChecks checkInfo = - Just - (Rule.errorWithFix - { message = "`identity` should be removed" - , details = [ "`identity` can be a useful function to be passed as arguments to other functions, but calling it manually with an argument is the same thing as writing the argument on its own." ] - } - checkInfo.fnRange - (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg }) - ) - - -basicsIdentityCompositionErrorMessage : { message : String, details : List String } -basicsIdentityCompositionErrorMessage = - { message = "`identity` should be removed" - , details = [ "Composing a function with `identity` is the same as simplify referencing the function." ] - } - - -basicsIdentityCompositionChecks : CompositionCheckInfo -> Maybe (Error {}) -basicsIdentityCompositionChecks checkInfo = - if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.later.node then - Just - (Rule.errorWithFix - basicsIdentityCompositionErrorMessage - (Node.range checkInfo.later.node) - [ Fix.removeRange checkInfo.later.removeRange ] - ) - - else if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.earlier.node then - Just - (Rule.errorWithFix - basicsIdentityCompositionErrorMessage - (Node.range checkInfo.earlier.node) - [ Fix.removeRange checkInfo.earlier.removeRange ] - ) - - else - Nothing - - -basicsAlwaysChecks : CheckInfo -> Maybe (Error {}) -basicsAlwaysChecks checkInfo = - case secondArg checkInfo of - Just (Node secondArgRange _) -> - Just - (Rule.errorWithFix - { message = "Expression can be replaced by the first argument given to `always`" - , details = [ "The second argument will be ignored because of the `always` call." ] - } - checkInfo.fnRange - (replaceBySubExpressionFix - (Range.combine [ checkInfo.fnRange, Node.range checkInfo.firstArg, secondArgRange ]) - checkInfo.firstArg - ) - ) - - Nothing -> - Nothing - - -basicsAlwaysCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -basicsAlwaysCompositionChecks checkInfo = - case checkInfo.later.args of - _ :: [] -> + + Determined False -> Just - { info = - { message = "Function composed with always will be ignored" - , details = [ "`always` will swallow the function composed into it." ] + (Rule.errorWithFix + { message = "(&&) with any side being False will result in False" + , details = + [ "You can replace this operation by False." + , "Maybe you have hardcoded a value or mistyped a condition?" + ] } - , fix = - [ Fix.removeRange checkInfo.earlier.removeRange ] - } + checkInfo.parentRange + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range side.node }) + ) - _ -> + Undetermined -> Nothing @@ -4543,14 +4282,13 @@ tupleFirstCompositionChecks = tupleSecondChecks : CheckInfo -> Maybe (Error {}) -tupleSecondChecks checkInfo = +tupleSecondChecks = tuplePartChecks { part = TupleSecond , description = "second" , mapFn = ( [ "Tuple" ], "mapSecond" ) , mapUnrelatedFn = ( [ "Tuple" ], "mapFirst" ) } - checkInfo tupleSecondCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix @@ -4611,7 +4349,7 @@ tuplePartChecks partConfig = ) (AstHelpers.getTuple2 checkInfo.firstArg checkInfo.lookupTable) , \checkInfo -> - case AstHelpers.getSpecificFunctionCall partConfig.mapUnrelatedFn checkInfo.lookupTable checkInfo.firstArg of + case AstHelpers.getSpecificFnCall partConfig.mapUnrelatedFn checkInfo.lookupTable checkInfo.firstArg of Just mapSecondCall -> case mapSecondCall.argsAfterFirst of unmappedTuple :: [] -> @@ -4630,7 +4368,7 @@ tuplePartChecks partConfig = Nothing -> Nothing , \checkInfo -> - case AstHelpers.getSpecificFunctionCall ( [ "Tuple" ], "mapBoth" ) checkInfo.lookupTable checkInfo.firstArg of + case AstHelpers.getSpecificFnCall ( [ "Tuple" ], "mapBoth" ) checkInfo.lookupTable checkInfo.firstArg of Just tupleMapBothCall -> case tupleMapBothCall.argsAfterFirst of secondMapperArg :: _ :: [] -> @@ -4760,42 +4498,36 @@ stringConcatCompositionChecks = stringWordsChecks : CheckInfo -> Maybe (Error {}) -stringWordsChecks checkInfo = - callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } - stringCollection - checkInfo +stringWordsChecks = + callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } stringCollection stringLinesChecks : CheckInfo -> Maybe (Error {}) -stringLinesChecks checkInfo = - callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } - stringCollection - checkInfo +stringLinesChecks = + callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } stringCollection stringReverseChecks : CheckInfo -> Maybe (Error {}) -stringReverseChecks checkInfo = +stringReverseChecks = firstThatConstructsJust - [ \() -> emptiableReverseChecks stringCollection checkInfo - , \() -> callOnWrappedDoesNotChangeItCheck stringCollection checkInfo + [ emptiableReverseChecks stringCollection + , unnecessaryCallOnWrappedCheck stringCollection ] - () stringReverseCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -stringReverseCompositionChecks checkInfo = +stringReverseCompositionChecks = firstThatConstructsJust - [ \() -> compositionAfterWrapIsUnnecessaryCheck stringCollection checkInfo - , \() -> toggleCompositionChecks checkInfo + [ unnecessaryCompositionAfterWrapCheck stringCollection + , toggleCompositionChecks ] - () stringSliceChecks : CheckInfo -> Maybe (Error {}) -stringSliceChecks checkInfo = +stringSliceChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck stringCollection checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck stringCollection + , \checkInfo -> case secondArg checkInfo of Just endArg -> firstThatConstructsJust @@ -4861,14 +4593,13 @@ stringSliceChecks checkInfo = Nothing -> Nothing ] - () stringLeftChecks : CheckInfo -> Maybe (Error {}) -stringLeftChecks checkInfo = +stringLeftChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck stringCollection checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck stringCollection + , \checkInfo -> case Evaluate.getInt checkInfo checkInfo.firstArg of Just length -> callWithNonPositiveIntCanBeReplacedByCheck @@ -4881,7 +4612,6 @@ stringLeftChecks checkInfo = Nothing -> Nothing ] - () callWithNonPositiveIntCanBeReplacedByCheck : @@ -4913,10 +4643,10 @@ callWithNonPositiveIntCanBeReplacedByCheck config checkInfo = stringRightChecks : CheckInfo -> Maybe (Error {}) -stringRightChecks checkInfo = +stringRightChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck stringCollection checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck stringCollection + , \checkInfo -> case Evaluate.getInt checkInfo checkInfo.firstArg of Just length -> callWithNonPositiveIntCanBeReplacedByCheck @@ -4929,57 +4659,57 @@ stringRightChecks checkInfo = Nothing -> Nothing ] - () stringJoinChecks : CheckInfo -> Maybe (Error {}) -stringJoinChecks checkInfo = +stringJoinChecks = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck { resultAsString = stringCollection.empty.asString } listCollection checkInfo - , \() -> - case Node.value checkInfo.firstArg of - Expression.Literal "" -> - let - replacementFn : ( ModuleName, String ) - replacementFn = - ( [ "String" ], "concat" ) - in - Just - (Rule.errorWithFix - { message = qualifiedToString checkInfo.fn ++ " with separator \"\" is the same as " ++ qualifiedToString replacementFn - , details = [ "You can replace this call by " ++ qualifiedToString replacementFn ++ "." ] - } - checkInfo.fnRange - [ Fix.replaceRangeBy { start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).end } - (qualifiedToString (qualify replacementFn checkInfo)) - ] - ) + [ callOnEmptyReturnsCheck { resultAsString = stringCollection.empty.asString } listCollection + , \checkInfo -> + if stringCollection.empty.is checkInfo.lookupTable checkInfo.firstArg then + let + replacementFn : ( ModuleName, String ) + replacementFn = + ( [ "String" ], "concat" ) + in + Just + (Rule.errorWithFix + { message = qualifiedToString checkInfo.fn ++ " with separator \"\" is the same as " ++ qualifiedToString replacementFn + , details = [ "You can replace this call by " ++ qualifiedToString replacementFn ++ "." ] + } + checkInfo.fnRange + [ Fix.replaceRangeBy { start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).end } + (qualifiedToString (qualify replacementFn checkInfo)) + ] + ) - _ -> - Nothing + else + Nothing ] - () stringRepeatChecks : CheckInfo -> Maybe (Error {}) -stringRepeatChecks checkInfo = +stringRepeatChecks = firstThatConstructsJust - [ \() -> + [ \checkInfo -> case secondArg checkInfo of - Just (Node _ (Expression.Literal "")) -> - Just - (Rule.errorWithFix - { message = "String.repeat with " ++ emptyStringAsString ++ " will result in " ++ emptyStringAsString - , details = [ "You can replace this call by " ++ emptyStringAsString ++ "." ] - } - checkInfo.fnRange - [ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ] - ) + Just stringArg -> + if stringCollection.empty.is checkInfo.lookupTable stringArg then + Just + (Rule.errorWithFix + { message = "String.repeat with " ++ emptyStringAsString ++ " will result in " ++ emptyStringAsString + , details = [ "You can replace this call by " ++ emptyStringAsString ++ "." ] + } + checkInfo.fnRange + [ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ] + ) - _ -> + else + Nothing + + Nothing -> Nothing - , \() -> + , \checkInfo -> case Evaluate.getInt checkInfo checkInfo.firstArg of Just intValue -> firstThatConstructsJust @@ -5007,14 +4737,13 @@ stringRepeatChecks checkInfo = _ -> Nothing ] - () stringReplaceChecks : CheckInfo -> Maybe (Error {}) -stringReplaceChecks checkInfo = +stringReplaceChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck stringCollection checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck stringCollection + , \checkInfo -> case secondArg checkInfo of Just replacementArg -> firstThatConstructsJust @@ -5058,17 +4787,16 @@ stringReplaceChecks checkInfo = Nothing -> Nothing ] - () stringFoldlChecks : CheckInfo -> Maybe (Error {}) -stringFoldlChecks checkInfo = - emptiableFoldChecks stringCollection checkInfo +stringFoldlChecks = + emptiableFoldChecks stringCollection stringFoldrChecks : CheckInfo -> Maybe (Error {}) -stringFoldrChecks checkInfo = - emptiableFoldChecks stringCollection checkInfo +stringFoldrChecks = + emptiableFoldChecks stringCollection @@ -5076,26 +4804,24 @@ stringFoldrChecks checkInfo = maybeMapChecks : CheckInfo -> Maybe (Error {}) -maybeMapChecks checkInfo = +maybeMapChecks = firstThatConstructsJust - [ \() -> emptiableMapChecks maybeWithJustAsWrap checkInfo - , \() -> mapWrapChecks maybeWithJustAsWrap checkInfo + [ emptiableMapChecks maybeWithJustAsWrap + , mapWrapChecks maybeWithJustAsWrap ] - () maybeMapCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -maybeMapCompositionChecks checkInfo = - wrapToMapCompositionChecks maybeWithJustAsWrap checkInfo +maybeMapCompositionChecks = + wrapToMapCompositionChecks maybeWithJustAsWrap maybeMapNChecks : CheckInfo -> Maybe (Error {}) -maybeMapNChecks checkInfo = +maybeMapNChecks = firstThatConstructsJust - [ \() -> wrapperMapNChecks maybeWithJustAsWrap checkInfo - , \() -> emptiableMapNChecks maybeWithJustAsWrap checkInfo + [ wrapperMapNChecks maybeWithJustAsWrap + , emptiableMapNChecks maybeWithJustAsWrap ] - () @@ -5103,26 +4829,24 @@ maybeMapNChecks checkInfo = resultMapChecks : CheckInfo -> Maybe (Error {}) -resultMapChecks checkInfo = +resultMapChecks = firstThatConstructsJust - [ \() -> emptiableMapChecks resultWithOkAsWrap checkInfo - , \() -> mapWrapChecks resultWithOkAsWrap checkInfo + [ emptiableMapChecks resultWithOkAsWrap + , mapWrapChecks resultWithOkAsWrap ] - () resultMapCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -resultMapCompositionChecks checkInfo = - wrapToMapCompositionChecks resultWithOkAsWrap checkInfo +resultMapCompositionChecks = + wrapToMapCompositionChecks resultWithOkAsWrap resultMapNChecks : CheckInfo -> Maybe (Error {}) -resultMapNChecks checkInfo = +resultMapNChecks = firstThatConstructsJust - [ \() -> wrapperMapNChecks resultWithOkAsWrap checkInfo - , \() -> mapNOrFirstEmptyConstructionChecks resultWithOkAsWrap checkInfo + [ wrapperMapNChecks resultWithOkAsWrap + , mapNOrFirstEmptyConstructionChecks resultWithOkAsWrap ] - () mapWrapErrorInfo : @@ -5141,40 +4865,19 @@ mapWrapErrorInfo mapFn wrapper = resultMapErrorChecks : CheckInfo -> Maybe (Error {}) -resultMapErrorChecks checkInfo = +resultMapErrorChecks = firstThatConstructsJust - [ \() -> emptiableMapChecks resultWithErrAsWrap checkInfo - , \() -> mapWrapChecks resultWithErrAsWrap checkInfo + [ emptiableMapChecks resultWithErrAsWrap + , mapWrapChecks resultWithErrAsWrap ] - () resultMapErrorCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -resultMapErrorCompositionChecks checkInfo = +resultMapErrorCompositionChecks = firstThatConstructsJust - [ \() -> wrapToMapCompositionChecks resultWithErrAsWrap checkInfo - , \() -> - case checkInfo.later.args of - _ :: [] -> - case checkInfo.earlier.fn of - ( [ "Result" ], "Ok" ) -> - Just - { info = - operationDoesNotChangeSpecificLastArgErrorInfo - { fn = ( [ "Result" ], "mapError" ) - , specific = resultWithErrAsWrap.empty.description - } - , fix = - [ Fix.removeRange checkInfo.later.removeRange ] - } - - _ -> - Nothing - - _ -> - Nothing + [ wrapToMapCompositionChecks resultWithErrAsWrap + , unnecessaryCompositionAfterEmptyCheck resultWithErrAsWrap ] - () @@ -5182,12 +4885,12 @@ resultMapErrorCompositionChecks checkInfo = listConcatChecks : CheckInfo -> Maybe (Error {}) -listConcatChecks checkInfo = +listConcatChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck listCollection checkInfo - , \() -> callOnWrapReturnsItsValue listCollection checkInfo - , \() -> callOnListWithIrrelevantEmptyElement listCollection checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck listCollection + , callOnWrapReturnsItsValueCheck listCollection + , callOnListWithIrrelevantEmptyElement listCollection + , \checkInfo -> case Node.value checkInfo.firstArg of Expression.ListExpr list -> case list of @@ -5231,34 +4934,22 @@ listConcatChecks checkInfo = _ -> Nothing - , \() -> - callFromCanBeCombinedCheck - { fromFn = ( [ "List" ], "map" ), combinedFn = ( [ "List" ], "concatMap" ) } - checkInfo + , callFromCanBeCombinedCheck + { fromFn = ( [ "List" ], "map" ), combinedFn = ( [ "List" ], "concatMap" ) } ] - () listConcatCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listConcatCompositionChecks checkInfo = +listConcatCompositionChecks = firstThatConstructsJust - [ \() -> - compositionFromCanBeCombinedCheck - { fromFn = ( [ "List" ], "map" ), combinedFn = ( [ "List" ], "concatMap" ) } - checkInfo - , \() -> onWrapAlwaysReturnsIncomingCompositionCheck { operationArgCount = 1 } listCollection checkInfo + [ compositionFromCanBeCombinedCheck + { fromFn = ( [ "List" ], "map" ), combinedFn = ( [ "List" ], "concatMap" ) } + , onWrapAlwaysReturnsIncomingCompositionCheck listCollection ] - () callOnListWithIrrelevantEmptyElement : - { otherProperties - | empty : - { empty - | description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - } + EmptiableProperties (TypeSubsetProperties empty) otherProperties -> CheckInfo -> Maybe (Error {}) callOnListWithIrrelevantEmptyElement emptiableElement checkInfo = @@ -5306,14 +4997,12 @@ findConsecutiveListLiterals firstListElement restOfListElements = listConcatMapChecks : CheckInfo -> Maybe (Error {}) -listConcatMapChecks checkInfo = +listConcatMapChecks = firstThatConstructsJust - [ \() -> - operationWithIdentityCanBeReplacedChecks { replacementFn = ( [ "List" ], "concat" ) } checkInfo - , \() -> emptiableAndThenChecks listCollection checkInfo - , \() -> wrapperAndThenChecks listCollection checkInfo + [ operationWithIdentityCanBeReplacedChecks { replacementFn = ( [ "List" ], "concat" ) } + , emptiableAndThenChecks listCollection + , wrapperAndThenChecks listCollection ] - () {-| Turn `yourFn identity` into `replacementFn`. If `replacementFn` should be `identity`, use `alwaysReturnsLastArgError` instead @@ -5345,12 +5034,11 @@ operationWithIdentityCanBeReplacedChecks config checkInfo = listIndexedMapChecks : CheckInfo -> Maybe (Error {}) -listIndexedMapChecks checkInfo = +listIndexedMapChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck listCollection checkInfo - , \() -> operationWithExtraArgChecks { operationWithoutExtraArg = ( [ "List" ], "map" ) } checkInfo + [ unnecessaryCallOnEmptyCheck listCollection + , operationWithExtraArgChecks { operationWithoutExtraArg = ( [ "List" ], "map" ) } ] - () {-| Map where the usual map function has an extra argument with special information. @@ -5404,7 +5092,7 @@ getReplaceAlwaysByItsResultFix lookupTable expressionNode = Nothing _ -> - case AstHelpers.getSpecificFunctionCall ( [ "Basics" ], "always" ) lookupTable expressionNode of + case AstHelpers.getSpecificFnCall ( [ "Basics" ], "always" ) lookupTable expressionNode of Just alwaysCall -> Just (replaceBySubExpressionFix alwaysCall.nodeRange alwaysCall.firstArg) @@ -5414,30 +5102,23 @@ getReplaceAlwaysByItsResultFix lookupTable expressionNode = listIntersperseChecks : CheckInfo -> Maybe (Error {}) -listIntersperseChecks checkInfo = +listIntersperseChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck listCollection checkInfo - , \() -> callOnWrappedDoesNotChangeItCheck listCollection checkInfo + [ unnecessaryCallOnEmptyCheck listCollection + , unnecessaryCallOnWrappedCheck listCollection ] - () listIntersperseCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listIntersperseCompositionChecks checkInfo = - compositionAfterWrapIsUnnecessaryCheck listCollection checkInfo +listIntersperseCompositionChecks = + unnecessaryCompositionAfterWrapCheck listCollection listHeadChecks : CheckInfo -> Maybe (Error {}) -listHeadChecks checkInfo = - let - listArg : Node Expression - listArg = - checkInfo.firstArg - in +listHeadChecks = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } listCollection checkInfo - , \() -> + [ callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } listCollection + , \checkInfo -> Maybe.map (\listArgHead -> Rule.errorWithFix @@ -5445,15 +5126,14 @@ listHeadChecks checkInfo = , details = [ "You can replace this call by Just the first list element." ] } checkInfo.fnRange - (replaceBySubExpressionFix (Node.range listArg) listArgHead + (replaceBySubExpressionFix (Node.range checkInfo.firstArg) listArgHead ++ [ Fix.replaceRangeBy checkInfo.fnRange (qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo)) ] ) ) - (getListHead checkInfo.lookupTable listArg) + (getListHead checkInfo.lookupTable checkInfo.firstArg) ] - () getListHead : ModuleNameLookupTable -> Node Expression -> Maybe (Node Expression) @@ -5474,48 +5154,45 @@ getListHead lookupTable expressionNode = Nothing -listTailChecks : CheckInfo -> Maybe (Error {}) -listTailChecks checkInfo = - let - listArg : Node Expression - listArg = - checkInfo.firstArg +listTailExistsError : List Fix -> CheckInfo -> Error {} +listTailExistsError replaceListArgByTailFix checkInfo = + Rule.errorWithFix + { message = qualifiedToString checkInfo.fn ++ " on a list with some elements will result in Just the elements after the first" + , details = [ "You can replace this call by Just the list elements after the first." ] + } + checkInfo.fnRange + (replaceListArgByTailFix + ++ [ Fix.replaceRangeBy checkInfo.fnRange + (qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo)) + ] + ) - listTailExistsError : List Fix -> Error {} - listTailExistsError replaceListArgByTailFix = - Rule.errorWithFix - { message = qualifiedToString checkInfo.fn ++ " on a list with some elements will result in Just the elements after the first" - , details = [ "You can replace this call by Just the list elements after the first." ] - } - checkInfo.fnRange - (replaceListArgByTailFix - ++ [ Fix.replaceRangeBy checkInfo.fnRange - (qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo)) - ] - ) - in + +listTailChecks : CheckInfo -> Maybe (Error {}) +listTailChecks = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } listCollection checkInfo - , \() -> - case Node.value (AstHelpers.removeParens listArg) of + [ callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } listCollection + , \checkInfo -> + case Node.value (AstHelpers.removeParens checkInfo.firstArg) of Expression.ListExpr ((Node headRange _) :: (Node tailFirstRange _) :: _) -> Just (listTailExistsError [ Fix.removeRange { start = headRange.start, end = tailFirstRange.start } ] + checkInfo ) Expression.OperatorApplication "::" _ _ tail -> Just (listTailExistsError - (replaceBySubExpressionFix (Node.range listArg) tail) + (replaceBySubExpressionFix (Node.range checkInfo.firstArg) tail) + checkInfo ) _ -> Nothing - , \() -> - case AstHelpers.getListSingleton checkInfo.lookupTable listArg of + , \checkInfo -> + case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of Just _ -> Just (Rule.errorWithFix @@ -5532,18 +5209,16 @@ listTailChecks checkInfo = Nothing -> Nothing ] - () listMapChecks : CheckInfo -> Maybe (Error {}) -listMapChecks checkInfo = +listMapChecks = firstThatConstructsJust - [ \() -> emptiableMapChecks listCollection checkInfo - , \() -> listMapOnSingletonCheck checkInfo - , \() -> dictToListMapChecks checkInfo - , \() -> arrayToIndexedListToListMapChecks checkInfo + [ emptiableMapChecks listCollection + , listMapOnSingletonCheck + , dictToListMapChecks + , arrayToIndexedListToListMapChecks ] - () listMapOnSingletonCheck : CheckInfo -> Maybe (Error {}) @@ -5599,7 +5274,7 @@ dictToListMapChecks : CheckInfo -> Maybe (Error {}) dictToListMapChecks listMapCheckInfo = case secondArg listMapCheckInfo of Just listArgument -> - case AstHelpers.getSpecificFunctionCall ( [ "Dict" ], "toList" ) listMapCheckInfo.lookupTable listArgument of + case AstHelpers.getSpecificFnCall ( [ "Dict" ], "toList" ) listMapCheckInfo.lookupTable listArgument of Just dictToListCall -> let error : { toEntryAspectList : String, tuplePart : String } -> Error {} @@ -5634,7 +5309,7 @@ arrayToIndexedListToListMapChecks : CheckInfo -> Maybe (Error {}) arrayToIndexedListToListMapChecks listMapCheckInfo = case secondArg listMapCheckInfo of Just listArgument -> - case AstHelpers.getSpecificFunctionCall ( [ "Array" ], "toIndexedList" ) listMapCheckInfo.lookupTable listArgument of + case AstHelpers.getSpecificFnCall ( [ "Array" ], "toIndexedList" ) listMapCheckInfo.lookupTable listArgument of Just arrayToIndexedList -> if AstHelpers.isTupleSecondAccess listMapCheckInfo.lookupTable listMapCheckInfo.firstArg then let @@ -5667,13 +5342,12 @@ arrayToIndexedListToListMapChecks listMapCheckInfo = listMapCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listMapCompositionChecks checkInfo = +listMapCompositionChecks = firstThatConstructsJust - [ \() -> wrapToMapCompositionChecks listCollection checkInfo - , \() -> dictToListIntoListMapCompositionCheck checkInfo - , \() -> arrayToIndexedListMapCompositionCheck checkInfo + [ wrapToMapCompositionChecks listCollection + , dictToListIntoListMapCompositionCheck + , arrayToIndexedListMapCompositionCheck ] - () dictToListIntoListMapCompositionCheck : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix @@ -5730,86 +5404,87 @@ arrayToIndexedListMapCompositionCheck checkInfo = listMemberChecks : CheckInfo -> Maybe (Error {}) -listMemberChecks checkInfo = - case secondArg checkInfo of - Just listArg -> - let - needleArg : Node Expression - needleArg = - checkInfo.firstArg +listMemberChecks = + firstThatConstructsJust + [ callOnEmptyReturnsCheck + { resultAsString = \res -> qualifiedToString (qualify ( [ "Basics" ], "False" ) res) } + listCollection + , \checkInfo -> + case secondArg checkInfo of + Just listArg -> + let + needleArg : Node Expression + needleArg = + checkInfo.firstArg - needleRange : Range - needleRange = - Node.range needleArg - in - firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck - { resultAsString = \res -> qualifiedToString (qualify ( [ "Basics" ], "False" ) res) } - listCollection - checkInfo - , \() -> - if checkInfo.expectNaN then - Nothing + needleRange : Range + needleRange = + Node.range needleArg + in + firstThatConstructsJust + [ \() -> + if checkInfo.expectNaN then + Nothing - else - let - needleArgNormalized : Node Expression - needleArgNormalized = - Normalize.normalize checkInfo needleArg - - isNeedle : Node Expression -> Bool - isNeedle element = - Normalize.compareWithoutNormalization - (Normalize.normalize checkInfo element) - needleArgNormalized - == Normalize.ConfirmedEquality - in - if List.any isNeedle (listKnownElements checkInfo.lookupTable listArg) then - Just - (resultsInConstantError - (qualifiedToString checkInfo.fn ++ " on a list which contains the given element") - (\res -> qualifiedToString (qualify ( [ "Basics" ], "True" ) res)) - checkInfo - ) + else + let + needleArgNormalized : Node Expression + needleArgNormalized = + Normalize.normalize checkInfo needleArg + + isNeedle : Node Expression -> Bool + isNeedle element = + Normalize.compareWithoutNormalization + (Normalize.normalize checkInfo element) + needleArgNormalized + == Normalize.ConfirmedEquality + in + if List.any isNeedle (listKnownElements checkInfo.lookupTable listArg) then + Just + (resultsInConstantError + (qualifiedToString checkInfo.fn ++ " on a list which contains the given element") + (\res -> qualifiedToString (qualify ( [ "Basics" ], "True" ) res)) + checkInfo + ) - else - Nothing - , \() -> - case AstHelpers.getListSingleton checkInfo.lookupTable listArg of - Just single -> - let - elementRange : Range - elementRange = - Node.range single.element - in - Just - (Rule.errorWithFix - { message = qualifiedToString checkInfo.fn ++ " on an list with a single element is the same as directly checking for equality" - , details = [ "You can replace this call by checking whether the member to find and the list element are equal." ] - } - checkInfo.fnRange - (List.concat - [ keepOnlyFix - { parentRange = checkInfo.parentRange - , keep = Range.combine [ needleRange, elementRange ] + else + Nothing + , \() -> + case AstHelpers.getListSingleton checkInfo.lookupTable listArg of + Just single -> + let + elementRange : Range + elementRange = + Node.range single.element + in + Just + (Rule.errorWithFix + { message = qualifiedToString checkInfo.fn ++ " on an list with a single element is the same as directly checking for equality" + , details = [ "You can replace this call by checking whether the member to find and the list element are equal." ] } - , [ Fix.replaceRangeBy - (rangeBetweenExclusive ( needleRange, elementRange )) - " == " - ] - , parenthesizeIfNeededFix single.element - ] - ) - ) + checkInfo.fnRange + (List.concat + [ keepOnlyFix + { parentRange = checkInfo.parentRange + , keep = Range.combine [ needleRange, elementRange ] + } + , [ Fix.replaceRangeBy + (rangeBetweenExclusive ( needleRange, elementRange )) + " == " + ] + , parenthesizeIfNeededFix single.element + ] + ) + ) - Nothing -> - Nothing - ] - () + Nothing -> + Nothing + ] + () - Nothing -> - Nothing + Nothing -> + Nothing + ] listKnownElements : ModuleNameLookupTable -> Node Expression -> List (Node Expression) @@ -5836,78 +5511,72 @@ listKnownElements lookupTable expressionNode = listSumChecks : CheckInfo -> Maybe (Error {}) -listSumChecks checkInfo = +listSumChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsCheck { resultAsString = \_ -> "0" } listCollection checkInfo - , \() -> callOnWrapReturnsItsValue listCollection checkInfo + [ callOnEmptyReturnsCheck { resultAsString = \_ -> "0" } listCollection + , callOnWrapReturnsItsValueCheck listCollection ] - () sumCompositionChecks : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -sumCompositionChecks wrapper checkInfo = - onWrapAlwaysReturnsIncomingCompositionCheck { operationArgCount = 1 } wrapper checkInfo +sumCompositionChecks wrapper = + onWrapAlwaysReturnsIncomingCompositionCheck wrapper listProductChecks : CheckInfo -> Maybe (Error {}) -listProductChecks checkInfo = +listProductChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsCheck { resultAsString = \_ -> "1" } listCollection checkInfo - , \() -> callOnWrapReturnsItsValue listCollection checkInfo + [ callOnEmptyReturnsCheck { resultAsString = \_ -> "1" } listCollection + , callOnWrapReturnsItsValueCheck listCollection ] - () productCompositionChecks : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -productCompositionChecks wrapper checkInfo = - onWrapAlwaysReturnsIncomingCompositionCheck { operationArgCount = 1 } wrapper checkInfo +productCompositionChecks wrapper = + onWrapAlwaysReturnsIncomingCompositionCheck wrapper listMinimumChecks : CheckInfo -> Maybe (Error {}) -listMinimumChecks checkInfo = +listMinimumChecks = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } listCollection checkInfo - , \() -> callOnWrapReturnsJustItsValue listCollection checkInfo + [ callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } listCollection + , callOnWrapReturnsJustItsValue listCollection ] - () minimumCompositionChecks : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -minimumCompositionChecks wrapper checkInfo = - onWrapAlwaysReturnsJustIncomingCompositionCheck { operationArgCount = 1 } wrapper checkInfo +minimumCompositionChecks wrapper = + onWrapAlwaysReturnsJustIncomingCompositionCheck wrapper listMaximumChecks : CheckInfo -> Maybe (Error {}) -listMaximumChecks checkInfo = +listMaximumChecks = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } listCollection checkInfo - , \() -> callOnWrapReturnsJustItsValue listCollection checkInfo + [ callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } listCollection + , callOnWrapReturnsJustItsValue listCollection ] - () maximumCompositionChecks : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -maximumCompositionChecks wrapper checkInfo = - onWrapAlwaysReturnsJustIncomingCompositionCheck { operationArgCount = 1 } wrapper checkInfo +maximumCompositionChecks wrapper = + onWrapAlwaysReturnsJustIncomingCompositionCheck wrapper listFoldlChecks : CheckInfo -> Maybe (Error {}) -listFoldlChecks checkInfo = - listFoldAnyDirectionChecks checkInfo +listFoldlChecks = + listFoldAnyDirectionChecks listFoldrChecks : CheckInfo -> Maybe (Error {}) -listFoldrChecks checkInfo = - listFoldAnyDirectionChecks checkInfo +listFoldrChecks = + listFoldAnyDirectionChecks listFoldAnyDirectionChecks : CheckInfo -> Maybe (Error {}) -listFoldAnyDirectionChecks checkInfo = +listFoldAnyDirectionChecks = firstThatConstructsJust - [ \() -> emptiableFoldChecks listCollection checkInfo - , \() -> + [ emptiableFoldChecks listCollection + , \checkInfo -> case secondArg checkInfo of Nothing -> Nothing @@ -6024,7 +5693,7 @@ listFoldAnyDirectionChecks checkInfo = [ \() -> case maybeListArg of Just listArg -> - case AstHelpers.getSpecificFunctionCall ( [ "Set" ], "toList" ) checkInfo.lookupTable listArg of + case AstHelpers.getSpecificFnCall ( [ "Set" ], "toList" ) checkInfo.lookupTable listArg of Just setToListCall -> Just (Rule.errorWithFix @@ -6073,17 +5742,16 @@ listFoldAnyDirectionChecks checkInfo = ] () ] - () listFoldlCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listFoldlCompositionChecks checkInfo = - foldAndSetToListCompositionChecks checkInfo +listFoldlCompositionChecks = + foldAndSetToListCompositionChecks listFoldrCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listFoldrCompositionChecks checkInfo = - foldAndSetToListCompositionChecks checkInfo +listFoldrCompositionChecks = + foldAndSetToListCompositionChecks foldAndSetToListCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix @@ -6107,19 +5775,17 @@ foldAndSetToListCompositionChecks checkInfo = listAllChecks : CheckInfo -> Maybe (Error {}) -listAllChecks checkInfo = - emptiableAllChecks listCollection checkInfo +listAllChecks = + emptiableAllChecks listCollection -emptiableAllChecks : EmptiableProperties otherProperties -> CheckInfo -> Maybe (Error {}) -emptiableAllChecks emptiable checkInfo = +emptiableAllChecks : EmptiableProperties (TypeSubsetProperties empty) otherProperties -> CheckInfo -> Maybe (Error {}) +emptiableAllChecks emptiable = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck - { resultAsString = \res -> qualifiedToString (qualify ( [ "Basics" ], "True" ) res) } - emptiable - checkInfo - , \() -> + [ callOnEmptyReturnsCheck + { resultAsString = \res -> qualifiedToString (qualify ( [ "Basics" ], "True" ) res) } + emptiable + , \checkInfo -> case Evaluate.isAlwaysBoolean checkInfo checkInfo.firstArg of Determined True -> Just @@ -6132,14 +5798,13 @@ emptiableAllChecks emptiable checkInfo = _ -> Nothing ] - () listAnyChecks : CheckInfo -> Maybe (Error {}) -listAnyChecks checkInfo = +listAnyChecks = firstThatConstructsJust - [ \() -> emptiableAnyChecks listCollection checkInfo - , \() -> + [ emptiableAnyChecks listCollection + , \checkInfo -> case Evaluate.isEqualToSomethingFunction checkInfo.firstArg of Nothing -> Nothing @@ -6161,18 +5826,15 @@ listAnyChecks checkInfo = ) ) ] - () -emptiableAnyChecks : EmptiableProperties otherProperties -> CheckInfo -> Maybe (Error {}) -emptiableAnyChecks emptiable checkInfo = +emptiableAnyChecks : EmptiableProperties (TypeSubsetProperties empty) otherProperties -> CheckInfo -> Maybe (Error {}) +emptiableAnyChecks emptiable = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck - { resultAsString = \res -> qualifiedToString (qualify ( [ "Basics" ], "False" ) res) } - emptiable - checkInfo - , \() -> + [ callOnEmptyReturnsCheck + { resultAsString = \res -> qualifiedToString (qualify ( [ "Basics" ], "False" ) res) } + emptiable + , \checkInfo -> case Evaluate.isAlwaysBoolean checkInfo checkInfo.firstArg of Determined False -> Just @@ -6185,15 +5847,14 @@ emptiableAnyChecks emptiable checkInfo = _ -> Nothing ] - () listFilterMapChecks : CheckInfo -> Maybe (Error {}) -listFilterMapChecks checkInfo = +listFilterMapChecks = firstThatConstructsJust - [ \() -> callOnListWithIrrelevantEmptyElement maybeWithJustAsWrap checkInfo - , \() -> emptiableWrapperFilterMapChecks listCollection checkInfo - , \() -> + [ callOnListWithIrrelevantEmptyElement maybeWithJustAsWrap + , emptiableWrapperFilterMapChecks listCollection + , \checkInfo -> if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.firstArg then case secondArg checkInfo of Just listArg -> @@ -6201,7 +5862,7 @@ listFilterMapChecks checkInfo = Just list -> case traverse - (AstHelpers.getSpecificFunctionCall ( [ "Maybe" ], "Just" ) checkInfo.lookupTable) + (AstHelpers.getSpecificFnCall ( [ "Maybe" ], "Just" ) checkInfo.lookupTable) list of Just justCalls -> @@ -6230,19 +5891,18 @@ listFilterMapChecks checkInfo = else Nothing ] - () listFilterMapCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listFilterMapCompositionChecks checkInfo = - mapToOperationWithIdentityCanBeCombinedToOperationCompositionChecks { mapFn = ( [ "List" ], "map" ) } checkInfo +listFilterMapCompositionChecks = + mapToOperationWithIdentityCanBeCombinedToOperationCompositionChecks { mapFn = ( [ "List" ], "map" ) } -emptiableWrapperFilterMapChecks : EmptiableProperties (WrapperProperties { otherProperties | mapFn : ( ModuleName, String ) }) -> CheckInfo -> Maybe (Error {}) -emptiableWrapperFilterMapChecks emptiableWrapper checkInfo = +emptiableWrapperFilterMapChecks : TypeProperties (WrapperProperties (EmptiableProperties ConstantProperties { otherProperties | mapFn : ( ModuleName, String ) })) -> CheckInfo -> Maybe (Error {}) +emptiableWrapperFilterMapChecks emptiableWrapper = firstThatConstructsJust - [ \() -> - case constructs (sameInAllBranches (AstHelpers.getSpecificFunctionCall ( [ "Maybe" ], "Just" ) checkInfo.lookupTable)) checkInfo.lookupTable checkInfo.firstArg of + [ \checkInfo -> + case constructs (sameInAllBranches (AstHelpers.getSpecificFnCall ( [ "Maybe" ], "Just" ) checkInfo.lookupTable)) checkInfo.lookupTable checkInfo.firstArg of Determined justCalls -> Just (Rule.errorWithFix @@ -6258,8 +5918,8 @@ emptiableWrapperFilterMapChecks emptiableWrapper checkInfo = Undetermined -> Nothing - , \() -> - case AstHelpers.getSpecificValueOrFunction ( [ "Maybe" ], "Just" ) checkInfo.lookupTable checkInfo.firstArg of + , \checkInfo -> + case AstHelpers.getSpecificValueOrFn ( [ "Maybe" ], "Just" ) checkInfo.lookupTable checkInfo.firstArg of Just _ -> Just (alwaysReturnsLastArgError @@ -6270,8 +5930,8 @@ emptiableWrapperFilterMapChecks emptiableWrapper checkInfo = Nothing -> Nothing - , \() -> - case constructs (sameInAllBranches (AstHelpers.getSpecificValueOrFunction ( [ "Maybe" ], "Nothing" ) checkInfo.lookupTable)) checkInfo.lookupTable checkInfo.firstArg of + , \checkInfo -> + case constructs (sameInAllBranches (AstHelpers.getSpecificValueOrFn ( [ "Maybe" ], "Nothing" ) checkInfo.lookupTable)) checkInfo.lookupTable checkInfo.firstArg of Determined _ -> Just (alwaysResultsInUnparenthesizedConstantError @@ -6282,11 +5942,9 @@ emptiableWrapperFilterMapChecks emptiableWrapper checkInfo = Undetermined -> Nothing - , \() -> - mapToOperationWithIdentityCanBeCombinedToOperationChecks { mapFn = emptiableWrapper.mapFn } checkInfo - , \() -> callOnEmptyReturnsEmptyCheck emptiableWrapper checkInfo + , mapToOperationWithIdentityCanBeCombinedToOperationChecks { mapFn = emptiableWrapper.mapFn } + , unnecessaryCallOnEmptyCheck emptiableWrapper ] - () mapToOperationWithIdentityCanBeCombinedToOperationChecks : { mapFn : ( ModuleName, String ) } -> CheckInfo -> Maybe (Error {}) @@ -6294,7 +5952,7 @@ mapToOperationWithIdentityCanBeCombinedToOperationChecks config checkInfo = case secondArg checkInfo of Just mappableArg -> if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.firstArg then - case AstHelpers.getSpecificFunctionCall config.mapFn checkInfo.lookupTable mappableArg of + case AstHelpers.getSpecificFnCall config.mapFn checkInfo.lookupTable mappableArg of Just mapCall -> Just (Rule.errorWithFix @@ -6366,7 +6024,7 @@ callFromCanBeCombinedCheck : -> CheckInfo -> Maybe (Error {}) callFromCanBeCombinedCheck config checkInfo = - case AstHelpers.getSpecificFunctionCall config.fromFn checkInfo.lookupTable checkInfo.firstArg of + case AstHelpers.getSpecificFnCall config.fromFn checkInfo.lookupTable checkInfo.firstArg of Just fromFnCall -> Just (Rule.errorWithFix @@ -6442,84 +6100,75 @@ listRangeChecks checkInfo = listRepeatChecks : CheckInfo -> Maybe (Error {}) -listRepeatChecks checkInfo = +listRepeatChecks = firstThatConstructsJust - [ \() -> emptiableRepeatChecks listCollection checkInfo - , \() -> wrapperRepeatChecks listCollection checkInfo + [ emptiableRepeatChecks listCollection + , wrapperRepeatChecks listCollection ] - () arrayToListChecks : CheckInfo -> Maybe (Error {}) -arrayToListChecks checkInfo = +arrayToListChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } arrayCollection checkInfo - , \() -> onCallToInverseReturnsItsArgumentCheck ( [ "Array" ], "fromList" ) checkInfo - , \() -> - callFromCanBeCombinedCheck - { fromFn = ( [ "Array" ], "repeat" ), combinedFn = ( [ "List" ], "repeat" ) } - checkInfo + [ callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } arrayCollection + , onCallToInverseReturnsItsArgumentCheck ( [ "Array" ], "fromList" ) + , callFromCanBeCombinedCheck + { fromFn = ( [ "Array" ], "repeat" ), combinedFn = ( [ "List" ], "repeat" ) } ] - () arrayToListCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -arrayToListCompositionChecks checkInfo = +arrayToListCompositionChecks = firstThatConstructsJust - [ \() -> inversesCompositionCheck ( [ "Array" ], "fromList" ) checkInfo - , \() -> - compositionFromCanBeCombinedCheck - { fromFn = ( [ "Array" ], "repeat" ), combinedFn = ( [ "List" ], "repeat" ) } - checkInfo + [ inversesCompositionCheck ( [ "Array" ], "fromList" ) + , compositionFromCanBeCombinedCheck + { fromFn = ( [ "Array" ], "repeat" ), combinedFn = ( [ "List" ], "repeat" ) } ] - () arrayToIndexedListChecks : CheckInfo -> Maybe (Error {}) -arrayToIndexedListChecks checkInfo = - callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } arrayCollection checkInfo +arrayToIndexedListChecks = + callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } arrayCollection arrayFromListChecks : CheckInfo -> Maybe (Error {}) -arrayFromListChecks checkInfo = +arrayFromListChecks = firstThatConstructsJust - [ \() -> collectionFromListChecks arrayCollection checkInfo - , \() -> onCallToInverseReturnsItsArgumentCheck ( [ "Array" ], "toList" ) checkInfo + [ collectionFromListChecks arrayCollection + , onCallToInverseReturnsItsArgumentCheck ( [ "Array" ], "toList" ) ] - () arrayFromListCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -arrayFromListCompositionChecks checkInfo = - inversesCompositionCheck ( [ "Array" ], "toList" ) checkInfo +arrayFromListCompositionChecks = + inversesCompositionCheck ( [ "Array" ], "toList" ) arrayRepeatChecks : CheckInfo -> Maybe (Error {}) -arrayRepeatChecks checkInfo = - emptiableRepeatChecks arrayCollection checkInfo +arrayRepeatChecks = + emptiableRepeatChecks arrayCollection arrayInitializeChecks : CheckInfo -> Maybe (Error {}) -arrayInitializeChecks checkInfo = - emptiableRepeatChecks arrayCollection checkInfo +arrayInitializeChecks = + emptiableRepeatChecks arrayCollection arrayIndexedMapChecks : CheckInfo -> Maybe (Error {}) -arrayIndexedMapChecks checkInfo = +arrayIndexedMapChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck arrayCollection checkInfo - , \() -> operationWithExtraArgChecks { operationWithoutExtraArg = ( [ "Array" ], "map" ) } checkInfo + [ unnecessaryCallOnEmptyCheck arrayCollection + , operationWithExtraArgChecks { operationWithoutExtraArg = ( [ "Array" ], "map" ) } ] - () -emptiableRepeatChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) +emptiableRepeatChecks : CollectionProperties (EmptiableProperties ConstantProperties otherProperties) -> CheckInfo -> Maybe (Error {}) emptiableRepeatChecks collection checkInfo = case Evaluate.getInt checkInfo checkInfo.firstArg of Just intValue -> callWithNonPositiveIntCanBeReplacedByCheck { int = intValue - , intDescription = collection.nameForSize + , intDescription = collection.size.description , replacement = collection.empty.asString } checkInfo @@ -6534,7 +6183,7 @@ wrapperRepeatChecks wrapper checkInfo = Just 1 -> Just (Rule.errorWithFix - { message = qualifiedToString checkInfo.fn ++ " with " ++ wrapper.nameForSize ++ " 1 will result in " ++ qualifiedToString wrapper.wrap.fn + { message = qualifiedToString checkInfo.fn ++ " with " ++ wrapper.size.description ++ " 1 will result in " ++ qualifiedToString wrapper.wrap.fn , details = [ "You can replace this call by " ++ qualifiedToString wrapper.wrap.fn ++ "." ] } checkInfo.fnRange @@ -6552,12 +6201,11 @@ wrapperRepeatChecks wrapper checkInfo = arrayLengthChecks : CheckInfo -> Maybe (Error {}) -arrayLengthChecks checkInfo = +arrayLengthChecks = firstThatConstructsJust - [ \() -> collectionSizeChecks arrayCollection checkInfo - , \() -> arrayLengthOnArrayRepeatOrInitializeChecks checkInfo + [ collectionSizeChecks arrayCollection + , arrayLengthOnArrayRepeatOrInitializeChecks ] - () arrayLengthOnArrayRepeatOrInitializeChecks : CheckInfo -> Maybe (Error {}) @@ -6567,10 +6215,10 @@ arrayLengthOnArrayRepeatOrInitializeChecks checkInfo = maybeCall = firstThatConstructsJust [ \() -> - AstHelpers.getSpecificFunctionCall ( [ "Array" ], "repeat" ) checkInfo.lookupTable checkInfo.firstArg + AstHelpers.getSpecificFnCall ( [ "Array" ], "repeat" ) checkInfo.lookupTable checkInfo.firstArg |> Maybe.map (Tuple.pair "repeat") , \() -> - AstHelpers.getSpecificFunctionCall ( [ "Array" ], "initialize" ) checkInfo.lookupTable checkInfo.firstArg + AstHelpers.getSpecificFnCall ( [ "Array" ], "initialize" ) checkInfo.lookupTable checkInfo.firstArg |> Maybe.map (Tuple.pair "initialize") ] () @@ -6598,30 +6246,26 @@ arrayLengthOnArrayRepeatOrInitializeChecks checkInfo = arrayFoldlChecks : CheckInfo -> Maybe (Error {}) -arrayFoldlChecks checkInfo = - emptiableFoldChecks arrayCollection checkInfo +arrayFoldlChecks = + emptiableFoldChecks arrayCollection arrayFoldrChecks : CheckInfo -> Maybe (Error {}) -arrayFoldrChecks checkInfo = - emptiableFoldChecks arrayCollection checkInfo +arrayFoldrChecks = + emptiableFoldChecks arrayCollection -getChecks : EmptiableProperties (IndexableProperties otherProperties) -> CheckInfo -> Maybe (Error {}) -getChecks collection checkInfo = +getChecks : TypeProperties (IndexableProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties)) -> CheckInfo -> Maybe (Error {}) +getChecks collection = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } - collection - checkInfo - , \() -> + [ callOnEmptyReturnsCheck { resultAsString = maybeWithJustAsWrap.empty.asString } collection + , \checkInfo -> Evaluate.getInt checkInfo checkInfo.firstArg |> Maybe.andThen (indexAccessChecks collection checkInfo) ] - () -indexAccessChecks : IndexableProperties otherProperties -> CheckInfo -> Int -> Maybe (Error {}) +indexAccessChecks : TypeProperties (IndexableProperties otherProperties) -> CheckInfo -> Int -> Maybe (Error {}) indexAccessChecks collection checkInfo n = if n < 0 then Just @@ -6667,11 +6311,11 @@ indexAccessChecks collection checkInfo n = Nothing -setChecks : CollectionProperties (FromListProperties (IndexableProperties {})) -> CheckInfo -> Maybe (Error {}) -setChecks collection checkInfo = +setChecks : TypeProperties (CollectionProperties (IndexableProperties (FromListProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties)))) -> CheckInfo -> Maybe (Error {}) +setChecks collection = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck collection checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck collection + , \checkInfo -> case Evaluate.getInt checkInfo checkInfo.firstArg of Just n -> if n < 0 then @@ -6693,11 +6337,10 @@ setChecks collection checkInfo = Nothing -> Nothing ] - () setOnKnownElementChecks : - CollectionProperties (FromListProperties (IndexableProperties {})) + TypeProperties (CollectionProperties (FromListProperties (IndexableProperties otherProperties))) -> CheckInfo -> Int -> Range @@ -6711,174 +6354,76 @@ setOnKnownElementChecks collection checkInfo n replacementArgRange = Just element -> Just (Rule.errorWithFix - { message = qualifiedToString checkInfo.fn ++ " will replace a known element in the " ++ collection.fromListLiteralDescription - , details = [ "You can move the replacement argument directly into the " ++ collection.fromListLiteralDescription ++ "." ] + { message = qualifiedToString checkInfo.fn ++ " will replace a known element in the " ++ collection.fromListLiteral.description + , details = [ "You can move the replacement argument directly into the " ++ collection.fromListLiteral.description ++ "." ] } checkInfo.fnRange (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range collectionArg } ++ [ Fix.replaceRangeBy (Node.range element) (checkInfo.extractSourceCode replacementArgRange) ] ) - ) - - Nothing -> - Just - (Rule.errorWithFix - { message = qualifiedToString checkInfo.fn ++ " with an index out of bounds of the given " ++ collection.represents ++ " will always return the same given " ++ collection.represents - , details = [ "You can replace this call by the given " ++ collection.represents ++ "." ] - } - checkInfo.fnRange - (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range collectionArg }) - ) - - Nothing -> - Nothing - - Nothing -> - Nothing - - -emptiableReverseChecks : EmptiableProperties otherProperties -> CheckInfo -> Maybe (Error {}) -emptiableReverseChecks emptiable checkInfo = - firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck emptiable checkInfo - , \() -> removeAlongWithOtherFunctionCheck checkInfo - ] - () - - -listReverseChecks : CheckInfo -> Maybe (Error {}) -listReverseChecks checkInfo = - firstThatConstructsJust - [ \() -> emptiableReverseChecks listCollection checkInfo - , \() -> callOnWrappedDoesNotChangeItCheck listCollection checkInfo - ] - () - - -listReverseCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listReverseCompositionChecks checkInfo = - firstThatConstructsJust - [ \() -> compositionAfterWrapIsUnnecessaryCheck listCollection checkInfo - , \() -> toggleCompositionChecks checkInfo - ] - () - - -listSortChecks : CheckInfo -> Maybe (Error {}) -listSortChecks checkInfo = - firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck listCollection checkInfo - , \() -> callOnWrappedDoesNotChangeItCheck listCollection checkInfo - , \() -> operationDoesNotChangeResultOfOperationCheck checkInfo - ] - () - - -listSortCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listSortCompositionChecks checkInfo = - operationDoesNotChangeResultOfOperationCompositionCheck { argCount = 1 } checkInfo - - -{-| Condense applying the same function with equal arguments (except the last one) twice in sequence into one. -This applies to functions that are equivalent to identity when operating on the result another such function. - -Examples of such functions: - - - one argument: `Simplify.expectNaN`, `Review.Rule.providesFixesForModuleRule`, `List.sort`, `List.Extra.unique`, [`AVL.Set.clear`](https://package.elm-lang.org/packages/owanturist/elm-avl-dict/2.1.0/AVL-Set#clear) - - two arguments: `List.filter f`, `List.Extra.filterNot f`, `List.Extra.takeWhile/dropWhile(Right) f`, `List.sortBy f`, `List.Extra.uniqueBy f` - - three arguments: `Array.set i new`, `Array.Extra.resizelRepeat l pad`, `List.Extra.setAt i new` - -Note that `update` or `setWhere` operations for example _can_ have an effect even after the same operation has already been applied. - -For operations that toggle between 2 states, like `reverse` or `List.Extra.swapAt i j`, use `removeAlongWithOtherFunctionCheck` - --} -operationDoesNotChangeResultOfOperationCheck : CheckInfo -> Maybe (Error {}) -operationDoesNotChangeResultOfOperationCheck checkInfo = - case Maybe.andThen (AstHelpers.getSpecificFunctionCall checkInfo.fn checkInfo.lookupTable) (fullyAppliedLastArg checkInfo) of - Just lastArgCall -> - let - areAllArgsEqual : Bool - areAllArgsEqual = - List.all - (\( arg, lastArgCallArg ) -> - Normalize.compare checkInfo arg lastArgCallArg == Normalize.ConfirmedEquality - ) - (List.map2 Tuple.pair - (listFilledInit ( checkInfo.firstArg, checkInfo.argsAfterFirst )) - (listFilledInit ( lastArgCall.firstArg, lastArgCall.argsAfterFirst )) - ) - in - if areAllArgsEqual then - Just - (Rule.errorWithFix - { message = - case checkInfo.argCount of - 1 -> - "Unnecessary " ++ qualifiedToString checkInfo.fn ++ " after " ++ qualifiedToString checkInfo.fn + ) - _ -> - "Unnecessary " ++ qualifiedToString checkInfo.fn ++ " after equivalent " ++ qualifiedToString checkInfo.fn - , details = [ "You can remove this additional operation." ] - } - checkInfo.fnRange - (keepOnlyFix { parentRange = checkInfo.parentRange, keep = lastArgCall.nodeRange }) - ) + Nothing -> + Just + (Rule.errorWithFix + { message = qualifiedToString checkInfo.fn ++ " with an index out of bounds of the given " ++ collection.represents ++ " will always return the same given " ++ collection.represents + , details = [ "You can replace this call by the given " ++ collection.represents ++ "." ] + } + checkInfo.fnRange + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range collectionArg }) + ) - else - Nothing + Nothing -> + Nothing Nothing -> Nothing -operationDoesNotChangeResultOfOperationCompositionCheck : { argCount : Int } -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -operationDoesNotChangeResultOfOperationCompositionCheck config checkInfo = - let - areAllArgsEqual : () -> Bool - areAllArgsEqual () = - List.all - (\( arg, earlierArg ) -> - Normalize.compare checkInfo arg earlierArg == Normalize.ConfirmedEquality - ) - (List.map2 Tuple.pair checkInfo.later.args checkInfo.earlier.args) - in - if (List.length checkInfo.later.args == (config.argCount - 1)) && (checkInfo.earlier.fn == checkInfo.later.fn) == areAllArgsEqual () then - Just - { info = - { message = - case config.argCount of - 1 -> - "Unnecessary " ++ qualifiedToString checkInfo.later.fn ++ " after " ++ qualifiedToString checkInfo.earlier.fn +emptiableReverseChecks : EmptiableProperties (TypeSubsetProperties empty) otherProperties -> CheckInfo -> Maybe (Error {}) +emptiableReverseChecks emptiable = + firstThatConstructsJust + [ unnecessaryCallOnEmptyCheck emptiable + , toggleCallChecks + ] - _ -> - "Unnecessary " ++ qualifiedToString checkInfo.later.fn ++ " after equivalent " ++ qualifiedToString checkInfo.earlier.fn - , details = [ "You can remove this additional operation." ] - } - , fix = [ Fix.removeRange checkInfo.later.removeRange ] - } - else - Nothing +listReverseChecks : CheckInfo -> Maybe (Error {}) +listReverseChecks = + firstThatConstructsJust + [ emptiableReverseChecks listCollection + , unnecessaryCallOnWrappedCheck listCollection + ] -{-| The last argument of a fully applied function (the given `argCount` specifies what is considered "fully applied"). +listReverseCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix +listReverseCompositionChecks = + firstThatConstructsJust + [ unnecessaryCompositionAfterWrapCheck listCollection + , toggleCompositionChecks + ] -For example, `fullyAppliedLastArg` on `Array.set 3 "Hitagi"` would return `Nothing` -while `fullyAppliedLastArg` on `Array.set 3 "Hitagi" arr` would return `Just arr`. --} -fullyAppliedLastArg : { callInfo | firstArg : Node Expression, argsAfterFirst : List (Node Expression), argCount : Int } -> Maybe (Node Expression) -fullyAppliedLastArg callInfo = - List.drop (callInfo.argCount - 1) (callInfo.firstArg :: callInfo.argsAfterFirst) |> List.head +listSortChecks : CheckInfo -> Maybe (Error {}) +listSortChecks = + firstThatConstructsJust + [ unnecessaryCallOnEmptyCheck listCollection + , unnecessaryCallOnWrappedCheck listCollection + , operationDoesNotChangeResultOfOperationCheck + ] + + +listSortCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix +listSortCompositionChecks = + operationDoesNotChangeResultOfOperationCompositionCheck listSortByChecks : CheckInfo -> Maybe (Error {}) -listSortByChecks checkInfo = +listSortByChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck listCollection checkInfo - , \() -> callOnWrappedDoesNotChangeItCheck listCollection checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck listCollection + , unnecessaryCallOnWrappedCheck listCollection + , \checkInfo -> case AstHelpers.getAlwaysResult checkInfo.lookupTable checkInfo.firstArg of Just _ -> Just @@ -6890,24 +6435,22 @@ listSortByChecks checkInfo = Nothing -> Nothing - , \() -> - operationWithIdentityCanBeReplacedChecks { replacementFn = ( [ "List" ], "sort" ) } checkInfo - , \() -> operationDoesNotChangeResultOfOperationCheck checkInfo + , operationWithIdentityCanBeReplacedChecks { replacementFn = ( [ "List" ], "sort" ) } + , operationDoesNotChangeResultOfOperationCheck ] - () listSortByCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -listSortByCompositionChecks checkInfo = - operationDoesNotChangeResultOfOperationCompositionCheck { argCount = 2 } checkInfo +listSortByCompositionChecks = + operationDoesNotChangeResultOfOperationCompositionCheck listSortWithChecks : CheckInfo -> Maybe (Error {}) -listSortWithChecks checkInfo = +listSortWithChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck listCollection checkInfo - , \() -> callOnWrappedDoesNotChangeItCheck listCollection checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck listCollection + , unnecessaryCallOnWrappedCheck listCollection + , \checkInfo -> let alwaysAlwaysOrder : Maybe Order alwaysAlwaysOrder = @@ -6950,13 +6493,12 @@ listSortWithChecks checkInfo = Nothing -> Nothing ] - () listTakeChecks : CheckInfo -> Maybe (Error {}) -listTakeChecks checkInfo = +listTakeChecks = firstThatConstructsJust - [ \() -> + [ \checkInfo -> case Evaluate.getInt checkInfo checkInfo.firstArg of Just length -> callWithNonPositiveIntCanBeReplacedByCheck @@ -6968,15 +6510,14 @@ listTakeChecks checkInfo = Nothing -> Nothing - , \() -> callOnEmptyReturnsEmptyCheck listCollection checkInfo + , unnecessaryCallOnEmptyCheck listCollection ] - () listDropChecks : CheckInfo -> Maybe (Error {}) -listDropChecks checkInfo = +listDropChecks = firstThatConstructsJust - [ \() -> + [ \checkInfo -> case Evaluate.getInt checkInfo checkInfo.firstArg of Just 0 -> Just @@ -6988,12 +6529,11 @@ listDropChecks checkInfo = _ -> Nothing - , \() -> callOnEmptyReturnsEmptyCheck listCollection checkInfo + , unnecessaryCallOnEmptyCheck listCollection ] - () -emptiableMapNChecks : EmptiableProperties otherProperties -> CheckInfo -> Maybe (Error {}) +emptiableMapNChecks : TypeProperties (EmptiableProperties ConstantProperties otherProperties) -> CheckInfo -> Maybe (Error {}) emptiableMapNChecks emptiable checkInfo = if List.any (emptiable.empty.is checkInfo.lookupTable) checkInfo.argsAfterFirst then Just @@ -7021,7 +6561,7 @@ For example given `resultWithOkAsWrap`: This is pretty similar to `wrapperSequenceChecks` where we look at arguments instead of list elements. -} -wrapperMapNChecks : WrapperProperties otherProperties -> CheckInfo -> Maybe (Error {}) +wrapperMapNChecks : TypeProperties (WrapperProperties otherProperties) -> CheckInfo -> Maybe (Error {}) wrapperMapNChecks wrapper checkInfo = if List.length checkInfo.argsAfterFirst == (checkInfo.argCount - 1) then -- fully applied @@ -7098,14 +6638,7 @@ This is pretty similar to `sequenceOrFirstEmptyChecks` where we look at argument -} mapNOrFirstEmptyConstructionChecks : - WrapperProperties - { otherProperties - | empty : - { empty - | is : ModuleNameLookupTable -> Node Expression -> Bool - , description : Description - } - } + WrapperProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) -> CheckInfo -> Maybe (Error {}) mapNOrFirstEmptyConstructionChecks emptiable checkInfo = @@ -7252,37 +6785,35 @@ mapNOrFirstEmptyConstructionChecks emptiable checkInfo = listUnzipChecks : CheckInfo -> Maybe (Error {}) -listUnzipChecks checkInfo = - callOnEmptyReturnsCheck { resultAsString = \_ -> "( [], [] )" } listCollection checkInfo +listUnzipChecks = + callOnEmptyReturnsCheck { resultAsString = \_ -> "( [], [] )" } listCollection setFromListChecks : CheckInfo -> Maybe (Error {}) -setFromListChecks checkInfo = +setFromListChecks = firstThatConstructsJust - [ \() -> collectionFromListChecks setCollection checkInfo - , \() -> wrapperFromListSingletonChecks setCollection checkInfo - , \() -> onCallToInverseReturnsItsArgumentCheck ( [ "Set" ], "toList" ) checkInfo + [ collectionFromListChecks setCollection + , wrapperFromListSingletonChecks setCollection + , onCallToInverseReturnsItsArgumentCheck ( [ "Set" ], "toList" ) ] - () setFromListCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -setFromListCompositionChecks checkInfo = +setFromListCompositionChecks = firstThatConstructsJust - [ \() -> wrapperFromListSingletonCompositionChecks setCollection checkInfo - , \() -> inversesCompositionCheck ( [ "Set" ], "toList" ) checkInfo + [ wrapperFromListSingletonCompositionChecks setCollection + , inversesCompositionCheck ( [ "Set" ], "toList" ) ] - () setFoldlChecks : CheckInfo -> Maybe (Error {}) -setFoldlChecks checkInfo = - emptiableFoldChecks setCollection checkInfo +setFoldlChecks = + emptiableFoldChecks setCollection setFoldrChecks : CheckInfo -> Maybe (Error {}) -setFoldrChecks checkInfo = - emptiableFoldChecks setCollection checkInfo +setFoldrChecks = + emptiableFoldChecks setCollection {-| The folding/reducing check @@ -7316,20 +6847,13 @@ Any other argument order is not supported: -} emptiableFoldChecks : - TypeProperties - { otherProperties - | empty : - { empty - | is : ModuleNameLookupTable -> Node Expression -> Bool - , description : Description - } - } + TypeProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) -> CheckInfo -> Maybe (Error {}) -emptiableFoldChecks emptiable checkInfo = +emptiableFoldChecks emptiable = firstThatConstructsJust - [ \() -> foldToUnchangedAccumulatorCheck emptiable checkInfo - , \() -> + [ foldToUnchangedAccumulatorCheck emptiable + , \checkInfo -> case checkInfo.argsAfterFirst of initialArg :: emptiableArg :: [] -> if emptiable.empty.is checkInfo.lookupTable emptiableArg then @@ -7348,7 +6872,6 @@ emptiableFoldChecks emptiable checkInfo = _ -> Nothing ] - () foldToUnchangedAccumulatorCheck : TypeProperties otherProperties -> CheckInfo -> Maybe (Error {}) @@ -7403,35 +6926,33 @@ foldToUnchangedAccumulatorCheck typeProperties checkInfo = dictFromListChecks : CheckInfo -> Maybe (Error {}) -dictFromListChecks checkInfo = +dictFromListChecks = firstThatConstructsJust - [ \() -> collectionFromListChecks dictCollection checkInfo - , \() -> onCallToInverseReturnsItsArgumentCheck ( [ "Dict" ], "toList" ) checkInfo + [ collectionFromListChecks dictCollection + , onCallToInverseReturnsItsArgumentCheck ( [ "Dict" ], "toList" ) ] - () dictFromListCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -dictFromListCompositionChecks checkInfo = - inversesCompositionCheck ( [ "Dict" ], "toList" ) checkInfo +dictFromListCompositionChecks = + inversesCompositionCheck ( [ "Dict" ], "toList" ) subAndCmdBatchChecks : - EmptiableProperties otherProperties + EmptiableProperties ConstantProperties otherProperties -> CheckInfo -> Maybe (Error {}) -subAndCmdBatchChecks batchable checkInfo = +subAndCmdBatchChecks batchable = firstThatConstructsJust - [ \() -> callOnEmptyReturnsCheck { resultAsString = batchable.empty.asString } listCollection checkInfo - , \() -> callOnWrapReturnsItsValue listCollection checkInfo - , \() -> callOnListWithIrrelevantEmptyElement batchable checkInfo + [ callOnEmptyReturnsCheck { resultAsString = batchable.empty.asString } listCollection + , callOnWrapReturnsItsValueCheck listCollection + , callOnListWithIrrelevantEmptyElement batchable ] - () batchCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -batchCompositionChecks checkInfo = - onWrapAlwaysReturnsIncomingCompositionCheck { operationArgCount = 1 } listCollection checkInfo +batchCompositionChecks = + onWrapAlwaysReturnsIncomingCompositionCheck listCollection @@ -7439,83 +6960,70 @@ batchCompositionChecks checkInfo = taskMapChecks : CheckInfo -> Maybe (Error {}) -taskMapChecks checkInfo = +taskMapChecks = firstThatConstructsJust - [ \() -> emptiableMapChecks taskWithSucceedAsWrap checkInfo - , \() -> mapWrapChecks taskWithSucceedAsWrap checkInfo + [ emptiableMapChecks taskWithSucceedAsWrap + , mapWrapChecks taskWithSucceedAsWrap ] - () taskMapCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -taskMapCompositionChecks checkInfo = - wrapToMapCompositionChecks taskWithSucceedAsWrap checkInfo +taskMapCompositionChecks = + wrapToMapCompositionChecks taskWithSucceedAsWrap taskMapNChecks : CheckInfo -> Maybe (Error {}) -taskMapNChecks checkInfo = +taskMapNChecks = firstThatConstructsJust - [ \() -> wrapperMapNChecks taskWithSucceedAsWrap checkInfo - , \() -> mapNOrFirstEmptyConstructionChecks taskWithSucceedAsWrap checkInfo + [ wrapperMapNChecks taskWithSucceedAsWrap + , mapNOrFirstEmptyConstructionChecks taskWithSucceedAsWrap ] - () taskAndThenChecks : CheckInfo -> Maybe (Error {}) -taskAndThenChecks checkInfo = +taskAndThenChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck taskWithSucceedAsWrap checkInfo - , \() -> wrapperAndThenChecks taskWithSucceedAsWrap checkInfo + [ unnecessaryCallOnEmptyCheck taskWithSucceedAsWrap + , wrapperAndThenChecks taskWithSucceedAsWrap ] - () taskMapErrorChecks : CheckInfo -> Maybe (Error {}) -taskMapErrorChecks checkInfo = +taskMapErrorChecks = firstThatConstructsJust - [ \() -> emptiableMapChecks taskWithFailAsWrap checkInfo - , \() -> mapWrapChecks taskWithFailAsWrap checkInfo + [ emptiableMapChecks taskWithFailAsWrap + , mapWrapChecks taskWithFailAsWrap ] - () taskMapErrorCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -taskMapErrorCompositionChecks checkInfo = - wrapToMapCompositionChecks taskWithFailAsWrap checkInfo +taskMapErrorCompositionChecks = + wrapToMapCompositionChecks taskWithFailAsWrap taskOnErrorChecks : CheckInfo -> Maybe (Error {}) -taskOnErrorChecks checkInfo = +taskOnErrorChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck taskWithFailAsWrap checkInfo - , \() -> wrapperAndThenChecks taskWithFailAsWrap checkInfo + [ unnecessaryCallOnEmptyCheck taskWithFailAsWrap + , wrapperAndThenChecks taskWithFailAsWrap ] - () taskSequenceChecks : CheckInfo -> Maybe (Error {}) -taskSequenceChecks checkInfo = +taskSequenceChecks = firstThatConstructsJust - [ \() -> wrapperSequenceChecks taskWithSucceedAsWrap checkInfo - , \() -> sequenceOrFirstEmptyChecks taskWithSucceedAsWrap checkInfo + [ wrapperSequenceChecks taskWithSucceedAsWrap + , sequenceOrFirstEmptyChecks taskWithSucceedAsWrap ] - () taskSequenceCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -taskSequenceCompositionChecks checkInfo = - mappableSequenceCompositionChecks taskWithSucceedAsWrap checkInfo +taskSequenceCompositionChecks = + mappableSequenceCompositionChecks taskWithSucceedAsWrap sequenceOrFirstEmptyChecks : - WrapperProperties - { otherProperties - | empty : - { empty - | is : ModuleNameLookupTable -> Node Expression -> Bool - , description : Description - } - } + WrapperProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) -> CheckInfo -> Maybe (Error {}) sequenceOrFirstEmptyChecks emptiable checkInfo = @@ -7571,16 +7079,14 @@ sequenceOrFirstEmptyChecks emptiable checkInfo = wrapperSequenceChecks : WrapperProperties { otherProperties | mapFn : ( ModuleName, String ) } -> CheckInfo -> Maybe (Error {}) -wrapperSequenceChecks wrapper checkInfo = +wrapperSequenceChecks wrapper = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck - { resultAsString = - \res -> qualifiedToString (qualify wrapper.wrap.fn res) ++ " []" - } - listCollection - checkInfo - , \() -> + [ callOnEmptyReturnsCheck + { resultAsString = + \res -> qualifiedToString (qualify wrapper.wrap.fn res) ++ " []" + } + listCollection + , \checkInfo -> case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of Just singletonList -> let @@ -7604,7 +7110,7 @@ wrapperSequenceChecks wrapper checkInfo = Nothing -> Nothing - , \() -> + , \checkInfo -> case AstHelpers.getListLiteral checkInfo.firstArg of Just list -> case traverse (getValueWithNodeRange (wrapper.wrap.getValue checkInfo.lookupTable)) list of @@ -7630,7 +7136,6 @@ wrapperSequenceChecks wrapper checkInfo = Nothing -> Nothing ] - () mappableSequenceCompositionChecks : TypeProperties { otherProperties | mapFn : ( ModuleName, String ) } -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix @@ -7660,36 +7165,33 @@ mappableSequenceCompositionChecks mappable checkInfo = -- HTML.ATTRIBUTES -htmlAttributesClassListChecks : CheckInfo -> Maybe (Error {}) -htmlAttributesClassListChecks checkInfo = - let - listArg : Node Expression - listArg = - checkInfo.firstArg - - getTupleWithSpecificSecond : Bool -> Node Expression -> Maybe { range : Range, first : Node Expression } - getTupleWithSpecificSecond specificBool expressionNode = - case AstHelpers.getTuple2Literal expressionNode of - Just tuple -> - case AstHelpers.getSpecificBool specificBool checkInfo.lookupTable tuple.second of - Just _ -> - Just { range = tuple.range, first = tuple.first } - - Nothing -> - Nothing +getTupleWithSpecificSecond : Bool -> Node Expression -> ModuleNameLookupTable -> Maybe { range : Range, first : Node Expression } +getTupleWithSpecificSecond specificBool expressionNode lookupTable = + case AstHelpers.getTuple2Literal expressionNode of + Just tuple -> + case AstHelpers.getSpecificBool specificBool lookupTable tuple.second of + Just _ -> + Just { range = tuple.range, first = tuple.first } Nothing -> Nothing - htmlAttributesClassListFalseElementError : { message : String, details : List String } - htmlAttributesClassListFalseElementError = - { message = "In a " ++ qualifiedToString checkInfo.fn ++ ", a tuple paired with False can be removed" - , details = [ "You can remove the tuple list element where the second part is False." ] - } - in + Nothing -> + Nothing + + +htmlAttributesClassListFalseElementError : CheckInfo -> { message : String, details : List String } +htmlAttributesClassListFalseElementError checkInfo = + { message = "In a " ++ qualifiedToString checkInfo.fn ++ ", a tuple paired with False can be removed" + , details = [ "You can remove the tuple list element where the second part is False." ] + } + + +htmlAttributesClassListChecks : CheckInfo -> Maybe (Error {}) +htmlAttributesClassListChecks = firstThatConstructsJust - [ \() -> - case AstHelpers.getListSingleton checkInfo.lookupTable listArg of + [ \checkInfo -> + case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of Just single -> case AstHelpers.getTuple2Literal single.element of Just tuple -> @@ -7707,7 +7209,7 @@ htmlAttributesClassListChecks checkInfo = , details = [ "You can replace this call by " ++ qualifiedToString replacementFn ++ " with the String from the single tuple list element." ] } checkInfo.fnRange - (replaceBySubExpressionFix (Node.range listArg) tuple.first + (replaceBySubExpressionFix (Node.range checkInfo.firstArg) tuple.first ++ [ Fix.replaceRangeBy checkInfo.fnRange (qualifiedToString (qualify replacementFn checkInfo)) ] @@ -7716,9 +7218,9 @@ htmlAttributesClassListChecks checkInfo = else Just - (Rule.errorWithFix htmlAttributesClassListFalseElementError + (Rule.errorWithFix (htmlAttributesClassListFalseElementError checkInfo) checkInfo.fnRange - [ Fix.replaceRangeBy (Node.range listArg) "[]" ] + [ Fix.replaceRangeBy (Node.range checkInfo.firstArg) "[]" ] ) Nothing -> @@ -7729,13 +7231,13 @@ htmlAttributesClassListChecks checkInfo = Nothing -> Nothing - , \() -> - case AstHelpers.getListLiteral listArg of + , \checkInfo -> + case AstHelpers.getListLiteral checkInfo.firstArg of Just (tuple0 :: tuple1 :: tuple2Up) -> - case findMapNeighboring (getTupleWithSpecificSecond False) (tuple0 :: tuple1 :: tuple2Up) of + case findMapNeighboring (\el -> getTupleWithSpecificSecond False el checkInfo.lookupTable) (tuple0 :: tuple1 :: tuple2Up) of Just classPart -> Just - (Rule.errorWithFix htmlAttributesClassListFalseElementError + (Rule.errorWithFix (htmlAttributesClassListFalseElementError checkInfo) checkInfo.fnRange (listLiteralElementRemoveFix classPart) ) @@ -7745,13 +7247,13 @@ htmlAttributesClassListChecks checkInfo = _ -> Nothing - , \() -> - case AstHelpers.getCollapsedCons listArg of + , \checkInfo -> + case AstHelpers.getCollapsedCons checkInfo.firstArg of Just classParts -> - case findMapNeighboring (getTupleWithSpecificSecond False) classParts.consed of + case findMapNeighboring (\el -> getTupleWithSpecificSecond False el checkInfo.lookupTable) classParts.consed of Just classPart -> Just - (Rule.errorWithFix htmlAttributesClassListFalseElementError + (Rule.errorWithFix (htmlAttributesClassListFalseElementError checkInfo) checkInfo.fnRange (collapsedConsRemoveElementFix { toRemove = classPart @@ -7766,7 +7268,6 @@ htmlAttributesClassListChecks checkInfo = Nothing -> Nothing ] - () @@ -7774,35 +7275,32 @@ htmlAttributesClassListChecks checkInfo = jsonDecodeMapChecks : CheckInfo -> Maybe (Error {}) -jsonDecodeMapChecks checkInfo = +jsonDecodeMapChecks = firstThatConstructsJust - [ \() -> emptiableMapChecks jsonDecoderWithSucceedAsWrap checkInfo - , \() -> mapWrapChecks jsonDecoderWithSucceedAsWrap checkInfo + [ emptiableMapChecks jsonDecoderWithSucceedAsWrap + , mapWrapChecks jsonDecoderWithSucceedAsWrap ] - () jsonDecodeMapCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -jsonDecodeMapCompositionChecks checkInfo = - wrapToMapCompositionChecks jsonDecoderWithSucceedAsWrap checkInfo +jsonDecodeMapCompositionChecks = + wrapToMapCompositionChecks jsonDecoderWithSucceedAsWrap jsonDecodeMapNChecks : CheckInfo -> Maybe (Error {}) -jsonDecodeMapNChecks checkInfo = +jsonDecodeMapNChecks = firstThatConstructsJust - [ \() -> wrapperMapNChecks jsonDecoderWithSucceedAsWrap checkInfo - , \() -> mapNOrFirstEmptyConstructionChecks jsonDecoderWithSucceedAsWrap checkInfo + [ wrapperMapNChecks jsonDecoderWithSucceedAsWrap + , mapNOrFirstEmptyConstructionChecks jsonDecoderWithSucceedAsWrap ] - () jsonDecodeAndThenChecks : CheckInfo -> Maybe (Error {}) -jsonDecodeAndThenChecks checkInfo = +jsonDecodeAndThenChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck jsonDecoderWithSucceedAsWrap checkInfo - , \() -> wrapperAndThenChecks jsonDecoderWithSucceedAsWrap checkInfo + [ unnecessaryCallOnEmptyCheck jsonDecoderWithSucceedAsWrap + , wrapperAndThenChecks jsonDecoderWithSucceedAsWrap ] - () @@ -7901,14 +7399,9 @@ randomWeightedChecks checkInfo = randomListChecks : CheckInfo -> Maybe (Error {}) -randomListChecks checkInfo = - let - maybeElementGeneratorArg : Maybe (Node Expression) - maybeElementGeneratorArg = - secondArg checkInfo - in +randomListChecks = firstThatConstructsJust - [ \() -> + [ \checkInfo -> case Evaluate.getInt checkInfo checkInfo.firstArg of Just 1 -> Just @@ -7949,10 +7442,10 @@ randomListChecks checkInfo = Nothing -> Nothing - , \() -> - case maybeElementGeneratorArg of + , \checkInfo -> + case secondArg checkInfo of Just elementGeneratorArg -> - case AstHelpers.getSpecificFunctionCall ( [ "Random" ], "constant" ) checkInfo.lookupTable elementGeneratorArg of + case AstHelpers.getSpecificFnCall ( [ "Random" ], "constant" ) checkInfo.lookupTable elementGeneratorArg of Just constantCall -> let currentAsString : String @@ -7987,35 +7480,32 @@ randomListChecks checkInfo = Nothing -> Nothing ] - () randomMapChecks : CheckInfo -> Maybe (Error {}) -randomMapChecks checkInfo = +randomMapChecks = firstThatConstructsJust - [ \() -> mapIdentityChecks randomGeneratorWrapper checkInfo - , \() -> mapWrapChecks randomGeneratorWrapper checkInfo - , \() -> nonEmptiableWrapperMapAlwaysChecks randomGeneratorWrapper checkInfo + [ mapIdentityChecks randomGeneratorWrapper + , mapWrapChecks randomGeneratorWrapper + , nonEmptiableWrapperMapAlwaysChecks randomGeneratorWrapper ] - () randomMapCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -randomMapCompositionChecks checkInfo = - wrapperMapCompositionChecks randomGeneratorWrapper checkInfo +randomMapCompositionChecks = + wrapperMapCompositionChecks randomGeneratorWrapper randomAndThenChecks : CheckInfo -> Maybe (Error {}) -randomAndThenChecks checkInfo = +randomAndThenChecks = firstThatConstructsJust - [ \() -> wrapperAndThenChecks randomGeneratorWrapper checkInfo - , \() -> nonEmptiableWrapperAndThenAlwaysChecks randomGeneratorWrapper checkInfo + [ wrapperAndThenChecks randomGeneratorWrapper + , nonEmptiableWrapperAndThenAlwaysChecks randomGeneratorWrapper ] - () nonEmptiableWrapperAndThenAlwaysChecks : - NonEmptiableProperties (WrapperProperties otherProperties) + TypeProperties (NonEmptiableProperties (WrapperProperties otherProperties)) -> CheckInfo -> Maybe (Error {}) nonEmptiableWrapperAndThenAlwaysChecks wrapper checkInfo = @@ -8058,23 +7548,15 @@ type alias TypeProperties properties = } -{-| Properties of a type that can hold some data or none. +{-| Properties of a type that either holds some data or is "empty" with the given properties. -} -type alias EmptiableProperties otherProperties = - TypeProperties - { otherProperties - | empty : ConstantProperties - } - - -type alias ConstantProperties = - { asString : QualifyResources {} -> String - , description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool +type alias EmptiableProperties empty otherProperties = + { otherProperties + | empty : empty } -{-| `TypeProperties` of a structure that will always have data inside, for example a non-empty list, a `Test`, a `Benchmark` or a tree (but not a forest). +{-| Properties of a structure type that will always have data inside, for example a non-empty list, a `Test`, a `Benchmark` or a tree (but not a forest). This can be really valuable, for example when you want to know whether the function of a map or andThen will always be called. @@ -8083,10 +7565,7 @@ it is impossible to have one type that has both `EmptiableProperties` and `NonEm -} type alias NonEmptiableProperties otherProperties = - TypeProperties - { otherProperties - | empty : { invalid : () } - } + EmptiableProperties { invalid : () } otherProperties {-| Properties of a type that has a construction function that takes one value. @@ -8096,48 +7575,72 @@ Note that for example `Cmd.batch [ a ]` is not a "wrap" because it keeps the typ -} type alias WrapperProperties otherProperties = - TypeProperties - { otherProperties - | wrap : ConstructWithOneArgProperties - } + { otherProperties + | wrap : ConstructWithOneArgProperties + } type alias FromListProperties otherProperties = - TypeProperties - { otherProperties - | fromListLiteralRange : ModuleNameLookupTable -> Node Expression -> Maybe Range - , fromListLiteralDescription : String - , literalUnionLeftElementsStayOnTheLeft : Bool - } + { otherProperties + | fromListLiteral : + { description : String + , getListRange : ModuleNameLookupTable -> Node Expression -> Maybe Range + } + , unionLeftElementsStayOnTheLeft : Bool + } type alias IndexableProperties otherProperties = - TypeProperties - { otherProperties - | literalElements : ModuleNameLookupTable -> Node Expression -> Maybe (List (Node Expression)) - } + { otherProperties + | literalElements : ModuleNameLookupTable -> Node Expression -> Maybe (List (Node Expression)) + } -type alias ConstructWithOneArgProperties = - { description : Description - , fn : ( ModuleName, String ) - , getValue : ModuleNameLookupTable -> Node Expression -> Maybe (Node Expression) +{-| Properties of a type with with multiple elements. +-} +type alias CollectionProperties otherProperties = + { otherProperties + | size : + { description : String + , determine : Infer.Resources {} -> Node Expression -> Maybe CollectionSize + } } -{-| Properties of a type with with multiple elements. Includes `EmptiableProperties`. +{-| Common properties of a specific set of values for a type. + +Examples: + + - a task that is known to fail + - a non-empty list with exactly one element + - an empty string + +The first 2 are examples of a subset with `ConstructWithOneArgProperties`, +the last one is an example of a subset with `ConstantProperties` + -} -type alias CollectionProperties otherProperties = - EmptiableProperties - { otherProperties - | nameForSize : String - , determineSize : Infer.Resources {} -> Node Expression -> Maybe CollectionSize +type alias TypeSubsetProperties otherProperties = + { otherProperties + | description : Description + , is : ModuleNameLookupTable -> Node Expression -> Bool + } + + +type alias ConstructWithOneArgProperties = + TypeSubsetProperties + { fn : ( ModuleName, String ) + , getValue : ModuleNameLookupTable -> Node Expression -> Maybe (Node Expression) } +type alias ConstantProperties = + TypeSubsetProperties + { asString : QualifyResources {} -> String } + + getEmpty : ModuleNameLookupTable - -> { otherProperties | empty : { empty | is : ModuleNameLookupTable -> Node Expression -> Bool } } + -> EmptiableProperties (TypeSubsetProperties empty) otherProperties -> Node Expression -> Maybe { range : Range } getEmpty lookupTable emptiable expressionNode = @@ -8150,7 +7653,7 @@ getEmpty lookupTable emptiable expressionNode = getEmptyExpressionNode : ModuleNameLookupTable - -> { otherProperties | empty : { empty | is : ModuleNameLookupTable -> Node Expression -> Bool } } + -> EmptiableProperties (TypeSubsetProperties empty) otherProperties -> Node Expression -> Maybe (Node Expression) getEmptyExpressionNode lookupTable emptiable expressionNode = @@ -8227,36 +7730,46 @@ extractInferResources resources = } -emptyAsString : QualifyResources a -> { emptiable | empty : { empty | asString : QualifyResources {} -> String } } -> String +emptyAsString : QualifyResources a -> EmptiableProperties ConstantProperties otherProperties -> String emptyAsString qualifyResources emptiable = emptiable.empty.asString (extractQualifyResources qualifyResources) -randomGeneratorWrapper : NonEmptiableProperties (WrapperProperties { mapFn : ( ModuleName, String ) }) +randomGeneratorWrapper : TypeProperties (NonEmptiableProperties (WrapperProperties { mapFn : ( ModuleName, String ) })) randomGeneratorWrapper = { represents = "random generator" - , wrap = - { description = A "constant generator" - , fn = ( [ "Random" ], "constant" ) - , getValue = - \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "Random" ], "constant" ) lookupTable expr) - } + , wrap = randomGeneratorConstantConstruct , empty = { invalid = () } , mapFn = ( [ "Random" ], "map" ) } +randomGeneratorConstantConstruct : ConstructWithOneArgProperties +randomGeneratorConstantConstruct = + { description = A "constant generator" + , fn = ( [ "Random" ], "constant" ) + , getValue = + \lookupTable expr -> + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Random" ], "constant" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "Random" ], "constant" ) lookupTable expr) + } + + maybeWithJustAsWrap : - EmptiableProperties - (WrapperProperties { mapFn : ( ModuleName, String ) }) + TypeProperties + (EmptiableProperties + ConstantProperties + (WrapperProperties { mapFn : ( ModuleName, String ) }) + ) maybeWithJustAsWrap = { represents = "maybe" , empty = { description = Constant "Nothing" , is = \lookupTable expr -> - isJust (AstHelpers.getSpecificValueOrFunction ( [ "Maybe" ], "Nothing" ) lookupTable expr) + isJust (AstHelpers.getSpecificValueOrFn ( [ "Maybe" ], "Nothing" ) lookupTable expr) , asString = \resources -> qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) resources) @@ -8266,178 +7779,219 @@ maybeWithJustAsWrap = , fn = ( [ "Maybe" ], "Just" ) , getValue = \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "Maybe" ], "Just" ) lookupTable expr) + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Maybe" ], "Just" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "Maybe" ], "Just" ) lookupTable expr) } , mapFn = ( [ "Maybe" ], "map" ) } resultWithOkAsWrap : - WrapperProperties - { empty : - { description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - , mapFn : ( ModuleName, String ) - } + TypeProperties + (WrapperProperties + (EmptiableProperties + ConstructWithOneArgProperties + { mapFn : ( ModuleName, String ) } + ) + ) resultWithOkAsWrap = { represents = "result" - , wrap = - { description = An "okay result" - , fn = ( [ "Result" ], "Ok" ) - , getValue = - \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "Result" ], "Ok" ) lookupTable expr) - } - , empty = - { description = An "error" - , is = - \lookupTable expr -> - isJust (AstHelpers.getSpecificFunctionCall ( [ "Result" ], "Err" ) lookupTable expr) - } + , wrap = resultOkayConstruct + , empty = resultErrorConstruct , mapFn = ( [ "Result" ], "map" ) } +resultOkayConstruct : ConstructWithOneArgProperties +resultOkayConstruct = + { description = An "okay result" + , fn = ( [ "Result" ], "Ok" ) + , getValue = + \lookupTable expr -> + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Result" ], "Ok" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "Result" ], "Ok" ) lookupTable expr) + } + + +resultErrorConstruct : ConstructWithOneArgProperties +resultErrorConstruct = + { description = An "error" + , fn = ( [ "Result" ], "Err" ) + , getValue = + \lookupTable expr -> + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Result" ], "Err" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "Result" ], "Err" ) lookupTable expr) + } + + resultWithErrAsWrap : - WrapperProperties - { empty : - { description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - , mapFn : ( ModuleName, String ) - } + TypeProperties + (WrapperProperties + (EmptiableProperties + ConstructWithOneArgProperties + { mapFn : ( ModuleName, String ) } + ) + ) resultWithErrAsWrap = { represents = "result" - , wrap = - { description = An "error" - , fn = ( [ "Result" ], "Err" ) - , getValue = - \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "Result" ], "Err" ) lookupTable expr) - } - , empty = - { description = An "okay result" - , is = - \lookupTable expr -> - isJust (AstHelpers.getSpecificFunctionCall ( [ "Result" ], "Ok" ) lookupTable expr) - } + , wrap = resultErrorConstruct + , empty = resultOkayConstruct , mapFn = ( [ "Result" ], "mapError" ) } taskWithSucceedAsWrap : - WrapperProperties - { empty : - { description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - , mapFn : ( ModuleName, String ) - } + TypeProperties + (WrapperProperties + (EmptiableProperties + ConstructWithOneArgProperties + { mapFn : ( ModuleName, String ) } + ) + ) taskWithSucceedAsWrap = { represents = "task" - , wrap = - { description = A "succeeding task" - , fn = ( [ "Task" ], "succeed" ) - , getValue = - \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "Task" ], "succeed" ) lookupTable expr) - } - , empty = - { description = A "failing task" - , is = - \lookupTable expr -> - isJust (AstHelpers.getSpecificFunctionCall ( [ "Task" ], "fail" ) lookupTable expr) - } + , wrap = taskSucceedingConstruct + , empty = taskFailingConstruct , mapFn = ( [ "Task" ], "map" ) } +taskSucceedingConstruct : ConstructWithOneArgProperties +taskSucceedingConstruct = + { description = A "succeeding task" + , fn = ( [ "Task" ], "succeed" ) + , getValue = + \lookupTable expr -> + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Task" ], "succeed" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "Task" ], "succeed" ) lookupTable expr) + } + + +taskFailingConstruct : ConstructWithOneArgProperties +taskFailingConstruct = + { description = A "failing task" + , fn = ( [ "Task" ], "fail" ) + , getValue = + \lookupTable expr -> + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Task" ], "fail" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "Task" ], "fail" ) lookupTable expr) + } + + taskWithFailAsWrap : - WrapperProperties - { empty : - { description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - , mapFn : ( ModuleName, String ) - } + TypeProperties + (WrapperProperties + (EmptiableProperties + ConstructWithOneArgProperties + { mapFn : ( ModuleName, String ) } + ) + ) taskWithFailAsWrap = { represents = "task" - , wrap = - { description = A "failing task" - , fn = ( [ "Task" ], "fail" ) - , getValue = - \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "Task" ], "fail" ) lookupTable expr) - } - , empty = - { description = A "succeeding task" - , is = - \lookupTable expr -> - isJust (AstHelpers.getSpecificFunctionCall ( [ "Task" ], "succeed" ) lookupTable expr) - } + , wrap = taskFailingConstruct + , empty = taskSucceedingConstruct , mapFn = ( [ "Task" ], "mapError" ) } -jsonDecoderWithSucceedAsWrap : - WrapperProperties - { empty : - { description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - , mapFn : ( ModuleName, String ) - } -jsonDecoderWithSucceedAsWrap = - { represents = "json decoder" - , wrap = - { description = A "succeeding decoder" - , fn = ( [ "Json", "Decode" ], "succeed" ) - , getValue = - \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "Json", "Decode" ], "succeed" ) lookupTable expr) - } - , empty = - { description = A "failing decoder" - , is = - \lookupTable expr -> - isJust (AstHelpers.getSpecificFunctionCall ( [ "Json", "Decode" ], "fail" ) lookupTable expr) - } - , mapFn = ( [ "Json", "Decode" ], "map" ) +jsonDecoderWithSucceedAsWrap : + TypeProperties + (WrapperProperties + (EmptiableProperties + ConstructWithOneArgProperties + { mapFn : ( ModuleName, String ) } + ) + ) +jsonDecoderWithSucceedAsWrap = + { represents = "json decoder" + , wrap = jsonDecoderSucceedingConstruct + , empty = jsonDecoderFailingConstruct + , mapFn = ( [ "Json", "Decode" ], "map" ) + } + + +jsonDecoderSucceedingConstruct : ConstructWithOneArgProperties +jsonDecoderSucceedingConstruct = + { description = A "succeeding decoder" + , fn = ( [ "Json", "Decode" ], "succeed" ) + , getValue = + \lookupTable expr -> + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Json", "Decode" ], "succeed" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "Json", "Decode" ], "succeed" ) lookupTable expr) + } + + +jsonDecoderFailingConstruct : ConstructWithOneArgProperties +jsonDecoderFailingConstruct = + { description = A "failing decoder" + , fn = ( [ "Json", "Decode" ], "fail" ) + , getValue = + \lookupTable expr -> + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Json", "Decode" ], "fail" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "Json", "Decode" ], "fail" ) lookupTable expr) } -listCollection : CollectionProperties (WrapperProperties (FromListProperties { mapFn : ( ModuleName, String ) })) +listCollection : TypeProperties (CollectionProperties (EmptiableProperties ConstantProperties (WrapperProperties (FromListProperties { mapFn : ( ModuleName, String ) })))) listCollection = { represents = "list" - , empty = - { description = Constant "[]" - , is = \_ expr -> AstHelpers.getListLiteral expr == Just [] - , asString = \_ -> "[]" - } - , nameForSize = "length" - , determineSize = listDetermineLength - , wrap = - { description = A "singleton list" - , fn = ( [ "List" ], "singleton" ) - , getValue = - \lookupTable expr -> - Maybe.map .element (AstHelpers.getListSingleton lookupTable expr) + , empty = listEmptyConstant + , size = + { description = "length" + , determine = listDetermineLength } + , wrap = listSingletonConstruct , mapFn = ( [ "List" ], "map" ) - , fromListLiteralRange = \_ expr -> AstHelpers.getListLiteralRange expr - , fromListLiteralDescription = "list literal" - , literalUnionLeftElementsStayOnTheLeft = True + , fromListLiteral = + { description = "list literal" + , getListRange = \_ expr -> AstHelpers.getListLiteralRange expr + } + , unionLeftElementsStayOnTheLeft = True + } + + +listEmptyConstant : ConstantProperties +listEmptyConstant = + { description = Constant "[]" + , is = \_ expr -> AstHelpers.getListLiteral expr == Just [] + , asString = \_ -> "[]" + } + + +listSingletonConstruct : ConstructWithOneArgProperties +listSingletonConstruct = + { description = A "singleton list" + , fn = ( [ "List" ], "singleton" ) + , getValue = + \lookupTable expr -> + Maybe.map .element (AstHelpers.getListSingleton lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getListSingleton lookupTable expr) } listDetermineLength : Infer.Resources a -> Node Expression -> Maybe CollectionSize listDetermineLength resources expressionNode = - case Node.value (AstHelpers.removeParens expressionNode) of - Expression.ListExpr list -> + case AstHelpers.removeParens expressionNode of + Node _ (Expression.ListExpr list) -> Just (Exactly (List.length list)) - Expression.OperatorApplication "::" _ _ right -> + Node _ (Expression.OperatorApplication "::" _ _ right) -> case listDetermineLength resources right of Just (Exactly n) -> Just (Exactly (n + 1)) @@ -8445,40 +7999,48 @@ listDetermineLength resources expressionNode = _ -> Just NotEmpty - Expression.Application ((Node fnRange (Expression.FunctionOrValue _ "singleton")) :: _ :: []) -> - if ModuleNameLookupTable.moduleNameAt resources.lookupTable fnRange == Just [ "List" ] then - Just (Exactly 1) - - else - Nothing + nonConsOrLiteral -> + Maybe.map (\_ -> Exactly 1) (AstHelpers.getSpecificFnCall ( [ "List" ], "singleton" ) resources.lookupTable nonConsOrLiteral) - _ -> - Nothing - -stringCollection : CollectionProperties (WrapperProperties (FromListProperties {})) +stringCollection : TypeProperties (CollectionProperties (WrapperProperties (EmptiableProperties ConstantProperties (FromListProperties {})))) stringCollection = { represents = "string" - , empty = - { description = Constant emptyStringAsString - , asString = \_ -> emptyStringAsString - , is = \_ (Node _ expr) -> expr == Expression.Literal "" + , empty = stringEmptyConstant + , size = + { description = "length" + , determine = \_ (Node _ expr) -> stringDetermineLength expr } - , nameForSize = "length" - , determineSize = \_ (Node _ expr) -> stringDetermineLength expr - , wrap = - { description = A "single-char string" - , fn = ( [ "String" ], "fromChar" ) - , getValue = + , wrap = singleCharConstruct + , fromListLiteral = + { description = "String.fromList call" + , getListRange = \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "String" ], "fromChar" ) lookupTable expr) + AstHelpers.getSpecificFnCall ( [ "String" ], "fromList" ) lookupTable expr + |> Maybe.andThen (\stringFromListCall -> AstHelpers.getListLiteralRange stringFromListCall.firstArg) } - , fromListLiteralRange = + , unionLeftElementsStayOnTheLeft = True + } + + +stringEmptyConstant : ConstantProperties +stringEmptyConstant = + { description = Constant emptyStringAsString + , asString = \_ -> emptyStringAsString + , is = \_ (Node _ expr) -> expr == Expression.Literal "" + } + + +singleCharConstruct : ConstructWithOneArgProperties +singleCharConstruct = + { description = A "single-char string" + , fn = ( [ "String" ], "fromChar" ) + , getValue = \lookupTable expr -> - AstHelpers.getSpecificFunctionCall ( [ "String" ], "fromList" ) lookupTable expr - |> Maybe.andThen (\{ firstArg } -> AstHelpers.getListLiteralRange firstArg) - , fromListLiteralDescription = "String.fromList call" - , literalUnionLeftElementsStayOnTheLeft = True + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "String" ], "fromChar" ) lookupTable expr) + , is = + \lookupTable expr -> + isJust (AstHelpers.getSpecificFnCall ( [ "String" ], "fromChar" ) lookupTable expr) } @@ -8492,30 +8054,31 @@ stringDetermineLength expression = Nothing -arrayCollection : CollectionProperties (FromListProperties (IndexableProperties {})) +arrayCollection : TypeProperties (CollectionProperties (FromListProperties (IndexableProperties (EmptiableProperties ConstantProperties {})))) arrayCollection = { represents = "array" , empty = { description = Constant (qualifiedToString ( [ "Array" ], "empty" )) , is = \lookupTable expr -> - isJust (AstHelpers.getSpecificValueOrFunction ( [ "Array" ], "empty" ) lookupTable expr) + isJust (AstHelpers.getSpecificValueOrFn ( [ "Array" ], "empty" ) lookupTable expr) , asString = \resources -> qualifiedToString (qualify ( [ "Array" ], "empty" ) resources) } - , nameForSize = "length" - , determineSize = arrayDetermineSize + , size = { description = "length", determine = arrayDetermineSize } , literalElements = \lookupTable expr -> - AstHelpers.getSpecificFunctionCall ( [ "Array" ], "fromList" ) lookupTable expr - |> Maybe.andThen (\{ firstArg } -> AstHelpers.getListLiteral firstArg) - , fromListLiteralRange = - \lookupTable expr -> - AstHelpers.getSpecificFunctionCall ( [ "Array" ], "fromList" ) lookupTable expr - |> Maybe.andThen (\call -> AstHelpers.getListLiteralRange call.firstArg) - , fromListLiteralDescription = "Array.fromList call" - , literalUnionLeftElementsStayOnTheLeft = True + AstHelpers.getSpecificFnCall ( [ "Array" ], "fromList" ) lookupTable expr + |> Maybe.andThen (\arrayFromListCall -> AstHelpers.getListLiteral arrayFromListCall.firstArg) + , fromListLiteral = + { description = "Array.fromList call" + , getListRange = + \lookupTable expr -> + AstHelpers.getSpecificFnCall ( [ "Array" ], "fromList" ) lookupTable expr + |> Maybe.andThen (\call -> AstHelpers.getListLiteralRange call.firstArg) + } + , unionLeftElementsStayOnTheLeft = True } @@ -8523,32 +8086,32 @@ arrayDetermineSize : Infer.Resources a -> Node Expression -> Maybe CollectionSize -arrayDetermineSize resources expressionNode = +arrayDetermineSize resources = firstThatConstructsJust - [ \() -> - case AstHelpers.getSpecificValueOrFunction ( [ "Array" ], "empty" ) resources.lookupTable expressionNode of + [ \expressionNode -> + case AstHelpers.getSpecificValueOrFn ( [ "Array" ], "empty" ) resources.lookupTable expressionNode of Just _ -> Just (Exactly 0) Nothing -> Nothing - , \() -> - case AstHelpers.getSpecificFunctionCall ( [ "Array" ], "fromList" ) resources.lookupTable expressionNode of + , \expressionNode -> + case AstHelpers.getSpecificFnCall ( [ "Array" ], "fromList" ) resources.lookupTable expressionNode of Just fromListCall -> listDetermineLength resources fromListCall.firstArg Nothing -> Nothing - , \() -> - case AstHelpers.getSpecificFunctionCall ( [ "Array" ], "repeat" ) resources.lookupTable expressionNode of + , \expressionNode -> + case AstHelpers.getSpecificFnCall ( [ "Array" ], "repeat" ) resources.lookupTable expressionNode of Just repeatCall -> Evaluate.getInt resources repeatCall.firstArg |> Maybe.map (\n -> Exactly (max 0 n)) Nothing -> Nothing - , \() -> - case AstHelpers.getSpecificFunctionCall ( [ "Array" ], "initialize" ) resources.lookupTable expressionNode of + , \expressionNode -> + case AstHelpers.getSpecificFnCall ( [ "Array" ], "initialize" ) resources.lookupTable expressionNode of Just repeatCall -> Evaluate.getInt resources repeatCall.firstArg |> Maybe.map (\n -> Exactly (max 0 n)) @@ -8556,36 +8119,43 @@ arrayDetermineSize resources expressionNode = Nothing -> Nothing ] - () -setCollection : CollectionProperties (WrapperProperties (FromListProperties {})) +setCollection : TypeProperties (CollectionProperties (EmptiableProperties ConstantProperties (WrapperProperties (FromListProperties {})))) setCollection = { represents = "set" , empty = { description = Constant (qualifiedToString ( [ "Set" ], "empty" )) , is = \lookupTable expr -> - isJust (AstHelpers.getSpecificValueOrFunction ( [ "Set" ], "empty" ) lookupTable expr) + isJust (AstHelpers.getSpecificValueOrFn ( [ "Set" ], "empty" ) lookupTable expr) , asString = \resources -> qualifiedToString (qualify ( [ "Set" ], "empty" ) resources) } - , nameForSize = "size" - , determineSize = setDetermineSize - , wrap = - { description = A "singleton set" - , fn = ( [ "Set" ], "singleton" ) - , getValue = + , size = { description = "size", determine = setDetermineSize } + , wrap = setSingletonConstruct + , fromListLiteral = + { description = "Set.fromList call" + , getListRange = \lookupTable expr -> - Maybe.map .firstArg (AstHelpers.getSpecificFunctionCall ( [ "Set" ], "singleton" ) lookupTable expr) + AstHelpers.getSpecificFnCall ( [ "Set" ], "fromList" ) lookupTable expr + |> Maybe.andThen (\setFromListCall -> AstHelpers.getListLiteralRange setFromListCall.firstArg) } - , fromListLiteralRange = + , unionLeftElementsStayOnTheLeft = True + } + + +setSingletonConstruct : ConstructWithOneArgProperties +setSingletonConstruct = + { description = A "singleton set" + , fn = ( [ "Set" ], "singleton" ) + , getValue = + \lookupTable expr -> + Maybe.map .firstArg (AstHelpers.getSpecificFnCall ( [ "Set" ], "singleton" ) lookupTable expr) + , is = \lookupTable expr -> - AstHelpers.getSpecificFunctionCall ( [ "Set" ], "fromList" ) lookupTable expr - |> Maybe.andThen (\{ firstArg } -> AstHelpers.getListLiteralRange firstArg) - , fromListLiteralDescription = "Set.fromList call" - , literalUnionLeftElementsStayOnTheLeft = True + isJust (AstHelpers.getSpecificFnCall ( [ "Set" ], "singleton" ) lookupTable expr) } @@ -8593,24 +8163,24 @@ setDetermineSize : Infer.Resources a -> Node Expression -> Maybe CollectionSize -setDetermineSize resources expressionNode = +setDetermineSize resources = firstThatConstructsJust - [ \() -> - case AstHelpers.getSpecificValueOrFunction ( [ "Set" ], "empty" ) resources.lookupTable expressionNode of + [ \expressionNode -> + case AstHelpers.getSpecificValueOrFn ( [ "Set" ], "empty" ) resources.lookupTable expressionNode of Just _ -> Just (Exactly 0) Nothing -> Nothing - , \() -> - case AstHelpers.getSpecificFunctionCall ( [ "Set" ], "singleton" ) resources.lookupTable expressionNode of + , \expressionNode -> + case AstHelpers.getSpecificFnCall ( [ "Set" ], "singleton" ) resources.lookupTable expressionNode of Just _ -> Just (Exactly 1) Nothing -> Nothing - , \() -> - case AstHelpers.getSpecificFunctionCall ( [ "Set" ], "fromList" ) resources.lookupTable expressionNode of + , \expressionNode -> + case AstHelpers.getSpecificFnCall ( [ "Set" ], "fromList" ) resources.lookupTable expressionNode of Just fromListCall -> case AstHelpers.getListLiteral fromListCall.firstArg of Just [] -> @@ -8633,29 +8203,29 @@ setDetermineSize resources expressionNode = _ -> Nothing ] - () -dictCollection : CollectionProperties (FromListProperties {}) +dictCollection : TypeProperties (CollectionProperties (EmptiableProperties ConstantProperties (FromListProperties {}))) dictCollection = { represents = "dict" , empty = { description = Constant (qualifiedToString ( [ "Dict" ], "empty" )) , is = \lookupTable expr -> - isJust (AstHelpers.getSpecificValueOrFunction ( [ "Dict" ], "empty" ) lookupTable expr) + isJust (AstHelpers.getSpecificValueOrFn ( [ "Dict" ], "empty" ) lookupTable expr) , asString = \resources -> qualifiedToString (qualify ( [ "Dict" ], "empty" ) resources) } - , nameForSize = "size" - , determineSize = dictDetermineSize - , fromListLiteralRange = - \lookupTable expr -> - AstHelpers.getSpecificFunctionCall ( [ "Dict" ], "fromList" ) lookupTable expr - |> Maybe.andThen (\{ firstArg } -> AstHelpers.getListLiteralRange firstArg) - , fromListLiteralDescription = "Dict.fromList call" - , literalUnionLeftElementsStayOnTheLeft = False + , size = { description = "size", determine = dictDetermineSize } + , fromListLiteral = + { description = "Dict.fromList call" + , getListRange = + \lookupTable expr -> + AstHelpers.getSpecificFnCall ( [ "Dict" ], "fromList" ) lookupTable expr + |> Maybe.andThen (\dictFromListCall -> AstHelpers.getListLiteralRange dictFromListCall.firstArg) + } + , unionLeftElementsStayOnTheLeft = False } @@ -8663,17 +8233,17 @@ dictDetermineSize : Infer.Resources a -> Node Expression -> Maybe CollectionSize -dictDetermineSize resources expressionNode = - findMap (\f -> f ()) - [ \() -> - case AstHelpers.getSpecificValueOrFunction ( [ "Dict" ], "empty" ) resources.lookupTable expressionNode of +dictDetermineSize resources = + firstThatConstructsJust + [ \expressionNode -> + case AstHelpers.getSpecificValueOrFn ( [ "Dict" ], "empty" ) resources.lookupTable expressionNode of Just _ -> Just (Exactly 0) Nothing -> Nothing - , \() -> - case AstHelpers.getSpecificFunctionCall ( [ "Dict" ], "singleton" ) resources.lookupTable expressionNode of + , \expressionNode -> + case AstHelpers.getSpecificFnCall ( [ "Dict" ], "singleton" ) resources.lookupTable expressionNode of Just singletonCall -> case singletonCall.argsAfterFirst of _ :: [] -> @@ -8684,8 +8254,8 @@ dictDetermineSize resources expressionNode = Nothing -> Nothing - , \() -> - case AstHelpers.getSpecificFunctionCall ( [ "Dict" ], "fromList" ) resources.lookupTable expressionNode of + , \expressionNode -> + case AstHelpers.getSpecificFnCall ( [ "Dict" ], "fromList" ) resources.lookupTable expressionNode of Just fromListCall -> case AstHelpers.getListLiteral fromListCall.firstArg of Just [] -> @@ -8710,7 +8280,7 @@ dictDetermineSize resources expressionNode = ] -cmdCollection : EmptiableProperties {} +cmdCollection : TypeProperties (EmptiableProperties ConstantProperties {}) cmdCollection = { represents = "command" , empty = @@ -8718,7 +8288,7 @@ cmdCollection = Constant "Cmd.none" , is = \lookupTable expr -> - isJust (AstHelpers.getSpecificValueOrFunction ( [ "Platform", "Cmd" ], "none" ) lookupTable expr) + isJust (AstHelpers.getSpecificValueOrFn ( [ "Platform", "Cmd" ], "none" ) lookupTable expr) , asString = \resources -> qualifiedToString (qualify ( [ "Platform", "Cmd" ], "none" ) resources) @@ -8726,7 +8296,7 @@ cmdCollection = } -subCollection : EmptiableProperties {} +subCollection : TypeProperties (EmptiableProperties ConstantProperties {}) subCollection = { represents = "subscription" , empty = @@ -8734,7 +8304,7 @@ subCollection = Constant "Sub.none" , is = \lookupTable expr -> - isJust (AstHelpers.getSpecificValueOrFunction ( [ "Platform", "Sub" ], "none" ) lookupTable expr) + isJust (AstHelpers.getSpecificValueOrFn ( [ "Platform", "Sub" ], "none" ) lookupTable expr) , asString = \resources -> qualifiedToString (qualify ( [ "Platform", "Sub" ], "none" ) resources) @@ -8743,22 +8313,14 @@ subCollection = emptiableMapChecks : - TypeProperties - { otherProperties - | empty : - { empty - | description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - } + TypeProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) -> CheckInfo -> Maybe (Error {}) -emptiableMapChecks emptiable checkInfo = +emptiableMapChecks emptiable = firstThatConstructsJust - [ \() -> mapIdentityChecks emptiable checkInfo - , \() -> callOnEmptyReturnsEmptyCheck emptiable checkInfo + [ mapIdentityChecks emptiable + , unnecessaryCallOnEmptyCheck emptiable ] - () mapIdentityChecks : @@ -8779,12 +8341,11 @@ mapIdentityChecks mappable checkInfo = wrapperMapCompositionChecks : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -wrapperMapCompositionChecks wrapper checkInfo = +wrapperMapCompositionChecks wrapper = firstThatConstructsJust - [ \() -> wrapToMapCompositionChecks wrapper checkInfo - , \() -> mapAlwaysCompositionChecks wrapper checkInfo + [ wrapToMapCompositionChecks wrapper + , mapAlwaysCompositionChecks wrapper ] - () mapWrapChecks : @@ -8947,15 +8508,13 @@ mapAlwaysCompositionChecks wrapper checkInfo = emptiableAndThenChecks : - { otherProperties - | empty : ConstantProperties - } + EmptiableProperties ConstantProperties otherProperties -> CheckInfo -> Maybe (Error {}) -emptiableAndThenChecks emptiable checkInfo = +emptiableAndThenChecks emptiable = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck emptiable checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck emptiable + , \checkInfo -> case constructs (sameInAllBranches (getEmpty checkInfo.lookupTable emptiable)) checkInfo.lookupTable checkInfo.firstArg of Determined _ -> Just @@ -8968,7 +8527,6 @@ emptiableAndThenChecks emptiable checkInfo = Undetermined -> Nothing ] - () getValueWithNodeRange : @@ -8981,12 +8539,12 @@ getValueWithNodeRange getValue expressionNode = wrapperAndThenChecks : - WrapperProperties { otherProperties | mapFn : ( ModuleName, String ) } + TypeProperties (WrapperProperties { otherProperties | mapFn : ( ModuleName, String ) }) -> CheckInfo -> Maybe (Error {}) -wrapperAndThenChecks wrapper checkInfo = +wrapperAndThenChecks wrapper = firstThatConstructsJust - [ \() -> + [ \checkInfo -> case secondArg checkInfo of Just maybeArg -> case sameInAllBranches (getValueWithNodeRange (wrapper.wrap.getValue checkInfo.lookupTable)) maybeArg of @@ -9007,8 +8565,8 @@ wrapperAndThenChecks wrapper checkInfo = Nothing -> Nothing - , \() -> - case AstHelpers.getSpecificValueOrFunction wrapper.wrap.fn checkInfo.lookupTable checkInfo.firstArg of + , \checkInfo -> + case AstHelpers.getSpecificValueOrFn wrapper.wrap.fn checkInfo.lookupTable checkInfo.firstArg of Just _ -> Just (alwaysReturnsLastArgError @@ -9019,7 +8577,7 @@ wrapperAndThenChecks wrapper checkInfo = Nothing -> Nothing - , \() -> + , \checkInfo -> case constructs (sameInAllBranches (\expr -> getValueWithNodeRange (wrapper.wrap.getValue checkInfo.lookupTable) expr)) @@ -9042,59 +8600,42 @@ wrapperAndThenChecks wrapper checkInfo = Undetermined -> Nothing ] - () maybeAndThenChecks : CheckInfo -> Maybe (Error {}) -maybeAndThenChecks checkInfo = +maybeAndThenChecks = firstThatConstructsJust - [ \() -> wrapperAndThenChecks maybeWithJustAsWrap checkInfo - , \() -> emptiableAndThenChecks maybeWithJustAsWrap checkInfo + [ wrapperAndThenChecks maybeWithJustAsWrap + , emptiableAndThenChecks maybeWithJustAsWrap ] - () resultAndThenChecks : CheckInfo -> Maybe (Error {}) -resultAndThenChecks checkInfo = +resultAndThenChecks = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck resultWithOkAsWrap checkInfo - , \() -> wrapperAndThenChecks resultWithOkAsWrap checkInfo + [ unnecessaryCallOnEmptyCheck resultWithOkAsWrap + , wrapperAndThenChecks resultWithOkAsWrap ] - () withDefaultChecks : - WrapperProperties - { otherProperties - | empty : - { empty - | description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - } + WrapperProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) -> CheckInfo -> Maybe (Error {}) -withDefaultChecks emptiable checkInfo = +withDefaultChecks emptiable = firstThatConstructsJust - [ \() -> emptiableWithDefaultChecks emptiable checkInfo - , \() -> callOnWrapReturnsItsValue emptiable checkInfo + [ emptiableWithDefaultChecks emptiable + , callOnWrapReturnsItsValueCheck emptiable ] - () wrapperWithDefaultChecks : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -wrapperWithDefaultChecks wrapper checkInfo = - onWrapAlwaysReturnsIncomingCompositionCheck { operationArgCount = 2 } wrapper checkInfo +wrapperWithDefaultChecks wrapper = + onWrapAlwaysReturnsIncomingCompositionCheck wrapper emptiableWithDefaultChecks : - { otherProperties - | empty : - { empty - | description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - } + EmptiableProperties (TypeSubsetProperties empty) otherProperties -> CheckInfo -> Maybe (Error {}) emptiableWithDefaultChecks emptiable checkInfo = @@ -9119,33 +8660,23 @@ emptiableWithDefaultChecks emptiable checkInfo = unwrapToMaybeChecks : - WrapperProperties - { otherProperties - | empty : - { empty - | is : ModuleNameLookupTable -> Node Expression -> Bool - , description : Description - } - } + WrapperProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) -> CheckInfo -> Maybe (Error {}) -unwrapToMaybeChecks emptiableWrapper checkInfo = +unwrapToMaybeChecks emptiableWrapper = firstThatConstructsJust - [ \() -> callOnWrapReturnsJustItsValue emptiableWrapper checkInfo - , \() -> - callOnEmptyReturnsCheck - { resultAsString = \res -> qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) res) } - emptiableWrapper - checkInfo + [ callOnWrapReturnsJustItsValue emptiableWrapper + , callOnEmptyReturnsCheck + { resultAsString = \res -> qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) res) } + emptiableWrapper ] - () resultToMaybeCompositionChecks : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -resultToMaybeCompositionChecks checkInfo = +resultToMaybeCompositionChecks = firstThatConstructsJust - [ \() -> onWrapAlwaysReturnsJustIncomingCompositionCheck { operationArgCount = 1 } resultWithOkAsWrap checkInfo - , \() -> + [ onWrapAlwaysReturnsJustIncomingCompositionCheck resultWithOkAsWrap + , \checkInfo -> case checkInfo.earlier.fn of ( [ "Result" ], "Err" ) -> Just @@ -9165,14 +8696,12 @@ resultToMaybeCompositionChecks checkInfo = _ -> Nothing ] - () resultFromMaybeChecks : CheckInfo -> Maybe (Error {}) -resultFromMaybeChecks checkInfo = +resultFromMaybeChecks = fromMaybeChecks { onNothingFn = ( [ "Result" ], "Err" ), onJustFn = ( [ "Result" ], "Ok" ) } - checkInfo fromMaybeChecks : { onNothingFn : ( ModuleName, String ), onJustFn : ( ModuleName, String ) } -> CheckInfo -> Maybe (Error {}) @@ -9181,7 +8710,7 @@ fromMaybeChecks config checkInfo = Just maybeArg -> firstThatConstructsJust [ \() -> - case sameInAllBranches (AstHelpers.getSpecificValueOrFunction ( [ "Maybe" ], "Nothing" ) checkInfo.lookupTable) maybeArg of + case sameInAllBranches (AstHelpers.getSpecificValueOrFn ( [ "Maybe" ], "Nothing" ) checkInfo.lookupTable) maybeArg of Determined _ -> Just (Rule.errorWithFix @@ -9201,7 +8730,7 @@ fromMaybeChecks config checkInfo = Undetermined -> Nothing , \() -> - case sameInAllBranches (AstHelpers.getSpecificFunctionCall ( [ "Maybe" ], "Just" ) checkInfo.lookupTable) maybeArg of + case sameInAllBranches (AstHelpers.getSpecificFnCall ( [ "Maybe" ], "Just" ) checkInfo.lookupTable) maybeArg of Determined justCalls -> Just (Rule.errorWithFix @@ -9256,12 +8785,11 @@ pipelineChecks : , direction : LeftOrRightDirection } -> Maybe (Error {}) -pipelineChecks checkInfo = +pipelineChecks = firstThatConstructsJust - [ \() -> pipingIntoCompositionChecks { commentRanges = checkInfo.commentRanges, extractSourceCode = checkInfo.extractSourceCode } checkInfo.direction checkInfo.pipedInto - , \() -> fullyAppliedLambdaInPipelineChecks { nodeRange = checkInfo.nodeRange, function = checkInfo.pipedInto, firstArgument = checkInfo.arg } + [ \checkInfo -> pipingIntoCompositionChecks { commentRanges = checkInfo.commentRanges, extractSourceCode = checkInfo.extractSourceCode } checkInfo.direction checkInfo.pipedInto + , \checkInfo -> fullyAppliedLambdaInPipelineChecks { nodeRange = checkInfo.nodeRange, function = checkInfo.pipedInto, firstArgument = checkInfo.arg } ] - () fullyAppliedLambdaInPipelineChecks : { nodeRange : Range, firstArgument : Node Expression, function : Node Expression } -> Maybe (Error {}) @@ -9388,13 +8916,81 @@ pipingIntoCompositionChecks context compositionDirection expressionNode = ) -compositionAfterWrapIsUnnecessaryCheck : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -compositionAfterWrapIsUnnecessaryCheck wrapper checkInfo = - if checkInfo.earlier.fn == wrapper.wrap.fn then +{-| Condense applying the same function with equal arguments (except the last one) twice in sequence into one. +This applies to functions that are equivalent to identity when operating on the result another such function. + +Examples of such functions: + + - one argument: `Simplify.expectNaN`, `Review.Rule.providesFixesForModuleRule`, `List.sort`, `List.Extra.unique`, [`AVL.Set.clear`](https://package.elm-lang.org/packages/owanturist/elm-avl-dict/2.1.0/AVL-Set#clear) + - two arguments: `List.filter f`, `List.Extra.filterNot f`, `List.Extra.takeWhile/dropWhile(Right) f`, `List.sortBy f`, `List.Extra.uniqueBy f` + - three arguments: `Array.set i new`, `Array.Extra.resizelRepeat l pad`, `List.Extra.setAt i new` + +Note that `update` or `setWhere` operations for example _can_ have an effect even after the same operation has already been applied. + +For operations that toggle between 2 states, like `reverse` or `List.Extra.swapAt i j`, use `toggleCallChecks` + +-} +operationDoesNotChangeResultOfOperationCheck : CheckInfo -> Maybe (Error {}) +operationDoesNotChangeResultOfOperationCheck checkInfo = + case Maybe.andThen (AstHelpers.getSpecificFnCall checkInfo.fn checkInfo.lookupTable) (fullyAppliedLastArg checkInfo) of + Just lastArgCall -> + let + areAllArgsEqual : Bool + areAllArgsEqual = + List.all + (\( arg, lastArgCallArg ) -> + Normalize.compare checkInfo arg lastArgCallArg == Normalize.ConfirmedEquality + ) + (List.map2 Tuple.pair + (listFilledInit ( checkInfo.firstArg, checkInfo.argsAfterFirst )) + (listFilledInit ( lastArgCall.firstArg, lastArgCall.argsAfterFirst )) + ) + in + if areAllArgsEqual then + Just + (Rule.errorWithFix + { message = + case checkInfo.argCount of + 1 -> + "Unnecessary " ++ qualifiedToString checkInfo.fn ++ " after " ++ qualifiedToString checkInfo.fn + + _ -> + "Unnecessary " ++ qualifiedToString checkInfo.fn ++ " after equivalent " ++ qualifiedToString checkInfo.fn + , details = [ "You can remove this additional operation." ] + } + checkInfo.fnRange + (keepOnlyFix { parentRange = checkInfo.parentRange, keep = lastArgCall.nodeRange }) + ) + + else + Nothing + + Nothing -> + Nothing + + +operationDoesNotChangeResultOfOperationCompositionCheck : CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix +operationDoesNotChangeResultOfOperationCompositionCheck checkInfo = + let + areAllArgsEqual : () -> Bool + areAllArgsEqual () = + List.all + (\( arg, earlierArg ) -> + Normalize.compare checkInfo arg earlierArg == Normalize.ConfirmedEquality + ) + (List.map2 Tuple.pair checkInfo.later.args checkInfo.earlier.args) + in + if onlyLastArgIsCurried checkInfo.later && (checkInfo.earlier.fn == checkInfo.later.fn) && areAllArgsEqual () then Just { info = - { message = qualifiedToString checkInfo.later.fn ++ " on " ++ descriptionForIndefinite wrapper.wrap.description ++ " will result in the given " ++ wrapper.represents - , details = [ "You can replace this call by " ++ qualifiedToString (qualify wrapper.wrap.fn defaultQualifyResources) ++ "." ] + { message = + case checkInfo.later.argCount of + 1 -> + "Unnecessary " ++ qualifiedToString checkInfo.later.fn ++ " after " ++ qualifiedToString checkInfo.earlier.fn + + _ -> + "Unnecessary " ++ qualifiedToString checkInfo.later.fn ++ " after equivalent " ++ qualifiedToString checkInfo.earlier.fn + , details = [ "You can remove this additional operation." ] } , fix = [ Fix.removeRange checkInfo.later.removeRange ] } @@ -9403,23 +8999,18 @@ compositionAfterWrapIsUnnecessaryCheck wrapper checkInfo = Nothing -callOnWrappedDoesNotChangeItCheck : WrapperProperties otherProperties -> CheckInfo -> Maybe (Error {}) -callOnWrappedDoesNotChangeItCheck wrapper checkInfo = - callOnDoesNotChangeItCheck - { description = wrapper.wrap.description - , is = \lookupTable expr -> isJust (wrapper.wrap.getValue lookupTable expr) - } - checkInfo +unnecessaryCompositionAfterWrapCheck : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix +unnecessaryCompositionAfterWrapCheck wrapper = + unnecessaryCompositionAfterCheck wrapper.wrap -callOnDoesNotChangeItCheck : - { a - | description : Description - , is : ModuleNameLookupTable -> Node Expression -> Bool - } - -> CheckInfo - -> Maybe (Error {}) -callOnDoesNotChangeItCheck constructable checkInfo = +unnecessaryCallOnWrappedCheck : WrapperProperties otherProperties -> CheckInfo -> Maybe (Error {}) +unnecessaryCallOnWrappedCheck wrapper = + unnecessaryCallOnCheck wrapper.wrap + + +unnecessaryCallOnCheck : TypeSubsetProperties otherProperties -> CheckInfo -> Maybe (Error {}) +unnecessaryCallOnCheck constructable checkInfo = case fullyAppliedLastArg checkInfo of Just constructableArg -> let @@ -9451,30 +9042,17 @@ callOnDoesNotChangeItCheck constructable checkInfo = Nothing -callOnEmptyReturnsEmptyCheck : - { a - | empty : - { empty - | is : ModuleNameLookupTable -> Node Expression -> Bool - , description : Description - } - } +unnecessaryCallOnEmptyCheck : + EmptiableProperties (TypeSubsetProperties empty) otherProperties -> CheckInfo -> Maybe (Error {}) -callOnEmptyReturnsEmptyCheck emptiable checkInfo = - callOnDoesNotChangeItCheck emptiable.empty checkInfo +unnecessaryCallOnEmptyCheck emptiable = + unnecessaryCallOnCheck emptiable.empty callOnEmptyReturnsCheck : { resultAsString : QualifyResources {} -> String } - -> - { a - | empty : - { empty - | is : ModuleNameLookupTable -> Node Expression -> Bool - , description : Description - } - } + -> EmptiableProperties (TypeSubsetProperties empty) otherProperties -> CheckInfo -> Maybe (Error {}) callOnEmptyReturnsCheck config collection checkInfo = @@ -9504,6 +9082,67 @@ callOnEmptyReturnsCheck config collection checkInfo = Nothing +{-| The operation is equivalent to identity when applied on an empty value: + + operation << empty --> empty + +Examples + + Json.Decode.map f << Json.Decode.fail + --> Json.Decode.fail + + Task.mapError << Task.succeed + --> Task.succeed + +Use together with `unnecessaryCallOnEmptyCheck` + +-} +unnecessaryCompositionAfterEmptyCheck : + EmptiableProperties ConstructWithOneArgProperties otherProperties + -> CompositionIntoCheckInfo + -> Maybe ErrorInfoAndFix +unnecessaryCompositionAfterEmptyCheck emptiable = + unnecessaryCompositionAfterCheck emptiable.empty + + +unnecessaryCompositionAfterCheck : + { construct + | description : Description + , fn : ( ModuleName, String ) + } + -> CompositionIntoCheckInfo + -> Maybe ErrorInfoAndFix +unnecessaryCompositionAfterCheck construct checkInfo = + if onlyLastArgIsCurried checkInfo.later && (checkInfo.earlier.fn == construct.fn) then + Just + { info = + { message = qualifiedToString checkInfo.later.fn ++ " on " ++ descriptionForIndefinite construct.description ++ " will result in " ++ descriptionForDefinite "the unchanged" construct.description + , details = [ "You can replace this composition by " ++ qualifiedToString (qualify construct.fn checkInfo) ++ "." ] + } + , fix = + [ Fix.removeRange checkInfo.later.removeRange ] + } + + else + Nothing + + +{-| The last argument of a fully applied function (the given `argCount` specifies what is considered "fully applied"). + +For example, `fullyAppliedLastArg` on `Array.set 3 "Hitagi"` would return `Nothing` +while `fullyAppliedLastArg` on `Array.set 3 "Hitagi" arr` would return `Just arr`. + +-} +fullyAppliedLastArg : { callInfo | firstArg : Node Expression, argsAfterFirst : List (Node Expression), argCount : Int } -> Maybe (Node Expression) +fullyAppliedLastArg callInfo = + List.drop (callInfo.argCount - 1) (callInfo.firstArg :: callInfo.argsAfterFirst) |> List.head + + +onlyLastArgIsCurried : { function | args : List (Node Expression), argCount : Int } -> Bool +onlyLastArgIsCurried functionInfo = + List.length functionInfo.args == (functionInfo.argCount - 1) + + {-| This operation is equivalent to identity when called on a wrapped value. operation (wrap a) --> a @@ -9517,13 +9156,13 @@ For example Use together with `onWrapAlwaysReturnsIncomingCompositionCheck` -} -callOnWrapReturnsItsValue : +callOnWrapReturnsItsValueCheck : { otherProperties | wrap : ConstructWithOneArgProperties } -> CheckInfo -> Maybe (Error {}) -callOnWrapReturnsItsValue wrapper checkInfo = +callOnWrapReturnsItsValueCheck wrapper checkInfo = case fullyAppliedLastArg checkInfo of Just wrapperArg -> case sameInAllBranches (getValueWithNodeRange (wrapper.wrap.getValue checkInfo.lookupTable)) wrapperArg of @@ -9556,12 +9195,12 @@ For example Cmd.batch << List.singleton --> identity -Use together with `callOnWrapReturnsItsValue`. +Use together with `callOnWrapReturnsItsValueCheck`. -} -onWrapAlwaysReturnsIncomingCompositionCheck : { operationArgCount : Int } -> WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -onWrapAlwaysReturnsIncomingCompositionCheck config wrapper checkInfo = - if (checkInfo.earlier.fn == wrapper.wrap.fn) && (List.length checkInfo.later.args == (config.operationArgCount - 1)) then +onWrapAlwaysReturnsIncomingCompositionCheck : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix +onWrapAlwaysReturnsIncomingCompositionCheck wrapper checkInfo = + if onlyLastArgIsCurried checkInfo.later && (checkInfo.earlier.fn == wrapper.wrap.fn) then Just (compositionAlwaysReturnsIncomingError (qualifiedToString (qualify checkInfo.later.fn defaultQualifyResources) ++ " on " ++ descriptionForIndefinite wrapper.wrap.description ++ " will always result in the value inside") @@ -9630,9 +9269,9 @@ For example Use together with `callOnWrapReturnsJustItsValue`. -} -onWrapAlwaysReturnsJustIncomingCompositionCheck : { operationArgCount : Int } -> WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix -onWrapAlwaysReturnsJustIncomingCompositionCheck config wrapper checkInfo = - if (checkInfo.earlier.fn == wrapper.wrap.fn) && (List.length checkInfo.later.args == (config.operationArgCount - 1)) then +onWrapAlwaysReturnsJustIncomingCompositionCheck : WrapperProperties otherProperties -> CompositionIntoCheckInfo -> Maybe ErrorInfoAndFix +onWrapAlwaysReturnsJustIncomingCompositionCheck wrapper checkInfo = + if onlyLastArgIsCurried checkInfo.later && (checkInfo.earlier.fn == wrapper.wrap.fn) then Just { info = { message = qualifiedToString checkInfo.later.fn ++ " on " ++ descriptionForIndefinite wrapper.wrap.description ++ " will always result in Just the value inside" @@ -9645,11 +9284,11 @@ onWrapAlwaysReturnsJustIncomingCompositionCheck config wrapper checkInfo = Nothing -emptiableFilterChecks : EmptiableProperties otherProperties -> CheckInfo -> Maybe (Error {}) -emptiableFilterChecks emptiable checkInfo = +emptiableFilterChecks : TypeProperties (EmptiableProperties ConstantProperties otherProperties) -> CheckInfo -> Maybe (Error {}) +emptiableFilterChecks emptiable = firstThatConstructsJust - [ \() -> callOnEmptyReturnsEmptyCheck emptiable checkInfo - , \() -> + [ unnecessaryCallOnEmptyCheck emptiable + , \checkInfo -> case Evaluate.isAlwaysBoolean checkInfo checkInfo.firstArg of Determined True -> Just @@ -9670,18 +9309,17 @@ emptiableFilterChecks emptiable checkInfo = Undetermined -> Nothing ] - () -collectionRemoveChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) -collectionRemoveChecks collection checkInfo = - callOnEmptyReturnsEmptyCheck collection checkInfo +collectionRemoveChecks : CollectionProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) -> CheckInfo -> Maybe (Error {}) +collectionRemoveChecks collection = + unnecessaryCallOnEmptyCheck collection -collectionIntersectChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) -collectionIntersectChecks collection checkInfo = +collectionIntersectChecks : CollectionProperties (EmptiableProperties ConstantProperties otherProperties) -> CheckInfo -> Maybe (Error {}) +collectionIntersectChecks collection = firstThatConstructsJust - [ \() -> + [ \checkInfo -> if collection.empty.is checkInfo.lookupTable checkInfo.firstArg then Just (alwaysResultsInUnparenthesizedConstantError @@ -9692,42 +9330,32 @@ collectionIntersectChecks collection checkInfo = else Nothing - , \() -> callOnEmptyReturnsEmptyCheck collection checkInfo + , unnecessaryCallOnEmptyCheck collection ] - () -collectionDiffChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) -collectionDiffChecks collection checkInfo = - let - maybeCollectionArg : Maybe (Node Expression) - maybeCollectionArg = - secondArg checkInfo - - collectionEmptyAsString : String - collectionEmptyAsString = - emptyAsString checkInfo collection - in +collectionDiffChecks : TypeProperties (CollectionProperties (EmptiableProperties ConstantProperties otherProperties)) -> CheckInfo -> Maybe (Error {}) +collectionDiffChecks collection = firstThatConstructsJust - [ \() -> + [ \checkInfo -> if collection.empty.is checkInfo.lookupTable checkInfo.firstArg then Just (alwaysResultsInUnparenthesizedConstantError - (qualifiedToString checkInfo.fn ++ " on " ++ collectionEmptyAsString) + (qualifiedToString checkInfo.fn ++ " " ++ emptyAsString checkInfo collection) { replacement = collection.empty.asString } checkInfo ) else Nothing - , \() -> - case maybeCollectionArg of + , \checkInfo -> + case secondArg checkInfo of Just collectionArg -> if collection.empty.is checkInfo.lookupTable collectionArg then Just (Rule.errorWithFix - { message = "Diffing a " ++ collection.represents ++ " with " ++ collectionEmptyAsString ++ " will result in the " ++ collection.represents ++ " itself" - , details = [ "You can replace this call by the " ++ collection.represents ++ " itself." ] + { message = "Unnecessary " ++ qualifiedToString checkInfo.fn ++ " with " ++ emptyAsString checkInfo collection + , details = [ "You can replace this call by the given first " ++ collection.represents ++ "." ] } checkInfo.fnRange (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg }) @@ -9739,13 +9367,12 @@ collectionDiffChecks collection checkInfo = Nothing -> Nothing ] - () -collectionUnionChecks : CollectionProperties (FromListProperties otherProperties) -> CheckInfo -> Maybe (Error {}) -collectionUnionChecks collection checkInfo = +collectionUnionChecks : TypeProperties (CollectionProperties (FromListProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties))) -> CheckInfo -> Maybe (Error {}) +collectionUnionChecks collection = firstThatConstructsJust - [ \() -> + [ \checkInfo -> if collection.empty.is checkInfo.lookupTable checkInfo.firstArg then Just (alwaysReturnsLastArgError @@ -9756,68 +9383,68 @@ collectionUnionChecks collection checkInfo = else Nothing - , \() -> + , \checkInfo -> case secondArg checkInfo of Just secondArg_ -> if collection.empty.is checkInfo.lookupTable secondArg_ then Just (Rule.errorWithFix { message = "Unnecessary " ++ qualifiedToString (qualify checkInfo.fn defaultQualifyResources) ++ " with " ++ descriptionForIndefinite collection.empty.description - , details = [ "You can replace this call by the " ++ collection.represents ++ " itself." ] + , details = [ "You can replace this call by the given first " ++ collection.represents ++ "." ] } checkInfo.fnRange (keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg }) ) else - collectionUnionWithLiteralsChecks collection - { lookupTable = checkInfo.lookupTable - , extractSourceCode = checkInfo.extractSourceCode - , parentRange = checkInfo.parentRange - , first = checkInfo.firstArg + collectionUnionWithLiteralsChecks + { first = checkInfo.firstArg , second = secondArg_ , operationRange = checkInfo.fnRange , operation = qualifiedToString (qualify checkInfo.fn defaultQualifyResources) } + collection + checkInfo Nothing -> Nothing ] - () collectionUnionWithLiteralsChecks : - CollectionProperties (FromListProperties otherProperties) + { first : Node Expression + , second : Node Expression + , operationRange : Range + , operation : String + } + -> CollectionProperties (FromListProperties otherProperties) -> - { lookupTable : ModuleNameLookupTable - , extractSourceCode : Range -> String - , parentRange : Range - , first : Node Expression - , second : Node Expression - , operationRange : Range - , operation : String + { checkInfo + | lookupTable : ModuleNameLookupTable + , extractSourceCode : Range -> String + , parentRange : Range } -> Maybe (Error {}) -collectionUnionWithLiteralsChecks collection checkInfo = - case collection.fromListLiteralRange checkInfo.lookupTable checkInfo.second of +collectionUnionWithLiteralsChecks operationInfo collection checkInfo = + case collection.fromListLiteral.getListRange checkInfo.lookupTable operationInfo.second of Just literalListRangeSecond -> - case collection.fromListLiteralRange checkInfo.lookupTable checkInfo.first of + case collection.fromListLiteral.getListRange checkInfo.lookupTable operationInfo.first of Just literalListRangeFirst -> Just (Rule.errorWithFix - { message = checkInfo.operation ++ " on " ++ collection.fromListLiteralDescription ++ "s can be turned into a single " ++ collection.fromListLiteralDescription - , details = [ "Try moving all the elements into a single " ++ collection.fromListLiteralDescription ++ "." ] + { message = operationInfo.operation ++ " on " ++ collection.fromListLiteral.description ++ "s can be turned into a single " ++ collection.fromListLiteral.description + , details = [ "Try moving all the elements into a single " ++ collection.fromListLiteral.description ++ "." ] } - checkInfo.operationRange - (if collection.literalUnionLeftElementsStayOnTheLeft then - keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.second } + operationInfo.operationRange + (if collection.unionLeftElementsStayOnTheLeft then + keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range operationInfo.second } ++ [ Fix.insertAt (rangeWithoutBoundaries literalListRangeSecond).start (checkInfo.extractSourceCode (rangeWithoutBoundaries literalListRangeFirst) ++ ",") ] else - keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.first } + keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range operationInfo.first } ++ [ Fix.insertAt (rangeWithoutBoundaries literalListRangeFirst).start (checkInfo.extractSourceCode (rangeWithoutBoundaries literalListRangeSecond) ++ ",") @@ -9832,7 +9459,7 @@ collectionUnionWithLiteralsChecks collection checkInfo = Nothing -collectionInsertChecks : CollectionProperties (WrapperProperties otherProperties) -> CheckInfo -> Maybe (Error {}) +collectionInsertChecks : CollectionProperties (EmptiableProperties (TypeSubsetProperties empty) (WrapperProperties otherProperties)) -> CheckInfo -> Maybe (Error {}) collectionInsertChecks collection checkInfo = case secondArg checkInfo of Just collectionArg -> @@ -9857,17 +9484,16 @@ collectionInsertChecks collection checkInfo = Nothing -collectionMemberChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) -collectionMemberChecks collection checkInfo = +collectionMemberChecks : CollectionProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties) -> CheckInfo -> Maybe (Error {}) +collectionMemberChecks collection = callOnEmptyReturnsCheck { resultAsString = \res -> qualifiedToString (qualify ( [ "Basics" ], "False" ) res) } collection - checkInfo -collectionIsEmptyChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) +collectionIsEmptyChecks : TypeProperties (CollectionProperties (EmptiableProperties (TypeSubsetProperties empty) otherProperties)) -> CheckInfo -> Maybe (Error {}) collectionIsEmptyChecks collection checkInfo = - case collection.determineSize (extractInferResources checkInfo) checkInfo.firstArg of + case collection.size.determine (extractInferResources checkInfo) checkInfo.firstArg of Just (Exactly 0) -> Just (resultsInConstantError @@ -9888,14 +9514,14 @@ collectionIsEmptyChecks collection checkInfo = Nothing -collectionSizeChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) +collectionSizeChecks : TypeProperties (CollectionProperties otherProperties) -> CheckInfo -> Maybe (Error {}) collectionSizeChecks collection checkInfo = - case collection.determineSize (extractInferResources checkInfo) checkInfo.firstArg of + case collection.size.determine (extractInferResources checkInfo) checkInfo.firstArg of Just (Exactly size) -> Just (Rule.errorWithFix - { message = "The " ++ collection.nameForSize ++ " of the " ++ collection.represents ++ " is " ++ String.fromInt size - , details = [ "The " ++ collection.nameForSize ++ " of the " ++ collection.represents ++ " can be determined by looking at the code." ] + { message = "The " ++ collection.size.description ++ " of the " ++ collection.represents ++ " is " ++ String.fromInt size + , details = [ "The " ++ collection.size.description ++ " of the " ++ collection.represents ++ " can be determined by looking at the code." ] } checkInfo.fnRange [ Fix.replaceRangeBy checkInfo.parentRange (String.fromInt size) ] @@ -9905,11 +9531,9 @@ collectionSizeChecks collection checkInfo = Nothing -collectionFromListChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) -collectionFromListChecks collection checkInfo = - callOnEmptyReturnsCheck { resultAsString = collection.empty.asString } - listCollection - checkInfo +collectionFromListChecks : CollectionProperties (EmptiableProperties ConstantProperties otherProperties) -> CheckInfo -> Maybe (Error {}) +collectionFromListChecks collection = + callOnEmptyReturnsCheck { resultAsString = collection.empty.asString } listCollection wrapperFromListSingletonChecks : WrapperProperties otherProperties -> CheckInfo -> Maybe (Error {}) @@ -9949,33 +9573,20 @@ wrapperFromListSingletonCompositionChecks wrapper checkInfo = emptiableToListChecks : - { otherProperties - | empty : - { empty - | is : ModuleNameLookupTable -> Node Expression -> Bool - , description : Description - } - } + EmptiableProperties (TypeSubsetProperties empty) otherProperties -> CheckInfo -> Maybe (Error {}) -emptiableToListChecks collection checkInfo = - callOnEmptyReturnsCheck { resultAsString = \_ -> "[]" } collection checkInfo +emptiableToListChecks collection = + callOnEmptyReturnsCheck { resultAsString = listCollection.empty.asString } collection -collectionPartitionChecks : CollectionProperties otherProperties -> CheckInfo -> Maybe (Error {}) -collectionPartitionChecks collection checkInfo = - let - collectionEmptyAsString : String - collectionEmptyAsString = - emptyAsString checkInfo collection - in +collectionPartitionChecks : TypeProperties (CollectionProperties (EmptiableProperties ConstantProperties otherProperties)) -> CheckInfo -> Maybe (Error {}) +collectionPartitionChecks collection = firstThatConstructsJust - [ \() -> - callOnEmptyReturnsCheck - { resultAsString = \res -> "( " ++ collection.empty.asString res ++ ", " ++ collection.empty.asString res ++ " )" } - collection - checkInfo - , \() -> + [ callOnEmptyReturnsCheck + { resultAsString = \res -> "( " ++ collection.empty.asString res ++ ", " ++ collection.empty.asString res ++ " )" } + collection + , \checkInfo -> case Evaluate.isAlwaysBoolean checkInfo checkInfo.firstArg of Determined True -> case secondArg checkInfo of @@ -9987,7 +9598,7 @@ collectionPartitionChecks collection checkInfo = } checkInfo.fnRange [ Fix.replaceRangeBy { start = checkInfo.fnRange.start, end = listArgRange.start } "( " - , Fix.insertAt listArgRange.end (", " ++ collectionEmptyAsString ++ " )") + , Fix.insertAt listArgRange.end (", " ++ emptyAsString checkInfo collection ++ " )") ] ) @@ -10003,7 +9614,7 @@ collectionPartitionChecks collection checkInfo = checkInfo.fnRange (case secondArg checkInfo of Just listArg -> - [ Fix.replaceRangeBy { start = checkInfo.fnRange.start, end = (Node.range listArg).start } ("( " ++ collectionEmptyAsString ++ ", ") + [ Fix.replaceRangeBy { start = checkInfo.fnRange.start, end = (Node.range listArg).start } ("( " ++ emptyAsString checkInfo collection ++ ", ") , Fix.insertAt (Node.range listArg).end " )" ] @@ -10012,7 +9623,7 @@ collectionPartitionChecks collection checkInfo = ("(" ++ qualifiedToString (qualify ( [ "Tuple" ], "pair" ) checkInfo) ++ " " - ++ collectionEmptyAsString + ++ emptyAsString checkInfo collection ++ ")" ) ] @@ -10022,7 +9633,6 @@ collectionPartitionChecks collection checkInfo = Undetermined -> Nothing ] - () type CollectionSize @@ -10148,9 +9758,9 @@ targetIfKeyword ifExpressionRange = ifChecks : IfCheckInfo -> Maybe { errors : Error {}, rangesToIgnore : RangeDict () } -ifChecks checkInfo = +ifChecks = firstThatConstructsJust - [ \() -> + [ \checkInfo -> case Evaluate.getBoolean checkInfo checkInfo.condition of Determined determinedConditionResultIsTrue -> let @@ -10175,7 +9785,7 @@ ifChecks checkInfo = Undetermined -> Nothing - , \() -> + , \checkInfo -> case ( Evaluate.getBoolean checkInfo checkInfo.trueBranch, Evaluate.getBoolean checkInfo checkInfo.falseBranch ) of ( Determined True, Determined False ) -> Just @@ -10207,7 +9817,7 @@ ifChecks checkInfo = _ -> Nothing - , \() -> + , \checkInfo -> case Normalize.compare checkInfo checkInfo.trueBranch checkInfo.falseBranch of Normalize.ConfirmedEquality -> Just @@ -10224,7 +9834,6 @@ ifChecks checkInfo = _ -> Nothing ] - () @@ -10761,6 +10370,33 @@ rangeBetweenExclusive ( aRange, bRange ) = { start = aRange.end, end = bRange.start } +{-| Takes the ranges of two neighboring elements and +returns a range that includes the specified element and everything between them. + +This is useful when you can't use `replaceBySubExpressionFix` and `keepOnlyFix` because there is no +existing node that could be kept. + +For example, you might want to remove `|> identity` in `f |> g |> identity`. `elm-syntax` might represent this as (simplified) + + Op (Var "f") "|>" (Op (Var "g") "|>" (Var "identity")) + +In practice, you will check this syntax tree recursively, leading to situations where we only know + + - the previous/next element which we want to keep + - and the current element which we want to remove + +-} +andBetweenRange : { excluded : Range, included : Range } -> Range +andBetweenRange ranges = + case Range.compare ranges.excluded ranges.included of + LT -> + { start = ranges.excluded.end, end = ranges.included.end } + + -- GT | EQ -> + _ -> + { start = ranges.included.start, end = ranges.excluded.start } + + rangeContainsLocation : Location -> Range -> Bool rangeContainsLocation location = \range -> @@ -10789,9 +10425,7 @@ endWithoutBoundary range = removeBoundariesFix : Node a -> List Fix removeBoundariesFix (Node nodeRange _) = - [ Fix.removeRange (leftBoundaryRange nodeRange) - , Fix.removeRange (rightBoundaryRange nodeRange) - ] + keepOnlyFix { parentRange = nodeRange, keep = rangeWithoutBoundaries nodeRange } leftBoundaryRange : Range -> Range @@ -10801,13 +10435,6 @@ leftBoundaryRange range = } -rightBoundaryRange : Range -> Range -rightBoundaryRange range = - { start = { row = range.end.row, column = range.end.column - 1 } - , end = range.end - } - - {-| Shortcut for `alwaysResultsInConstantError` with `replacementNeedsParens = False`. If you want to replace to something like `Just []`, @@ -10819,12 +10446,11 @@ alwaysResultsInUnparenthesizedConstantError : -> { replacement : QualifyResources {} -> String } -> CheckInfo -> Error {} -alwaysResultsInUnparenthesizedConstantError usingSituation config checkInfo = +alwaysResultsInUnparenthesizedConstantError usingSituation config = alwaysResultsInConstantError usingSituation { replacement = config.replacement , replacementNeedsParens = False } - checkInfo {-| Regardless of what the next incoming value will be, the result is already determined to be a given constant. @@ -10917,13 +10543,13 @@ Use `returnsArgError` with the given last arg as `arg` when the last arg is alre -} alwaysReturnsLastArgError : String - -> { b | represents : String } - -> QualifyResources { a | fnRange : Range, parentRange : Range, argCount : Int, argsAfterFirst : List (Node Expression) } + -> TypeProperties otherProperties + -> QualifyResources { a | fnRange : Range, parentRange : Range, argCount : Int, firstArg : Node Expression, argsAfterFirst : List (Node Expression) } -> Error {} -alwaysReturnsLastArgError usingSpecificSituation config checkInfo = - case List.drop (checkInfo.argCount - 2) checkInfo.argsAfterFirst |> List.head of +alwaysReturnsLastArgError usingSpecificSituation lastArgProperties checkInfo = + case fullyAppliedLastArg checkInfo of Just lastArg -> - returnsArgError usingSpecificSituation { arg = lastArg, argRepresents = config.represents } checkInfo + returnsArgError usingSpecificSituation { arg = lastArg, argRepresents = lastArgProperties.represents } checkInfo Nothing -> -- Not enough arguments @@ -10949,12 +10575,12 @@ alwaysReturnsLastArgError usingSpecificSituation config checkInfo = _ -> -- Use-case is absent for now - { description = "the " ++ config.represents ++ " argument" + { description = "the " ++ lastArgProperties.represents ++ " argument" , fix = [] } in Rule.errorWithFix - { message = usingSpecificSituation ++ " will always return the same given " ++ config.represents + { message = usingSpecificSituation ++ " will always return the same given " ++ lastArgProperties.represents , details = [ "You can replace this call by " ++ replacement.description ++ "." ] } @@ -11145,7 +10771,7 @@ constructs : -> Node Expression -> Match specific constructs getSpecific lookupTable expressionNode = - case AstHelpers.getSpecificFunctionCall ( [ "Basics" ], "always" ) lookupTable expressionNode of + case AstHelpers.getSpecificFnCall ( [ "Basics" ], "always" ) lookupTable expressionNode of Just alwaysCall -> getSpecific alwaysCall.firstArg diff --git a/src/Simplify/AstHelpers.elm b/src/Simplify/AstHelpers.elm index 38aa57db4..15eb2049e 100644 --- a/src/Simplify/AstHelpers.elm +++ b/src/Simplify/AstHelpers.elm @@ -1,7 +1,7 @@ module Simplify.AstHelpers exposing ( removeParens, removeParensFromPattern - , getValueOrFunctionOrFunctionCall - , getSpecificFunctionCall, getSpecificValueOrFunction + , getValueOrFnOrFnCall + , getSpecificFnCall, getSpecificValueOrFn , isIdentity, getAlwaysResult, isSpecificUnappliedBinaryOperation , isTupleFirstAccess, isTupleSecondAccess , getOrder, getSpecificBool, getBool, getBoolPattern, getUncomputedNumberValue @@ -23,8 +23,8 @@ module Simplify.AstHelpers exposing ### value/function/function call/composition -@docs getValueOrFunctionOrFunctionCall -@docs getSpecificFunctionCall, getSpecificValueOrFunction +@docs getValueOrFnOrFnCall +@docs getSpecificFnCall, getSpecificValueOrFn ### certain kind @@ -103,7 +103,7 @@ getListSingleton lookupTable expressionNode = Nothing Nothing -> - case getSpecificFunctionCall ( [ "List" ], "singleton" ) lookupTable expressionNode of + case getSpecificFnCall ( [ "List" ], "singleton" ) lookupTable expressionNode of Just singletonCall -> case singletonCall.argsAfterFirst of [] -> @@ -118,7 +118,7 @@ getListSingleton lookupTable expressionNode = {-| Parses calls and lambdas that are reducible to a call of a function with the given name -} -getSpecificFunctionCall : +getSpecificFnCall : ( ModuleName, String ) -> ModuleNameLookupTable -> Node Expression @@ -129,8 +129,8 @@ getSpecificFunctionCall : , firstArg : Node Expression , argsAfterFirst : List (Node Expression) } -getSpecificFunctionCall ( moduleName, name ) lookupTable expressionNode = - case getValueOrFunctionOrFunctionCall expressionNode of +getSpecificFnCall ( moduleName, name ) lookupTable expressionNode = + case getValueOrFnOrFnCall expressionNode of Just call -> case call.args of firstArg :: argsAfterFirst -> @@ -157,7 +157,7 @@ getSpecificFunctionCall ( moduleName, name ) lookupTable expressionNode = {-| Parse a value or the collapsed function or a lambda fully reduced to a function -} -getValueOrFunctionOrFunctionCall : +getValueOrFnOrFnCall : Node Expression -> Maybe @@ -166,7 +166,7 @@ getValueOrFunctionOrFunctionCall : , fnRange : Range , args : List (Node Expression) } -getValueOrFunctionOrFunctionCall expressionNode = +getValueOrFnOrFnCall expressionNode = case getCollapsedUnreducedValueOrFunctionCall expressionNode of Just valueOrCall -> Just valueOrCall @@ -194,8 +194,8 @@ getValueOrFunctionOrFunctionCall expressionNode = a function reference with the given name without arguments or a lambda that is reducible to a function with the given name without arguments -} -getSpecificValueOrFunction : ( ModuleName, String ) -> ModuleNameLookupTable -> Node Expression -> Maybe Range -getSpecificValueOrFunction ( moduleName, name ) lookupTable expressionNode = +getSpecificValueOrFn : ( ModuleName, String ) -> ModuleNameLookupTable -> Node Expression -> Maybe Range +getSpecificValueOrFn ( moduleName, name ) lookupTable expressionNode = case getValueOrFunction expressionNode of Just normalFn -> if @@ -300,7 +300,7 @@ Either a function reducible to `Tuple.first` or `\( first, ... ) -> first`. -} isTupleFirstAccess : ModuleNameLookupTable -> Node Expression -> Bool isTupleFirstAccess lookupTable expressionNode = - case getSpecificValueOrFunction ( [ "Tuple" ], "first" ) lookupTable expressionNode of + case getSpecificValueOrFn ( [ "Tuple" ], "first" ) lookupTable expressionNode of Just _ -> True @@ -328,7 +328,7 @@ Either a function reducible to `Tuple.second` or `\( ..., second ) -> second`. -} isTupleSecondAccess : ModuleNameLookupTable -> Node Expression -> Bool isTupleSecondAccess lookupTable expressionNode = - case getSpecificValueOrFunction ( [ "Tuple" ], "second" ) lookupTable expressionNode of + case getSpecificValueOrFn ( [ "Tuple" ], "second" ) lookupTable expressionNode of Just _ -> True @@ -375,7 +375,7 @@ Either a function reducible to `Basics.identity` or `\a -> a`. -} isIdentity : ModuleNameLookupTable -> Node Expression -> Bool isIdentity lookupTable baseExpressionNode = - case getSpecificValueOrFunction ( [ "Basics" ], "identity" ) lookupTable baseExpressionNode of + case getSpecificValueOrFn ( [ "Basics" ], "identity" ) lookupTable baseExpressionNode of Just _ -> True @@ -398,7 +398,7 @@ Either a function reducible to `Basics.always x`, `\_ -> x` or even for example -} getAlwaysResult : ModuleNameLookupTable -> Node Expression -> Maybe (Node Expression) getAlwaysResult lookupTable expressionNode = - case getSpecificFunctionCall ( [ "Basics" ], "always" ) lookupTable expressionNode of + case getSpecificFnCall ( [ "Basics" ], "always" ) lookupTable expressionNode of Just alwaysCall -> Just alwaysCall.firstArg @@ -691,7 +691,7 @@ getBool lookupTable expressionNode = getSpecificBool : Bool -> ModuleNameLookupTable -> Node Expression -> Maybe Range getSpecificBool specificBool lookupTable expressionNode = - getSpecificValueOrFunction ( [ "Basics" ], boolToString specificBool ) lookupTable expressionNode + getSpecificValueOrFn ( [ "Basics" ], boolToString specificBool ) lookupTable expressionNode getTuple2Literal : Node Expression -> Maybe { range : Range, first : Node Expression, second : Node Expression } @@ -711,7 +711,7 @@ getTuple2 expressionNode lookupTable = Just { first = first, second = second } _ -> - case getSpecificFunctionCall ( [ "Tuple" ], "pair" ) lookupTable expressionNode of + case getSpecificFnCall ( [ "Tuple" ], "pair" ) lookupTable expressionNode of Just tuplePairCall -> case tuplePairCall.argsAfterFirst of second :: _ -> @@ -754,7 +754,7 @@ getBoolPattern lookupTable basePatternNode = getSpecificOrder : Order -> ModuleNameLookupTable -> Node Expression -> Maybe Range getSpecificOrder specificOrder lookupTable expression = - getSpecificValueOrFunction ( [ "Basics" ], orderToString specificOrder ) lookupTable expression + getSpecificValueOrFn ( [ "Basics" ], orderToString specificOrder ) lookupTable expression getOrder : ModuleNameLookupTable -> Node Expression -> Maybe Order diff --git a/tests/SimplifyTest.elm b/tests/SimplifyTest.elm index 0ae8f2c94..19945dc03 100644 --- a/tests/SimplifyTest.elm +++ b/tests/SimplifyTest.elm @@ -1052,34 +1052,12 @@ booleanTests = ] -alwaysSameDetails : List String -alwaysSameDetails = - [ "This condition will always result in the same value. You may have hardcoded a value or mistyped a condition." - ] - - -unnecessaryMessage : String -unnecessaryMessage = - "Part of the expression is unnecessary" - - -unnecessaryDetails : List String -unnecessaryDetails = - [ "A part of this condition is unnecessary. You can remove it and it would not impact the behavior of the program." - ] - - sameThingOnBothSidesDetails : String -> List String sameThingOnBothSidesDetails value = - [ "Based on the values and/or the context, we can determine that the value of this operation will always be " ++ value ++ "." + [ "Based on the values and/or the context, we can determine the result. You can replace this operation by " ++ value ++ "." ] -comparisonIsAlwaysMessage : String -> String -comparisonIsAlwaysMessage value = - "Comparison is always " ++ value - - orTests : Test orTests = describe "||" @@ -1098,15 +1076,18 @@ a = True || x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" - , details = alwaysSameDetails + { message = "(||) with any side being True will result in True" + , details = + [ "You can replace this operation by True." + , "Maybe you have hardcoded a value or mistyped a condition?" + ] , under = "True || x" } |> Review.Test.whenFixed """module A exposing (..) a = True """ ] - , test "should simplify 'x || True' to x" <| + , test "should simplify 'x || True' to True" <| \() -> """module A exposing (..) a = x || True @@ -1114,8 +1095,11 @@ a = x || True |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = unnecessaryMessage - , details = unnecessaryDetails + { message = "(||) with any side being True will result in True" + , details = + [ "You can replace this operation by True." + , "Maybe you have hardcoded a value or mistyped a condition?" + ] , under = "x || True" } |> Review.Test.whenFixed """module A exposing (..) @@ -1130,9 +1114,9 @@ a = False || x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = unnecessaryMessage - , details = unnecessaryDetails - , under = "False || x" + { message = "Unnecessary check for || False" + , details = [ "You can replace this operation by the right bool." ] + , under = "False ||" } |> Review.Test.whenFixed """module A exposing (..) a = x @@ -1146,9 +1130,9 @@ a = x || False |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = unnecessaryMessage - , details = unnecessaryDetails - , under = "x || False" + { message = "Unnecessary check for || False" + , details = [ "You can replace this operation by the left bool." ] + , under = "|| False" } |> Review.Test.whenFixed """module A exposing (..) a = x @@ -1162,9 +1146,9 @@ a = x || (False) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = unnecessaryMessage - , details = unnecessaryDetails - , under = "x || (False)" + { message = "Unnecessary check for || False" + , details = [ "You can replace this operation by the left bool." ] + , under = "|| (False)" } |> Review.Test.whenFixed """module A exposing (..) a = x @@ -1178,8 +1162,11 @@ a = (True) || x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" - , details = alwaysSameDetails + { message = "(||) with any side being True will result in True" + , details = + [ "You can replace this operation by True." + , "Maybe you have hardcoded a value or mistyped a condition?" + ] , under = "(True) || x" } |> Review.Test.whenFixed """module A exposing (..) @@ -1355,9 +1342,9 @@ a = True && x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = unnecessaryMessage - , details = unnecessaryDetails - , under = "True && x" + { message = "Unnecessary check for && True" + , details = [ "You can replace this operation by the right bool." ] + , under = "True &&" } |> Review.Test.whenFixed """module A exposing (..) a = x @@ -1371,9 +1358,9 @@ a = x && True |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = unnecessaryMessage - , details = unnecessaryDetails - , under = "x && True" + { message = "Unnecessary check for && True" + , details = [ "You can replace this operation by the left bool." ] + , under = "&& True" } |> Review.Test.whenFixed """module A exposing (..) a = x @@ -1387,8 +1374,11 @@ a = False && x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" - , details = alwaysSameDetails + { message = "(&&) with any side being False will result in False" + , details = + [ "You can replace this operation by False." + , "Maybe you have hardcoded a value or mistyped a condition?" + ] , under = "False && x" } |> Review.Test.whenFixed """module A exposing (..) @@ -1403,8 +1393,11 @@ a = x && False |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" - , details = alwaysSameDetails + { message = "(&&) with any side being False will result in False" + , details = + [ "You can replace this operation by False." + , "Maybe you have hardcoded a value or mistyped a condition?" + ] , under = "x && False" } |> Review.Test.whenFixed """module A exposing (..) @@ -1631,10 +1624,11 @@ a = not (not x) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double Basics.not" - , details = [ "Chaining Basics.not with Basics.not makes both functions cancel each other out." ] - , under = "not (not" + { message = "Basics.not, then Basics.not cancels each other out" + , details = [ "You can replace this call by the argument given to Basics.not." ] + , under = "not" } + |> Review.Test.atExactly { start = { row = 2, column = 5 }, end = { row = 2, column = 8 } } |> Review.Test.whenFixed """module A exposing (..) a = x """ @@ -1647,10 +1641,11 @@ a = x |> not |> not |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double Basics.not" - , details = [ "Chaining Basics.not with Basics.not makes both functions cancel each other out." ] - , under = "not |> not" + { message = "Basics.not, then Basics.not cancels each other out" + , details = [ "You can replace this call by the argument given to Basics.not." ] + , under = "not" } + |> Review.Test.atExactly { start = { row = 2, column = 17 }, end = { row = 2, column = 20 } } |> Review.Test.whenFixed """module A exposing (..) a = x """ @@ -1663,10 +1658,11 @@ a = (x |> not) |> not |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double Basics.not" - , details = [ "Chaining Basics.not with Basics.not makes both functions cancel each other out." ] - , under = "not) |> not" + { message = "Basics.not, then Basics.not cancels each other out" + , details = [ "You can replace this call by the argument given to Basics.not." ] + , under = "not" } + |> Review.Test.atExactly { start = { row = 2, column = 19 }, end = { row = 2, column = 22 } } |> Review.Test.whenFixed """module A exposing (..) a = x """ @@ -1679,10 +1675,11 @@ a = (not <| x) |> not |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double Basics.not" - , details = [ "Chaining Basics.not with Basics.not makes both functions cancel each other out." ] - , under = "not <| x) |> not" + { message = "Basics.not, then Basics.not cancels each other out" + , details = [ "You can replace this call by the argument given to Basics.not." ] + , under = "not" } + |> Review.Test.atExactly { start = { row = 2, column = 19 }, end = { row = 2, column = 22 } } |> Review.Test.whenFixed """module A exposing (..) a = x """ @@ -1695,10 +1692,11 @@ a = not x |> not |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double Basics.not" - , details = [ "Chaining Basics.not with Basics.not makes both functions cancel each other out." ] - , under = "not x |> not" + { message = "Basics.not, then Basics.not cancels each other out" + , details = [ "You can replace this call by the argument given to Basics.not." ] + , under = "not" } + |> Review.Test.atExactly { start = { row = 2, column = 14 }, end = { row = 2, column = 17 } } |> Review.Test.whenFixed """module A exposing (..) a = x """ @@ -1711,10 +1709,11 @@ a = not <| not x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double Basics.not" - , details = [ "Chaining Basics.not with Basics.not makes both functions cancel each other out." ] - , under = "not <| not" + { message = "Basics.not, then Basics.not cancels each other out" + , details = [ "You can replace this call by the argument given to Basics.not." ] + , under = "not" } + |> Review.Test.atExactly { start = { row = 2, column = 5 }, end = { row = 2, column = 8 } } |> Review.Test.whenFixed """module A exposing (..) a = x """ @@ -2223,8 +2222,8 @@ a = n + 0 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary addition with 0" - , details = [ "Adding 0 does not change the value of the number." ] + { message = "Unnecessary adding 0" + , details = [ "You can replace this operation by the left number you added 0 to." ] , under = "+ 0" } |> Review.Test.whenFixed """module A exposing (..) @@ -2239,8 +2238,8 @@ a = n + 0.0 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary addition with 0" - , details = [ "Adding 0 does not change the value of the number." ] + { message = "Unnecessary adding 0" + , details = [ "You can replace this operation by the left number you added 0 to." ] , under = "+ 0.0" } |> Review.Test.whenFixed """module A exposing (..) @@ -2255,8 +2254,8 @@ a = 0 + n |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary addition with 0" - , details = [ "Adding 0 does not change the value of the number." ] + { message = "Unnecessary adding 0" + , details = [ "You can replace this operation by the right number you added 0 to." ] , under = "0 +" } |> Review.Test.whenFixed """module A exposing (..) @@ -2271,8 +2270,8 @@ a = n + (-n) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Addition always results in 0" - , details = [ "These two expressions have an equal absolute value but an opposite sign. This means adding them they will cancel out to 0." ] + { message = "Adding opposite numbers will result in 0" + , details = [ "Adding two numbers with an equal absolute value and an opposite sign will cancel each other out. You can replace this operation by 0." ] , under = "n + (-n)" } |> Review.Test.whenFixed """module A exposing (..) @@ -2287,8 +2286,8 @@ a = -n + n |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Addition always results in 0" - , details = [ "These two expressions have an equal absolute value but an opposite sign. This means adding them they will cancel out to 0." ] + { message = "Adding opposite numbers will result in 0" + , details = [ "Adding two numbers with an equal absolute value and an opposite sign will cancel each other out. You can replace this operation by 0." ] , under = "-n + n" } |> Review.Test.whenFixed """module A exposing (..) @@ -2324,9 +2323,9 @@ a = n - 0 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary subtraction with 0" - , details = [ "Subtracting 0 does not change the value of the number." ] - , under = "- 0" + { message = "Unnecessary subtracting 0" + , details = [ "You can replace this operation by the left number you subtracted 0 from." ] + , under = "-" } |> Review.Test.whenFixed """module A exposing (..) a = n @@ -2340,9 +2339,9 @@ a = n - 0.0 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary subtraction with 0" - , details = [ "Subtracting 0 does not change the value of the number." ] - , under = "- 0.0" + { message = "Unnecessary subtracting 0" + , details = [ "You can replace this operation by the left number you subtracted 0 from." ] + , under = "-" } |> Review.Test.whenFixed """module A exposing (..) a = n @@ -2356,9 +2355,9 @@ a = 0 - n |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary subtracting from 0" - , details = [ "You can negate the expression on the right like `-n`." ] - , under = "0 -" + { message = "Subtracting from 0 is the same as negating" + , details = [ "You can replace this operation by the negated right number you subtracted from 0, like `-n`." ] + , under = "-" } |> Review.Test.whenFixed """module A exposing (..) a = -n @@ -2372,9 +2371,9 @@ a = 0 - List.length list |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary subtracting from 0" - , details = [ "You can negate the expression on the right like `-n`." ] - , under = "0 -" + { message = "Subtracting from 0 is the same as negating" + , details = [ "You can replace this operation by the negated right number you subtracted from 0, like `-n`." ] + , under = "-" } |> Review.Test.whenFixed """module A exposing (..) a = -(List.length list) @@ -2388,8 +2387,8 @@ a = n - n |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Subtraction always results in 0" - , details = [ "These two expressions have the same value, which means they will cancel add when subtracting one by the other." ] + { message = "Subtracting equal numbers will result in 0" + , details = [ "You can replace this operation by 0." ] , under = "n - n" } |> Review.Test.whenFixed """module A exposing (..) @@ -2425,8 +2424,8 @@ a = n * 1 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary multiplication by 1" - , details = [ "Multiplying by 1 does not change the value of the number." ] + { message = "Unnecessary multiplying by 1" + , details = [ "You can replace this operation by the left number you multiplied by 1." ] , under = "* 1" } |> Review.Test.whenFixed """module A exposing (..) @@ -2441,8 +2440,8 @@ a = n * 1.0 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary multiplication by 1" - , details = [ "Multiplying by 1 does not change the value of the number." ] + { message = "Unnecessary multiplying by 1" + , details = [ "You can replace this operation by the left number you multiplied by 1." ] , under = "* 1.0" } |> Review.Test.whenFixed """module A exposing (..) @@ -2457,8 +2456,8 @@ a = 1 * n |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary multiplication by 1" - , details = [ "Multiplying by 1 does not change the value of the number." ] + { message = "Unnecessary multiplying by 1" + , details = [ "You can replace this operation by the right number you multiplied by 1." ] , under = "1 *" } |> Review.Test.whenFixed """module A exposing (..) @@ -2619,9 +2618,9 @@ a = n / 1 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary division by 1" - , details = [ "Dividing by 1 does not change the value of the number." ] - , under = "/ 1" + { message = "Unnecessary dividing by 1" + , details = [ "You can replace this operation by the left number you divided by 1." ] + , under = "/" } |> Review.Test.whenFixed """module A exposing (..) a = n @@ -2635,9 +2634,9 @@ a = n / 1.0 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary division by 1" - , details = [ "Dividing by 1 does not change the value of the number." ] - , under = "/ 1.0" + { message = "Unnecessary dividing by 1" + , details = [ "You can replace this operation by the left number you divided by 1." ] + , under = "/" } |> Review.Test.whenFixed """module A exposing (..) a = n @@ -2651,12 +2650,12 @@ a = 0 / n |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Dividing 0 always returns 0" + { message = "Dividing 0 will result in 0" , details = [ "Dividing 0 by anything, even infinite numbers, gives 0 which means you can replace the whole division operation by 0." , "Most likely, dividing 0 was unintentional and you had a different number in mind." ] - , under = "0 /" + , under = "/" } |> Review.Test.whenFixed """module A exposing (..) a = 0 @@ -2670,12 +2669,12 @@ a = 0.0 / n |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Dividing 0 always returns 0" + { message = "Dividing 0 will result in 0" , details = [ "Dividing 0 by anything, even infinite numbers, gives 0 which means you can replace the whole division operation by 0." , "Most likely, dividing 0 was unintentional and you had a different number in mind." ] - , under = "0.0 /" + , under = "/" } |> Review.Test.whenFixed """module A exposing (..) a = 0.0 @@ -2776,8 +2775,8 @@ a = n // 1 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary division by 1" - , details = [ "Dividing by 1 using (//) does not change the value of the number." ] + { message = "Unnecessary dividing by 1" + , details = [ "You can replace this operation by the left integer you divided by 1." ] , under = "//" } |> Review.Test.whenFixed """module A exposing (..) @@ -2792,8 +2791,8 @@ a = n // m // 1 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary division by 1" - , details = [ "Dividing by 1 using (//) does not change the value of the number." ] + { message = "Unnecessary dividing by 1" + , details = [ "You can replace this operation by the left integer you divided by 1." ] , under = "//" } |> Review.Test.atExactly { start = { row = 2, column = 12 }, end = { row = 2, column = 14 } } @@ -2809,7 +2808,7 @@ a = 0 // n |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Dividing 0 always returns 0" + { message = "Dividing 0 will result in 0" , details = [ "Dividing 0 by anything using (//), even 0, gives 0 which means you can replace the whole division operation by 0." , "Most likely, dividing 0 was unintentional and you had a different number in mind." @@ -2828,7 +2827,7 @@ a = n // 0 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Dividing by 0 always returns 0" + { message = "Dividing by 0 will result in 0" , details = [ "Dividing anything by 0 using (//) gives 0 which means you can replace the whole division operation by 0." , "Most likely, dividing by 0 was unintentional and you had a different number in mind." @@ -3025,10 +3024,11 @@ a = negate <| negate x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double Basics.negate" - , details = [ "Chaining Basics.negate with Basics.negate makes both functions cancel each other out." ] - , under = "negate <| negate" + { message = "Basics.negate, then Basics.negate cancels each other out" + , details = [ "You can replace this call by the argument given to Basics.negate." ] + , under = "negate" } + |> Review.Test.atExactly { start = { row = 2, column = 5 }, end = { row = 2, column = 11 } } |> Review.Test.whenFixed """module A exposing (..) a = x """ @@ -3054,7 +3054,7 @@ a = 1 < 2 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = comparisonIsAlwaysMessage "True" + { message = "(<) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "1 < 2" } @@ -3070,7 +3070,7 @@ a = 1 < 2 + 3 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = comparisonIsAlwaysMessage "True" + { message = "(<) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "1 < 2 + 3" } @@ -3086,7 +3086,7 @@ a = 2 < 1 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = comparisonIsAlwaysMessage "False" + { message = "(<) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "2 < 1" } @@ -3102,7 +3102,7 @@ a = 1 > 2 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = comparisonIsAlwaysMessage "False" + { message = "(>) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "1 > 2" } @@ -3118,7 +3118,7 @@ a = 1 >= 2 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = comparisonIsAlwaysMessage "False" + { message = "(>=) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "1 >= 2" } @@ -3134,7 +3134,7 @@ a = 1 <= 2 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = comparisonIsAlwaysMessage "True" + { message = "(<=) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "1 <= 2" } @@ -3255,12 +3255,28 @@ a = not x == not y |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary negation on both sides" - , details = [ "Since both sides are negated using `not`, they are redundant and can be removed." ] - , under = "not x == not y" + { message = "Unnecessary `not` on both sides of (==)" + , details = [ "You can replace the bool on each side by the value given to `not`." ] + , under = "==" } |> Review.Test.whenFixed """module A exposing (..) -a = x == y +a = x == y +""" + ] + , test "should simplify (x |> f |> not) == (y |> g |> not) to (x |> f) == (y |> g)" <| + \() -> + """module A exposing (..) +a = (x |> f |> not) == (y |> g |> not) +""" + |> Review.Test.run ruleWithDefaults + |> Review.Test.expectErrors + [ Review.Test.error + { message = "Unnecessary `not` on both sides of (==)" + , details = [ "You can replace the bool on each side by the value given to `not`." ] + , under = "==" + } + |> Review.Test.whenFixed """module A exposing (..) +a = (x |> f) == (y |> g) """ ] , test "should simplify not x /= not y to x /= y" <| @@ -3271,12 +3287,12 @@ a = not x /= not y |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary negation on both sides" - , details = [ "Since both sides are negated using `not`, they are redundant and can be removed." ] - , under = "not x /= not y" + { message = "Unnecessary `not` on both sides of (/=)" + , details = [ "You can replace the bool on each side by the value given to `not`." ] + , under = "/=" } |> Review.Test.whenFixed """module A exposing (..) -a = x /= y +a = x /= y """ ] , test "should simplify x == x to True" <| @@ -3287,7 +3303,7 @@ a = x == x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "x == x" } @@ -3310,7 +3326,7 @@ a = x == (x) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "x == (x)" } @@ -3326,7 +3342,7 @@ a = x /= x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(/=) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "x /= x" } @@ -3342,7 +3358,7 @@ a = List.map (\\a -> a.value) things == List.map (\\a -> a.value) things |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "List.map (\\a -> a.value) things == List.map (\\a -> a.value) things" } @@ -3358,7 +3374,7 @@ a = (f b) == (f <| b) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(f b) == (f <| b)" } @@ -3374,7 +3390,7 @@ a = (f b c) == (f b <| c) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(f b c) == (f b <| c)" } @@ -3390,7 +3406,7 @@ a = (f b) == (b |> f) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(f b) == (b |> f)" } @@ -3406,7 +3422,7 @@ a = (f b c) == (c |> f b) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(f b c) == (c |> f b)" } @@ -3422,7 +3438,7 @@ a = (f b c) == (c |> (b |> f)) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(f b c) == (c |> (b |> f))" } @@ -3438,7 +3454,7 @@ a = (let x = 1 in f b c) == (c |> (let x = 1 in f b)) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(let x = 1 in f b c) == (c |> (let x = 1 in f b))" } @@ -3454,7 +3470,7 @@ a = (if cond then f b c else g d c) == (c |> (if cond then f b else g d)) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(if cond then f b c else g d c) == (c |> (if cond then f b else g d))" } @@ -3478,7 +3494,7 @@ a = (case x of |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = """(case x of X -> f b c @@ -3502,7 +3518,7 @@ a = (b.c) == (.c b) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(b.c) == (.c b)" } @@ -3518,7 +3534,7 @@ a = (b.c) == (.c <| b) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(b.c) == (.c <| b)" } @@ -3534,7 +3550,7 @@ a = "a" == "b" |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "\"a\" == \"b\"" } @@ -3550,7 +3566,7 @@ a = 'a' == 'b' |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "'a' == 'b'" } @@ -3566,7 +3582,7 @@ a = "a" /= "b" |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(/=) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "\"a\" /= \"b\"" } @@ -3582,7 +3598,7 @@ a = 1 == 2 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "1 == 2" } @@ -3598,7 +3614,7 @@ a = 1 == 2.0 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "1 == 2.0" } @@ -3614,7 +3630,7 @@ a = 1.0 == 2 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "1.0 == 2" } @@ -3630,7 +3646,7 @@ a = 0x10 == 2 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "0x10 == 2" } @@ -3646,7 +3662,7 @@ a = 1 + 3 == 2 + 5 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "1 + 3 == 2 + 5" } @@ -3662,7 +3678,7 @@ a = 1 - 3 == 2 - 5 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "1 - 3 == 2 - 5" } @@ -3678,7 +3694,7 @@ a = 2 * 3 == 2 * 5 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "2 * 3 == 2 * 5" } @@ -3694,7 +3710,7 @@ a = 1 / 3 == 2 / 5 |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "1 / 3 == 2 / 5" } @@ -3710,7 +3726,7 @@ a = () == x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "() == x" } @@ -3726,7 +3742,7 @@ a = x == () |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "x == ()" } @@ -3742,7 +3758,7 @@ a = [ 1 ] == [ 1, 1 ] |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "[ 1 ] == [ 1, 1 ]" } @@ -3758,7 +3774,7 @@ a = [ 1, 2 ] == [ 1, 1 ] |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "[ 1, 2 ] == [ 1, 1 ]" } @@ -3774,7 +3790,7 @@ a = [ 1, 2 - 1 ] == [ 1, 1 ] |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "[ 1, 2 - 1 ] == [ 1, 1 ]" } @@ -3790,7 +3806,7 @@ a = (1) == (2) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "(1) == (2)" } @@ -3806,7 +3822,7 @@ a = ( 1, 2 ) == ( 1, 1 ) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "( 1, 2 ) == ( 1, 1 )" } @@ -3822,7 +3838,7 @@ a = { a = 1, b = 2 } == { b = 1, a = 1 } |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "{ a = 1, b = 2 } == { b = 1, a = 1 }" } @@ -3838,7 +3854,7 @@ a = { x | a = 1 } == { x | a = 2 } |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "{ x | a = 1 } == { x | a = 2 }" } @@ -3854,7 +3870,7 @@ a = { x | a = 1 } == { x | a = 1 } |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "{ x | a = 1 } == { x | a = 1 }" } @@ -3877,7 +3893,7 @@ a = { x | a = 1 } == { y | a = 2 } |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "{ x | a = 1 } == { y | a = 2 }" } @@ -3918,7 +3934,7 @@ b = 1 |> Review.Test.expectErrorsForModules [ ( "A" , [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "B.b == b" } @@ -3938,7 +3954,7 @@ a = List.map fn 1 == map fn (2 - 1) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "List.map fn 1 == map fn (2 - 1)" } @@ -3963,7 +3979,7 @@ a = (if 1 then 2 else 3) == (if 2 - 1 then 3 - 1 else 4 - 1) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(if 1 then 2 else 3) == (if 2 - 1 then 3 - 1 else 4 - 1)" } @@ -3986,7 +4002,7 @@ a = -1 == -(2 - 1) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "-1 == -(2 - 1)" } @@ -4002,7 +4018,7 @@ a = ({ a = 1 }).a == ({ a = 2 - 1 }).a |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "({ a = 1 }).a == ({ a = 2 - 1 }).a" } @@ -4036,7 +4052,7 @@ a = (1 |> fn) == (2 - 1 |> fn) |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "(1 |> fn) == (2 - 1 |> fn)" } @@ -4412,9 +4428,9 @@ a = |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Part of the expression is unnecessary" - , details = [ "A part of this condition is unnecessary. You can remove it and it would not impact the behavior of the program." ] - , under = "x && y" + { message = "Unnecessary check for && True" + , details = [ "You can replace this operation by the right bool." ] + , under = "x &&" } |> Review.Test.whenFixed """module A exposing (..) a = @@ -4560,7 +4576,7 @@ a = |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "x == 1" } @@ -4591,7 +4607,7 @@ a = |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "x == \"b\"" } @@ -4622,7 +4638,7 @@ a = |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "item.name == \"Sulfuras, Hand of Ragnaros\"" } @@ -4652,7 +4668,7 @@ a = |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(==) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "x == 2" } @@ -4750,7 +4766,7 @@ a = |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always False" + { message = "(/=) comparison will result in False" , details = sameThingOnBothSidesDetails "False" , under = "x /= 1" } @@ -4779,7 +4795,7 @@ a = |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "x == 1" } @@ -4887,7 +4903,7 @@ a = |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Comparison is always True" + { message = "(==) comparison will result in True" , details = sameThingOnBothSidesDetails "True" , under = "x == 1" } @@ -4966,10 +4982,12 @@ a = """ |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors - -- TODO Order of the errors seem to matter here. Should be fixed in `elm-review` [ Review.Test.error - { message = "Comparison is always False" - , details = alwaysSameDetails + { message = "(&&) with any side being False will result in False" + , details = + [ "You can replace this operation by False." + , "Maybe you have hardcoded a value or mistyped a condition?" + ] , under = "a && b" } |> Review.Test.whenFixed """module A exposing (..) @@ -5596,8 +5614,8 @@ a = "a" ++ "" |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary concatenation with \"\"" - , details = [ "You should remove the concatenation with the empty string." ] + { message = "Unnecessary appending \"\"" + , details = [ "You can replace this operation by the left string." ] , under = "++" } |> Review.Test.whenFixed """module A exposing (..) @@ -5619,8 +5637,8 @@ a = "" ++ "a" |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary concatenation with \"\"" - , details = [ "You should remove the concatenation with the empty string." ] + { message = "Unnecessary appending \"\"" + , details = [ "You can replace this operation by the right string." ] , under = "++" } |> Review.Test.whenFixed """module A exposing (..) @@ -5667,8 +5685,8 @@ a = [] ++ something |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary concatenation with []" - , details = [ "You should remove the concatenation with the empty list." ] + { message = "Unnecessary appending []" + , details = [ "You can replace this operation by the right list." ] , under = "++" } |> Review.Test.whenFixed """module A exposing (..) @@ -5683,8 +5701,8 @@ a = something ++ [] |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary concatenation with []" - , details = [ "You should remove the concatenation with the empty list." ] + { message = "Unnecessary appending []" + , details = [ "You can replace this operation by the left list." ] , under = "++" } |> Review.Test.whenFixed """module A exposing (..) @@ -6884,7 +6902,7 @@ a = String.append string "" |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary String.append with \"\"" - , details = [ "You can replace this call by the string itself." ] + , details = [ "You can replace this call by the given first string." ] , under = "String.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -6900,7 +6918,7 @@ a = "" |> String.append string |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary String.append with \"\"" - , details = [ "You can replace this call by the string itself." ] + , details = [ "You can replace this call by the given first string." ] , under = "String.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -6916,7 +6934,7 @@ a = "" |> String.append string |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary String.append with \"\"" - , details = [ "You can replace this call by the string itself." ] + , details = [ "You can replace this call by the given first string." ] , under = "String.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -7139,8 +7157,8 @@ a = String.reverse << String.fromChar |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "String.reverse on a single-char string will result in the given string" - , details = [ "You can replace this call by String.fromChar." ] + { message = "String.reverse on a single-char string will result in the unchanged single-char string" + , details = [ "You can replace this composition by String.fromChar." ] , under = "String.reverse" } |> Review.Test.whenFixed """module A exposing (..) @@ -7155,8 +7173,8 @@ a = String.fromChar >> String.reverse |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "String.reverse on a single-char string will result in the given string" - , details = [ "You can replace this call by String.fromChar." ] + { message = "String.reverse on a single-char string will result in the unchanged single-char string" + , details = [ "You can replace this composition by String.fromChar." ] , under = "String.reverse" } |> Review.Test.whenFixed """module A exposing (..) @@ -7171,10 +7189,11 @@ a = String.reverse <| String.reverse <| x |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double String.reverse" - , details = [ "Chaining String.reverse with String.reverse makes both functions cancel each other out." ] - , under = "String.reverse <| String.reverse" + { message = "String.reverse, then String.reverse cancels each other out" + , details = [ "You can replace this call by the argument given to String.reverse." ] + , under = "String.reverse" } + |> Review.Test.atExactly { start = { row = 2, column = 5 }, end = { row = 2, column = 19 } } |> Review.Test.whenFixed """module A exposing (..) a = x """ @@ -8075,7 +8094,7 @@ a = List.append list [] |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary List.append with []" - , details = [ "You can replace this call by the list itself." ] + , details = [ "You can replace this call by the given first list." ] , under = "List.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -8091,7 +8110,7 @@ a = List.append list <| [] |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary List.append with []" - , details = [ "You can replace this call by the list itself." ] + , details = [ "You can replace this call by the given first list." ] , under = "List.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -8107,7 +8126,7 @@ a = [] |> List.append list |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary List.append with []" - , details = [ "You can replace this call by the list itself." ] + , details = [ "You can replace this call by the given first list." ] , under = "List.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -10689,6 +10708,22 @@ a = List.isEmpty (List.singleton x) } |> Review.Test.whenFixed """module A exposing (..) a = False +""" + ] + , test "should replace List.isEmpty (a |> List.singleton) by False" <| + \() -> + """module A exposing (..) +a = List.isEmpty (b |> List.singleton) +""" + |> Review.Test.run ruleWithDefaults + |> Review.Test.expectErrors + [ Review.Test.error + { message = "List.isEmpty on this list will result in False" + , details = [ "You can replace this call by False." ] + , under = "List.isEmpty" + } + |> Review.Test.whenFixed """module A exposing (..) +a = False """ ] ] @@ -13529,6 +13564,8 @@ listSortByTests = a = List.sortBy fn b = List.sortBy fn list c = List.sortBy << List.sortBy fn +c = List.sortBy f << List.sortWith g +c = List.sortBy f << List.sort """ |> Review.Test.run ruleWithDefaults |> Review.Test.expectNoErrors @@ -14151,8 +14188,8 @@ a = List.reverse << List.singleton |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "List.reverse on a singleton list will result in the given list" - , details = [ "You can replace this call by List.singleton." ] + { message = "List.reverse on a singleton list will result in the unchanged singleton list" + , details = [ "You can replace this composition by List.singleton." ] , under = "List.reverse" } |> Review.Test.whenFixed """module A exposing (..) @@ -14167,8 +14204,8 @@ a = List.singleton >> List.reverse |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "List.reverse on a singleton list will result in the given list" - , details = [ "You can replace this call by List.singleton." ] + { message = "List.reverse on a singleton list will result in the unchanged singleton list" + , details = [ "You can replace this composition by List.singleton." ] , under = "List.reverse" } |> Review.Test.whenFixed """module A exposing (..) @@ -14183,10 +14220,11 @@ a = List.reverse <| List.reverse <| list |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Unnecessary double List.reverse" - , details = [ "Chaining List.reverse with List.reverse makes both functions cancel each other out." ] - , under = "List.reverse <| List.reverse" + { message = "List.reverse, then List.reverse cancels each other out" + , details = [ "You can replace this call by the argument given to List.reverse." ] + , under = "List.reverse" } + |> Review.Test.atExactly { start = { row = 2, column = 5 }, end = { row = 2, column = 17 } } |> Review.Test.whenFixed """module A exposing (..) a = list """ @@ -14560,6 +14598,7 @@ listIntersperseTests = """module A exposing (..) a = List.intersperse 2 list b = List.intersperse y [ 1, 2, 3 ] +c = List.intersperse << List.singleton """ |> Review.Test.run ruleWithDefaults |> Review.Test.expectNoErrors @@ -14635,8 +14674,8 @@ a = List.intersperse s << List.singleton |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "List.intersperse on a singleton list will result in the given list" - , details = [ "You can replace this call by List.singleton." ] + { message = "List.intersperse on a singleton list will result in the unchanged singleton list" + , details = [ "You can replace this composition by List.singleton." ] , under = "List.intersperse" } |> Review.Test.whenFixed """module A exposing (..) @@ -14651,8 +14690,8 @@ a = List.singleton >> List.intersperse s |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "List.intersperse on a singleton list will result in the given list" - , details = [ "You can replace this call by List.singleton." ] + { message = "List.intersperse on a singleton list will result in the unchanged singleton list" + , details = [ "You can replace this composition by List.singleton." ] , under = "List.intersperse" } |> Review.Test.whenFixed """module A exposing (..) @@ -17311,7 +17350,7 @@ a = Array.append array Array.empty |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Array.append with Array.empty" - , details = [ "You can replace this call by the array itself." ] + , details = [ "You can replace this call by the given first array." ] , under = "Array.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -17329,7 +17368,7 @@ a = Array.append array <| Array.empty |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Array.append with Array.empty" - , details = [ "You can replace this call by the array itself." ] + , details = [ "You can replace this call by the given first array." ] , under = "Array.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -17347,7 +17386,7 @@ a = Array.empty |> Array.append array |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Array.append with Array.empty" - , details = [ "You can replace this call by the array itself." ] + , details = [ "You can replace this call by the given first array." ] , under = "Array.append" } |> Review.Test.whenFixed """module A exposing (..) @@ -19401,6 +19440,22 @@ a = Ok z |> Result.mapError f } |> Review.Test.whenFixed """module A exposing (..) a = Ok z +""" + ] + , test "should replace Result.mapError f << Ok by Ok" <| + \() -> + """module A exposing (..) +a = Result.mapError f << Ok +""" + |> Review.Test.run ruleWithDefaults + |> Review.Test.expectErrors + [ Review.Test.error + { message = "Result.mapError on an okay result will result in the unchanged okay result" + , details = [ "You can replace this composition by Ok." ] + , under = "Result.mapError" + } + |> Review.Test.whenFixed """module A exposing (..) +a = Ok """ ] , test "should replace Result.mapError identity x by x" <| @@ -21663,7 +21718,7 @@ a = Set.diff Set.empty set |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Set.diff on Set.empty will always result in Set.empty" + { message = "Set.diff Set.empty will always result in Set.empty" , details = [ "You can replace this call by Set.empty." ] , under = "Set.diff" } @@ -21681,8 +21736,8 @@ a = Set.diff set Set.empty |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Diffing a set with Set.empty will result in the set itself" - , details = [ "You can replace this call by the set itself." ] + { message = "Unnecessary Set.diff with Set.empty" + , details = [ "You can replace this call by the given first set." ] , under = "Set.diff" } |> Review.Test.whenFixed """module A exposing (..) @@ -21699,8 +21754,8 @@ a = Set.empty |> Set.diff set |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Diffing a set with Set.empty will result in the set itself" - , details = [ "You can replace this call by the set itself." ] + { message = "Unnecessary Set.diff with Set.empty" + , details = [ "You can replace this call by the given first set." ] , under = "Set.diff" } |> Review.Test.whenFixed """module A exposing (..) @@ -21750,7 +21805,7 @@ a = Set.union set Set.empty |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Set.union with Set.empty" - , details = [ "You can replace this call by the set itself." ] + , details = [ "You can replace this call by the given first set." ] , under = "Set.union" } |> Review.Test.whenFixed """module A exposing (..) @@ -21768,7 +21823,7 @@ a = Set.empty |> Set.union set |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Set.union with Set.empty" - , details = [ "You can replace this call by the set itself." ] + , details = [ "You can replace this call by the given first set." ] , under = "Set.union" } |> Review.Test.whenFixed """module A exposing (..) @@ -21786,7 +21841,7 @@ a = Set.empty |> Set.union set |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Set.union with Set.empty" - , details = [ "You can replace this call by the set itself." ] + , details = [ "You can replace this call by the given first set." ] , under = "Set.union" } |> Review.Test.whenFixed """module A exposing (..) @@ -23024,7 +23079,7 @@ a = Dict.diff Dict.empty dict |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Dict.diff on Dict.empty will always result in Dict.empty" + { message = "Dict.diff Dict.empty will always result in Dict.empty" , details = [ "You can replace this call by Dict.empty." ] , under = "Dict.diff" } @@ -23042,8 +23097,8 @@ a = Dict.diff dict Dict.empty |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Diffing a dict with Dict.empty will result in the dict itself" - , details = [ "You can replace this call by the dict itself." ] + { message = "Unnecessary Dict.diff with Dict.empty" + , details = [ "You can replace this call by the given first dict." ] , under = "Dict.diff" } |> Review.Test.whenFixed """module A exposing (..) @@ -23060,8 +23115,8 @@ a = Dict.empty |> Dict.diff dict |> Review.Test.run ruleWithDefaults |> Review.Test.expectErrors [ Review.Test.error - { message = "Diffing a dict with Dict.empty will result in the dict itself" - , details = [ "You can replace this call by the dict itself." ] + { message = "Unnecessary Dict.diff with Dict.empty" + , details = [ "You can replace this call by the given first dict." ] , under = "Dict.diff" } |> Review.Test.whenFixed """module A exposing (..) @@ -23111,7 +23166,7 @@ a = Dict.union dict Dict.empty |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Dict.union with Dict.empty" - , details = [ "You can replace this call by the dict itself." ] + , details = [ "You can replace this call by the given first dict." ] , under = "Dict.union" } |> Review.Test.whenFixed """module A exposing (..) @@ -23129,7 +23184,7 @@ a = Dict.empty |> Dict.union dict |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Dict.union with Dict.empty" - , details = [ "You can replace this call by the dict itself." ] + , details = [ "You can replace this call by the given first dict." ] , under = "Dict.union" } |> Review.Test.whenFixed """module A exposing (..) @@ -23147,7 +23202,7 @@ a = Dict.empty |> Dict.union dict |> Review.Test.expectErrors [ Review.Test.error { message = "Unnecessary Dict.union with Dict.empty" - , details = [ "You can replace this call by the dict itself." ] + , details = [ "You can replace this call by the given first dict." ] , under = "Dict.union" } |> Review.Test.whenFixed """module A exposing (..)