A Formal Commitment to New Language Features

In recent months, JSHint has been receiving requests to support proposed JavaScript language features like async/await, method decorators, and class property declarations [1]. The JSHint team has turned down each request, even after the release of ES2015. This has not been easy; as open source project maintainers, nothing gives us more pleasure than to write code in benefit of our users. We've tried to explain our motivations in an ad-hoc way, but it's always come off as a bit haphazard. We'd like to take some time to more thoroughly describe our thought process.

The long and short of it is: JSHint will require all language extension proposals to be at "Stage 2" of TC-39's standardization process before parsing and linting them.

Why so defensive?

Projects like Traceur and Babel have become very popular through progressive implementation of the latest features and proposals. It's fair to question why JSHint should be any different.

First off, it's difficult for the project maintainers. This is not really a defensible motivation; we only offer it in the interest of full disclosure. Over the years, JSHint has grown from JSLint thanks to the efforts of hundreds of contributors. This has been key to its success, but it has also contributed to a fair amount of technical debt. Extending the parser requires a fair amount of study, and even then, it isn't always clear how to do this cleanly.

The project has tried to be progressive about new syntax in the past [2], and this has often contributed to technical debt. For instance, JSHint continues to (grudgingly) maintain a moz option for Mozilla-specific extensions, and the current esnext option includes non-standard array comprehensions. (By the way: now that ES2015 is released, that name describes a non-existent specification draft.)

A plugin system is one possible way to address this, but that will require a large effort involving careful design, spanning refactoring, and a long-term commitment to implementation details.

More importantly, it's hazardous for developers. Inconsistencies within toolchains will gate developers on the lowest common denominator. Imagine the day that your transpiler supports draft 17 but JSHint has moved to draft 18. Even if you're not struggling with coordination issues between parsers, the release cycle for a "progressive" parser would leave most application developers behind. Projects would frequently rely on outdated release channels that no longer received bug fixes or new features.

JSHint was born out of a reluctance to make decisions on behalf of the user, so while we think the above considerations should be made clear to JSHint's users, we haven't made this decision based on them.

We also believe it's harmful for the ecosystem. Empowering developers to write non-standard code can have long-term effects on the open source ecosystem. Code has a tendency to live longer than we expect, but it isn't always maintained throughout its lifetime. We all look forward to the day that features like method decorators are standard and widely-supported. Prior to that, it's important to remember that the code we write with experimental language features is itself non-standard. It will be frustrating if, in 2 years, you are reviewing older code that seems to use "standard" function decorators but that in reality depends on the syntax and semantics of "revision 12" of the function decorator proposal. The differences may be subtle, and you will be forced to research the behavior of this not-quite-JavaScript before you can contribute to the project.

Finally, it's unhealthy for the language. TC39 does not operate from an ivory tower. They recognize practical considerations of patterns in existing code. Look no further than Annex B for proof of that. Sometimes, that is a good thing. The Promises/A+ spec offered a clear way forward when Promises were first considered for inclusion in ES2015. Sometimes, this is a bad thing. It's the reason why we expect Array.prototype.includes instead of Array.prototype.contains. The proliferation of production code can have a solidifying effect on new features. To the extent that JSHint is a channel through which new JavaScript flows (a minuscule one to be sure), we want to cooperate with the design process.

So why stage 2?

Stage 2 seems specifically designed for our position in the ecosystem.

It's not too early. Features in this stage have a formal definition (so we have a consistent and complete set of instructions to build from). These features are also relatively stable, so JSHint has a chance to keep up with the latest draft modifications.

It's also not too late. The design process can still benefit from the experience of developers applying a given pattern to their code. The process document defines "incremental" changes expected for Stage 2 proposals, and only recommends "experimental" implementations. You might say, "these features seem stable enough", but the truth is, JSHint was dealing with non-trivial modifications to ES2015 from the moment we implemented them until as late as May of this year [3]. So it's worth noting that JSHint is still taking some risk here.

We think there's tremendous value in experimentation at earlier stages in the process. We also feel that research should be conducted in non-production settings in order to avoid the more fundamental problems discussed above. Early-stage experiments have less to gain from automated code analysis both because of their limited scope and because their syntax is already verified by a "transpiler". In these contexts, linting has much more limited relevance.

Moving forward

Here's the practical effect of all that talk:

  • JSHint 3 will not expose an esnext option; it will instead support esversion: 6.
  • JSHint will continue to support the moz option, and it will be the only setting that enables array comprehension parsing.
  • JSHint will not expose an esversion: 7 option until that specification is finalized by ECMA.
  • JSHint will support for stage-2-and-above proposals under the experimental configuration namespace. These features will be clearly documented as subject to breaking changes between major releases.

This policy means denying first-class support for non-standard and early proposal features. JSHint will continue to support ignore directives as a coarse-grained mechanism for operating on non-standard and early-proposal features, but we know other linting tools go much farther than that. We're motivated by our responsibility to developers but also to the open source ecosystem and to the standards process itself. We hope JSHint's usership continues to enjoy the tool because they share these same values.


[1] Requests for proposed language features: [2] Introduction of ES6 features [3] Issues involving changes to the spec: