Il semble que la perfection soit atteinte non quand il n’y a plus rien à ajouter, mais quand il n’y a plus rien à retrancher.
The Principle of the Good Parts is
If a feature is sometimes useful and sometimes dangerous and if there is a better option then always use the better option.
JSLint is a JavaScript program that looks for problems in JavaScript programs. It is a code quality tool.
When C was
a young
programming language, there were several common programming errors that
were not caught by the primitive compilers, so an accessory program called
lint
was developed that would scan a source file, looking for problems.
As the language matured, the definition of the language was
strengthened to eliminate some insecurities, and compilers got better
at issuing warnings. lint
is no longer needed.
JavaScript is a
young-for-its-age language. It was originally intended to do small tasks in
webpages, tasks for which Java was too heavy and clumsy. But JavaScript is
a surprisingly capable language, and it is now being used in larger
projects. Many of the features that were intended to make the language easy
to use are troublesome when projects become complicated. A lint
for JavaScript is needed: JSLint, a JavaScript syntax checker
and validator.
JSLint takes a JavaScript source and scans it. If it finds a problem, it returns a message describing the problem and an approximate location within the source. The problem is not necessarily a syntax error, although it often is. JSLint looks at some style conventions as well as structural problems. It does not prove that your program is correct. It just provides another set of eyes to help spot problems.
JSLint defines a professional subset of JavaScript, a stricter language than that defined by the ECMAScript Programming Language Standard (the strangely named document that governs JavaScript). JSLint will reject most legal programs. It is a higher standard.
JavaScript is a sloppy language, but hidden deep inside there is an elegant, better language. JSLint helps you to program in that better language and to avoid most of the slop. JSLint will reject programs that browsers will accept because JSLint is concerned with the quality of your code and browsers are not. You should gladly accept all of JSLint's advice.
JSLint can operate on JavaScript source or JSON text.
Some of ES6’s features are good, so JSLint will recognize the good parts of ES6.
Currently, these features are recognized:
...
ellipsis marker in parameter
lists and argument lists, replacing the arguments
object
for variadic functions.let
statement, which is like the var
statement except that it respects block scope.
You may use let
or var
but not both.const
statement is like the let
statement
except that it disallows the use of assignment on the variable, although
if the value of the variable is mutable, it can still be mutated.
const
is preferred to let
.
let
, and const
, but not
var
or assignment statements, and not deep destructuring
or eliding.=>
fart functions.import
and export
.`
Megastring`
literals, but not nested
`
megastring`
literals.Map
, Set
,
WeakMap
, and WeakSet
.0b
- and 0o
- number literals.The most important new feature of ES6 is proper tail calls. This has no
new syntax, so JSLint doesn’t see it. But it makes recursion
much more attractive, which makes loops, particularly for
loops, much less attractive.
import export
The ES6 module feature will be an important improvement over JavaScript’s global variables as a means of linking separate files together. JSLint recognizes a small but essential subset of the module syntax.
import
namefrom
stringliteral;
import {
name} from
stringliteral;
import(
string).then(
function);
export default function () {};
export default function name() {};
export default
expression;
export function name() {}
export
name; //
where name isconst
export {
name}
;
JSLint provides three directives that may be placed in a
file to manage JSLint’s behavior. Use of these directives is
optional. If they are used, they should be placed in a source file before
the first statement. They are written in the form of a comment, where the
directive name is placed immediately after the opening of the comment before
any whitespace. The three directives are global
,
jslint
, and property
. Directives in a file are
stronger than options selected from the UI or passed with the option object.
/*global*/
The /*global*/
directive is used to specify a set of globals
(usually functions and objects containing functions) that are available to
this file. This was commonly used in browsers to link source files together
before ES6 modules appeared. Use of global variables is strongly
discouraged, but unfortunately web browsers require their use. The
/*global*/
directive can only be used when the
Assume a browser option is selected.
Each of the names listed will indicate a read-only global variable. The names
are separated by ,
comma. This directive
should not be used if import
or export
is used.
This directive inhibits warnings, but it does not declare the names in the
execution environment.
For example:
/*global
ADSAFE, report, jslint
*/
instructs JSLint to not give warnings about the global
variables ADsafe
, report
, and jslint
.
However, if any of those names are expected to be supplied by other files
and those other files fail to do so, then execution errors will result. It
is usually better to use top-level variable declarations instead:
var ADSAFE; var report; var jslint;
Using var
in this way allows comparing a global variable to the
undefined
value to determine whether it is has been used in the global context.
/*jslint*/
The /*jslint*/
directive allows for the control of several
options. These options can also be set from the
jslint.com user interface.
Description | option |
Meaning |
---|---|---|
Enable experimental warnings. | beta |
true will enable the following additional warnings:
|
Allow bitwise operator. | bitwise |
true if bitwise operators should be allowed. The
bitwise operators are rarely used in JavaScript programs, so it
is usually more likely that & is a mistyping of
&& than that it indicates a bitwise
and.
JSLint will give warnings on the bitwise operators
unless this option is selected. |
Assume browser environment. | browser |
true if the standard browser globals should be
predefined. This option will reject the use of
import and export . This option also
disallows the file form of the "use
strict" pragma. It does not supply
self ; you will have to request that unnecessary
alias of the dreaded global object yourself. It adds the same
globals as this directive:
|
Allow conversion operator. | convert |
true if !! , + prefix,
and concatenation with "" are allowed.
The Boolean , Number , and
String functions are preferred |
Assume CouchDB environment. | couch |
true if
Couch DB
globals should be predefined. It adds the same globals as this
directive:
|
Assume in development. | devel |
true if browser globals that are useful in
development should be predefined, and if debugger
statements and TODO comments
should be allowed. It adds the
same globals as this directive:
Be sure to turn this option off before going into production. |
Allow eval . |
eval |
true if eval should be allowed. In the
past, the eval function was the most misused
feature of the language. |
Allow complex fat-arrow. | fart |
true if complex fat-arrows are allowed. |
Allow for statement. |
for |
true if the for statement should be
allowed. It is almost always better to use the array methods
instead. |
Allow get and set . |
getset |
true if accessor properties are allowed in object
literals. |
Use 2-space indent. | indent2 |
true if using 2-space indent. |
Allow long lines. | long |
true if a line can contain more than 80 characters.
|
Assume Node.js environment. | node |
true if Node.js globals should be predefined. It
will predefine globals that are used in the Node.js environment.
It adds the same globals as this directive:
|
Allow weird property name. | nomen |
true if weird property names like
$, _foo, fooSync, foo_
are allowed.
|
Allow single quote strings. | single |
true if ' single quote
should be allowed to enclose string literals. |
Allow identifier in subscript-notation. | subscript |
true to allow identifier in
subscript-notation, e.g.: foo["bar"] = 1; .
This allows linting of scripts targeting Google Closure Compiler,
where subscript-notation is commonly used to prevent renaming of
properties. |
Allow this . |
this |
true if this should be allowed. |
Include jslint stack-trace in warnings. |
trace |
true is used by developers to debug JSLint. |
Allow unordered cases, params, properties, and variables. | unordered |
true if objects and functions are allowed
to declare properties and params in non-ascii order. |
Allow messy whitespace. | white |
true if the whitespace rules should be ignored.
|
For example:
/*jslint bitwise, node */
/*property*/
The /*property*/
directive is used to declare a list of
property identifiers that are used in the file. Each property name in the
program is looked up in this list. If a name is not found, that indicates an
error, most likely a typing error.
The list can also be used to evade some of JSLint’s naming rules.
JSLint can build the /*property*/
list for you. At
the bottom of its report, JSLint displays a
/*property*/
directive. It contains all of the names that were
used with dot notation, subscript notation, and object literals to name the
properties of objects. You can look through the list for misspellings. You
can copy the /*property*/
directive to the top of your
script file. JSLint will check the spelling of all property
names against the list. That way, you can have JSLint look for
misspellings for you.
For example,
/*property
charAt, slice, _$_
*/
ignore
JSLint introduces a new reserved word: ignore
. It
is used in parameter lists and in catch
clauses to indicate a
parameter that will be ignored. Unused warnings will not be produced for
ignore
.
function handler(ignore, value) { return do_something_useful(value); }
var let const
JavaScript provides three statements for declaring variables:
var
, let
, and const
. The
var
statement suffers from a bad practice called hoisting. The
let
statement does not do hoisting and respects block scope.
The const
statement is like let
except that it
marks the variable (but not its contents) as read only, making it an error
to attempt to assign to the variable. When given a choice,
const
is the best, var
is the worst.
JSLint uses the intersection of the var
rules and the
let
rules, and by doing so avoids the errors related to either.
A name should be declared only once in a function. It should be declared
before it is used. It should not be used outside of the block in which it is
declared. A variable should not have the same name as a variable or
parameter in an outer function. Do not mix var
and
let
. Declare one name per statement.
= == ===
FORTRAN made a terrible
mistake in using the equality operator as its assignment operator. That
mistake has been replicated in most languages since then. C compounded that
mistake by making ==
its equality operator. The visual
similarity is a source of errors. JavaScript compounded this further by
making ==
a type coercing comparison operator that produces
false positive results. This was mitigated by adding the ===
operator, leaving the broken ==
operator in place.
JSLint attempts to minimize errors by the following rules:
==
is not allowed. This avoids the false positives, and
increases the visual distance between =
and ===
.
Assignments are not allowed in expression position, and comparisons are not
allowed in statement position. This also reduces confusion.
JavaScript uses a C-like syntax that requires the use of semicolons to delimit certain statements. JavaScript attempts to make those semicolons optional with an automatic semicolon insertion mechanism, but it does not work very well. Automatic semicolon insertion was added to make things easier for beginners. Unfortunately, it sometimes fails. Do not rely on it unless you are a beginner.
JSLint expects that every statement will be followed by
;
except for for
, function
,
if
, switch
, try
, and
while
. JSLint does not expect to see unnecessary
semicolons, the empty statement, or empty blocks.
function =>
JavaScript has four syntactic forms for making function objects: function
statements, function expressions, enhanced object literals, and the
=>
fart operator.
The function statement creates a variable and assigns the function object to it. It should be used in a file or function body, but not inside of a block.
function
name(
parameters) {
statements}
The function expression unfortunately looks like the function statement. It may appear anywhere that an expression may appear, but not in statement position and not in a loop. It produces a function object but does not create a variable in which to store it.
function (
parameters) {
statements}
The enhanced object literal provides an ES6 shorthand for creating a property
whose value is a function, saving you from having to type
:
colon and function
.
{
name(
parameters) {
statements}
}
Finally, ES6 provides an even shorter form of function expression that leaves
out the words function
and return
:
(
parameters) =>
expression
JSLint requires the parens around the parameters, and forbids a
{
left brace after the
=>
fart to avoid syntactic ambiguity.
The ,
comma operator is unnecessary and can
mask programming errors.
JSLint expects to see the comma used as a separator, but not as an operator. It does not expect to see elided elements in array literals. A comma should not appear after the last element of an array literal or object literal.
JSLint expects blocks with function
,
if
, switch
, while
, for
,
do
, and try
statements and nowhere else.
JSLint expects that if
, while
,
do
and for
statements will be made with blocks
{
that is, with statements enclosed in braces}
.
JavaScript allows an if
to be written like this:
if (condition)
statement;
That form is known to contribute to mistakes in projects where many programmers are working on the same code. That is why JSLint expects the use of a block:
if (condition) { statements; }
Experience shows that this form is more resilient.
An expression statement is expected to be an assignment or a function/method call. All other expression statements are considered to be errors.
for
JSLint does not recommend use of the for
statement.
Use array methods like forEach
instead. The for
option will suppress some warnings. The forms of for
that
JSLint accepts are restricted, excluding the new ES6 forms.
for
in
JSLint does not recommend use of the for
in
statement. Use Object.keys
instead.
The for
in
statement allows for looping through
the names of all of the properties of an object. Unfortunately, it also
loops through all of the properties that were inherited through the
prototype chain. This has the bad side effect of serving up method
functions when the interest is in data properties. If a program is written
without awareness of this situation, then it can fail.
The body of every for
in
statement should be
wrapped in an if
statement that does filtering. It can select
for a particular type or range of values, or it can exclude functions,
or it can exclude properties from the prototype. For example,
for (name in object) { if (object.hasOwnProperty(name)) { .... } }
Note that the above code will fail if the object contains a property
named hasOwnProperty
. Use Object.keys
instead.
switch
A common error in switch
statements is to forget to place a
break
statement after each case, resulting in unintended
fall-through. JSLint expects that the statement before the next
case
or default
is one of these:
break
, return
, or throw
.
with
The with
statement was intended to provide a shorthand in
accessing properties in deeply nested objects. Unfortunately, it behaves
very badly when setting new properties. Never use the with
statement.
JSLint does not expect to see a with
statement.
JavaScript allows any statement to have a label, and labels have a separate name space. JSLint is more strict.
JSLint expects labels only on statements that interact
with break
: switch
, while
,
do
, and for
. JSLint expects that
labels will be distinct from vars and parameters.
JSLint expects that a return
, break
,
or throw
statement will be followed by a
}
right brace or case
or
default
.
JSLint expects that +
will not be followed by
+
or ++
, and that -
will not be
followed by -
or --
. A misplaced space can turn
+ +
into ++
, an error that is difficult to see.
Use parens to avoid confusion.
++
and --
The ++
increment and
--
decrement operators have been known to
contribute to bad code by encouraging excessive trickiness. They are second
only to faulty architecture in enabling to viruses and other security
menaces. Also, preincrement/postincrement confusion can produce off-by-one
errors that are extremely difficult to diagnose. Fortunately, they are also
complete unnecessary. There are better ways to add 1 to a variable.
It is best to avoid these operators entirely and rely on +=
and
-=
instead.
void
In most C-like languages, void
is a type. In
JavaScript, void
is a prefix operator that always
returns undefined
. JSLint does not expect to
see void
because it is confusing and not very useful.
Regular expressions are written in a terse and cryptic notation. JSLint looks for problems that may cause portability problems. It also attempts to resolve visual ambiguities by recommending explicit escapement.
JavaScript’s syntax for regular expression literals overloads the
/
slash character. To avoid ambiguity,
JSLint expects that the character preceding a regular
expression literal is a (
left paren or
=
equal or
:
colon or
,
comma character.
this
Having this
in the language makes it harder to talk
about the language. It is like pair programming with Abbott and Costello.
Avoid using this
. Warnings about this
can be
suppressed with option.this
.
new
Constructors are functions that are designed to be used with the
new
prefix. The new
prefix creates a new object
based on the function's prototype
, and binds that object to the
function's implied this
parameter. If you neglect to use the
new
prefix, no new object will be made and this
will be bound to the global object.
JSLint enforces the convention that constructor functions be
given names with initial uppercase. JSLint does not expect to
see a function invocation with an initial uppercase name unless it has the
new
prefix. JSLint does not expect to see the
new
prefix used with functions whose names do not start with
initial uppercase.
JSLint does not expect to see the wrapper forms
new Number
, new String
, new Boolean
.
JSLint does not expect to see new Object
.
Use Object.create(null)
instead.
JSLint has a specific set of rules about the use of whitespace. Where possible, these rules are consistent with centuries of good practice with literary style.
The indentation increases by 4 spaces when the last token on a line is
{
left brace,
[
left bracket,
(
left paren. The matching closing
token will be the first token on a line, restoring the previous
indentation.
The ternary operator can be visually confusing, so
?
question mark and
:
colon always begin a line and increase
the indentation by 4 spaces.
return ( (the_token.id === "(string)" || the_token.id === "(number)") ? String(the_token.value) : the_token.id );
The word function
is always followed with one space.
Clauses (case
, catch
, default
,
else
, finally
) are not statements and so should
not be indented like statements.
Spaces are used to make things that are not invocations look less like invocations.
Tabs and spaces should not be mixed. We should pick just one in order to avoid the problems that come from having both. Personal preference is an extremely unreliable criterion. Neither offers a powerful advantage over the other. Fifty years ago, tab had the advantage of consuming less memory, but Moore's Law has eliminated that advantage. Space has one clear advantage over tab: there is no reliable standard for how many spaces a tab represents, but it is universally accepted that a space occupies a space. So use spaces. You can edit with tabs if you must, but make sure it is spaces again before you commit. Maybe someday we will finally get a universal standard for tabs, but until that day comes, the better choice is spaces.
If JSLint is able to complete its scan, it generates a function report. It lists for each function:
«
guess»
the name.catch
clauses of try
statements.The report will also include a list of all of the property names that were used.
Please let me know if JSLint is useful for you. Is it too strict? Is there a check or a report that could help you to improve the quality of your programs? douglas@crockford.com. But please don't ask me to dumb JSLint down or to make it more forgiving of bad practices. You would only be disappointed.
I intend to continue to adapt JSLint based on your comments. Keep watching for improvements.
Try it. Paste your script into the window and click the JSLint button. The analysis is done by a script running on your machine. Your script is not sent over the network. You can set the options used.
JSLint is written entirely in JavaScript, so it can run anywhere that JavaScript (or even Java) can run.
JSLint was designed to reject code that some would consider to be perfectly fine. The reason for this is that JSLint's purpose is to help produce programs that are free of error. That is difficult in any language and is especially hard in JavaScript. JSLint attempts to help you increase the visual distance between correct programs and incorrect programs, making the remaining errors more obvious. JSLint will give warnings about things that are not necessarily wrong in the current situation, but which have been observed to mask or obscure errors. Avoid those things when there are better options available.
It is dangerous out there. JSLint is here to help.
JSLint will hurt your feelings. Side effects may include headache, irritability, dizziness, snarkiness, stomach pain, defensiveness, dry mouth, cleaner code, and a reduced error rate.
JSLint is delivered as a set of files:
Filename | Content |
---|---|
help.html |
Using jslint as a single page JSLint application. |
index.html |
Single page JSLint application that uses the
js files. |
jslint.mjs |
The jslint function. |
function jslint
The jslint
function is written in ES6 JavaScript. It has no
dependence on other files.
The jslint
function has three arguments:
Parameter name | Type | Volition | Description |
---|---|---|---|
source | string or
array | required | This is the source of the
program to be analyzed. It can be either a string containing
\n , \r , or \r\n line breaks, or an
array of strings, one element per line. |
option_object | object |
optional | This object contains the chosen options for this
analysis. The keys are the names of the options described on the
help page. The values are either the boolean
true or a small number. |
global_array | array | optional | This is an array of strings. Each string names one global variable that may be used by the program. Listing names here will silence warnings about the names. It will not introduce the names into the runtime environment. |
The jslint
function will not modify any of its arguments.
The jslint
function will return a result object contain the
following properties:
Property name | Type | Content |
---|---|---|
directives |
array | An array of comments containing directives. |
edition | string | The verison of jslint that produced the result. |
exports | object | All of the exported names and values. |
froms | array | All of the strings named in import statements. |
functions | array | An array of function objects. |
global | object | The global object, a body that contains the outermost statements and variables. |
id | string | "(JSLint)" |
json | boolean | true if the source was a
JSON text. |
lines | array | An array of strings, one for each line of text in the source. |
module | boolean | true if the file contains an import or
export . |
ok | boolean | true if no warnings were found. |
option | object | The option object that was passed in, or an empty substitute. |
property | object | The names are the names of properties used in the source. The values are the number of times each name occurred. |
stop | boolean | true if JSLint was not able to process the
entire file. |
tokens | array | All of the token objects in source order. |
tree | array | The token objects that represent the outermost statements. Those will be linked to other tokens, forming an abstract parse tree. |
warnings | array | The warning objects. |
A source file is composed of tokens. Each identifier, each operator, each punctuator, each literal, and each comment is a token. The whitespace between tokens is not a token.
An object is made for each token. The properties in each token will vary according to the type of the token. More properties will be added to the tokens during the analysis to indicate the token's purpose or relationship with other tokens. The tokens will be woven together to form a tree.
Property name | Type | Description | Where |
---|---|---|---|
arity |
string | unary , binary , ternary ,
assignment , statement ,
variable , function , pre ,
post |
non-literals |
block |
token or array of tokens | This is the contents of a block or compound statement. Each token represents a statement. | statement |
body |
boolean | true if the block is a function body. |
block |
catch |
token | The catch clause. |
try |
closure |
boolean | true if accessed by inner functions |
variable |
complex |
boolean | true if one or more parameters use new ES6 syntax |
function |
constant |
boolean | true if the thing is a compile-time constant |
token |
context |
object | The container of variables, parameters, labels, and exception variables declared in a function. | function |
directive |
boolean | jslint , global , property |
comment |
disrupt |
boolean | true if a disruptive statement, or a block ending
with a distruption. An if will disrupt if both of
its branches disrupt. |
statement |
dot |
boolean | true if the previous token was a dot. |
identifier |
ellipsis |
boolean | true if the parameter or argument is preceded by
the ellipsis. |
token |
else |
array of tokens | Alternate block in
if (else ),
switch (default ),
try (finally ) |
statement |
expression |
token or array of tokens | One or more expressions. | operator or statement |
extra |
string | get , set |
properties |
flag |
object | An object containing the properties g ,
i , m , u , or
y . |
regexp |
from |
number | The starting position of the token within its line of source
code. A token at the extreme left margin has a from
of 0. |
token |
parent |
token | The function in which the variable was declared. | variable |
id |
string | The id of the token. For most tokens, this is the
token text itself. For comments, id is "(comment)" .For number literals, id is "(number)" . For regular expression literals, id is
"(regexp)" .For string literals, id is
"(string)" .The end of the file has an id of "(end)" .The global object has an id of "(global)" . |
token |
identifier |
boolean | true if the token is an identifier. |
token |
import |
string | The import from string literal. |
import |
inc |
token | The increment clause of a for statement. |
for |
initial |
token | The initialization clause of a for statement. |
for |
label |
token | The label of a statement, or the name of a property in an object literal. | statement or object literal |
level |
number | The function nesting level. The global context is 0. The outermost functions are 1. Their inner functions are 2. | function |
line |
number | The line number on which the token was found. A token on the
first line will have a line of 0. If the token
spans multiple lines (such as a long comment) line
will be the number of the last line it occupied. |
token |
name |
token or string | The name of a function | function |
names |
token or array of tokens | Parameters or variables on the left of = . |
= or ( |
nr |
number | The token sequence number. | token |
parameters |
array of tokens | The parameter list. | function |
parent |
token | The function in which the variable was declared. | variable |
quote |
string | The quote character. | string literal |
role |
string | exception , function ,
label , parameter , variable |
identifier |
shebang |
string | The first line of text if it started with #! |
line |
statement |
boolean | true if the token is the first token of a statement. |
statement |
strict |
token | The "use strict" pragma. |
block |
thru |
number | The ending position of the token within its line of source code.
It is usually from plus the length of the token. |
token |
value |
string or array of strings | The text of the token. For a long comment or megastring, this could be an array of strings. | literals |
variable |
token | This links a variable to its definition. | variable |
warning |
object | A warning object triggered by this token. | token |
wrapped |
boolean | true if the expression was wrapped in parens. |
expression |
writable |
boolean | true if the variable may be assigned to. |
variable |
The report
object contains three functions that all take a
result from the jslint
function as input.
Function name | Description |
---|---|
error |
This function takes a result and returns an HTML text fragment from the warnings that are found. |
function |
This function takes a result and returns an HTML text fragment detailing the declarations of every function. |
property |
This function takes a result and returns a JSLint property directive. This directive could be pasted into a file. |