eslint is the industry-standard, extensible linting tool for JavaScript and TypeScript, serving as the core engine for most modern code quality checks. @typescript-eslint/eslint-plugin is a critical plugin that extends eslint with specific rules and parsers to fully support TypeScript syntax and type-aware analysis. jshint is a legacy tool focused strictly on detecting errors in older JavaScript (ES5) without support for modern syntax or extensibility. jslint is an opinionated, non-extensible validator created by Douglas Crockford, now largely obsolete for modern development workflows. tslint was the original linter for TypeScript but has been officially deprecated in favor of using eslint with the @typescript-eslint plugin.
If you are building modern web applications, the debate over which linter to use is effectively over. eslint is the undisputed standard, while tools like jslint, jshint, and tslint represent previous generations of technology that have either stagnated or been officially retired. For TypeScript projects, the correct architectural decision is to combine eslint with the @typescript-eslint/eslint-plugin. Let's break down why this is the case and how these tools actually behave in your codebase.
The biggest difference between modern and legacy linters is whether you can adapt them to your team's needs.
eslint is built on a plugin architecture. You install the core engine and then add only the rules you need. This allows you to mix standard JavaScript rules with React, Vue, or TypeScript rules in a single configuration file.
// eslint.config.js (Flat config format)
import js from "@eslint/js";
import react from "eslint-plugin-react";
export default [
js.configs.recommended,
{
files: ["**/*.jsx"],
plugins: { react },
rules: {
"react/react-in-jsx-scope": "error",
"no-unused-vars": "warn"
}
}
];
jslint takes the opposite approach. It enforces a specific set of rules defined by its creator with very little room for negotiation. You cannot easily add custom rules or disable specific checks that don't fit your project.
// jslint configuration is often embedded in comments or a rigid JSON file
/*jslint node: true, browser: true, indent: 2 */
// You cannot easily add a custom rule here to enforce your own patterns
jshint improved on jslint by allowing more configuration options, but it still lacks a true plugin system. You are limited to the rules built into the tool itself.
// .jshintrc
{
"esversion": 6,
"curly": true,
"undef": true
// No way to import a plugin for framework-specific rules
}
TypeScript introduces types, interfaces, and generics that standard JavaScript linters cannot understand without help.
@typescript-eslint/eslint-plugin works with eslint to parse TypeScript code and apply rules that understand types. It can catch errors like accessing a property that doesn't exist on a specific interface.
// .eslintrc.js configuration for TypeScript
module.exports = {
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "warn"
}
};
// Code example: This rule catches type mismatches
interface User { id: number; }
const user: User = { id: "123" }; // Error: Type 'string' is not assignable to type 'number'
tslint was designed specifically for TypeScript and was the go-to tool for years. However, it has been officially deprecated. The maintainers stopped development and explicitly advised everyone to migrate to eslint. Using it today means you will not get support for new TypeScript features.
// tslint.json (Deprecated - Do not use in new projects)
{
"rules": {
"no-unused-variable": true,
"interface-name": [true, "always-prefix-with-I"]
}
}
// Migration path: Move these rules to eslint config using @typescript-eslint equivalents
jshint and jslint have no understanding of TypeScript syntax. If you try to run them on a .ts file, they will fail immediately upon encountering type annotations or interfaces.
// jshint will throw a parse error on this valid TypeScript code
function greet(user: { name: string }): void {
console.log(user.name);
}
// Error: Expected an identifier and instead saw ':' (a type annotation)
How you configure these tools dictates how easily your team can adopt them.
eslint supports multiple configuration formats (JavaScript, JSON, YAML) and allows you to share configs across packages. You can override rules for specific folders, which is crucial in large monorepos.
// eslint.config.js - Overriding rules for test files
export default [
{
files: ["**/*.test.ts"],
rules: {
"@typescript-eslint/no-non-null-assertion": "off" // Allow ! in tests
}
}
];
tslint used a simple JSON format but lacked the ability to use JavaScript logic in your config. You couldn't dynamically compute rules or conditionally apply them based on environment variables.
// tslint.json - Static configuration only
{
"extends": ["tslint:recommended"],
"rules": {
"quotemark": [true, "single"]
}
}
jslint often relies on inline comments within the source code to toggle rules, which can clutter your business logic and make it hard to enforce global standards consistently.
// source.js
/*jslint sloppy: true */
function doSomething() {
// Logic here
}
When making architectural decisions, you must consider the future viability of the tool.
tslint: Deprecated. The repository is archived. No new features or bug fixes are being added. Migrating away from it is a mandatory task for any team still using it.jslint: Obsolete. While technically still available, it is not maintained for modern JavaScript standards (ES2020+). It is effectively dead for professional frontend work.jshint: Maintenance Mode. It receives occasional updates but has fallen far behind in feature parity with eslint. It is not recommended for new stacks.eslint & @typescript-eslint/eslint-plugin: Active. These are the current industry standards, with frequent updates to support the latest ECMAScript and TypeScript releases.Imagine you have an old project using tslint and you want to add React components written in JSX.
With tslint, you would struggle because it doesn't handle JSX well without complex, often broken, configurations. You would be stuck with two different linters for different file types.
// TSLint struggles with mixed JSX/TS logic without heavy customization
// tslint.json
{
"jsRules": { ... } // Separate config for JS/JSX, often out of sync
}
With eslint and @typescript-eslint, you use a single configuration for everything. You simply install the React plugin and the TypeScript parser, and you are done.
// eslint.config.js - One config for .ts, .tsx, .js, .jsx
import tsParser from "@typescript-eslint/parser";
import reactPlugin from "eslint-plugin-react";
export default [
{
files: ["**/*.{ts,tsx,js,jsx}"],
languageOptions: { parser: tsParser },
plugins: { react: reactPlugin },
rules: {
"react/jsx-no-target-blank": "error",
"@typescript-eslint/no-explicit-any": "warn"
}
}
];
| Feature | eslint + @typescript-eslint | tslint | jshint | jslint |
|---|---|---|---|---|
| Status | ✅ Active Standard | ❌ Deprecated | ⚠️ Legacy | ❌ Obsolete |
| TypeScript | ✅ Full Support (Type-Aware) | ✅ Native (but dead) | ❌ No Support | ❌ No Support |
| Extensibility | ✅ High (Plugins) | ⚠️ Low (Custom Rules only) | ❌ None | ❌ None |
| Config Format | ✅ JS/JSON/YAML (Dynamic) | ⚠️ JSON (Static) | ⚠️ JSON/Comments | ⚠️ Comments/JSON |
| JSX Support | ✅ Excellent | ⚠️ Limited/Clunky | ❌ No | ❌ No |
| Auto-Fix | ✅ Robust | ⚠️ Basic | ❌ No | ❌ No |
For any professional frontend development today, the choice is clear. Use eslint as your core engine. If you are writing TypeScript, add @typescript-eslint/eslint-plugin and @typescript-eslint/parser. This combination gives you the most powerful, flexible, and future-proof linting setup available.
Avoid tslint entirely and plan your migration if you haven't already. Ignore jslint and jshint unless you are working on extremely specific, legacy maintenance tasks where modern tooling cannot be introduced. In 99% of cases, sticking to the eslint ecosystem will save your team time, reduce bugs, and simplify your build configuration.
Choose @typescript-eslint/eslint-plugin alongside eslint whenever your project uses TypeScript. It is essential for catching type-related bugs, enforcing consistent TypeScript patterns, and understanding complex type structures that standard JavaScript linters miss. Do not use it standalone; it requires eslint as the host engine.
Choose eslint as your foundational linter for any new JavaScript or TypeScript project. It offers a massive ecosystem of plugins, supports modern ECMAScript features, and provides the flexibility to configure rules that fit your team's specific style guide. It is the only viable choice for projects requiring extensibility and long-term maintenance.
Avoid jshint for new projects. Only consider it if you are maintaining a legacy codebase strictly locked to ES5 that cannot adopt modern build tools. It lacks support for JSX, modern ES6+ syntax, and cannot be extended with custom rules, making it unsuitable for modern frontend architecture.
Do not use jslint in any professional setting. It enforces a rigid, personal coding style that cannot be configured, lacks support for modern JavaScript standards, and has no active ecosystem. It offers no practical value compared to configurable alternatives like eslint.
Do not use tslint for any new or existing project. It has been officially deprecated by its maintainers, who recommend migrating to eslint with @typescript-eslint. Continuing to use tslint means missing out on critical updates, community rules, and the unified linting experience for mixed JS/TS repositories.
@typescript-eslint/eslint-pluginAn ESLint plugin which provides lint rules for TypeScript codebases.
👉 See https://typescript-eslint.io/getting-started for our Getting Started docs.
See https://typescript-eslint.io for general documentation on typescript-eslint, the tooling that allows you to run ESLint and Prettier on TypeScript code.