spotifyovercastrssapple-podcasts

Writing an elm-review Rule

We walk through how to write an elm-review rule from scratch. Also, how to test your rules, and how to write an automated fix.
April 26, 2021
#29

Getting started

Some repos to look at for inspiration

The elm-review package docs are very through and worth reading

Transcript

[00:00:00]
Hello, Jeroen.
[00:00:02]
Hello, Dillon.
[00:00:03]
And once again, we're back for more Elm Review.
[00:00:07]
I was building an Elm Review rule
[00:00:10]
for the first time recently,
[00:00:12]
and I was thinking it would be nice to do an episode
[00:00:15]
just doing a deep dive on not Elm Review
[00:00:18]
as a consumer of rules,
[00:00:20]
but writing your own Elm Review rules.
[00:00:23]
Yeah.
[00:00:28]
So I noticed that for your Elm Review rule,
[00:00:30]
which is called Elm Review HTML to Elm, which is very cool.
[00:00:34]
So if you haven't heard about it before,
[00:00:36]
what it does is it looks at your Elm code.
[00:00:39]
It looks for debug to do calls
[00:00:41]
in which you have pasted HTML strings,
[00:00:44]
so the less than HTML,
[00:00:47]
bigger than with spans and attributes and stuff like that.
[00:00:51]
And then it transforms it to Elm code, Elm View code,
[00:00:56]
Elm tailwind modules, which we talked about before,
[00:00:59]
or just regular Elm HTML or Elm CSS.
[00:01:03]
I don't know what you support at the moment.
[00:01:05]
Yeah, what it does is it,
[00:01:07]
I think I have checks for Elm CSS or regular Elm HTML.
[00:01:13]
And I mean, it was a really cool experience writing the rule
[00:01:16]
because I was able to very easily hook into a few places
[00:01:20]
and say,
[00:01:25]
like, you know, then I've literally just got this Elm data structure
[00:01:29]
that represents all of the imports
[00:01:32]
and what it's the imports aliased as.
[00:01:34]
So if it's import HTML.attributes as adder exposing div,
[00:01:40]
then I actually take that
[00:01:43]
and I use that import alias for the auto generated HTML.
[00:01:47]
So if you have that import,
[00:01:52]
it's not exposing div, it would be exposing class.
[00:01:55]
So if you have import HTML.attributes as adder exposing class,
[00:02:01]
when you have HTML with a class attribute,
[00:02:04]
it's going to use the unqualified name class.
[00:02:06]
And when you do, you know, ID,
[00:02:10]
it's going to do adder.id and use that alias.
[00:02:13]
So like, it's really cool because, you know,
[00:02:16]
writing an Elm review rule,
[00:02:21]
you can go to a few places
[00:02:23]
and just kind of collect the data you need.
[00:02:25]
And it was so easy to, you know,
[00:02:27]
just take the kind of code generation that I wrote for basically,
[00:02:32]
like, originally I wrote this online generator
[00:02:35]
that you paste in some HTML
[00:02:37]
and it generates Elm code for that.
[00:02:39]
And then you can configure your import aliases
[00:02:42]
and exposing and all that.
[00:02:44]
But it was so easy to just basically hook into the AST
[00:02:49]
and give me the imports just as an Elm data structure.
[00:02:52]
And then I just kind of use that as configuration for my role.
[00:02:56]
So it was a really good experience.
[00:02:58]
And particularly, it was very comforting
[00:03:00]
to be able to just write a unit test
[00:03:02]
and iterate on it until it's green.
[00:03:05]
Yeah.
[00:03:06]
I actually wonder whether you should call those unit tests
[00:03:09]
because there's so many checks done under the hood.
[00:03:14]
That's a good point.
[00:03:16]
Yeah, it's more of a sort of integration test, isn't it?
[00:03:19]
Maybe.
[00:03:20]
But it's written using just plain old Elm test, which is awesome.
[00:03:25]
Yeah.
[00:03:26]
So I guess that qualifies as unit test, maybe?
[00:03:28]
No.
[00:03:30]
It's a potato, potato.
[00:03:32]
Yeah.
[00:03:33]
What is Elm program test?
[00:03:34]
Is that an integration test in a way?
[00:03:36]
I think of it as an integration test.
[00:03:38]
All right, then I think that's close to it at least.
[00:03:43]
Yeah.
[00:03:44]
So for Elm review rules, what concepts do we need to define
[00:03:48]
and lay out for writing an Elm review rule?
[00:03:52]
First of all, you're writing Elm code
[00:03:55]
using jfm angles slash Elm review.
[00:03:59]
So that's an Elm package, and it's also an NPM executable CLI.
[00:04:06]
So you use the Elm package, you define a review rule,
[00:04:11]
and then basically that gives you a few places
[00:04:14]
where you can describe which parts,
[00:04:18]
where you want to match and mark a rule.
[00:04:21]
And it also allows you to hook into these visitors
[00:04:25]
and get context about a module or a project.
[00:04:29]
Is that like a fair highlight or something?
[00:04:31]
It's in a way oversimplified, but I guess it works, yeah.
[00:04:35]
Are there any key pieces missing?
[00:04:40]
Yeah, yeah.
[00:04:41]
So just first of all, if you try to write an Elm review rule,
[00:04:46]
you should know about the fact that the CLI gives you
[00:04:49]
two commands, which are Elm review new package,
[00:04:52]
new dash package, and Elm review new dash rule,
[00:04:56]
which will help you get set up.
[00:04:58]
Especially the new package is useful,
[00:05:00]
but if you add a rule to your configuration
[00:05:03]
at your work project, then new rule is already plenty useful.
[00:05:08]
Okay, so before we talk about ASTs and those details,
[00:05:13]
let's say you're starting a new,
[00:05:16]
you want to write an Elm review rule at work.
[00:05:19]
You want to check that a particular convention is followed.
[00:05:23]
You want to check that, let's just take an example
[00:05:25]
that you don't want to use a color function
[00:05:29]
for your styling for Elm UI or for Elm CSS or something,
[00:05:34]
unless it's defined in a color palette module, let's just say.
[00:05:38]
Yeah, so you don't want to call CSS.RGB?
[00:05:42]
Yes.
[00:05:43]
All right.
[00:05:44]
Yeah, so you want to enforce in your company
[00:05:46]
that that convention is followed with a review rule.
[00:05:50]
So you're saying your first step is
[00:05:52]
you install the Elm review NPM package,
[00:05:55]
and then you say Elm review new rule from the CLI,
[00:06:00]
and then that gives you what?
[00:06:02]
That gives you a folder.
[00:06:03]
Where does the folder go?
[00:06:04]
So it asks you for the rule name,
[00:06:08]
which is formatted as an Elm module name,
[00:06:11]
because in practice it will just be an Elm module.
[00:06:14]
So let's actually kind of pretend that we're building this
[00:06:19]
and sort of live pairing on this.
[00:06:21]
What are we going to call our rule?
[00:06:23]
It's like no direct color.
[00:06:28]
No custom color, maybe,
[00:06:31]
because you want to use a color from your palette.
[00:06:34]
Yeah, I'd go for something like no custom color
[00:06:37]
or no color outside of, yeah, no custom color is good.
[00:06:42]
Okay, and you have some guys,
[00:06:45]
so the package docs for Elm review are really helpful.
[00:06:49]
They're very thorough,
[00:06:51]
and I definitely recommend leveraging them
[00:06:56]
because there's a lot of good stuff there.
[00:06:58]
You've clearly put a lot of thought into that.
[00:07:00]
And you talk about naming conventions for that.
[00:07:03]
What's a quick high level advice for how to name something?
[00:07:09]
I usually try to always name things with no something
[00:07:14]
because often you want to forbid something.
[00:07:16]
So make it easy to understand
[00:07:18]
what you're trying to achieve just with the rule name.
[00:07:23]
If you tease some rule in the configuration,
[00:07:25]
they should have a clue as to what it should do.
[00:07:29]
So for instance, instead of saying something like
[00:07:32]
use color from color palette module,
[00:07:36]
it's clear to say no custom color.
[00:07:40]
So yeah, also the advice that you would give
[00:07:43]
may depend on situation.
[00:07:45]
So sometimes it will be use a color from this module
[00:07:50]
and in this case use color from this module.
[00:07:52]
When you multiply the advice that you would give,
[00:07:55]
that's a bit harder to get into the rule name.
[00:07:57]
So that's why no something is usually faster.
[00:08:01]
Right, and also like writing an Elm review rule
[00:08:05]
for something that applies sometimes but not always
[00:08:09]
is not a good idea, right?
[00:08:10]
You shouldn't do that.
[00:08:11]
You want to enforce,
[00:08:14]
because it's going to mark it as an error.
[00:08:19]
Or magic comment.
[00:08:21]
So you don't want to like,
[00:08:23]
there's no escape hatch like that.
[00:08:24]
You can't ignore a specific file.
[00:08:26]
But in general, that would be a smell
[00:08:29]
that you're not using it as intended, right?
[00:08:31]
Yeah, or it has to be an exception
[00:08:34]
that is built in the rule.
[00:08:36]
So for instance, calling color.rgb or css.rgb
[00:08:41]
is fine in this particular palette module.
[00:08:46]
You can encode that into the rule.
[00:08:48]
Yes, exactly.
[00:08:50]
Or maybe you say,
[00:08:53]
here's a function that is from palette
[00:08:56]
that's an escape hatch.
[00:08:58]
So you do palette.I'm a bad person
[00:09:00]
and I'm using a custom color.
[00:09:02]
And you say, okay, well, we're going to ignore
[00:09:04]
if they use that or something like that.
[00:09:07]
Yeah, so yeah, that's a good distinction.
[00:09:09]
You want to be able to, within the rule
[00:09:14]
in your rule, figure out if something is okay or not
[00:09:18]
and not have ambiguity.
[00:09:20]
So where does it create that folder
[00:09:23]
when you do elm review new rule?
[00:09:26]
So what it does, it creates a source file.
[00:09:30]
So it's source slash no custom color dot elm
[00:09:33]
and a test file.
[00:09:35]
So test slash no color module test dot elm.
[00:09:40]
It's in the current folder,
[00:09:42]
so you should move to the review folder
[00:09:45]
in your project before you do that.
[00:09:47]
Okay, cool.
[00:09:48]
So that's the convention you typically use,
[00:09:50]
is just to have your custom elm review rules
[00:09:53]
that you write for your project
[00:09:55]
living alongside your source code?
[00:09:57]
Yeah, it lives along the source code
[00:09:59]
for the review configuration.
[00:10:01]
It doesn't live next to your regular source files.
[00:10:04]
So the review folder is its own elm application.
[00:10:09]
So it has an elm JSON, it has source files,
[00:10:11]
and potentially test files, as we will show now.
[00:10:15]
So the dependencies of the review project
[00:10:18]
doesn't mix with the dependencies
[00:10:21]
of your regular project.
[00:10:23]
But in this case, wouldn't you have to add
[00:10:26]
your source folder as a dependency
[00:10:29]
of the review project,
[00:10:30]
because you've defined your review rule
[00:10:33]
in your source directory, in your src folder?
[00:10:38]
Review slash src.
[00:10:40]
Oh, review slash src.
[00:10:41]
Okay, got it.
[00:10:42]
Thank you for clarifying.
[00:10:43]
Okay, so that's the convention you use.
[00:10:45]
That seems like a nice convention.
[00:10:47]
Okay, so your convention that's sort of built into
[00:10:50]
the elm review CLI sort of scaffolding command,
[00:10:55]
elm review new rule,
[00:10:57]
it's assuming this convention of your local project
[00:11:01]
review rules that you define
[00:11:06]
as your local package source.
[00:11:09]
Yeah, it's kind of a convention,
[00:11:11]
but it's also just the more practical version.
[00:11:14]
So you already have a source slash review config
[00:11:17]
dot elm file, which is your configuration,
[00:11:19]
which is the main for elm review,
[00:11:23]
and you use rules from packages.
[00:11:25]
So just adding a new source file for a rule
[00:11:29]
is actually very easy from the design
[00:11:34]
point of view.
[00:11:34]
Yeah, that makes sense.
[00:11:36]
And it probably would potentially get weird
[00:11:39]
to clutter up actually having access
[00:11:43]
to your actual source project.
[00:11:46]
Oh, yeah.
[00:11:47]
The top level source,
[00:11:48]
because then you could be in this interesting gray area
[00:11:52]
where you can sort of scan the source code
[00:11:56]
using elm review to mark errors,
[00:11:58]
but you can also just use code
[00:12:03]
that you're scanning, which is a weird sort of mix, right?
[00:12:06]
Yeah, maybe you would go into meta programming or something.
[00:12:10]
But yeah, it is something that I thought about first
[00:12:13]
because you know you've got dependencies in your elm JSON,
[00:12:16]
you've got test dependencies.
[00:12:18]
I was thinking I should just add a new section
[00:12:21]
called review dependencies.
[00:12:23]
But it turns out any time that the compiler
[00:12:27]
adds a new dependency,
[00:12:32]
it removed all the review dependencies,
[00:12:34]
so I had to think of something else.
[00:12:36]
And this current design is actually pretty neat.
[00:12:39]
It's annoying that you have a folder,
[00:12:41]
but it's fine.
[00:12:43]
I think it makes sense.
[00:12:44]
Yeah, I think it's nice to find things in there.
[00:12:46]
Yeah, I like that.
[00:12:48]
Okay, so that's if you're doing
[00:12:50]
a sort of project specific rule,
[00:12:53]
which you could later extract out to a package
[00:12:56]
and publish it if you wanted to,
[00:13:01]
but I did it with a package
[00:13:03]
because it wasn't something I wanted for one project.
[00:13:05]
It was something I knew I wanted to publish as a package,
[00:13:08]
so I did elm review new package.
[00:13:10]
So okay, so I think we've sort of set up
[00:13:13]
how you would get started
[00:13:15]
and where your review rule lives and all that.
[00:13:19]
So you were starting to talk about ASTs.
[00:13:22]
So what do we need to know about ASTs
[00:13:24]
for writing our own elm review rule?
[00:13:29]
So what is an AST?
[00:13:31]
An AST stands for Abstract Syntax Tree.
[00:13:33]
So it is a data representation of your code.
[00:13:38]
So often it's a tree.
[00:13:40]
Well, it is always just a tree of nodes,
[00:13:45]
and each node describes a specific part of your code.
[00:13:49]
So for instance, if you have A plus B in your code,
[00:13:52]
that is an expression,
[00:13:57]
addition, so it's the application of the operator plus,
[00:14:01]
which has two nodes, a left one and a right one.
[00:14:05]
And the left one is the expression one,
[00:14:08]
which is a int expression.
[00:14:10]
I don't remember what it is exactly.
[00:14:12]
Integer.
[00:14:13]
Yeah, expression.integer.
[00:14:15]
And everything you're talking about,
[00:14:17]
you're talking about a general concept
[00:14:18]
of an abstract syntax tree,
[00:14:20]
but for the context of elm review,
[00:14:25]
we're talking about the still4m slash elm syntax,
[00:14:27]
which is an elm package,
[00:14:29]
and we're talking about this AST data structure
[00:14:32]
is just an elm custom type.
[00:14:34]
So for example, you're talking about
[00:14:37]
an addition expression,
[00:14:39]
and you're talking about the integer expression type,
[00:14:41]
like a literal integer.
[00:14:43]
So the integer, if you have one, two, three,
[00:14:46]
a literal integer,
[00:14:47]
then that is,
[00:14:48]
if you go to the still4m slash elm syntax package,
[00:14:53]
go to the elm.syntax.expression module,
[00:14:56]
it defines a type called expression.
[00:14:58]
It's just type expression equals,
[00:15:00]
and then a bunch of things
[00:15:01]
that an elm expression can be.
[00:15:03]
And one of those things is integer,
[00:15:05]
which has an int.
[00:15:06]
So type expression equals integer int,
[00:15:09]
or et cetera, et cetera.
[00:15:12]
And so if we were looking at that part of the AST,
[00:15:15]
which is the number one, two, three,
[00:15:17]
that's what we would have.
[00:15:22]
So what an AST does is it gives you
[00:15:26]
what the code kind of means.
[00:15:28]
It doesn't necessarily give you how it is formatted.
[00:15:31]
In this case, we do get the positions
[00:15:34]
of every expression on every node,
[00:15:37]
but it's not always enough to reformat it as it was before.
[00:15:41]
I think we touched on that
[00:15:42]
in maybe our original elm format,
[00:15:45]
original elm review episode that we did,
[00:15:50]
where we had a concrete syntax tree
[00:15:52]
and an abstract syntax tree,
[00:15:54]
where an abstract syntax tree
[00:15:57]
sort of has the information
[00:15:58]
to represent what your program means,
[00:16:00]
whereas a concrete syntax tree
[00:16:05]
has the information to represent
[00:16:07]
what your program looks like on the file
[00:16:10]
in a more structured format.
[00:16:13]
So with elm review,
[00:16:18]
we have a more abstract representation
[00:16:20]
which is whitespace agnostic.
[00:16:22]
Yeah.
[00:16:23]
So I think,
[00:16:25]
so I now maintain the elm syntax package with Martin Stewart,
[00:16:29]
and I think we will get it more and more
[00:16:31]
to a concrete syntax tree.
[00:16:34]
Interesting. Cool.
[00:16:35]
Because, yeah,
[00:16:37]
we need the information of the position to do better fixes
[00:16:40]
and to do better reports,
[00:16:42]
and some of them are missing,
[00:16:47]
and the code is actually formatted.
[00:16:48]
Yeah, so one of the core concepts
[00:16:51]
that comes up a lot in elm review
[00:16:54]
is the concept of a range,
[00:16:56]
which is literally just,
[00:16:58]
like you can go to the module,
[00:17:01]
which we'll link to in the show notes,
[00:17:03]
which is just the module elm.syntax.range.
[00:17:06]
It exposes a record type alias
[00:17:09]
where a range is a start location
[00:17:11]
and an end location,
[00:17:16]
meaning the row and column that it starts at
[00:17:19]
and the row and column that it ends at.
[00:17:21]
And so when you parse,
[00:17:24]
like the AST that you get access to in an elm review rule
[00:17:29]
gives you a range,
[00:17:32]
and you can say what part you want to underline
[00:17:35]
when you're marking a problem.
[00:17:37]
So if we were doing,
[00:17:39]
if we said css.rgb,
[00:17:44]
we could underline css.rgb
[00:17:46]
and then the list of arguments
[00:17:48]
that are being applied to that function, for example.
[00:17:51]
And those would be different ranges
[00:17:52]
that we would have access to from that AST.
[00:17:55]
Okay, so...
[00:17:56]
So I think we covered the AST.
[00:17:58]
I think that covers AST.
[00:18:00]
Let's sort of continue our pairing session
[00:18:03]
on this no custom colors review rule
[00:18:06]
that we're writing
[00:18:07]
as an internal project specific review rule
[00:18:12]
in our project.
[00:18:13]
So we did elm review new rule.
[00:18:16]
What's next?
[00:18:17]
Like, what do we...
[00:18:19]
I think we might have
[00:18:21]
a special phantom builder compiler error
[00:18:24]
at this point if we try to run our rule
[00:18:26]
after we did elm review new rule, right?
[00:18:28]
Yeah, so what the rule starts with
[00:18:32]
is rule.newModuleRuleSchema,
[00:18:35]
then between quotes the name of the rule,
[00:18:40]
and then the context,
[00:18:42]
which we'll go into later,
[00:18:45]
which is not needed for this case, I think.
[00:18:48]
So it's just a unit.
[00:18:50]
And then you've got a common saying,
[00:18:52]
add your visitors here.
[00:18:54]
So that's where you will have to add your visitors.
[00:18:57]
And then a call to rule.fromModuleRuleSchema.
[00:19:02]
So that concludes the building of your rule.
[00:19:07]
Add this and this, visitors,
[00:19:09]
and then fromModuleRuleSchema,
[00:19:11]
and that gives you your rule.
[00:19:12]
Right, and all of the visitors
[00:19:15]
that you apply for your rule,
[00:19:18]
so you're basically just defining
[00:19:20]
a set of types of things you want to visit.
[00:19:23]
You say rule.with simple import visitor,
[00:19:27]
and now you can...
[00:19:28]
So this is a really important concept
[00:19:31]
for building elm review rules,
[00:19:36]
what module context is, or project context,
[00:19:39]
and understanding the concept of a visitor.
[00:19:42]
So basically,
[00:19:46]
if you were to go try to emulate
[00:19:50]
the functionality of elm review from scratch,
[00:19:53]
then you would probably create a little elm app
[00:19:58]
that is run by a Node.js script
[00:20:03]
to make a headless elm application,
[00:20:05]
and then you're going to read a bunch of files.
[00:20:07]
You're going to read all the elm files
[00:20:09]
in your elm source directories,
[00:20:11]
and you're going to pass those files in
[00:20:13]
so that you have them in elm,
[00:20:15]
and then you're going to use the elm syntax package
[00:20:19]
to parse all those files.
[00:20:22]
So then you would do a bunch of pattern matching
[00:20:25]
to figure out what's going on.
[00:20:30]
If you have no custom color rule
[00:20:33]
using that sort of hand built AST traversal,
[00:20:37]
then what we're doing is we're just going to
[00:20:39]
just load in all of the files,
[00:20:42]
parse the whole AST of every module,
[00:20:44]
and then we're just going to go through each
[00:20:47]
and mark a problem if we see css.rgb called,
[00:20:51]
and the module is not our palette module.
[00:20:56]
So we're going to detect that css.rgb was called.
[00:20:58]
Where do you go in the AST to find the information?
[00:21:02]
And that's where visitors help.
[00:21:05]
Yes, right, exactly.
[00:21:07]
Okay, so this is...
[00:21:09]
The reason I'm talking about what it would be like
[00:21:13]
to build something from scratch
[00:21:16]
just for our listeners is because, as I say often,
[00:21:20]
I think that to understand a particular design in an API,
[00:21:25]
it helps to understand why was this design done.
[00:21:28]
Otherwise, it just seems like magic.
[00:21:30]
So to understand why it's done,
[00:21:33]
it helps to talk about how would you build it from scratch,
[00:21:36]
and then you realize, oh my God, this is super convenient
[00:21:39]
that I don't have to do it from scratch,
[00:21:41]
and this is why the design is done this way.
[00:21:44]
So essentially what I'm trying to do is to show
[00:21:47]
that we would essentially have to build our own visitor.
[00:21:52]
So by doing it from scratch, you've got this AST,
[00:21:55]
which, what is it at the top level?
[00:21:57]
Is it like a bunch of modules that you've parsed?
[00:22:01]
You've got these modules which are then like
[00:22:03]
a list of top level declarations or something like that?
[00:22:06]
You mean the declaration list?
[00:22:07]
Yeah, that's what it would parse into, right?
[00:22:10]
Oh yeah, you would have, for each file,
[00:22:13]
a record which contains the module declaration,
[00:22:18]
a list of declarations.
[00:22:20]
Right, and then those declarations would have expressions in them, right?
[00:22:24]
Yeah, well, there are types, and there are functions,
[00:22:27]
and functions have expressions.
[00:22:29]
Right, so basically when we're writing our no custom colors rule by scratch
[00:22:36]
using our own hand built Node script and Elm code,
[00:22:41]
we need to find all those places where expressions occur.
[00:22:46]
So we need to go in and find all those places
[00:22:48]
and iterate over them.
[00:22:50]
But with Elm review, you just say rule.withExpressionVisitor.
[00:22:54]
And now what you've done is you've gotten access to every expression.
[00:23:00]
It's just going to get passed into your function,
[00:23:02]
and you can sort of do a pattern match on that
[00:23:06]
and say if it is a function application expression,
[00:23:10]
and which is a fancy name for a function call,
[00:23:15]
is this specific function I'm looking for,
[00:23:18]
in which case you're just destructuring the list,
[00:23:22]
which is like the module namespace.
[00:23:25]
In this case, it's just CSS as the first element,
[00:23:29]
and RGB as the second one.
[00:23:31]
And you check for that, and then you say,
[00:23:34]
if the module I'm in is palette, then that's fine.
[00:23:38]
And otherwise, now you need to do something.
[00:23:43]
But it depends on how you want to make the rule work.
[00:23:47]
So how do you imagine it to work?
[00:23:49]
I would imagine that if I call CSS.RGB,
[00:23:55]
let's say the rule takes a configuration option,
[00:23:58]
which is the name of my palette module.
[00:24:00]
And if I have a call to CSS.RGB
[00:24:04]
anywhere outside of that palette module,
[00:24:06]
then it marks that function application as an error.
[00:24:11]
As I said before, you just want to match for expressions.
[00:24:14]
You can go for a simpler expression,
[00:24:17]
which is the function of value.
[00:24:19]
So you don't really care that it's a function call.
[00:24:22]
You just want to match any CSS.RGB functions.
[00:24:25]
Because potentially someone reassigns that function
[00:24:30]
to another name, and then they use that name somewhere else.
[00:24:34]
I see.
[00:24:39]
So they could be calling a function,
[00:24:41]
but that function is an alias somewhere.
[00:24:44]
Yeah, in a way it's an alias.
[00:24:46]
In other words, when they assign it to an alias,
[00:24:50]
that's not a function application.
[00:24:52]
And then when they do the function application,
[00:24:54]
it's applying a different function.
[00:24:56]
So you're saying you would forbid an expression
[00:25:00]
that even refers to the function or value CSS.RGB.
[00:25:05]
That makes sense.
[00:25:09]
So that would be a more robust way to do it.
[00:25:11]
And this is sort of the mindset you have to have
[00:25:13]
when you're writing Elm review rules.
[00:25:14]
From hearing you talk about it and doing it a little myself,
[00:25:17]
you do have to think about these corner cases
[00:25:19]
to a certain extent, and you try to abstract that away
[00:25:22]
as much as is reasonable.
[00:25:24]
But to a certain extent, you're doing static analysis.
[00:25:27]
You sort of have to think about some of these details.
[00:25:32]
The false positives and all the false negatives.
[00:25:34]
You don't want the rule to report more things than necessary.
[00:25:39]
You don't want to annoy your user
[00:25:41]
without needing them to do anything.
[00:25:44]
And you want to catch everything
[00:25:45]
that you're supposed to catch.
[00:25:47]
Right.
[00:25:48]
But in this case, there's a simple elegant solution,
[00:25:51]
which is just you can't even refer to this function,
[00:25:54]
and then we don't have to get fancy
[00:25:56]
keeping track of context
[00:26:01]
if we needed to.
[00:26:02]
If for some reason we needed to check
[00:26:04]
that you can refer to this function,
[00:26:06]
but you can't invoke it,
[00:26:07]
we could do that by basically building up context
[00:26:10]
in one of our visitors, right?
[00:26:12]
Yeah.
[00:26:13]
So the next step, once we have this rule
[00:26:16]
that detects RGB functions,
[00:26:18]
is to not report an issue when you're in the palette module,
[00:26:23]
the other module that you gave as a configuration.
[00:26:28]
One is you can just ignore the rule for this module.
[00:26:33]
Like manually, you would say
[00:26:36]
rule.ignoreFile or something like that?
[00:26:39]
Yeah, ignoreErrorForFiles,
[00:26:40]
and then you would pass that in.
[00:26:42]
And you could do that in your rule definition.
[00:26:45]
You don't have to do that in your review config.
[00:26:48]
So that's the easy case
[00:26:50]
when you just don't want to report it for a single file.
[00:26:55]
If you do something more clever,
[00:26:57]
then you will need to use a context.
[00:27:00]
That makes sense.
[00:27:02]
What is your idea of a context?
[00:27:04]
What do you gather from it?
[00:27:06]
What I think of a context as
[00:27:08]
is one of those cool things
[00:27:11]
that I love seeing in the Elm world,
[00:27:14]
which is it's sort of whatever type you use there.
[00:27:19]
So if your context is a unit type,
[00:27:24]
that's what the type is.
[00:27:26]
If the data that you need to build up
[00:27:29]
is a list of places where something is invoked
[00:27:33]
and you need the range,
[00:27:35]
you need to grab the range
[00:27:37]
so you can mark an error in those places,
[00:27:40]
then that would be the context you need
[00:27:42]
to later report the error.
[00:27:44]
Or if you needed to keep track of,
[00:27:49]
like for example, for my HTML to Elm Elm review rule,
[00:27:53]
the context I need to get is the configuration
[00:27:57]
of your import aliases and exposed values
[00:28:01]
for HTML, HTML.attributes, that sort of thing.
[00:28:05]
Yeah, so in a way, it's all the information
[00:28:07]
that you need to gather
[00:28:10]
to better report an error
[00:28:12]
or to better not report an error
[00:28:17]
that you need to report errors or not.
[00:28:19]
Right, because there are sort of like,
[00:28:22]
it's actually possible to give an error
[00:28:25]
in a visitor directly, right?
[00:28:27]
Or you can update the context
[00:28:31]
and then check the context at the end
[00:28:34]
in a final step and report errors based on that, right?
[00:28:38]
Yeah, so what you're referring to
[00:28:41]
is the final evaluation.
[00:28:46]
You report errors after you're done
[00:28:49]
looking at the entire file.
[00:28:50]
So I want to report when this function
[00:28:53]
is not used in this module.
[00:28:56]
Well, you can't know that until you've looked
[00:28:58]
at the whole file.
[00:28:59]
So that's why you need a final evaluation.
[00:29:02]
Yeah, so in a way,
[00:29:04]
the context is very, very similar
[00:29:07]
to a model in the Elm architecture.
[00:29:10]
So you can kind of see
[00:29:15]
the function. So you get a new message,
[00:29:17]
which is the new node, the expression,
[00:29:20]
or the declaration, or any kind of node
[00:29:22]
of the AST that you see.
[00:29:24]
And then you return some errors,
[00:29:27]
which is the commands.
[00:29:29]
Yeah, right.
[00:29:30]
And or you return a updated context,
[00:29:32]
which is your model.
[00:29:33]
So, oh, I've seen an import.
[00:29:35]
OK, now I need to know that class has been imported
[00:29:39]
and is in scope.
[00:29:44]
So it's a model and commands,
[00:29:47]
but model is the context,
[00:29:48]
and commands are the errors that you report,
[00:29:51]
except that the order is the other way around.
[00:29:54]
Yeah, yeah, but it's still a tuple,
[00:29:56]
and one of the things is saying,
[00:29:58]
do something.
[00:29:59]
In this case, the only thing you can do
[00:30:01]
is report an error or a fix, I think.
[00:30:05]
Right?
[00:30:06]
The only things that can do fixes are errors.
[00:30:11]
You can either, right,
[00:30:12]
you can report an error,
[00:30:14]
which either is going to warn you about an error,
[00:30:16]
and it optionally may recommend a fix.
[00:30:19]
And the second thing in the tuple
[00:30:21]
is updating the state that you're tracking
[00:30:24]
as you run all your visitors.
[00:30:27]
So, yeah, it's really cool.
[00:30:29]
So basically, every visitor that you apply,
[00:30:32]
so you start your rule with your rule builder,
[00:30:37]
rule.newModuleRuleSchema,
[00:30:39]
and then you give it your initial context,
[00:30:41]
which in the way you're framing it,
[00:30:43]
that's your init for your initial model.
[00:30:46]
You actually don't have any initial commands of errors
[00:30:50]
because that would just be weird
[00:30:52]
before you even start, like,
[00:30:54]
whoa, they're like,
[00:30:55]
you haven't even looked at my code.
[00:30:57]
Don't tell me there's an error.
[00:30:58]
That would be rude.
[00:30:59]
Well, by the time that this episode airs,
[00:31:01]
I will have released
[00:31:06]
a new version of the code.
[00:31:07]
If you pass in a bad configuration,
[00:31:09]
before looking at the code,
[00:31:12]
we can say,
[00:31:13]
hey, there's a problem with what you gave me.
[00:31:15]
This doesn't make sense.
[00:31:16]
This is not proper configuration.
[00:31:18]
That makes sense.
[00:31:19]
So you can validate the actual options
[00:31:21]
that are passed into your rule.
[00:31:22]
That's great.
[00:31:23]
Yeah.
[00:31:24]
Well, validate or parse it.
[00:31:25]
Yes.
[00:31:27]
Right.
[00:31:28]
And then, so that's the initial way
[00:31:30]
that you start the builder.
[00:31:35]
You have chained function calls,
[00:31:37]
so then you apply your visitors.
[00:31:39]
So rule.withExpression, enter visitor,
[00:31:41]
and then that visitor,
[00:31:43]
they all have to be of the same module context type
[00:31:47]
or project context if you're doing a project visitor.
[00:31:50]
And so every single one you chain is going to be,
[00:31:54]
because that's your model type.
[00:31:55]
Just like the update function,
[00:31:58]
in your update function,
[00:31:59]
you have a particular model type.
[00:32:04]
In the subscriptions function,
[00:32:06]
you receive an argument of that model type.
[00:32:08]
So all those types snap together.
[00:32:10]
It's the same with these visitors.
[00:32:12]
They all have the same context type
[00:32:15]
that they either receive or update,
[00:32:18]
receive and update.
[00:32:19]
Okay.
[00:32:20]
So we've gotten this far,
[00:32:23]
and we've talked about writing a test,
[00:32:25]
but we haven't really talked about what that looks like.
[00:32:28]
So let's continue our pairing session here,
[00:32:33]
and we're going to write a test
[00:32:36]
for our no custom color review rule.
[00:32:39]
So when we did elm review,
[00:32:42]
new rule on our command line,
[00:32:44]
that generated a test module called tests,
[00:32:49]
which is in our review slash tests folder.
[00:32:53]
Yeah, okay.
[00:32:54]
So then, and that gives us sort of like a starting point,
[00:32:59]
we get the elm package documentation
[00:33:01]
in the elm review package.
[00:33:03]
There's a review dot test module,
[00:33:05]
and it's got really good documentation
[00:33:08]
on just kind of the basics,
[00:33:09]
but the basic idea is,
[00:33:11]
you can give it elm source code as a string.
[00:33:16]
You just define a string.
[00:33:17]
So let's continue pairing on that.
[00:33:22]
So we would say like module navbar view.
[00:33:27]
Navbar module, exposing view,
[00:33:29]
and then we would say view,
[00:33:31]
and we would write some valid elm syntax
[00:33:34]
that uses CSS dot RGB,
[00:33:36]
and so we would expect there to be a failure there.
[00:33:39]
And so then we can use the elm review test helpers
[00:33:43]
to basically say,
[00:33:45]
in this case, I expect errors,
[00:33:47]
and then we describe the expected error message,
[00:33:52]
and then you give it under, which is just a string
[00:33:55]
of where you expect it to underline.
[00:33:58]
Yeah, where you would see these quickly lines
[00:33:59]
in your editor or in your terminal
[00:34:01]
when you run elm review.
[00:34:02]
Right, so in our case,
[00:34:03]
that would just be CSS dot RGB.
[00:34:06]
Yep.
[00:34:07]
Actually, we probably would need to keep track
[00:34:12]
of the import context to check for import aliases or...
[00:34:15]
Yeah, but there is a nifty tool inside elm review
[00:34:20]
that makes it so you don't have to do it manually,
[00:34:23]
at the start, we did have to.
[00:34:25]
Yeah.
[00:34:26]
And that was just annoying and elm prone,
[00:34:29]
so I made it built in.
[00:34:31]
That makes sense as a sort of built in feature.
[00:34:33]
So how would we use that?
[00:34:36]
That's where it gets a bit more complex.
[00:34:40]
So you would need to use a module name, lookup table.
[00:34:45]
That's the name.
[00:34:46]
So for every expression or every type annotation,
[00:34:51]
you can ask it, what is the module name for this thing?
[00:34:55]
So it only works for a few kinds of expressions and types.
[00:35:00]
So the function of value, which we talked about before,
[00:35:04]
which represents CSS dot RGB,
[00:35:07]
you can ask it, what is the real module name for this node?
[00:35:13]
So maybe CSS refers to HTML dot custom dot CSS,
[00:35:18]
because it's been aliased as CSS,
[00:35:20]
or it represents CSS from the RT Feldman elm CSS package.
[00:35:26]
And the way that you get it is by calling
[00:35:29]
with module name lookup table.
[00:35:31]
So that is something that you will then have to store
[00:35:34]
in your context that we talked about before.
[00:35:36]
And the way you need to get it is a bit more complicated.
[00:35:41]
It's not that complicated,
[00:35:42]
but I'm not sure we want to get into this here.
[00:35:47]
We want to get into the actual application.
[00:35:49]
Cool, cool.
[00:35:50]
Okay, I see.
[00:35:51]
But that's something that you have to wire in
[00:35:54]
to your model, essentially, to your context,
[00:35:56]
and pull it in there and then look at something.
[00:36:00]
And then you pass it around, and it's easy to use.
[00:36:03]
That's cool.
[00:36:04]
Yeah, so these are the kinds of things
[00:36:06]
that having Elm Review as a platform allows you to do.
[00:36:10]
Whereas if we're writing our own from scratch,
[00:36:15]
we could traverse every single element in our AST,
[00:36:20]
look at every expression,
[00:36:22]
and look for a certain type of expression.
[00:36:24]
That's not too complex,
[00:36:28]
but the things that we lack there are,
[00:36:31]
for one, having this sort of lifecycle
[00:36:34]
that you describe sort of like an Elm application
[00:36:38]
with an init and update and a model and commands.
[00:36:43]
You're able to hook into those sort of lifecycle events
[00:36:48]
and build up that context
[00:36:50]
and maintain that state more easily.
[00:36:52]
And when you mark an error, you pass the range,
[00:36:56]
and then you get really nice error messages
[00:36:58]
that look a lot like Elm's error messages,
[00:37:01]
and it underlines the appropriate thing.
[00:37:03]
So those are some of the things that,
[00:37:05]
not that anyone would want to build it from scratch
[00:37:07]
because it's just way more convenient
[00:37:12]
but these are the kinds of things
[00:37:14]
that this visitor pattern gives you.
[00:37:16]
And it also allows you to be increasingly complex or simple
[00:37:20]
depending on what you need,
[00:37:21]
kind of like browser.element
[00:37:24]
versus browser.application.
[00:37:26]
Like if you wanted to define a module,
[00:37:30]
you can define a module visitor,
[00:37:32]
or you can define a project visitor.
[00:37:34]
Yeah, a module rule or a project rule.
[00:37:36]
Yes, and the module rule
[00:37:41]
is a module rule that will only look at one file at a time.
[00:37:47]
So it takes the AST for one file,
[00:37:50]
go through it, report some errors,
[00:37:52]
and then when it's done, you go to another file
[00:37:55]
and you forget all about the previous one.
[00:37:57]
So when you can use that
[00:37:59]
and that is sufficient for your needs,
[00:38:00]
that's much easier to use than project rule.
[00:38:03]
A project rule is kind of the same thing,
[00:38:05]
but when you go to another file,
[00:38:10]
and that way you can gather a lot of information
[00:38:13]
for the entire project,
[00:38:15]
and based on what you found,
[00:38:17]
you can report different things.
[00:38:20]
Right.
[00:38:21]
So I was kind of thinking like a cool rule,
[00:38:25]
which may be challenging to write,
[00:38:27]
but an interesting rule to explore
[00:38:29]
would be like a parse don't validate rule
[00:38:32]
where you, like for example,
[00:38:37]
want to catch would be
[00:38:39]
if I have a particular value that I check
[00:38:42]
to see if it equals nothing
[00:38:44]
or any other custom type variant,
[00:38:46]
and then I later,
[00:38:48]
I check that with an equals
[00:38:50]
if value equals nothing,
[00:38:53]
else I pass it on somewhere,
[00:38:55]
and then later in that function I pass it along to
[00:38:58]
if it then destructures it
[00:39:01]
and unwraps the just case
[00:39:06]
sort of a shotgun surgery that we talked about
[00:39:09]
in our parse don't validate episode
[00:39:10]
where you're checking a condition over and over.
[00:39:13]
Ideally you want to check it once.
[00:39:15]
So that would be interesting,
[00:39:17]
but it's difficult because it kind of requires
[00:39:19]
more of a context to be built up
[00:39:21]
of following the flow of
[00:39:24]
I checked this equals nothing,
[00:39:26]
I didn't else,
[00:39:28]
I went and followed the module
[00:39:30]
that it got called in, right?
[00:39:35]
I'm not entirely sure because I haven't
[00:39:38]
delved in it enough,
[00:39:39]
but it is not something that I find to be
[00:39:42]
very easy with Elm review.
[00:39:44]
That said, I have not really tried anything with it,
[00:39:47]
but I imagine that it will be a bit tricky.
[00:39:49]
So you can do this kind of rule, I think,
[00:39:52]
but to the extent to which you can do it
[00:39:56]
will be getting harder and harder.
[00:39:59]
So if you want to do something like
[00:40:04]
where it says if lists is empty of a value,
[00:40:08]
then you have a case where it is true
[00:40:10]
and inside of there you do, you call list.head,
[00:40:13]
then if it's narrowly,
[00:40:16]
if those calls are in the same function
[00:40:18]
or even just inline,
[00:40:20]
so then the list.head is in the branch
[00:40:24]
where you called lists is empty,
[00:40:26]
then I think it's doable.
[00:40:28]
Right, because in that case, it's a single expression.
[00:40:33]
And you can destructure that AST case expression
[00:40:37]
and get the condition or the value that it's doing the case on,
[00:40:43]
and then you can get the body of the branches
[00:40:47]
and you've got all the context you need there.
[00:40:49]
You don't need to continue following that context.
[00:40:52]
Yeah, it might be a bit trickier if you say
[00:40:55]
if list is empty or if this or if that
[00:41:00]
was a bit more blurry, like,
[00:41:01]
do you really need to parse don't validate?
[00:41:04]
But the tricky part is when you start using indirection.
[00:41:08]
So if in that branch you call another function
[00:41:12]
or a function from a different module,
[00:41:14]
that's where it becomes very tricky.
[00:41:16]
So I made it very easy to get information about functions.
[00:41:21]
So I made it very easy to get information about modules
[00:41:26]
but not the other way around.
[00:41:28]
So I made it easy to get information from modules that you import,
[00:41:31]
but I didn't make it easy to get information from modules that use you.
[00:41:37]
Yeah, that makes sense.
[00:41:38]
Right, the upstream dependencies you can inspect
[00:41:42]
but not the downstream.
[00:41:44]
Yeah, so I think I want to make that possible,
[00:41:47]
but to make that very nice, I need new information
[00:41:50]
that I'm working on but that is very tricky to get
[00:41:55]
Can you give me a little more of an idea of what,
[00:41:58]
so I'm guessing looking at the docs that you're talking about
[00:42:00]
with context from imported modules,
[00:42:03]
is that what you're talking about?
[00:42:04]
Yeah, so you were talking about project context.
[00:42:07]
So we talked about the context before.
[00:42:09]
So that is the model that you get from
[00:42:12]
for a single module visit.
[00:42:15]
So you go through expressions,
[00:42:18]
you go through declarations, et cetera.
[00:42:23]
If you are in a project role, you transform that to a project role.
[00:42:27]
You basically compile all the information that you got from this module,
[00:42:31]
and that's what we call a project context.
[00:42:33]
And then this project context, we merge them together
[00:42:36]
for all the imports that you have in the new module.
[00:42:40]
So if I import CSS or I import navbar,
[00:42:44]
then the project context for all of those
[00:42:49]
will merge together, and that's what I will use
[00:42:52]
to start my module context with.
[00:42:54]
So that's what I will init my module context with.
[00:42:58]
So that's how you can pass data around
[00:43:01]
from one module to another.
[00:43:02]
I see.
[00:43:03]
So there's this fold project context notion
[00:43:06]
that tells you how to give in,
[00:43:08]
what would that be, give in all of the modules that import me,
[00:43:13]
give me the context from all of those modules,
[00:43:18]
with context from imported modules?
[00:43:20]
Yes.
[00:43:21]
Yeah, that is just the information from the imports
[00:43:25]
that this file imports.
[00:43:27]
So if I import navbar,
[00:43:29]
then I need to get information from navbar
[00:43:32]
to do my reports correctly,
[00:43:34]
my understanding of the context correctly.
[00:43:37]
How it is used will usually not matter too much,
[00:43:40]
so it's more important to have it this way than the other way.
[00:43:45]
It's the other way around too, but that is trickier,
[00:43:48]
because you will still need some information
[00:43:50]
from the imported modules.
[00:43:52]
Right, interesting.
[00:43:54]
Yeah, so I feel like this is a good sensibility to develop,
[00:43:58]
is what is easy to analyze and what's difficult to analyze,
[00:44:03]
and try to find rules, try to identify rules
[00:44:08]
that you can write that are going to be very easy to spot,
[00:44:13]
real problems, not like maybe a problem.
[00:44:17]
And so those are the rules ideally that you want to be writing.
[00:44:21]
In some cases, maybe writing a parse don't validate rule
[00:44:24]
is extremely difficult, but possible,
[00:44:27]
and really valuable as a community resource
[00:44:30]
that is worth a lot of time doing flow analysis
[00:44:34]
and all of that.
[00:44:35]
But often you probably want to find the low hanging fruit
[00:44:40]
of a rule, and in a certain sense,
[00:44:42]
if it's going to be easy to analyze it using a rule,
[00:44:47]
it may be going to be easy for people to understand
[00:44:50]
when they're violating that rule or not.
[00:44:53]
You mean when Elm Review reports an error?
[00:44:55]
Yeah, if you make it overly complex, or maybe that's...
[00:44:59]
Yeah, I try to make the rules very actionable.
[00:45:03]
So whenever you get an error,
[00:45:08]
you want to make sure you have a reasonable amount of time
[00:45:10]
and not have to refactor your whole project to comply.
[00:45:15]
Right, which having too much context
[00:45:18]
and too much dependency on context
[00:45:20]
might imply that you might be making something
[00:45:23]
that would be difficult to fix.
[00:45:25]
So for instance, if you...
[00:45:26]
There's the Elm Review unused package
[00:45:30]
with a lot of no unused rules.
[00:45:35]
So I try to make all of these very actionable.
[00:45:38]
So there's a few rules that tell you
[00:45:40]
when a function is not used.
[00:45:42]
So imagine you have a function that gets reported as unused
[00:45:45]
because it is used in a few functions,
[00:45:49]
but all of those are unused.
[00:45:51]
So the thing is, if you try to remove the first one,
[00:45:54]
then you get compiler errors.
[00:45:56]
So that is not actionable.
[00:45:58]
It is much easier to tell the user,
[00:46:03]
and then when all of those are removed,
[00:46:05]
then you can remove the one that reported at first.
[00:46:08]
So don't try to be too smart
[00:46:10]
and try to let the user do tiny steps, as you like to say.
[00:46:15]
Interesting.
[00:46:16]
That's a great point.
[00:46:18]
So you're kind of saying,
[00:46:20]
if a problem can be fixed kind of recursively,
[00:46:26]
like applied over and over
[00:46:31]
and you can point out a root problem,
[00:46:33]
it's okay for it to be a multi step problem
[00:46:36]
where you address an error
[00:46:39]
and then another error arises
[00:46:41]
because it uncovers a new issue
[00:46:44]
that's now at the leaf that was before a parent of a leaf.
[00:46:48]
Yeah, and also it's just much easier to write
[00:46:51]
as an Elm Review rule.
[00:46:52]
Yep, that makes sense.
[00:46:54]
So okay, that brings up another topic
[00:46:59]
and another cool feature of Elm Review.
[00:47:01]
One of the most fun features in my mind, which is fixes.
[00:47:05]
So you want to introduce what an Elm Review fix is?
[00:47:09]
Yeah, so Elm Review, when it reports an error,
[00:47:11]
it tells you sometimes,
[00:47:13]
hey, this rule is fixable.
[00:47:15]
If you run the Elm Review dash dash fix
[00:47:17]
or dash dash fix all,
[00:47:18]
I can give you suggestions as to how to fix it.
[00:47:23]
Right, and suggestions being an actual diff of,
[00:47:28]
here is your code before,
[00:47:29]
here's what it would look like after.
[00:47:31]
Does that sound good to you?
[00:47:32]
Yeah, so you can only add fixes to a problem.
[00:47:37]
So it always needs to be an error.
[00:47:39]
In practice, I don't see that.
[00:47:41]
I haven't seen that as an limitation.
[00:47:43]
But yeah, what a fix is basically
[00:47:45]
is just a list of edits to a module.
[00:47:50]
So it's very string based,
[00:47:52]
but it's basically take this range,
[00:47:54]
so from point A to point B, and remove it.
[00:47:57]
At point A, add this text,
[00:48:01]
and at this other range,
[00:48:03]
replace it by something else.
[00:48:04]
Right, which you would get a range from,
[00:48:07]
when you do a visitor, it gives you nodes,
[00:48:11]
which are just these AST elements
[00:48:14]
like expressions with a range, right?
[00:48:19]
So you can guess it or calculate it yourself.
[00:48:21]
You get that from your visitors.
[00:48:23]
Yeah, or just very simple computations.
[00:48:26]
Yeah.
[00:48:27]
Like merging two ranges,
[00:48:29]
because I want to remove this and that,
[00:48:31]
and that's it.
[00:48:32]
So if I did CSS.RGB 0.0.0,
[00:48:36]
and then I could imagine
[00:48:40]
we could have gotten some context
[00:48:45]
where we visited the color palette module,
[00:48:49]
and then determined that there's something
[00:48:51]
with that definition of RGB 0.0.0 called black,
[00:48:55]
and then we could suggest a fix then,
[00:48:59]
and we could say,
[00:49:00]
hey, this is defined in your color module,
[00:49:02]
so there's a problem.
[00:49:04]
You're not supposed to be using custom colors,
[00:49:07]
and here's a proposed fix.
[00:49:09]
Would you like me to replace this
[00:49:14]
or not?
[00:49:15]
That would be very useful.
[00:49:17]
Would it be able to add and import
[00:49:19]
in that process of doing that fix?
[00:49:20]
As long as it's in the same file, yeah.
[00:49:22]
Okay.
[00:49:23]
You would get a big diff, though.
[00:49:25]
No, it would be fine, actually.
[00:49:27]
Would it reformat the file potentially?
[00:49:30]
So every time you apply a fix,
[00:49:33]
we run elm formats.
[00:49:35]
Right, so like if I had a record that I defined
[00:49:40]
as a record alias on a single line,
[00:49:42]
it might end up on multi line or vice versa
[00:49:46]
after applying that.
[00:49:47]
No, because we're not actually trying to...
[00:49:51]
So fixes do not work on the AST.
[00:49:54]
We do not take the AST
[00:49:56]
and transform it back to a string.
[00:49:58]
We take the raw string,
[00:50:00]
the raw source code,
[00:50:01]
and we apply the fixes on that.
[00:50:03]
So the only things that will get changed potentially
[00:50:08]
are the fixes that are then changed by elm formats.
[00:50:12]
I see.
[00:50:13]
So if something parses into a different AST,
[00:50:15]
it'll get reformatted.
[00:50:17]
Elm format will be run,
[00:50:19]
and whatever elm format produces based on that
[00:50:21]
will be what it results in.
[00:50:23]
But if the syntax tree didn't change
[00:50:26]
in that particular part of it with your fix,
[00:50:29]
it's not going to touch that part of the AST.
[00:50:31]
Yeah, unless the code was not elm formatted to start with.
[00:50:36]
That makes sense.
[00:50:37]
Cool. That's very cool.
[00:50:39]
I'm a bit sad that the Fix API is so crude
[00:50:43]
because it works with strings basically,
[00:50:45]
but it's a bit tricky to transform things into new ASTs
[00:50:50]
and replace ASTs because that can change the behavior
[00:50:53]
and mostly the formatting.
[00:50:55]
So the behavior that is actually normal
[00:50:58]
because that is something you might want to,
[00:51:00]
but the formatting is a bit annoying
[00:51:05]
to get it right.
[00:51:06]
So hopefully at some point I will have an idea
[00:51:08]
about how to improve this,
[00:51:10]
but it works okay, I guess.
[00:51:12]
I've done some contributions
[00:51:15]
to the IntelliJ Elm plugin as well,
[00:51:17]
and one of the things
[00:51:20]
in the contributing guidelines for that project
[00:51:23]
is that it is recommended
[00:51:27]
that you, just in general with IntelliJ plugin authoring,
[00:51:32]
don't manually try to tweak the AST of files,
[00:51:36]
but rather that you actually reparse it
[00:51:40]
and replace the AST from reparsing text.
[00:51:44]
So you can build up an AST,
[00:51:46]
but then stringify it and then reparse it.
[00:51:49]
And the reason is because basically the syntax parser
[00:51:53]
for the Elm language for the IntelliJ Elm plugin
[00:51:58]
will never be able to produce the same AST
[00:52:00]
that you manually created.
[00:52:03]
So even if it's like valid types for the AST types,
[00:52:07]
it may not be something that the parser could ever yield,
[00:52:10]
which would be bad that you would produce that.
[00:52:13]
So that's why they recommend that you don't manually
[00:52:16]
update the AST in IntelliJ plugins,
[00:52:19]
but that you actually transform it into a string
[00:52:24]
so that your AST be parsed by the plugin
[00:52:27]
to make sure it doesn't give you some weird special case AST
[00:52:32]
that would never actually occur.
[00:52:33]
So I think that that's the right approach.
[00:52:35]
Also, I think that it's reasonable to do code generation
[00:52:39]
with string templating in certain cases.
[00:52:42]
So I don't think it's necessarily a bad thing.
[00:52:45]
Sometimes that's perfectly okay.
[00:52:48]
Well, code generation I think is fine,
[00:52:53]
but if you pass in a bad range,
[00:52:56]
which shouldn't happen because you should just reuse
[00:52:59]
the ranges that the AST gives you, but still it's possible,
[00:53:03]
you can create parsing errors.
[00:53:05]
So if you have an if expression,
[00:53:08]
and somehow you remove the f from if,
[00:53:11]
well, you've got a parsing error.
[00:53:13]
So Elm Review will have several mechanisms
[00:53:16]
to work against that.
[00:53:21]
If you get such an error, it will tell you,
[00:53:24]
and it will fail the test.
[00:53:25]
And also the CLI, when it tries to apply a fix,
[00:53:30]
it will tell you that there's a problem.
[00:53:32]
But before you run it with fix, it will tell you,
[00:53:34]
hey, I think I can fix it.
[00:53:36]
And then when you try it, it says,
[00:53:38]
oh, I thought I could.
[00:53:40]
Yeah.
[00:53:41]
Sorry.
[00:53:41]
Yeah.
[00:53:44]
The most challenging part of this sort of static code stuff
[00:53:49]
that Elm has been generating valid white space
[00:53:52]
for AST transformations, it's such a pain.
[00:53:56]
It's like, I mean, like I built in IntelliJ Elm,
[00:54:01]
I added this feature where it can add or remove pipelines,
[00:54:06]
which is like a fairly complex feature.
[00:54:09]
But by far, the hardest part was preserving valid white space
[00:54:14]
because if you have like a case expression
[00:54:17]
or an if or anything anywhere in there,
[00:54:19]
you have to like do all this extremely complex static analysis
[00:54:24]
to make sure that you preserve the white space
[00:54:26]
to make it valid.
[00:54:27]
It's kind of a nightmare.
[00:54:29]
I've given, you've heard my rant on this,
[00:54:31]
but basically I wish that, I don't know,
[00:54:34]
like somehow in the Elm syntax,
[00:54:37]
I wish that like curly braces were valid there
[00:54:42]
at least for like tooling authors do code generation
[00:54:46]
without worrying about white space significance.
[00:54:49]
But that's my biggest complaint.
[00:54:53]
Yeah.
[00:54:54]
That's why I don't try to make anything too smart
[00:54:58]
because I don't think I can nail that
[00:55:00]
without a lot of research.
[00:55:02]
Yeah.
[00:55:03]
For the Elm review rule that I wrote
[00:55:06]
for transforming debug.todos with HTML into Elm code,
[00:55:11]
what I did there is I just indent things a whole bunch
[00:55:18]
because I'm like, hey, I'm just generating
[00:55:20]
all this code on one line
[00:55:22]
and it's possible that something goes wrong.
[00:55:25]
But if I just indent everything a bunch,
[00:55:27]
then it's going to be valid.
[00:55:29]
I'm guessing the diff will not look great on Elm review.
[00:55:33]
No, the diff looks fine
[00:55:34]
because it runs Elm format before.
[00:55:39]
I don't know why the fix
[00:55:40]
because Elm format has the property that it's a CLI
[00:55:44]
which means it's an async call
[00:55:47]
and Elm review does most of its things
[00:55:50]
in one pure Elm function.
[00:55:53]
I'll have to check.
[00:55:53]
It's a bit tricky to call Elm format.
[00:55:55]
Maybe I made the diff really bad after I made that change.
[00:55:59]
I'm not sure.
[00:56:00]
I'll have to take a look.
[00:56:01]
I didn't notice it being looking bad, but yeah.
[00:56:06]
One of the things about fixes is that a lot of people
[00:56:08]
enjoy having fixes for their rules
[00:56:11]
and sometimes they apply them without looking,
[00:56:14]
which is dangerous.
[00:56:16]
But as a rule author, it is a lot more tricky
[00:56:20]
to fix an issue than to report it.
[00:56:24]
Maybe 10 times as hard because then you will have to
[00:56:27]
gather a lot more context.
[00:56:29]
You will have to make sure you don't report an error twice.
[00:56:34]
You might understand why I mean that.
[00:56:36]
Let's take the example of CSS.RGB.
[00:56:41]
We said we reported functional value expressions.
[00:56:46]
Because we want to report even when you age it,
[00:56:52]
not only when you call it.
[00:56:55]
But when you want to fix it,
[00:56:57]
you want to look at the function call.
[00:57:02]
And then you will pattern match to see
[00:57:06]
is the function CSS.RGB,
[00:57:09]
in which case you can report it.
[00:57:11]
And then you will also need to look at the arguments.
[00:57:14]
Do they match with something that I've seen
[00:57:18]
by the way in a different module?
[00:57:20]
So you need a project context.
[00:57:22]
You also need to look at the imports
[00:57:24]
to see how palette has been imported.
[00:57:26]
And then you add a fix.
[00:57:31]
You can import this node for this function call
[00:57:34]
by palette.black.
[00:57:36]
But you still keep the pattern that reports function or values
[00:57:41]
because you still don't want the function to be an alias.
[00:57:44]
The thing is, now you will report function calls to CSS.RGB twice.
[00:57:49]
Once in the function call and once for the function value
[00:57:53]
for the reference.
[00:57:55]
So you will need to add to your context
[00:58:00]
reports.
[00:58:01]
So that's a lot more complex than just reporting it,
[00:58:04]
which was very, very easy.
[00:58:06]
So basically, would you say that you want to catch problems
[00:58:12]
pretty consistently and robustly,
[00:58:16]
except for that advice that you gave about
[00:58:19]
it's okay if you have to keep reapplying it at the leaves
[00:58:24]
and work your way up, that's fine.
[00:58:29]
But you don't necessarily need to suggest a fix for everything.
[00:58:34]
So if you just have some case where you're like,
[00:58:37]
in this context, I can't consistently give an accurate,
[00:58:42]
precise, correct fix, then just say,
[00:58:44]
I'm not going to give you a fix.
[00:58:46]
I'm just going to report the error.
[00:58:47]
That would be fine?
[00:58:48]
Yeah, I would say that it's perfectly fine.
[00:58:51]
Because one, it's a lot of work,
[00:58:53]
and that's not necessarily what most of the value is,
[00:58:58]
but there are multiple ways to fix an issue.
[00:59:01]
And choosing one is making the choice of not suggesting
[00:59:05]
the other one, which could be the better solution.
[00:59:08]
Yeah, I would say as a user,
[00:59:10]
the impact of having some bad apples,
[00:59:14]
some poison pills would be pretty bad.
[00:59:18]
Like if I have some rules with fixes that are suggested
[00:59:23]
that aren't actually correct transformations
[00:59:26]
that change the meaning or whether my code compiles,
[00:59:31]
it would make me lose trust
[00:59:33]
in just applying automated fixes in general.
[00:59:37]
So I would say being extremely conservative
[00:59:40]
is a good approach here.
[00:59:42]
It's okay to just back out and say,
[00:59:45]
I'm not going to try to fix this.
[00:59:46]
I'm just going to report a problem.
[00:59:51]
That's the way to go.
[00:59:53]
Because ideally, we want to be able to just run
[00:59:56]
elmreview fixall without worrying about it.
[00:59:59]
So we should hold a high standard for fixes, basically.
[01:00:02]
Yeah, so I did write a lot about this issue of fixing
[01:00:05]
and one not to fix in the elmreview package documentation
[01:00:10]
under review.fix.
[01:00:13]
And as you said,
[01:00:15]
you really, really don't want to introduce compiler errors.
[01:00:20]
Because we can determine that.
[01:00:22]
But determining whether the project will still compile
[01:00:25]
is a lot trickier because we don't have the power
[01:00:28]
of the compiler.
[01:00:29]
I would say that I would rather have a compiler error
[01:00:32]
than a semantic change unintentionally from a fix.
[01:00:36]
Because if it's a compiler error,
[01:00:37]
then I know that something went wrong.
[01:00:39]
But if it's a silent change to behavior...
[01:00:42]
Well, it depends on whether you deem that it's always better.
[01:00:47]
I don't know when that would apply,
[01:00:49]
but for instance, if you would need to sanitize some HTML,
[01:00:55]
some string, then sure, always apply it.
[01:00:59]
It's fine.
[01:01:00]
Right, right, right.
[01:01:02]
The problem with compiler errors or introducing those issues
[01:01:07]
is that you need to imagine that the user will do...
[01:01:10]
I will run elmreview with fixall.
[01:01:15]
I will apply multiple fixes.
[01:01:16]
Imagine I have 200 errors.
[01:01:19]
All of them are fixable.
[01:01:20]
And I apply all of them, and I'm very happy
[01:01:22]
because at some point it tells me that there's no more errors.
[01:01:26]
And then I try to compile, and it doesn't compile.
[01:01:28]
Okay, when was this problem introduced?
[01:01:32]
Which fix introduced it?
[01:01:34]
You don't want the user to have to answer that question.
[01:01:37]
Right, right.
[01:01:38]
So if you don't think you can fix the issue entirely
[01:01:43]
don't propose a fix.
[01:01:45]
It's better to let the user do it,
[01:01:47]
even if it's annoying to them.
[01:01:49]
Yeah, that makes sense.
[01:01:50]
Well, did we miss anything that's important for people to know
[01:01:53]
when they're getting started writing their own elm review rules?
[01:01:57]
Definitely look at the docs because you wrote a lot.
[01:02:00]
I mean, it's like a tutorial practically.
[01:02:03]
It's pretty in depth.
[01:02:04]
Yeah, so there's a lot of information and guidelines
[01:02:07]
in the elm review packages,
[01:02:12]
that's thought into there.
[01:02:13]
So if we miss something, it's probably in there,
[01:02:15]
or you can ask about it in the elmslack.
[01:02:18]
One thing I would say is also,
[01:02:21]
there's a lot of examples in the review rule module documentation.
[01:02:26]
And actually, if you look at with module name lookup table,
[01:02:31]
which we talked about before,
[01:02:33]
there is an example that does what?
[01:02:35]
Forbids calling css.caller.
[01:02:40]
I actually saw that midway through our conversation.
[01:02:42]
I actually hadn't noticed that until we started going through it.
[01:02:45]
But it's, yeah.
[01:02:47]
Yeah, you said, let's spare on this.
[01:02:50]
And I was like,
[01:02:51]
I'm pretty sure I already have an example of this one.
[01:02:55]
Yep.
[01:02:56]
Because this is one of the basic examples I would give
[01:03:01]
to new guarantees to your project
[01:03:03]
that the Elm Compiler can give.
[01:03:08]
One thing I would like to touch on before we finish
[01:03:11]
is that elm review is a great tool
[01:03:14]
to add a lot of new guarantees about your project,
[01:03:18]
a lot of niceties.
[01:03:20]
So if you want consistent ways of using code,
[01:03:25]
so all the colors should come from the palette module,
[01:03:28]
for instance, elm review is a great tool for that.
[01:03:31]
It's a great linting tool,
[01:03:36]
and we've seen linting tools in other ecosystems,
[01:03:39]
but that's not what makes doing this in Elm
[01:03:42]
with elm review interesting.
[01:03:44]
Yeah, I would agree.
[01:03:45]
The thing is, elm review is just one tool.
[01:03:48]
I love it a lot.
[01:03:50]
But it really is a new way of thinking about problems.
[01:03:56]
So it detects how you write code,
[01:03:59]
not what it necessarily means.
[01:04:04]
It can catch, and the compiler won't be able to catch.
[01:04:07]
But there's a lot of things that the compiler
[01:04:09]
will be able to catch, and elm review won't.
[01:04:11]
And a successful strategy to catch new problems
[01:04:14]
is to try and make use of all the tools at your disposal
[01:04:17]
to get those guarantees.
[01:04:19]
So I have an article about safe, unsafe operations in Elm,
[01:04:24]
which takes the example of a function
[01:04:28]
to which you pass in a reg X as a string,
[01:04:33]
which is an unsafe operation,
[01:04:34]
because reg Xs can fail, right?
[01:04:36]
But the rule will just say,
[01:04:38]
this is fine if the reg X is valid.
[01:04:42]
But if it's not valid, then it will report an error.
[01:04:45]
I lost my train of thought.
[01:04:48]
So you can do more than just looking at code style,
[01:04:51]
but you can actually use elm review as a tool
[01:04:53]
to give you stronger guarantees about your application
[01:04:56]
in tandem with opaque types, the elm compiler.
[01:05:01]
You want to use the compiler, you want to use a type system.
[01:05:05]
And it's kind of like, sometimes you need to do something
[01:05:08]
like two pronged attacks, so on both sides.
[01:05:11]
So you want to reduce what is possible with types.
[01:05:15]
On the other hand, you want to reduce
[01:05:17]
what is possible to write with elm review,
[01:05:20]
and maybe also imply code generation.
[01:05:23]
And when you limit what you can have as values
[01:05:28]
in the type system and what you can write with elm review,
[01:05:30]
at some point, maybe, if you're lucky with your problem,
[01:05:33]
you can get to a state where it is not possible anymore
[01:05:37]
to write something that will create a problem.
[01:05:40]
So look at the safe and safe operations article.
[01:05:44]
I think it's a good example of how you can use
[01:05:47]
both the type system and elm review to make sure
[01:05:50]
that all reg Xs in your code are valid.
[01:05:55]
I was trying to think of some opportunities
[01:05:57]
to potentially use elm review with elm pages.
[01:06:01]
And one thing I was thinking about is,
[01:06:04]
it would be kind of interesting if, for example,
[01:06:07]
if I know the static paths in your app,
[01:06:11]
and you wanted to say,
[01:06:13]
give me a link to this static path.
[01:06:16]
I mean, you could have a function
[01:06:19]
that points to a static path,
[01:06:24]
and you could validate that it exists.
[01:06:26]
Or if I had a route type for paths
[01:06:30]
that has the dynamic parts of a URL.
[01:06:34]
So if a blog route has a slug,
[01:06:38]
which is a record slug which takes a string,
[01:06:40]
I could check that the slugs that you're passing in
[01:06:43]
for a route are valid.
[01:06:45]
And that's an interesting one,
[01:06:47]
because it's actually using code generation in the hood.
[01:06:52]
You check that,
[01:06:54]
but it's not just one technique or another.
[01:06:56]
It's using all these together.
[01:06:58]
But then you also have to ask,
[01:07:00]
what is the user experience at the end of it all?
[01:07:03]
And sometimes, if you're checking something
[01:07:07]
through static analysis,
[01:07:08]
that means you're not going to get auto completion,
[01:07:11]
for example.
[01:07:12]
So maybe, Philip,
[01:07:16]
who we talked to for our last episode,
[01:07:21]
when people are listening to this,
[01:07:23]
instead of doing code generation for Elm Tailwind modules,
[01:07:26]
Philip could have done an Elm review rule
[01:07:29]
where you have Tailwind CSS classes that it points to.
[01:07:32]
I mean, he would have missed out on a lot of the benefits
[01:07:34]
that he got in terms of dead code elimination
[01:07:37]
and that sort of thing.
[01:07:38]
But if you wanted to do a plain Tailwind CSS
[01:07:41]
to basically validate Tailwind CSS classes,
[01:07:44]
you could build an API in tandem with Elm review
[01:07:49]
and requires it to be literals.
[01:07:51]
But that would have limitations,
[01:07:53]
not least of which would be
[01:07:54]
that you don't get auto completion,
[01:07:56]
so you have to think about the full experience, I guess.
[01:07:58]
Okay, well, do you have any pointers
[01:08:00]
for people getting started?
[01:08:02]
Are there any repos that you would recommend
[01:08:05]
taking a look at for inspiration?
[01:08:07]
Yeah, so the most popular package for Elm review
[01:08:09]
is Elm review unused.
[01:08:11]
Unfortunately, it's a bit complex
[01:08:13]
because the domain is pretty complex
[01:08:18]
and there's a lot of things to look at.
[01:08:20]
And since we do fixes,
[01:08:22]
they're a lot more complex
[01:08:24]
than they need to be otherwise.
[01:08:27]
So if you want really simple rules,
[01:08:30]
I would look at Elm review debug under my name.
[01:08:33]
You can also look at Elm review common,
[01:08:35]
which is also very accessible, actually.
[01:08:39]
And other things like I've got a repo
[01:08:42]
that I haven't published
[01:08:47]
Elm review simplification.
[01:08:48]
Those are very accessible.
[01:08:50]
They're context free.
[01:08:51]
Yeah, usually context free
[01:08:53]
or they have little context.
[01:08:55]
But you can also look through
[01:08:57]
the review rule documentation.
[01:09:00]
There's a lot of examples in there
[01:09:02]
with reasonable but simplified approaches to rules.
[01:09:08]
And I think just looking at all the possible visitors
[01:09:13]
and possible APIs that are provided for you,
[01:09:16]
will give you a better sense
[01:09:18]
of what can I build with this?
[01:09:20]
What can I detect?
[01:09:22]
And how can I best do that?
[01:09:24]
Right.
[01:09:25]
Do you have any advice
[01:09:27]
on how to look at a problem
[01:09:30]
when you're working with some code
[01:09:32]
and how to think about
[01:09:34]
what can Elm review help me to do here?
[01:09:37]
Like what general kinds of smells
[01:09:42]
are you looking for?
[01:09:44]
For example, what kinds of invalid states
[01:09:47]
should people look for?
[01:09:48]
If you find yourself with some code that says,
[01:09:54]
error, this should never happen,
[01:09:56]
then you want to ask yourself the question,
[01:09:58]
can I make impossible states impossible
[01:10:00]
with my data modeling?
[01:10:01]
What are the kind of cues like that
[01:10:03]
for Elm review, would you say?
[01:10:04]
Yeah, usually I would go,
[01:10:09]
I would look for the type system.
[01:10:10]
And when I reach the limits of using the type system,
[01:10:14]
I try to see,
[01:10:16]
can I just by looking at the code,
[01:10:18]
which is basically what set analysis does,
[01:10:21]
but at a much larger level,
[01:10:23]
can I determine just by reading it
[01:10:25]
whether this is a problem or not?
[01:10:28]
And are there a lot of false positives?
[01:10:30]
Would there be a lot of false negatives?
[01:10:33]
False positives are the problems that are there
[01:10:38]
that are okay to have in some cases,
[01:10:40]
but false positives are the ones that you want to avoid
[01:10:43]
as much as possible.
[01:10:44]
I think you said false positives both times.
[01:10:46]
So false positives are reporting a problem
[01:10:49]
when there isn't one,
[01:10:50]
and a false negative is not reporting a problem
[01:10:54]
when there is one.
[01:10:55]
Yeah, exactly.
[01:10:56]
Every time you hear false,
[01:10:57]
it's something negative.
[01:10:59]
If that helps.
[01:11:04]
Yeah, I try to determine,
[01:11:06]
could I by looking at the code,
[01:11:08]
determine whether there's a problem?
[01:11:10]
How many false positives would there be?
[01:11:12]
How many false negatives would there be?
[01:11:14]
Yeah, right.
[01:11:15]
When I try to write a rule,
[01:11:16]
I just start simple, very simple,
[01:11:19]
like as we said, do the dumbest thing
[01:11:21]
and then just iterate
[01:11:23]
until you get to something very sophisticated.
[01:11:26]
And maybe in the course of doing that,
[01:11:28]
I will determine, I will find out,
[01:11:33]
and that becomes very complex.
[01:11:35]
I would also try to run the rule on a large project.
[01:11:39]
So I try to run it on my work project sometimes,
[01:11:42]
and sometimes it finds a lot of false negatives
[01:11:45]
or false positives.
[01:11:47]
It doesn't find false negatives,
[01:11:49]
but sometimes I know that there are false negatives.
[01:11:53]
Yeah, right.
[01:11:54]
Yeah, just use tests,
[01:11:56]
use actual applications to look at.
[01:12:01]
And just to emphasize the point,
[01:12:03]
I think the reason why you're saying that false negatives
[01:12:06]
are such a big problem
[01:12:08]
is because they give you a false sense of security, right?
[01:12:10]
You're like, oh, I don't have this kind of problem
[01:12:12]
in my code base.
[01:12:13]
Whereas false positives would block a user
[01:12:17]
and they would get annoyed.
[01:12:19]
Yeah.
[01:12:20]
At least I, as the author of the tool,
[01:12:22]
I really want my user to have a great experience,
[01:12:25]
and I hope you can see that.
[01:12:30]
The CLI, the error messages,
[01:12:33]
it's really, it's been a great experience for me
[01:12:36]
writing and using Elm review rules.
[01:12:39]
It's a really cool platform.
[01:12:41]
Very happy to have it.
[01:12:42]
Well, I think that gives people a lot to chew on.
[01:12:45]
I'm really excited to see what emerges in the ecosystem
[01:12:48]
because I think we're starting to see
[01:12:51]
more and more Elm review packages being built,
[01:12:54]
more and more active authors
[01:12:59]
and the best is yet to come.
[01:13:06]
I would have said yes
[01:13:08]
if you didn't say the last part that way.
[01:13:12]
You got to sell it, Jeroen.
[01:13:13]
You got to sell the project.
[01:13:15]
Who will be the next superstar Elm review author?
[01:13:19]
Will it be you?
[01:13:23]
If it is you,
[01:13:28]
you're going to be the next superstar.
[01:13:32]
All right, and do let a friend know
[01:13:34]
about the Elm Radio podcast
[01:13:36]
and give us a rating,
[01:13:38]
Elm Radio podcast on Apple podcasts.
[01:13:41]
And well, as always, Jeroen, until next time.
[01:13:46]
Elm Radio