JavaScript syntax errors compared (2021)
A JavaScript error beauty contest
Written by strager on
Updated on
Introduction
Making mistakes is inevitable. Even JavaScript pros write syntax errors. The words unexpected token are familiar to junior and senior engineers alike.
How much time do programmers waste hunting down syntax errors? I don't know. Probably a lot. Luckily, modern compilers and editors try to help us fix our mistakes.
Let's compare various tools to see how well they communicate JavaScript syntax errors to programmers.
Contestants
To automate the bug-finding process, let's use two kinds of JavaScript tools: tools you have to use (JS engines and transpilers) and static analysis tools designed to find bugs (linters). We'll test the following tools:
- Babel compiler (“Ba”) version v7.16.4
- ESLint with the Espree parser (“ESL”) version v8.4.1
- JavaScriptCore, used by Safari (“JSC”) version 278391
- quick-lint-js (“qljs”) version 0.7.1
- SpiderMonkey, used by Mozilla Firefox (“SM”) version 90.0b2
- TypeScript compiler (“TS”) version 4.5.3
- V8, used by Google Chrome and Node.js (“V8”) version 9.3.62
Code samples
There are so many syntax errors to choose from! Instead of inventing code for demonstration, let's take JavaScript snippets from real programmers on Stack Overflow:
-
<% for (let codeExampleID in codeExamples) { %>
- <%- codeExamples[codeExampleID] %> <% } %>
To compare tools, I rated each error message on a scale from 1 to 5:
- The message suggests the correct solution.
- The message suggests a technically correct solution. The suggestion is confusingly worded, in the wrong location, or leads to more problems.
- The message explains the problem but does not suggest a solution.
- The message explains a different problem or the problem in the wrong location.
- The message suggests an incorrect solution or misleads the user, or there is no message reported at all.
;
instead of
,
Object literal properties are separated by commas (,
). Semicolons (;
) are not allowed.
The following code uses a semicolon instead of a colon in an object
literal:
$('.view-content').hoverscroll({
arrows: true,
arrowsOpacity: 0.7;
});
(CC BY-SA 3.0)
note: { opened at line 1, column 31`, "TS": `',' expected.`, "V8": `Unexpected token ';'`, "qljs": `expected ',' between object literal entries`, }) %>
Tip: Hover over the message on the right to show the error on the left.
<%-rating(5)%> <%-Ba%>, <%-qljs%>, and <%-TS%> did a great job. They suggested a solution to the problem.
<%-rating(3)%> <%-ESL%> and <%-V8%> reported the problem but did not suggest a solution.
<%-rating(1)%> <%-JSC%> and <%-SM%> suggested incorrect solutions.
Invalid object literal key
Object literal property names must be simple identifiers or quoted
strings. The following code example tries to create a nested object
by writing a dot (.
) in the property
name:
var company = new Company({
name: body.name,
address: body.address,
friends.name: body.friendName,
statuses: { status: "New" },
});
(CC BY-SA 3.0)
',' expected.
',' expected.`, "V8": `Unexpected token '.'`, "qljs": `unexpected expression; missing key for object entry
missing comma between object literal entries
token not implemented in parse_object_literal: colon`, }) %>
Tip: Hover over the tool names below to focus on the errors above.
None of the tools suggested the correct solution to the problem.
<%-rating(4)%> <%-SM%> reported the problem and suggested a possible solution. However, the suggestion would lead to more syntax errors.
<%-rating(3)%> <%-JSC%> reported the problem and suggested a possible solution. However, the message is ambiguous because it reports only a line number and because the line contains two '.' characters.
<%-rating(3)%> <%-ESL%> and <%-V8%> reported the problem but did not suggest a solution.
<%-rating(1)%> <%-Ba%>, <%-qljs%>, and <%-TS%> and suggested incorrect solutions. qljs also crashed.
Newline after return
If return
is immediately followed by
a newline character, undefined
is
returned, and the following code is interpreted as more statements.
The following code expects the
return
statement to return an object
literal:
var homeModelTemplate = function(){
return
{
fromDateSearch: new Date(),
toDateSearch: new Date()
};
};
(CC BY-SA 3.0)
missing semicolon after statement
unexpected token
use of undeclared variable: toDateSearch`, }) %>
<%-rating(5)%> <%-qljs%> did a great job. It reported the real problem and hinted at the solution. qljs also reported unrelated errors.
<%-rating(2)%> <%-ESL%>, <%-JSC%>, <%-SM%>, and <%-V8%> reported a symptom of the problem, but did not guide the programmer toward a fix.
<%-rating(1)%> <%-Ba%> and <%-TS%> suggested incorrect solutions.
Missing ( )
around parameter
If an arrow function has a single parameter, and that parameter is an object destructuring, parentheses are required around the parameter. The following code omits the parentheses:
colls.map({id, ...other} => {
return preview({
key: id,
...other
});
})
on Stack Overflow
(CC BY-SA 4.0)
':' expected.
',' expected.`, "V8": `Malformed arrow function parameter list`, "qljs": `(no errors)`, }) %>
<%-rating(5)%> <%-SM%> did a great job. It suggested a solution to the problem.
<%-rating(4)%> <%-JSC%> and <%-V8%> hinted at a solution to the problem, but the message isn't as clear as SM's.
<%-rating(3)%> <%-ESL%> reported the problem but did not suggest a solution.
<%-rating(1)%> <%-Ba%> and <%-TS%> suggested incorrect solutions. TS also reported unrelated errors.
<%-rating(1)%> <%-qljs%> reported no error at all.
Keyword variable name
With a few exceptions for legacy reasons, function parameters cannot
be named a keyword. The following code tries to name a function
parameter class
, which is a keyword:
function Classes(class, sched) {
this.class = class;
this.scheduled = sched;
}
(CC BY-SA 4.0)
'{' expected.
Unexpected keyword or identifier.
Declaration or statement expected.
'{' expected.`, "V8": `Unexpected token 'class'`, "qljs": `token not implemented in parse_and_visit_function_parameters: kw_class`, }) %>
<%-rating(5)%> <%-JSC%> and <%-TS%> clearly reported the problem and hinted at a solution. TS also reported unrelated errors.
<%-rating(3)%> <%-Ba%>, <%-ESL%>, <%-qljs%>, and <%-V8%> reported the problem but did not suggest a solution. qljs also crashed.
<%-rating(1)%> <%-SM%> suggested an incorrect solution.
Missing ||
between expressions
Expressions can't be next to each other without an operator in
between. The following code is missing the logical or operator
(||
) between two expressions in the
while
loop:
while( s2[Y]=="#" || s2[Y+1] =="X" s3[Y]=="#" || s3[Y+1] =="X");
(CC BY-SA 3.0)
';' expected.`, "V8": `Unexpected identifier`, "qljs": `while loop is missing ')' around condition
unmatched parenthesis`, }) %>
None of the tools suggested the correct solution to the problem.
<%-rating(3)%> <%-ESL%> and <%-V8%> reported the problem but did not suggest a solution.
<%-rating(1)%> <%-Ba%>, <%-JSC%>, <%-qljs%>, <%-SM%>, and <%-TS%> suggested incorrect solutions to the problem.
elseif
instead of
else if
elseif
is a keyword in some
languages, but not in JavaScript. The correct code is
else if
(two words). The following
code uses elseif
by mistake:
var car;
if(input.val() == "Lamborghini") {
car = 389;
}elseif(input.val() == "Ferrari"){
car = 349;
}else{
car = 0;
}
(CC BY-SA 3.0)
Declaration or statement expected.`, "V8": `Unexpected token '{'`, "qljs": `missing semicolon after statement
'else' has no corresponding 'if'`, }) %>
<%-rating(3)%> <%-qljs%> reported that the
if
keyword was missing or misplaced.
However, qljs also suggested an incorrect solution to the problem.
<%-rating(2)%> <%-ESL%>, <%-JSC%>, <%-SM%>, and <%-V8%> reported an unrelated problem but did not suggest a solution.
<%-rating(1)%> <%-Ba%> and <%-TS%> suggested incorrect solutions to the problem.
Missing ,
between object properties
Object literals have a comma (,
)-separated key-value pairs. The following code forgets a comma
between two properties:
$.ajax({
url: "loadcontent1.php",
data: {
lastid: 'postitem',
}
success: function(html) {
$("#content").append(html);
}
});
(CC BY-SA 3.0)
<%-rating(5)%> <%-qljs%> did a great job. It suggested a solution to the problem.
<%-rating(4)%> <%-Ba%> and <%-TS%> also suggested a solution to the problem, but at a worse location than qljs.
<%-rating(3)%> <%-ESL%> and <%-V8%> reported the problem but did not suggest a solution.
<%-rating(1)%> <%-JSC%> and <%-SM%> suggested incorrect solutions.
Summary
Code sample | <% for (let engine of engines) { %><%- abbr(engine.name) %> | <% } %>avg |
---|---|---|
<%- codeExamples[codeExampleID] %> | <% for (let engine of engines) { %><%= scores[codeExampleID][engine.name] %> | <% } %><%= averageValues(scores[codeExampleID]).toFixed(1) %> |
(total) | <% for (let engine of engines) { %><%= totalScoreForEngine(engine.name) %> | <% } %>Code sample | <% for (let engine of engines) { %><%- abbr(engine.name) %> | <% } %>avg |
No tool was amazing at finding bugs in all of our code samples. To get the best error experience, use a mix of tools, not just one tool.
<%-ESL%>, <%-qljs%>, and <%-V8%> stand out as good tools for debugging syntax errors. ESL and V8 consistently do a decent job, never scoring 1 in any code sample.
<%-qljs%> and <%-TS%> are volatile, doing a great job (5/5) or a terrible job (1/5) depending on the error in question.
Despite being tied for the worst tool overall, <%-SM%> beat all other tools in two code samples.
<%-qljs%>, <%-TS%>, and <%-V8%> report good-quality error spans. <%-Ba%>, <%-ESL%>, and <%-SM%> report only a single line-column location, not a span. <%-JSC%> only reports line numbers, making some messages ambiguous (such as in invalid object literal key).
Most tools did a poor job in two code samples,
missing ||
between expressions
and
elseif
instead of
else if
. Sometimes, broken code is hard to understand for computers.
<%-qljs%> and <%-TS%> try to recover after errors. Recovering is helpful in an editor, but sometimes it leads to confusing reports. All other tools stop at the first error.