Compare commits

...

6 Commits

Author SHA1 Message Date
Jeroen Engels
5573f0a666 Fix import issue in documentation 2024-02-04 18:42:22 +01:00
Jeroen Engels
60a15216a9 Backport rules from cognitive-complexity 2023-09-03 11:45:53 +02:00
Jeroen Engels
cf2a955d40 Extract function 2023-09-03 11:36:57 +02:00
Jeroen Engels
2ee409c546 Extract function 2023-09-03 11:32:55 +02:00
Jeroen Engels
f01508a61c Backport rules from unused 2023-09-03 11:31:00 +02:00
Jeroen Engels
b7842e704b Backport rules from simplify 2023-09-03 11:30:45 +02:00
15 changed files with 9338 additions and 4867 deletions

File diff suppressed because one or more lines are too long

View File

@ -20,7 +20,7 @@
"elm/json": "1.1.3 <= v < 2.0.0",
"elm/project-metadata-utils": "1.0.0 <= v < 2.0.0",
"elm-explorations/test": "2.0.1 <= v < 3.0.0",
"stil4m/elm-syntax": "7.2.7 <= v < 8.0.0"
"stil4m/elm-syntax": "7.3.2 <= v < 8.0.0"
},
"test-dependencies": {
"elm/parser": "1.1.0 <= v < 2.0.0",

View File

@ -594,19 +594,22 @@ registerDeclaration declaration innerContext =
|> registerIfExposed (\name ctx -> registerExposedValue function name ctx) (Node.value nameNode)
Declaration.AliasDeclaration alias ->
{ innerContext | localTypes = Set.insert (Node.value alias.name) innerContext.localTypes }
|> (\ctx ->
case Node.value alias.typeAnnotation of
TypeAnnotation.Record _ ->
addToScope
{ variableType = TopLevelVariable
, node = alias.name
}
ctx
_ ->
let
registerAlias : Context -> Context
registerAlias ctx =
case Node.value alias.typeAnnotation of
TypeAnnotation.Record _ ->
addToScope
{ variableType = TopLevelVariable
, node = alias.name
}
ctx
)
_ ->
ctx
in
{ innerContext | localTypes = Set.insert (Node.value alias.name) innerContext.localTypes }
|> registerAlias
|> registerIfExposed registerExposedTypeAlias (Node.value alias.name)
Declaration.CustomTypeDeclaration { name, constructors } ->

View File

@ -1368,7 +1368,7 @@ mergeModuleVisitorsHelp ruleName_ initialProjectContext moduleContextCreator vis
)
emptyModuleVisitor
visitors
|> (\(ModuleRuleSchema moduleVisitorSchema) -> ModuleRuleSchema moduleVisitorSchema)
|> removeExtensibleRecordFromModuleRuleSchema
, moduleContextCreator
)
@ -1408,7 +1408,12 @@ removeExtensibleRecordTypeVariable :
(ModuleRuleSchema {} moduleContext -> ModuleRuleSchema { a | hasAtLeastOneVisitor : () } moduleContext)
-> (ModuleRuleSchema {} moduleContext -> ModuleRuleSchema { hasAtLeastOneVisitor : () } moduleContext)
removeExtensibleRecordTypeVariable function =
function >> (\(ModuleRuleSchema param) -> ModuleRuleSchema param)
function >> removeExtensibleRecordFromModuleRuleSchema
removeExtensibleRecordFromModuleRuleSchema : ModuleRuleSchema schemaState moduleContext -> ModuleRuleSchema a moduleContext
removeExtensibleRecordFromModuleRuleSchema (ModuleRuleSchema param) =
ModuleRuleSchema param
{-| Creates a rule that will **only** report a configuration error, which stops `elm-review` from reviewing the project
@ -1903,7 +1908,7 @@ and by reading the value at `<output>.extracts["YourRuleName"]` in the output.
|> Rule.withDataExtractor dataExtractor
|> Rule.fromProjectRuleSchema
dataExtractor : ProjectContext -> Encode.Value
dataExtractor : ProjectContext -> Json.Encode.Value
dataExtractor projectContext =
Json.Encode.list
(\thing ->

View File

@ -337,32 +337,7 @@ expressionEnterVisitorHelp : Node Expression -> ModuleContext -> ModuleContext
expressionEnterVisitorHelp node context =
case Node.value node of
Expression.IfBlock _ _ else_ ->
if not (List.member (Node.range node) context.elseIfToIgnore) then
{ context
| increases =
{ line = (Node.range node).start
, increase = context.nesting + 1
, nesting = context.nesting
, kind = If
}
:: context.increases
, nesting = context.nesting + 1
, elseIfToIgnore = Node.range else_ :: context.elseIfToIgnore
}
else
-- This if expression is an else if
-- We want to increase the complexity but keep the same nesting as the parent if
{ context
| increases =
{ line = (Node.range node).start
, increase = context.nesting
, nesting = context.nesting - 1
, kind = ElseIf
}
:: context.increases
, elseIfToIgnore = Node.range else_ :: context.elseIfToIgnore
}
visitElseExpression (Node.range node) else_ context
Expression.CaseExpression _ ->
{ context
@ -408,21 +383,62 @@ expressionEnterVisitorHelp node context =
{ context | nesting = context.nesting + 1 }
Expression.FunctionOrValue [] name ->
{ context
| references =
if Dict.member name context.references then
-- The reference already exists, and we want to keep the first reference
-- for a better presentation
context.references
if isFunctionReference name then
{ context
| references =
if Dict.member name context.references then
-- The reference already exists, and we want to keep the first reference
-- for a better presentation
context.references
else
Dict.insert name (Node.range node).start context.references
}
else
Dict.insert name (Node.range node).start context.references
}
else
context
_ ->
context
visitElseExpression : Range -> Node a -> ModuleContext -> ModuleContext
visitElseExpression ifExprRange else_ context =
if not (List.member ifExprRange context.elseIfToIgnore) then
{ context
| increases =
{ line = ifExprRange.start
, increase = context.nesting + 1
, nesting = context.nesting
, kind = If
}
:: context.increases
, nesting = context.nesting + 1
, elseIfToIgnore = Node.range else_ :: context.elseIfToIgnore
}
else
-- This if expression is an else if
-- We want to increase the complexity but keep the same nesting as the parent if
{ context
| increases =
{ line = ifExprRange.start
, increase = context.nesting
, nesting = context.nesting - 1
, kind = ElseIf
}
:: context.increases
, elseIfToIgnore = Node.range else_ :: context.elseIfToIgnore
}
isFunctionReference : String -> Bool
isFunctionReference name =
name
|> String.left 1
|> String.all Char.isLower
computeRangesForLetDeclarations : List (Node Expression.LetDeclaration) -> List Range
computeRangesForLetDeclarations declarations =
List.filterMap
@ -704,19 +720,18 @@ findRecursiveCalls : Dict String (Dict String a) -> RecursiveCalls
findRecursiveCalls graph =
graph
|> Dict.foldl
(\vertice _ ( recursiveCalls, visited ) ->
(\vertice _ recursiveCalls ->
let
res : { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
res =
processDFSTree
graph
[ vertice ]
(Dict.insert vertice InStack visited)
(Dict.singleton vertice InStack)
in
( mergeRecursiveCallsDict res.recursiveCalls recursiveCalls, res.visited )
mergeRecursiveCallsDict res.recursiveCalls recursiveCalls
)
( Dict.empty, Dict.empty )
|> Tuple.first
Dict.empty
mergeRecursiveCallsDict : RecursiveCalls -> RecursiveCalls -> RecursiveCalls
@ -732,45 +747,53 @@ mergeRecursiveCallsDict left right =
processDFSTree : Dict String (Dict String a) -> List String -> Visited -> { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
processDFSTree graph stack visited =
let
vertices : List String
vertices =
List.head stack
|> Maybe.andThen (\v -> Dict.get v graph)
|> Maybe.withDefault Dict.empty
|> Dict.keys
in
List.foldl
(\vertice acc ->
case Dict.get vertice visited of
Just InStack ->
{ acc | recursiveCalls = insertCycle stack vertice acc.recursiveCalls }
case stack of
[] ->
{ recursiveCalls = Dict.empty, visited = visited, stack = [] }
Just Done ->
acc
head :: restOfStack ->
let
vertices : List String
vertices =
Dict.get head graph
|> Maybe.withDefault Dict.empty
|> Dict.keys
in
List.foldl
(\vertice acc ->
case Dict.get vertice visited of
Just InStack ->
{ acc | recursiveCalls = insertCycle stack vertice acc.recursiveCalls }
Nothing ->
let
res : { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
res =
processDFSTree
graph
(vertice :: stack)
(Dict.insert vertice InStack visited)
in
{ recursiveCalls = mergeRecursiveCallsDict res.recursiveCalls acc.recursiveCalls, visited = res.visited }
)
{ recursiveCalls = Dict.empty, visited = visited }
vertices
|> (\res ->
{ recursiveCalls = res.recursiveCalls
, visited =
List.head stack
|> Maybe.map (\v -> Dict.insert v Done res.visited)
|> Maybe.withDefault res.visited
, stack = List.drop 1 stack
}
)
Just Done ->
acc
Nothing ->
let
res : { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
res =
processDFSTree
graph
(vertice :: stack)
(Dict.insert vertice InStack visited)
in
{ recursiveCalls = mergeRecursiveCallsDict res.recursiveCalls acc.recursiveCalls, visited = res.visited }
)
{ recursiveCalls = Dict.empty, visited = visited }
vertices
|> updateStack head restOfStack
updateStack :
String
-> List String
-> { recursiveCalls : RecursiveCalls, visited : Visited }
-> { recursiveCalls : RecursiveCalls, visited : Visited, stack : List String }
updateStack head stack res =
{ recursiveCalls = res.recursiveCalls
, visited = Dict.insert head Done res.visited
, stack = stack
}
dataExtractor : ProjectContext -> Encode.Value

View File

@ -611,6 +611,41 @@ fun5 n =
"fun5": 1
}
}"""
, test "recursive call complexity should not depend on alphabetical order" <|
\() ->
"""module A exposing (..)
b () = b ()
a = b ()
c = b ()
"""
|> expectAtExactly
[ { name = "a"
, complexity = 1
, atExactly = { start = { row = 5, column = 1 }, end = { row = 5, column = 2 } }
, details = [ "Line 5: +1 for the indirect recursive call to b" ]
}
, { name = "b"
, complexity = 1
, atExactly = { start = { row = 3, column = 1 }, end = { row = 3, column = 2 } }
, details = [ "Line 3: +1 for the recursive call" ]
}
, { name = "c"
, complexity = 1
, atExactly = { start = { row = 7, column = 1 }, end = { row = 7, column = 2 } }
, details = [ "Line 7: +1 for the indirect recursive call to b" ]
}
]
"""
{
"A": {
"a": 1,
"b": 1,
"c": 1
}
}"""
, test "the complexity of a function should not affect another function's computed complexity" <|
\() ->
"""module A exposing (..)

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
module NoUnused.ExportsTest exposing (all)
import NoUnused.Exports exposing (rule)
import NoUnused.Exports exposing (annotatedBy, defaults, definedInModule, prefixedBy, reportUnusedProductionExports, rule, suffixedBy, toRule)
import Review.Test
import Test exposing (Test, describe, test)
import TestProject exposing (application, lamderaApplication, package)
@ -28,6 +28,7 @@ all =
, importsTests
, lamderaTests
, unusedModuleTests
, reportUnusedProductionExportsTest
-- TODO Add tests that report exposing the type's variants if they are never used.
]
@ -1219,3 +1220,332 @@ main = text ""
|> Review.Test.runWithProjectData lamderaApplication rule
|> Review.Test.expectNoErrors
]
reportUnusedProductionExportsTest : Test
reportUnusedProductionExportsTest =
describe "reportUnusedProductionExports"
[ test "should report functions that are only used in ignored files (no helpers defined)" <|
\() ->
[ """
module Main exposing (main)
import A
main = A.used
""", """
module A exposing (used, unusedInProductionCode)
used = 1
unusedInProductionCode = 2
""", """
module ATest exposing (..)
import A
import Test
a = A.unusedInProductionCode
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = []
}
|> toRule
)
|> Review.Test.expectErrorsForModules
[ ( "A"
, [ Review.Test.error
{ message = "Exposed function or value `unusedInProductionCode` is never used in production code."
, details =
[ "This exposed element is only used in files you have marked as non-production code (e.g. the tests folder), and should therefore be removed along with the places it's used in. This will help reduce the amount of code you will need to maintain."
, "It is possible that this element is meant to enable work in your ignored folder (test helpers for instance), in which case you should keep it. To avoid this problem being reported again, please read the documentation on how to configure the rule."
]
, under = "unusedInProductionCode"
}
|> Review.Test.atExactly { start = { row = 2, column = 26 }, end = { row = 2, column = 48 } }
]
)
]
, test "should report functions that are only used in ignored files (helpers defined)" <|
\() ->
[ """
module Main exposing (main)
import A
main = A.used
""", """
module A exposing (used, unusedInProductionCode)
used = 1
unusedInProductionCode = 2
""", """
module ATest exposing (..)
import A
import Test
a = A.unusedInProductionCode
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre =
[ annotatedBy "@helper"
, annotatedBy "@test-helper"
, suffixedBy "_FOR_TESTS"
, prefixedBy "test_"
]
}
|> toRule
)
|> Review.Test.expectErrorsForModules
[ ( "A"
, [ Review.Test.error
{ message = "Exposed function or value `unusedInProductionCode` is never used in production code."
, details =
[ "This exposed element is only used in files you have marked as non-production code (e.g. the tests folder), and should therefore be removed along with the places it's used in. This will help reduce the amount of code you will need to maintain."
, "It is possible that this element is meant to enable work in your ignored folder (test helpers for instance), in which case you should keep it. To avoid this problem being reported again, you can:"
, """- Include @helper in the documentation of the element
- Include @test-helper in the documentation of the element
- Rename the element to end with _FOR_TESTS
- Rename the element to start with test_"""
]
, under = "unusedInProductionCode"
}
|> Review.Test.atExactly { start = { row = 2, column = 26 }, end = { row = 2, column = 48 } }
]
)
]
, test "should not report exposed tests even if they're in an ignored module" <|
\() ->
[ """
module Main exposing (main)
main = 1
""", """
module ATest exposing (tests)
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" []
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = []
}
|> toRule
)
|> Review.Test.expectNoErrors
, test "should not report elements from ignored modules used in other ignored modules exposed tests even if they're in an ignored module" <|
\() ->
[ """
module ATest exposing (tests)
import BTest
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" BTest.helper
""", """
module BTest exposing (helper)
helper = 1
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = []
}
|> toRule
)
|> Review.Test.expectNoErrors
, test "should not report elements only used in ignored modules if they're annotated with a tag" <|
\() ->
[ """
module ATest exposing (tests)
import B
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" B.helper
""", """
module B exposing (helper)
{-| @ignore-helper -}
helper = 1
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = [ annotatedBy "@ignore-helper" ]
}
|> toRule
)
|> Review.Test.expectNoErrors
, test "should not report elements from ignored modules if they're imported only in tests but also used locally in the module" <|
\() ->
[ """
module Main exposing (main)
import B
main = B.exposed
""", """
module ATest exposing (tests)
import B
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" B.usedLocally
""", """
module B exposing (exposed, usedLocally)
exposed = usedLocally + 1
usedLocally = 1
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = []
}
|> toRule
)
|> Review.Test.expectNoErrors
, test "should report elements never used anywhere even if they're annotated with a tag" <|
\() ->
[ """
module ATest exposing (tests)
import Test exposing (Test)
import B
tests : Test
tests = Test.describe "thing" []
""", """
module B exposing (helper)
{-| @ignore-helper -}
helper = 1
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = []
}
|> toRule
)
|> Review.Test.expectErrorsForModules
[ ( "B"
, [ Review.Test.error
{ message = "Exposed function or value `helper` is never used outside this module."
, details = unusedExposedElementDetails
, under = "helper"
}
|> Review.Test.atExactly { start = { row = 2, column = 20 }, end = { row = 2, column = 26 } }
]
)
]
, test "should report elements never used anywhere even if their name ends with the configured suffix" <|
\() ->
[ """
module Main exposing (main)
import B
main = B.b
""", """
module ATest exposing (tests)
import B
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" B.helperTEST
""", """
module B exposing (b, helperTEST)
b = 1
helperTEST = 1
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = [ suffixedBy "TEST" ]
}
|> toRule
)
|> Review.Test.expectNoErrors
, test "should report elements never used anywhere even if their name starts with the configured suffix" <|
\() ->
[ """
module Main exposing (main)
import B
main = B.b
""", """
module ATest exposing (tests)
import B
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" B.test_helper
""", """
module B exposing (b, test_helper)
b = 1
test_helper = 1
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = [ prefixedBy "test_" ]
}
|> toRule
)
|> Review.Test.expectNoErrors
, test "should report elements never used anywhere even if they're defined in a module marked as an exception" <|
\() ->
[ """
module Main exposing (main)
import Project.Utils.B as B
main = B.b
""", """
module ATest exposing (tests)
import Project.Utils.B as B
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" B.helper
""", """
module Project.Utils.B exposing (b, helper)
b = 1
helper = 1
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = [ definedInModule (\{ moduleName } -> List.member "Utils" moduleName) ]
}
|> toRule
)
|> Review.Test.expectNoErrors
, test "should report unused exports in ignored files as regular errors" <|
\() ->
[ """
module Main exposing (main)
import B
main = B.b
""", """
module ATest exposing (tests, unused)
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" []
unused = 1
""" ]
|> Review.Test.runOnModules
(defaults
|> reportUnusedProductionExports
{ isProductionFile = \{ moduleName } -> String.join "." moduleName |> String.endsWith "Test" |> not
, exceptionsAre = [ prefixedBy "test_" ]
}
|> toRule
)
|> Review.Test.expectErrorsForModules
[ ( "ATest"
, [ Review.Test.error
{ message = "Exposed function or value `unused` is never used outside this module."
, details = unusedExposedElementDetails
, under = "unused"
}
|> Review.Test.atExactly { start = { row = 2, column = 31 }, end = { row = 2, column = 37 } }
|> Review.Test.whenFixed """
module ATest exposing (tests)
import Test exposing (Test)
tests : Test
tests = Test.describe "thing" []
unused = 1
"""
]
)
]
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
module Simplify.Evaluate exposing (getBoolean, getInt, isAlwaysBoolean)
module Simplify.Evaluate exposing (getBoolean, getInt, isAlwaysBoolean, isEqualToSomethingFunction)
import Elm.Syntax.Expression as Expression exposing (Expression)
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern as Pattern
import Review.ModuleNameLookupTable as ModuleNameLookupTable
import Simplify.AstHelpers as AstHelpers
import Simplify.Infer as Infer
@ -81,6 +82,41 @@ isAlwaysBoolean resources node =
Undetermined
isEqualToSomethingFunction : Node Expression -> Maybe { something : Node Expression }
isEqualToSomethingFunction rawNode =
case Node.value (AstHelpers.removeParens rawNode) of
Expression.Application ((Node _ (Expression.PrefixOperator "==")) :: expr :: []) ->
Just { something = expr }
Expression.LambdaExpression lambda ->
case lambda.args of
[ Node _ (Pattern.VarPattern var) ] ->
case Node.value (AstHelpers.removeParens lambda.expression) of
Expression.OperatorApplication "==" _ left right ->
let
nodeToFind : Expression
nodeToFind =
Expression.FunctionOrValue [] var
in
if Node.value left == nodeToFind then
Just { something = right }
else if Node.value right == nodeToFind then
Just { something = left }
else
Nothing
_ ->
Nothing
_ ->
Nothing
_ ->
Nothing
getInt : Infer.Resources a -> Node Expression -> Maybe Int
getInt resources baseNode =
let

View File

@ -1,14 +1,12 @@
module Simplify.Match exposing
( Match(..)
, map
, maybeAndThen
, map, traverse
)
{-|
@docs Match
@docs map
@docs maybeAndThen
@docs map, traverse
-}
@ -28,11 +26,24 @@ map mapper match =
Undetermined
maybeAndThen : (a -> Match b) -> Maybe a -> Match b
maybeAndThen fn maybe =
case maybe of
Just a ->
fn a
{-| If all mapped elements are Determined, returns a List of the Determined values.
If any match is Undetermined, returns Undetermined
-}
traverse : (a -> Match b) -> List a -> Match (List b)
traverse f list =
traverseHelp f list []
Nothing ->
Undetermined
traverseHelp : (a -> Match b) -> List a -> List b -> Match (List b)
traverseHelp f list acc =
case list of
head :: tail ->
case f head of
Determined a ->
traverseHelp f tail (a :: acc)
Undetermined ->
Undetermined
[] ->
Determined (List.reverse acc)

View File

@ -148,6 +148,9 @@ normalize resources node =
Expression.Floatable float ->
toNode (Expression.Floatable -float)
Expression.Negation subExpr ->
subExpr
_ ->
toNode (Expression.Negation normalized)
@ -378,6 +381,14 @@ compareHelp leftNode right canFlip =
Expression.Floatable left ->
compareNumbers left right
Expression.Negation left ->
case Node.value (removeParens right) of
Expression.Negation rightValue ->
compareHelp left rightValue canFlip
_ ->
fallback ()
Expression.OperatorApplication leftOp _ leftLeft leftRight ->
if List.member leftOp [ "+", "-", "*", "/" ] then
case getNumberValue leftNode of

View File

@ -236,7 +236,7 @@ simpleNormalizationTests =
, n (OperatorApplication "^" Infix.Right (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
, n (OperatorApplication "&&" Infix.Right (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
, n (OperatorApplication "||" Infix.Right (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
, n (OperatorApplication "</>" Infix.Left (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
, n (OperatorApplication "</>" Infix.Right (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
, n (OperatorApplication "<?>" Infix.Left (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
, n (OperatorApplication "|." Infix.Left (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))
, n (OperatorApplication "|=" Infix.Left (n (FunctionOrValue [] "a")) (n (FunctionOrValue [] "b")))

File diff suppressed because it is too large Load Diff