This module contains various Predicate
s that can be used to filter
pipelines.
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
.
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
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:
logBase
atMost
atLeast
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:
Basics.(-)
Basics.(/)
Basics.(//)
Basics.(^)
Basics.(<)
Basics.(>)
Basics.(<=)
Basics.(>=)
Basics.(++)
Basics.(>>)
Basics.(<<)
Parser.(|.)
Parser.(|=)
Parser.Advanced.(|.)
Parser.Advanced.(|=)
Url.Parser.(</>)
Url.Parser.(<?>)
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:
Basics.compare
Array.append
Dict.diff
List.append
Set.diff
String.append
Basics.Extra.safeDivide
Basics.Extra.safeIntegerDivide
IntDict.diff
Maybe.Extra.or
Result.Extra.or
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
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.
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
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.
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))))
These are exposed only for the sake of type annotations; you shouldn't need to work with them directly.
Internal.Types.Predicate pipelineType
A predicate for filtering pipelines, or a logical combination of them.
Internal.Types.StepPredicate
A predicate for a single step of a pipeline.
Internal.Types.Operator pipelineType
The operator type of a pipeline, e.g. |>
or <<
.
Internal.Types.Pipeline
A detected pipeline. You only need be concerned with this type if you are writing a manual predicate.
Internal.Types.NestedWithin
The degree to which a parent or child is removed from a pipeline.
Internal.Types.ApplicationPipeline
Pipelines that are function application.
Internal.Types.CompositionPipeline
Pipelines that are function composition.
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.