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 Object.freeze(expression);

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 web interface.

Description option Meaning
Tolerate bitwise operators 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 a browser browser true if the standard browser globals should be predefined. This option disallows the file form of the "use strict" pragma.
Assume Bun bun true if Bun globals should be predefined.
Assume CouchDB 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 Deno deno true if Deno globals should be predefined.
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.
Tolerate eval eval true if eval should be allowed. In the past, the eval function was the most misused feature of the language.
Fudge the line and column numbers fudge true if the origin of a text file is line 1 column 1. Programmers know that number systems start at zero. Unfortunately, most text editors start numbering lines and columns at one. JSLint will by default start numbering at zero. Use this option to start the numbering at one in its reports.
Tolerate get and set getset true if accessor properties are allowed in object literals.
Tolerate globalThis global true if globalThis should be allowed. Use globalThis instead of window, self, or global.
Tolerate long source lines long true if a line can contain more than 80 characters.
Assume Node.js node true if Node.js globals should be predefined.
Tolerate null null true if null should be allowed anywhere except Object.create(null).
Tolerate this
this true if this should be allowed.
Assume Txiki tjs true if Txiki globals should be predefined.
Assume web web true if the web globals should be predefined.
Tolerate whitespace mess 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 thru 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, _$_
*/

_

JSLint introduces a new reserved word: _. 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 _.

function handler(_, 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 function, if, 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, while, do, and try statements and nowhere else.

JSLint expects that if, while and do 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

Iterating thru arrays is best achieved using the array methods, such as forEach, which handle the induction variable for you. This eliminates the potential for a whole class of errors.

For all other kinds of iteration, use a tail recursive function, a while loop or a do loop. These alternatives avoid the for loop's overly compacted syntax

for (initialization; condition; increment) {
    // do some work
}

yet do exactly the same thing:

initialization;
while (condition) {
    // do some work
    increment;
}

In fact, they are strictly more powerful, because the condition check can be moved anywhere within the block.

The for in statement loops thru the names of all of the properties of an object. Unfortunately, it also loops thru all of the properties that were inherited from the prototype chain. This has the bad side effect of serving up method functions when the interest is in data properties. Use Object.keys instead.

JSLint does not expect to see a for statement.

null

JSLint does not recommend the use of null, except in Object.create(null). Use undefined instead.

JavaScript has two bottom values, undefined and null (three if you count NaN). Using both is a source of ambiguity. JavaScript itself uses undefined, making it indispensable. On the other hand, null can usually be eliminated from well-written programs. For example, rather than assigning null to a property of an object, you can delete the property instead.

Warnings about null can be suppressed with the null option.

switch

The switch statement suffers from a fall thru hazard, as well as a bizarre syntax. Fortunately, there are better alternatives. The if statement has the else if form which is more regular and more compact than the switch statement.

if (variable === case_a) {
    statements;
} else if (variable === case_b) {
    statements;
} else {
    statements;
}

Alternatively, an object can be filled with functions representing the different cases.

const cases = {
    case_a() {
        statements;
    },
    case_b() {
        statements;
    }
};
cases[variable]();

If none of the functions are matched, an exception gets raised automatically.

JSLint does not expect to see a switch statement.

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: while and do. 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.

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 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 the this option.

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 always wrap it in parens. The ? question mark and : colon each begin a line.

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 (catch, else and 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 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.

Further Reading