JSLint
Help

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.
Antoine de Saint-Exupéry
Terre des Hommes (1939)

Good Parts

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.

ECMAScript Sixth Edition

Some of ES6’s features are good, so JSLint will recognize the good parts of ES6.

Currently, these features are recognized:

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 name from 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 is const

export {name};

Directives

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:
  • Warn if global variables are redefined.
  • Warn if const / let statements are not declared at top of function or script, similar to var statements.
  • Warn if const / let / var statements are not declared in ascii-order.
  • Warn if named-functions are not declared in ascii-order.
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:
/*global
AbortController, Blob, Event, EventTarget, FileReader, FormData, IntersectionObserver, MessageChannel, MessageEvent, MessagePort, MutationObserver, TextDecoder, TextEncoder, URL, URLSearchParams, WebAssembly, Worker, XMLHttpRequest, clearInterval, clearTimeout, document, fetch, importScripts, indexedDb, localStorage, location, navigator, performance, postMessage, queueMicrotask, sessionStorage, setInterval, setTimeout, structuredClone, window
*/
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:
/*global
emit, getRow, isArray, log, provides, registerType, require, send, start, sum, toJSON
*/
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:
/*global
alert, confirm, console, prompt
*/
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:
/*global
AbortController, Buffer, Event, EventTarget, MessageChannel, MessageEvent, MessagePort, TextDecoder, TextEncoder, URL, URLSearchParams, WebAssembly, __dirname, __filename, clearImmediate, clearInterval, clearTimeout, console, exports, global, module, performance, process, queueMicrotask, require, setImmediate, setInterval, setTimeout
*/
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. 

Semicolon

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.

Comma

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.

Blocks

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.

Expression Statements

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.

Labels

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.

Unreachable Code

JSLint expects that a return, break, or throw statement will be followed by a } right brace or case or default.

Confusing Pluses and Minuses

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

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.

Constructors and 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.

Whitespace

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.

Report

If JSLint is able to complete its scan, it generates a function report. It lists for each function:

The report will also include a list of all of the property names that were used.

Feedback

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

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.

Perfectly fine

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.

Warning

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

JSLint is delivered as a set of files:

FilenameContent
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 VolitionDescription
sourcestring or arrayrequiredThis 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_objectobject optionalThis 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_arrayarrayoptional 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.
editionstring The verison of jslint that produced the result.
exportsobject All of the exported names and values.
fromsarray All of the strings named in import statements.
functionsarray An array of function objects.
globalobject The global object, a body that contains the outermost statements and variables.
idstring "(JSLint)"
jsonboolean true if the source was a JSON text.
linesarray An array of strings, one for each line of text in the source.
moduleboolean true if the file contains an import or export.
okboolean true if no warnings were found.
optionobject The option object that was passed in, or an empty substitute.
propertyobject The names are the names of properties used in the source. The values are the number of times each name occurred.
stopboolean true if JSLint was not able to process the entire file.
tokensarray All of the token objects in source order.
treearray The token objects that represent the outermost statements. Those will be linked to other tokens, forming an abstract parse tree.
warningsarray The warning objects.

Tokens

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

Reports

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.

 

Further Reading

Code Conventions for the JavaScript Programming Language.

jslint github json