Jeroen Engels
@jfmengels
Jeroen Engels
@jfmengels everywhere
jfmengels.net
Fast, scalable, and affordable log management
Not a tutorial
Unused variables
Simplifiable code
Code style
Common bugs
Customizable
Configure rules
Custom rules
function shout(sentence) {
return sentence.toUpperCase();
}
shout(100);
// Uncaught TypeError: sentence.toUpperCase is not a functionfunction shout(sentence) {
return sentence.toUpperCase();
}
shout(100);function shout(sentence: string) {
return sentence.toUpperCase();
}
shout(100);
// Argument of type 'number' is not assignable to parameter of type 'string'.function canDeleteUsers(userRole: "user" | "admin"): bool {
if (userRole === "admin") {
return true;
} else {
return true;
}
}test("canDeleteUsers returns false when role is user", () => {
assert.equals(canDeleteUsers("user"), false);
});
function canDeleteUsers(userRole: "user" | "admin"): bool {
if (userRole === "admin") {
return true;
} else {
return false;
}
}| Constraint | Guarantee |
|---|---|
| Can't call shout with non-strings | shout() won't crash the application |
| canDeleteUsers should return false when called with "user" | Regular users can't delete other users |
import http from "http"
function createUser(authToken, newUserData) {
return http.post({
url: "our-company.com/api/users",
headers: {
auth: authToken
},
body: newUserData
});
}
function deleteUser(authToken, userId) {
return http.delete({
url: "our-company.com/api/users/" + userId,
// forgot to pass in the authentication token!
});
}// custom-http.ts
import coreHttp from "http";
export default {
post: post,
delete: delete,
}
function post({path: string, auth: string, body: string}) {
return coreHttp.post({...});
}
function delete({path: string, auth: string}) {
return coreHttp.delete({...});
}import http from "./custom-http";
function createUser(authToken, newUserData) {
return http.post({
path: "/users",
auth: authToken,
body: newUserData
});
}
function deleteUser(authToken, userId) {
return http.delete({
path: "/users/" + userId,
auth: authToken
});
}import http from "http"
function editUser(authToken, userId, userData) {
return http.put({
url: "our-company.com/api/users/" + userId,
headers: {
auth: authToken
},
body: userData
});
}| Type checker | Checks whether a value is in a set of possible values |
| Tests | Checks that given specific scenarios, specific variables have specific values |
| Linter | Checks that code was written in a way that follows a list of rules |
import http from "http"
^^^^^^^^^^^using grep
grep -e 'from "http"' src/**/*.ts#!/bin/bash
MATCH=$(
grep -e 'from "http"' src/**/*.ts \
--with-filename --line-number --color=always --exclude=src/custom-http.ts
)
if [ -n "$MATCH" ]; then
echo "Found linting problems:"
echo ""
echo "Error: Do not use the http module directly"
echo $MATCH
exit 1
fiusing grep
// Single-quotes
import http from 'http'
// Too many spaces
import http from "http"
// On several lines
import
{request}
from
"http"grep -e 'from "http"' src/**/*.ts/*
Don't ever import the core HTTP module
import http from "http"
Instead use our awesome custom wrapper.
import http from "./custom-http"
*/(a + b) / 2Integer 2Binary expression
using "+"Reference to "a"Binary expression
using "/"Reference to "b"Parenthesizedright
right
left
left
value
a / 0Integer 0Reference to "a"Binary expression
using "/"Rule: No division by 0
If I see a binary expression using "/" where on the right side there's an integer 0 , then report a problem.
right
left
a / 0Integer 0Reference to "a"Binary expression
using "/"function visitor(node) {
if (
node.type == "BinaryExpression"
&& node.operator == "/"
&& node.right.type == "Integer"
&& node.right.value == 0
) {
return error("Don't divide by 0");
}
return noErrors;
}right
left
import thing from "http"String literal "http"Identifier "thing"Import declarationsource
name
Reporting unused variables
"Hello " + nameReference "name"var name = ...Identifier "name"Variable declarationname
...Collect declaration names
Collect used names
Unused variables
Reporting unused variables
var declared = [];
var used = [];
function visitor(node) {
if (node.type == "Variable declaration") {
declared.push(node.name);
}
else if (node.type == "Reference") {
used.push(node.name);
}
}
function afterVisitingTheFile() {
return declared
.removeAllFrom(used)
.map(name => error("Unused variable " + name));
}button({ color: "#FF1122" }, "Click me!")
// ^^^^^^^^^ Don't hardcode colors
// instead
import Colors from "./colors"
button({ color: Colors.primary }, "Click me!")
✨ Guarantees ✨
- Only colors chosen by the designer can be used
- It's possible the change a color all over the app by changing a single variable
// Ids.ts
export var saveButtonId = "save-button"
export var cancelButtonId = "cancel-button"
export var purchaseDialogId = "purchase-dialog"
export var confirmButtonId = "save-button"
// oops it's a duplicate ^^^^^^^^^^^^^✨ Guarantees ✨
- No 2 elements will end up with the same ID
enum MenuItems {
Home,
Dashboards,
Profile,
Settings,
Help,
}
var allMenuItems: MenuItems[] =
[ Home,
Dashboards,
Profile,
Settings,
Help,
]import Something from "./domain/secret-keys/internal"
// ^^^^^^^^
// Only "domain/secret-keys" is allowed to import this internal module✨ Guarantees ✨
- Encapsulation
module MyModule exposing (Model, Msg, update, view)
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
type alias Model =
{ count : Int }
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }
Decrement ->
{ model | count = model.count - 1 }
view : Model -> Html Msg
view model =
div []
[ button [ onClick Increment ] [ text "+1" ]
, div [] [ text <| String.fromInt model.count ]
, button [ onClick Decrement ] [ text "-1" ]
]var z = f(m, y)No short variable names
function or(a, b) {
return a || b;
}
// is this better?
function or(left, right) {
return left || right;
}for (var i = 0, i < array.length; i++) {
// ...
}
a / bis b == 0?
a / 0Report it
Jeroen Engels
@jfmengels
Talk: Why you don't trust your linter
https://www.youtube.com/watch?v=XjwJeHRa53A
Talk: Static analysis tools love pure FP
https://www.youtube.com/watch?v=_rzoyBq4hJ0
Elm Radio podcast
Slides: https://slides.com/jfmengels/writing-linter-rules