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 function
function 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
fi
using 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) / 2
Integer 2
Binary expression
using "+"
Reference to "a"
Binary expression
using "/"
Reference to "b"
Parenthesized
right
right
left
left
value
a / 0
Integer 0
Reference 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 / 0
Integer 0
Reference 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 declaration
source
name
Reporting unused variables
"Hello " + name
Reference "name"
var name = ...
Identifier "name"
Variable declaration
name
...
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 / b
is b == 0?
a / 0
Report 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