SiriusStarr / elm-review-pipeline-styles / ReviewPipelineStyles.Predicates

This module contains various Predicates that can be used to filter pipelines.

Combining Predicates

Predicates can be combined and negated using and, or, and doNot.

and : Predicate anyType -> Predicate anyType -> Predicate anyType

Create a Predicate that matches pipelines that match both of two predicates.

or : Predicate anyType -> Predicate anyType -> Predicate anyType

Create a Predicate that matches pipelines that match either or both of two predicates.

doNot : Predicate anyType -> Predicate anyType

Negate a Predicate.

Predicates

spanMultipleLines : Predicate anyType

Checks whether or not a pipeline spans multiple lines of code.

haveMoreStepsThan : Basics.Int -> Predicate anyType

Checks whether the length of a pipeline is longer than a specified number. Note that the length of a pipeline is the number of operators in it, e.g.

foo
    |> bar
    |> baz

has length 2 for the purposes of this predicate.

haveFewerStepsThan : Basics.Int -> Predicate anyType

Checks whether the length of a pipeline is less than a specified number. Note that the length of a pipeline is the number of operators in it, e.g.

foo
    |> bar
    |> baz

has length 2 for the purposes of this predicate.

haveASimpleInputStep : Predicate ApplicationPipeline

Determine whether the pipeline has a simple input or not. This is somewhat subjective, of course. A pipeline is considered to have a simple input if it has an unnecessary input, if its second step is not a semantically-infix function (like logBase ), and if its input step is simple.

This predicate is constructed as follows:

haveASimpleInputStep =
    haveAnUnnecessaryInputStep
        |> and (doNot <| haveASecondStepThatIs aSemanticallyInfixFunction)
        |> and (haveAnInputStepThatIs aSimpleStep)

haveAnUnnecessaryInputStep : Predicate ApplicationPipeline

Determine whether the pipeline has an input step that simply isn't necessary, e.g. foo |> bar |> baz, which may be written as bar foo |> baz.

This is not perfectly exhaustive as it does not consider operator precedence and the like but will suffice for finding most simple cases.

Note that this will potentially flag quite complex inputs, so you might want to use haveASimpleInputStep instead, since that only detects visually simple inputs.

This predicate is constructed as follows:

haveAnUnnecessaryInputStep =
    let
        ableToBeAnArgument =
            stepPredicate <|
                \node ->
                    case Node.value node of
                        Application _ ->
                            False

                        OperatorApplication _ _ _ _ ->
                            False

                        IfBlock _ _ _ ->
                            False

                        LetExpression _ ->
                            False

                        CaseExpression _ ->
                            False

                        LambdaExpression _ ->
                            False

                        GLSLExpression _ ->
                            False

                        _ ->
                            True

        ableToTakeAnArgument =
            stepPredicate <|
                \node ->
                    case Node.value node of
                        Application _ ->
                            True

                        FunctionOrValue _ _ ->
                            True

                        PrefixOperator _ ->
                            True

                        RecordAccessFunction _ ->
                            True

                        ParenthesizedExpression _ ->
                            True

                        RecordAccess _ _ ->
                            True

                        _ ->
                            False
    in
    haveAnInputStepThatIs ableToBeAnArgument
        |> and (haveASecondStepThatIs ableToTakeAnArgument)

separateATestFromItsLambda : Predicate ApplicationPipeline

Checks if an operator (typically left pizza (<|)) is used in the "canonical" fashion in a test suite, to separate the lambda containing the test from the test. All of the following will pass this predicate, and all other uses will not:

import Test exposing (..)

suite =
    describe "tests"
        [ test "foo" <|
            \() ->
                a
        , fuzz fooFuzz "fuzz" <|
            \foo ->
                a
        , fuzz2 fooFuzz barFuzz "fuzz2" <|
            \foo bar ->
                a
        , fuzz3 fooFuzz barFuzz bazFuzz "fuzz3" <|
            \foo bar baz ->
                a
        , fuzzWith { runs = 117 } fooFuzz "fuzzWith" <|
            \foo ->
                a
        ]

Here is how to construct this predicate, for an example of how to build complex predicates:

separateATestFromItsLambda =
    let
        aLambdaExpression : StepPredicate
        aLambdaExpression =
            stepPredicate <|
                \node ->
                    case Node.value node of
                        LambdaExpression _ ->
                            True

                        _ ->
                            False

        aTestFunction : StepPredicate
        aTestFunction =
            stepPredicateWithLookupTable <|
                \lookupTable node ->
                    case Node.value node of
                        Application (h :: _) ->
                            case ( Node.value h, moduleNameFor lookupTable h ) of
                                ( FunctionOrValue _ n, Just [ "Test" ] ) ->
                                    List.member n
                                        [ "test"
                                        , "fuzz"
                                        , "fuzz2"
                                        , "fuzz3"
                                        , "fuzzWith"
                                        ]

                                _ ->
                                    False

                        _ ->
                            False
    in
    haveFewerStepsThan 3
        |> and (haveAnInputStepThatIs aLambdaExpression)
        |> and (haveASecondStepThatIs aTestFunction)

haveInternalComments : Predicate anyType

Checks whether any comments are located within the pipeline, e.g.

a =
    foo
        -- Comment
        |> bar
        |> baz

Comments around the pipeline are ignored, e.g.

a =
    -- Ignored
    foo
        |> bar
        |> baz

-- Ignored

Step Predicates

These predicates allow one to filter based on a specific step of a pipeline.

haveAnInputStepThatIs : StepPredicate -> Predicate anyType

Given a predicate for a single step, check if the first step in a pipeline matches said predicate.

haveASecondStepThatIs : StepPredicate -> Predicate anyType

Given a predicate for a single step, check if the second step in a pipeline matches said predicate.

haveAnyNonInputStepThatIs : StepPredicate -> Predicate anyType

Given a predicate for a single step, check if any step in a pipeline except for the first matches said predicate.

haveNonInputStepsThatAreAll : StepPredicate -> Predicate anyType

Given a predicate for a single step, check if all steps in a pipeline except for the first match said predicate.

haveAnyStepThatIs : StepPredicate -> Predicate anyType

Given a predicate for a single step, check if any step in a pipeline matches said predicate.

haveStepsThatAreAll : StepPredicate -> Predicate anyType

Given a predicate for a single step, check if all step in a pipeline match said predicate.

aSemanticallyInfixFunction : StepPredicate

A "semantically-infix" function is a function that is intended (by name) to be read in an infix fashion as part of a pipeline. For example, Maybe.andThen or remainderBy or Maybe.Extra.orElse:

List.head |> Maybe.andThen String.toFloat

10 |> remainderBy 2

In practice, it checks if a step begins with a function that begins with the word "or" or "and" or ends in "By", or is on the following whitelist of functions:

They are then required to have exactly one argument applied to them, as with no arguments, they are not yet infix (and with 2+ they are no longer functions). For example, the following are not infix:

-- This is "backwards" from how it reads
2 |> remainderBy |> 10

-- This doesn't even compile
foo |> remainderBy 2 10

It also rules out confusing, non-commutative functions, even if they match the above.

If you have suggestions for additions to this list, please open an issue or PR on Github: https://github.com/SiriusStarr/elm-review-pipeline-styles/issues

aConfusingNonCommutativePrefixOperator : StepPredicate

This checks if a step is a commonly-confused, non-commutative prefix operator by checking if it is on the following blacklist of such functions:

aConfusingNonCommutativeFunction : StepPredicate

This checks if a step is a commonly-confused, non-commutative function by checking if it is aConfusingNonCommutativePrefixOperator or on the following blacklist of such functions:

Note that fully-saturated functions on the blacklist are allowed, e.g.

-- Passes
Set.diff a b
    |> Set.toList

is allowed, while

b
    -- Fails
    |> Set.diff a
    |> Set.toList

is not.

If you have suggestions for additions to this list, please open an issue or PR on Github: https://github.com/SiriusStarr/elm-review-pipeline-styles/issues

aSimpleStep : StepPredicate

Checks if a step is "visually" simple. This is of course extremely subjective; if you require different behavior, you can use stepPredicate to fully customize it. A step is considered "simple" if it is 40 characters or less, is only a single line, and is one of the following:

-- Unit
()

-- Name
a

-- Prefix operator
(+)

-- Int literal
1

-- Hex literal
0x0F

-- Float literal
1.5

-- String literal
"bar"

-- Char literal
'c'

-- Record access function
.field

or is one of the following where all subexpressions are simple:

-- Application
foo bar baz

-- Tuple
( a, "b" )

-- Record
{ a = "value" }

-- List
[]

-- Record access
a.field

-- Negation
elmFormatWontLetThisBeAnExample

-- Parentheses
elmFormatWontLetThisBeAnExample

onASingleLine : StepPredicate

Checks if a step consists of a single line of code.

onMultipleLines : StepPredicate

Checks if a step consists of more than one line of code.

stepPredicate : (Elm.Syntax.Node.Node Elm.Syntax.Expression.Expression -> Basics.Bool) -> StepPredicate

Given a function of type Node Expression -> Bool, create a StepPredicate from it. This is only useful if you want to write custom predicates for steps.

If you think a generally useful step predicate is missing, please open an issue or PR on Github: https://github.com/SiriusStarr/elm-review-pipeline-styles/issues

stepPredicateWithLookupTable : (Review.ModuleNameLookupTable.ModuleNameLookupTable -> Elm.Syntax.Node.Node Elm.Syntax.Expression.Expression -> Basics.Bool) -> StepPredicate

Given a function of type ModuleNameLookupTable -> Node Expression -> Bool, create a StepPredicate from it. This is only useful if you want to write custom predicates for steps and need full module names.

If you think a generally useful step predicate is missing, please open an issue or PR on Github: https://github.com/SiriusStarr/elm-review-pipeline-styles/issues

Nesting Predicates

haveAParent : Predicate anyType

Checks whether the pipeline is nested to any degree within another pipeline. Note that this is quite a strict requirement and you probably want to use one of the other nesting predicates instead.

haveAParentNotSeparatedBy : List (NestedWithin -> Basics.Bool) -> Predicate anyType

Checks whether the immediate parent of a pipeline (if one exists) is not separated by one of a list of acceptable abstractions.

haveMoreNestedParentsThan : Basics.Int -> Predicate anyType

Checks whether the pipeline is nested to a greater degree than specified within other pipelines. For example, haveMoreNestedParentsThan 1 will forbid

a =
    foo
        |> (bar <| (a |> b |> c))
        |> baz

aLetBlock : NestedWithin -> Basics.Bool

Either within a let declaration or in the let expression of a let block from the surrounding pipeline.

aLambdaFunction : NestedWithin -> Basics.Bool

Within a lambda function in the surrounding pipeline.

aFlowControlStructure : NestedWithin -> Basics.Bool

Within a case expression of if...then expression in the surrounding pipeline.

aDataStructure : NestedWithin -> Basics.Bool

Within a tuple, list, or record in the surrounding pipeline.

Custom Predicates

If you need predicates beyond what is provided above, you can create them manually by writing a function of type Pipeline -> Bool or ModuleNameLookupTable -> Pipeline -> Bool and using one of the functions below.

Use the functions in Getting Information About Pipelines to build your custom predicate.

predicate : (Pipeline -> Basics.Bool) -> Predicate anyType

Given a function of type Pipeline -> Bool, create a Predicate from it. This is only useful if you want to write custom predicates. Note that this will allow you to create predicates that match any type of pipeline, so be careful in how you use it.

If you think a generally useful predicate is missing, please open an issue or PR on Github: https://github.com/SiriusStarr/elm-review-pipeline-styles/issues

predicateWithLookupTable : (Review.ModuleNameLookupTable.ModuleNameLookupTable -> Pipeline -> Basics.Bool) -> Predicate anyType

Given a function of type ModuleNameLookupTable -> Pipeline -> Bool, create a Predicate from it. This is only useful if you want to write custom predicates and are going to need the full module name for expressions in the pipeline. Note that this will allow you to create predicates that match any type of pipeline, so be careful in how you use it.

If you think a generally useful predicate is missing, please open an issue or PR on Github: https://github.com/SiriusStarr/elm-review-pipeline-styles/issues

Getting Information About Pipelines

Note that some of the types returned by these functions are from stil4m/elm-syntax if you need to work with them directly.

getSteps : Pipeline -> List (Elm.Syntax.Node.Node Elm.Syntax.Expression.Expression)

Get the "steps" in the pipeline. Note that this is in "logical" order, e.g. a 1 >> b >> c, c << b << a 1, a 1 |> b |> c, c <| b <| a 1, and c (b (a 1)) will all have the same steps of [a 1, b, c].

getParents : Pipeline -> List ( Operator (), NestedWithin )

Get the "parent" pipelines of the pipeline, i.e. a hierarchy of pipelines that this pipeline is nested within (start of the list being the most immediate parent). Note that you can use the functions in Nesting Predicates to interrogate the NestedWithin type and in Query Pipeline Types for the Operator type.

getNode : Pipeline -> Elm.Syntax.Node.Node Elm.Syntax.Expression.Expression

Get the outermost Node of a pipeline; you probably don't need to work with this directly.

getInternalComments : Pipeline -> List (Elm.Syntax.Node.Node String)

Get a list of all comments that are inside of a pipeline.

Query Pipeline Types

isRightPizza : Operator () -> Basics.Bool

Check if an Operator is the right "pizza" operator (right function application), i.e. |>. An example of this pipeline is below:

foo
    |> bar
    |> baz

isLeftPizza : Operator () -> Basics.Bool

Check if an Operator is the left "pizza" operator (left function application), i.e. <|. An example of this pipeline is below:

foo <| bar <| baz

isRightComposition : Operator () -> Basics.Bool

Check if an Operator is the right composition operator, i.e. >>. An example of this pipeline is below:

foo
    >> bar
    >> baz

isLeftComposition : Operator () -> Basics.Bool

Check if an Operator is the left composition operator, i.e. <<. An example of this pipeline is below:

foo << bar << baz

isParentheticalApplication : Operator () -> Basics.Bool

Check if an Operator is simply parenthetical application (not an operator), i.e. successive function calls using parentheses, e.g.

foo (bar (baz (i (j k))))

Types

These are exposed only for the sake of type annotations; you shouldn't need to work with them directly.


type alias Predicate pipelineType =
Internal.Types.Predicate pipelineType

A predicate for filtering pipelines, or a logical combination of them.


type alias StepPredicate =
Internal.Types.StepPredicate

A predicate for a single step of a pipeline.


type alias Operator pipelineType =
Internal.Types.Operator pipelineType

The operator type of a pipeline, e.g. |> or <<.


type alias Pipeline =
Internal.Types.Pipeline

A detected pipeline. You only need be concerned with this type if you are writing a manual predicate.


type alias NestedWithin =
Internal.Types.NestedWithin

The degree to which a parent or child is removed from a pipeline.


type alias ApplicationPipeline =
Internal.Types.ApplicationPipeline

Pipelines that are function application.


type alias CompositionPipeline =
Internal.Types.CompositionPipeline

Pipelines that are function composition.

Deprecated

These functions have been deprecated and are included only to avoid a breaking change.

haveAnInputStepOf : (Elm.Syntax.Node.Node Elm.Syntax.Expression.Expression -> Basics.Bool) -> Predicate anyType

@deprecated Use haveAnInputStepThatIs instead.