Hacking Semicolons
Semicolon-less
JavaScript
For the love of god,
WHY???
Why do we have semicolons in the first place?
Because required semicolons makes compilers easier to write!
Why do we have semicolons in JavaScript?
"Because it's C-like"
JavaSript ASI: the legendary flame war
But JavaScript is not C or Java!
Auto Semicolon Insertion (ASI)
Languages with optional semicolons
(and with a convention to omit them):
- Go
- Scala
- Ruby
- Python
- Swift
- Groovy
- ... more
Optional semicolon is a viable style because:
- Compilers today are smart enough to handle multi-line statements.
- We programmers can also easily recognize EOL as end of statement vs. multi-line statements, via proper whitespace formatting.
Non-trivial projects using semicolon-less style:
(3k+ stars on GitHub)
Understanding ASI
ECMA-262 - Edition 5.1 - Section 7.9
TL;DR
A semicolon is inserted when:
- The parser finds a token that is not allowed by the formal grammar, AND
- There is a line break or a closing brace at that point.
- Restricted productions (explained later)
// explicit termination
42;"hello"
// auto insertion, because
// `42 "hello"` is not valid statement
42\n"hello"
// explicit termination
a = 1;
b = 2;
// auto insertion, because
// `a = 1 b = 2` is not a valid statement
a = 1
b = 2
Restricted productions
postfix ++ or --
return
break
continue
ES6 yield
If a line break is found after one of these tokens,
a semicolon will be inserted regardless of what follows.
// auto insertion!
// because `return` is one of the "restricted productions"
return
{
a: 1
}
// this works as expected
return {
a: 1
}
But isn't this super hard to remember???
Simple rules to remember
Add a leading semicolon when a line starts with one of the following:
+ - [ ( /
The restricted productions can bite you
regardless of whether you write semicolons or not,
so you need to understand it anyway.
// treated as plus operation
a = b
+a
// treated as minus operation
a = b
-a
// treated as division operation
a = b
/something/.test(a)
// treated as function invocation
a = b
(function () {})()
// treated as property access
a = b
[1, 2, 3].forEach()
In practice: just [ and (
Let's compare it with the overhead of
"Just add semicolons"
var a = function () {
/* ... */
};
// VS.
function a () {
/* ... */
}
// If you forgot the semicolon...
var a = function () {
/* ... */
}
// uh oh!
[1, 2, 3].forEach();
if () {
} // no semicolon
for () {
} // no semicolon
while () {
} // no semicolon
// --- VS. ---
var a = function () {
}; // need semicolon
var a = {
prop: value
}; // need semicolon
Whenever you encounter a closing brace,
you need to do a lookbehind to decide whether a semicolon is needed there.
Enough!
I write semicolons because I like them!
(That's totally fine, there are many other good reasons too)
SEMI
A tool to automatically convert between
with/without semicolon styles.
Requirements:
- Two-way conversion
- Preserve original whitespace formatting
- Handle edge cases
First attempt: Recast
Unfortunately recast is about pure AST transform and doesn't expose the source.
Second attempt: JSHint!
Works, but is a big hack
Now: ESLint!
- Pluggable custom rule
- Full AST, Token stream and source context
- Actually found a bug in ESLint and submitted a PR that fixed it
THANKS
Slide available at:
https://slides.com/evanyou/semicolons
Hacking Semicolons
By Evan You
Hacking Semicolons
- 27,770