JavaScript is the only language that runs everywhere: in the browser, on the server via Node.js, in mobile apps via React Native, in cloud functions, and at the edge. That ubiquity comes with a quality tax. JavaScript’s dynamic typing, prototype chain, and async execution model make it easy to write code that works under normal conditions and fails in subtle ways when conditions change. TypeScript helps significantly, but type safety is not the same as code quality, security, or architectural health. Static analysis fills the gap.
Choosing the right combination of static analysis tools for a JavaScript or TypeScript project is not a single decision. Linting, security scanning, type checking, dead code detection, and architectural analysis are distinct problems addressed by distinct tool categories. Using a linter where a security scanner is needed, or relying on type checking where dependency analysis is required, produces incomplete coverage and false confidence. The tools in this guide are organized by what they actually do, so teams can build a stack that covers every quality dimension without redundancy.
How SMART TS XL Supports JavaScript Static Analysis at Enterprise Scale
Every tool covered in this guide operates within the JavaScript boundary. ESLint analyses JavaScript files. TypeScript checks types within the TypeScript project. Semgrep scans JavaScript and TypeScript source for vulnerability patterns. SonarQube tracks quality metrics across a JavaScript codebase. None of them can see past the edge of the JavaScript application into the systems it depends on or the systems that depend on it.
SMART TS XL approaches static analysis from the opposite direction: it starts from the full system and builds down to the component level. For JavaScript, this means it ingests JavaScript and TypeScript source alongside every other language in the environment , COBOL, JCL, Java, Python, RPG, PL/I, SQL , and constructs a unified cross-reference model that represents structural relationships across all of them. A JavaScript module that calls a REST API, that API backed by a Java service, that service reading from a DB2 table populated by a COBOL batch program: SMART TS XL maps all four layers and the connections between them. No JavaScript-specific tool can produce that picture.
For JavaScript development teams specifically, SMART TS XL provides several capabilities that complement the linting and security scanning layer:
Cross-language impact analysis. Before modifying a JavaScript module that consumes an enterprise API, SMART TS XL’s impact analysis identifies every other component in the system that the change will affect, including components written in other languages. Teams discover the true scope of a change before it is made, not after it breaks something unexpected in production.
Dead code and reachability analysis at the system level. Where Knip and ts-prune find unused exports within the JavaScript project, SMART TS XL can identify JavaScript functions and modules that have no callers anywhere in the system , including callers in Java services, backend APIs, or mainframe programs. This system-level dead code analysis is relevant in organizations where JavaScript frontends are tightly integrated with backends in other languages.
Dependency visualization across language boundaries. SMART TS XL’s code visualization generates dependency maps that show how JavaScript modules connect to Java services, COBOL programs, shared databases, and external APIs, in a single navigable diagram rather than separate language-specific views.
Unified quality metrics for heterogeneous stacks. Organizations that report code quality metrics to management or compliance teams benefit from metrics that cover the entire stack, not just the JavaScript layer. SMART TS XL’s static code analysis covers JavaScript and TypeScript with the same quality dimensions , cyclomatic complexity, maintainability index, dependency coupling , applied consistently across every language in the environment.
For teams building JavaScript applications in isolation, the open-source and commercial tools in this guide provide comprehensive coverage. For teams building JavaScript applications as one component in a larger enterprise system, SMART TS XL provides the architectural visibility layer that makes the rest of the analysis actionable at the system level rather than the file level.
Linting vs. Static Analysis: What Is the Difference?
These terms are often used interchangeably but they describe different levels of analysis. The distinction matters for tool selection.
Linting is a subset of static analysis focused on stylistic consistency, common error patterns, and coding convention enforcement. A linter reads source code and flags deviations from a defined rule set. ESLint is a linter. Biome is a linter-formatter. They catch no-unused-vars, no-console, and prefer-const violations. They do not track data flow across function calls or find security vulnerabilities like SQL injection.
Static analysis in the broader sense encompasses everything a linter does plus deeper analysis: control flow analysis, data flow (taint) analysis, call graph construction, type-level reasoning, and inter-procedural analysis across files and modules. Tools like CodeQL, Semgrep with taint mode, and SonarQube perform static analysis in this fuller sense. They find vulnerabilities that require understanding how untrusted data moves through the program, not just whether a variable is declared.
| Category | Finds | Representative Tools |
|---|---|---|
| Linting | Style, conventions, common mistakes | ESLint, Biome, OxcLint, StandardJS |
| Type checking | Type errors, missing types, type mismatches | TypeScript (TSC), typescript-eslint |
| SAST / security scanning | SQL injection, XSS, prototype pollution, insecure deps | Semgrep, CodeQL, Snyk Code, SonarQube |
| Dead code detection | Unused exports, unreachable code, unused variables | Knip, ts-prune, ESLint no-unused-vars |
| Architectural analysis | Dependency mapping, impact analysis, call graphs | SMART TS XL, CodeScene, Sourcetrail |
Every mature JavaScript project should cover at least the first three categories. Large or enterprise projects should cover all five.
ESLint: The Industry Standard for JavaScript Linting
ESLint is installed in virtually every JavaScript project. It is the default linter in create-react-app, Next.js, Vite, and most enterprise scaffolding. Its plugin ecosystem covers every major framework (React, Vue, Angular, Node.js) and language extension (TypeScript). Understanding ESLint well is a prerequisite for JavaScript development.
bash
# Install ESLint
npm init @eslint/config@latest
# Run on the project
npx eslint src/
# Auto-fix fixable issues
npx eslint src/ --fix
ESLint v9 and flat config: ESLint v9 replaced the .eslintrc.* configuration format with a flat eslint.config.js file. This is a breaking change that has affected many existing projects. The flat config format is simpler, removes the cascading inheritance system, and makes configuration explicit:
javascript
// eslint.config.js (ESLint v9 flat config)
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
languageOptions: {
globals: globals.browser,
},
rules: {
"no-unused-vars": "error",
"no-console": "warn",
"prefer-const": "error",
},
},
];
ESLint for TypeScript requires the typescript-eslint package, which replaces the older @typescript-eslint/eslint-plugin and @typescript-eslint/parser. It provides 100+ TypeScript-specific rules that TSC does not enforce:
bash
npm install --save-dev typescript-eslint
ESLint security plugin adds security-focused rules to ESLint, detecting issues like use of eval(), unsafe regular expressions, and prototype injection:
bash
npm install --save-dev eslint-plugin-security
javascript
// eslint.config.js
import security from "eslint-plugin-security";
export default [security.configs.recommended];
What ESLint covers: code style, common bugs (no-undef, no-unused-vars), anti-patterns, framework conventions, and basic security patterns via plugins.
What ESLint does not cover: data flow / taint analysis across function calls, cross-file impact analysis, dependency vulnerabilities, architectural mapping, or async-specific vulnerability patterns.
TypeScript: Static Safety at the Compiler Level
The TypeScript compiler (TSC) performs the most impactful static analysis available for JavaScript projects: it proves type correctness across the entire codebase at every function boundary. Enabling strict mode in tsconfig.json catches the largest number of issues:
json
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true
}
}
noUnusedLocals and noUnusedParameters catch unused variables and function parameters at the compiler level, overlapping with ESLint’s no-unused-vars but with more precision about TypeScript-specific patterns.
typescript-eslint bridges the gap between TypeScript’s type checker and ESLint’s rule system. Rules like @typescript-eslint/no-floating-promises and @typescript-eslint/await-thenable use type information to detect async programming errors that neither TSC nor ESLint alone can catch:
javascript
// eslint.config.js -- typescript-eslint with type-checked rules
import tseslint from "typescript-eslint";
export default tseslint.config(
...tseslint.configs.strictTypeChecked,
{
languageOptions: {
parserOptions: {
project: true, // enables type-aware rules
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-misused-promises": "error",
},
}
);
These three rules specifically address the async/await error patterns that appear in the Search Console data for this article, incorrect Promise handling is one of the most commonly introduced bugs in modern JavaScript, and typescript-eslint catches them without requiring separate tooling.
Biome and OxcLint: The Next Generation of JavaScript Tooling
ESLint has been the default JavaScript linter for a decade. Two newer tools are now challenging that position with dramatically better performance.
Biome is a single tool that replaces both ESLint and Prettier, providing linting, formatting, and import organization in one binary with no configuration required for basic usage. It is written in Rust and runs 25-35x faster than ESLint on large codebases. Biome supports JavaScript, TypeScript, JSX, and JSON.
bash
# Install
npm install --save-dev --save-exact @biomejs/biome
# Initialize config
npx @biomejs/biome init
# Check (lint + format check)
npx @biomejs/biome check --write src/
OxcLint (part of the Oxc project) is another Rust-based linter providing ESLint-compatible rules at 50-100x faster execution. It is designed as a drop-in replacement for ESLint’s core rules and is intended to run alongside ESLint during a migration rather than requiring an immediate full switch.
bash
# Install
npm install --save-dev oxlint
# Run
npx oxlint src/
When to use each: For new projects, Biome is the strongest single-tool choice for linting and formatting. For existing projects with extensive ESLint configuration and plugins, migrating to Biome requires validating rule coverage. OxcLint is better suited for incrementally replacing ESLint in large existing projects where the plugin ecosystem cannot be abandoned immediately.
| Tool | Speed vs ESLint | Replaces Prettier | TypeScript Support | Plugin Ecosystem |
|---|---|---|---|---|
| ESLint | Baseline | No (pair with Prettier) | Via typescript-eslint | Largest (~3,000 plugins) |
| Biome | 25-35x faster | Yes | Built-in | Limited but growing |
| OxcLint | 50-100x faster | No | Built-in | ESLint-compatible subset |
| StandardJS | Comparable to ESLint | Partial | Limited | Fixed rule set |
Semgrep: Pattern-Based SAST for JavaScript Security
Semgrep is a multi-language static analysis security testing (SAST) tool that finds security vulnerabilities through code pattern matching. Where ESLint enforces style and conventions, Semgrep finds SQL injection, XSS, prototype pollution, hardcoded credentials, insecure Express.js configurations, and hundreds of other security patterns across JavaScript and TypeScript.
The key difference from ESLint: Semgrep rules are written as code patterns using a syntax that closely mirrors the target language, making them readable and writable by developers without deep static analysis expertise:
yaml
# Custom Semgrep rule: flag direct use of user input in SQL queries
rules:
- id: sql-injection-express
patterns:
- pattern: |
$APP.get($ROUTE, ($REQ, $RES) => {
...
$DB.query($REQ.query.$INPUT, ...);
...
})
message: User input directly used in SQL query -- use parameterized queries
languages: [javascript, typescript]
severity: ERROR
bash
# Run Semgrep with the community security rule registry
semgrep scan --config=p/javascript src/
# Run with a specific rule set for Node.js
semgrep scan --config=p/nodejs src/
Semgrep vs ESLint: they are complementary, not competitive. Use ESLint for code quality and conventions. Use Semgrep for security scanning. Most JavaScript teams should run both in CI. GitLab recently announced transitioning its SAST analyzers from ESLint to Semgrep, phasing out ESLint as a security scanner while retaining it for linting, which reflects the emerging consensus that ESLint is the right tool for linting and Semgrep is the right tool for security analysis.
SonarQube and SonarLint: Continuous Quality Gates
SonarQube provides a quality gate model: each pull request is measured against a defined quality profile, and merges are blocked if the code does not meet the threshold. For JavaScript and TypeScript it detects bugs, code smells, security hotspots, and duplications, with trend tracking over time.
SonarLint is the IDE extension that surfaces SonarQube rules locally as developers write code, enabling immediate feedback rather than waiting for CI.
SonarQube’s value over pure linting tools is its continuous measurement model: it tracks how technical debt, coverage, and security hotspots evolve over time. This is the tool choice for teams that need management-level reporting on code quality alongside developer-facing diagnostics.
Key configuration for JavaScript/TypeScript projects:
- Set a quality gate that fails on any new blocker or critical security hotspot
- Enable the
Sonar Wayrule profile as the baseline - Pair with SonarLint in VS Code or IntelliJ for in-editor feedback
- Integrate with GitHub Actions or GitLab CI using the
SonarQube Scanaction
CodeQL: Semantic Code Scanning for Deep Vulnerability Detection
CodeQL, developed by GitHub, performs semantic analysis by converting code into a queryable database and running queries against it. It supports JavaScript and TypeScript and is available free for open-source projects through GitHub Advanced Security.
CodeQL finds vulnerabilities that require understanding how data flows through the entire program: a user-controlled value that flows through multiple function calls to reach an unsafe operation. It is the tool that catches vulnerabilities that pattern-matching tools like Semgrep miss when the code path is indirect.
yaml
# .github/workflows/codeql.yml
name: CodeQL Analysis
on: [push, pull_request]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript-typescript
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3
CodeQL has a steeper setup cost than Semgrep and runs more slowly, but it catches a different class of vulnerability: cross-function, cross-file taint flows that no pattern-based tool can identify without full dataflow analysis.
Dead Code Detection: Unused Exports and Unreachable Code
Dead code in JavaScript and TypeScript projects is particularly insidious because the module system does not prevent unused exports from accumulating. A function can be exported, never imported, and no standard tooling will warn about it unless specifically configured.
Knip is the most capable current tool for this. It analyzes the entire project graph to find unused exports, unused dependencies in package.json, and unreachable files:
bash
npm install --save-dev knip
npx knip
ts-prune targets TypeScript specifically, finding exported symbols that are never imported:
bash
npm install --save-dev ts-prune
npx ts-prune
ESLint no-unused-vars and @typescript-eslint/no-unused-vars catch unused local variables within files, but they cannot detect unused module-level exports. Knip covers the gaps that ESLint leaves.
Dead code has a direct impact on bundle size in frontend applications and on cognitive load for developers working in the codebase. Removing dead code is one of the highest-leverage maintenance activities available, and it is only discoverable through tooling because human reviewers cannot reliably track module-level usage across large codebases.
Async/Await and Promises: The Static Analysis Challenge
The Search Console data for this article shows a significant cluster of queries about static analysis tools for asynchronous JavaScript: TAJS, jelly static analyzer, SonarJS async rules, and similar. This reflects a real gap in the tooling landscape.
Standard linting tools do not model how Promises and async functions interact. A missing await, an unhandled rejection, or a race condition in concurrent async code looks syntactically valid and passes all lint rules. Detecting these requires tools that model asynchronous execution semantics.
The current practical approach:
typescript-eslint provides the most immediately useful async-specific rules:
javascript
// Rules that catch common async mistakes
"@typescript-eslint/no-floating-promises": "error", // await or .catch() required
"@typescript-eslint/await-thenable": "error", // only await actual Promises
"@typescript-eslint/no-misused-promises": "error", // Promises in non-async contexts
"@typescript-eslint/require-await": "warn", // async functions must use await
Research tools like TAJS (Type Analyzer for JavaScript), Jelly, and SAFE are academic static analyzers that model JavaScript’s async execution model, including Promise chains, async/await, and event loop semantics. These are not production development tools but rather research platforms used in vulnerability research and formal analysis work. The queries in the Search Console data about “jelly static analyzer javascript async support paper” and “TAJS async await support” reflect developers researching or citing these academic tools, not looking for daily development tooling.
SonarQube’s javascript:S4328 and related async rules detect some common async anti-patterns in production quality analysis.
For practical production use, the combination of TypeScript’s type checker, typescript-eslint‘s async-aware rules, and SonarQube’s quality gate provides the most thorough async safety coverage available in standard tooling today.
Snyk Code: Developer-First Security Scanning
Snyk Code provides SAST scanning with a developer-experience focus: it integrates into VS Code and JetBrains IDEs, surfaces findings inline as developers write code, and provides remediation examples alongside each finding. It uses a proprietary ML-based analysis engine that performs taint tracking across JavaScript and TypeScript codebases.
bash
# Install Snyk CLI
npm install --save-dev snyk
# Authenticate and scan
npx snyk auth
npx snyk code test
Snyk Code is particularly effective for teams that want security feedback without leaving the IDE. Its fix suggestions are more developer-friendly than CodeQL’s query-focused output, making it the better choice for security education alongside vulnerability detection.
Building a Layered JavaScript Static Analysis Stack
The correct approach to JavaScript static analysis is not choosing one tool but combining tools that cover different layers without significant overlap:
| Layer | Tool | When It Runs |
|---|---|---|
| Formatting | Biome or Prettier | Pre-commit (fast) |
| Linting | ESLint + typescript-eslint | Pre-commit + CI |
| Type checking | tsc --noEmit | CI |
| Security scanning | Semgrep or Snyk Code | CI (every PR) |
| Deep vulnerability scanning | CodeQL | CI (scheduled or PR) |
| Dead code detection | Knip | CI (weekly or monthly) |
| Quality gates + trend tracking | SonarQube | CI (every PR) |
| Dependency vulnerability scanning | npm audit + Snyk | CI (every build) |
A minimal stack for a team starting from zero: ESLint + typescript-eslint + npm audit. Add Semgrep or Snyk Code when security requirements increase. Add SonarQube when the team needs quality trend visibility and management reporting.
yaml
# .github/workflows/quality.yml
name: JavaScript Code Quality
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20" }
- run: npm ci
- run: npx tsc --noEmit
- run: npx eslint src/ --max-warnings 0
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20" }
- run: npm ci
- run: npm audit --audit-level=high
- run: npx semgrep scan --config=p/javascript --error src/
When JavaScript Lives in a Larger Enterprise System
JavaScript and TypeScript services increasingly coexist with COBOL programs, Java backends, Python data pipelines, and legacy mainframe systems in enterprise environments. In these contexts, the static analysis tools above provide thorough visibility within the JavaScript boundary but are entirely blind to the connections that cross it.
A Node.js service that reads from a database populated by a COBOL batch job depends on that COBOL program in a way that no JavaScript analysis tool can see. A React frontend that calls a Java API that calls a COBOL program has a dependency chain that spans three language boundaries, none of which are visible from any single-language tool’s perspective.
SMART TS XL addresses this by providing cross-language dependency analysis across the full application portfolio. It builds a unified model that represents how JavaScript modules depend on shared data structures, how API contracts connect frontend and backend services, and how changes in one part of the system propagate through components in other languages. This is the cross-language architectural analysis that enterprise architecture teams need when planning changes to systems that span multiple languages and platforms, and it is the capability that complements the JavaScript-specific tools in this guide rather than competing with them. As described in the context of dependency graphs and application risk, understanding the full dependency structure of a system before making changes is what separates safe refactoring from changes that produce unexpected failures in components no one thought to test.
For JavaScript-specific analysis within these larger environments, SMART TS XL’s enterprise code intelligence coverage includes JavaScript and TypeScript alongside COBOL, JCL, Java, Python, and other enterprise languages, providing unified quality metrics and dependency visibility in a single platform.
Choosing the Right Tool for Your Context
No single tool covers every dimension of JavaScript static analysis. The decision depends on team size, security requirements, existing toolchain, and whether the JavaScript application operates in isolation or as part of a larger multi-language enterprise system.
For a solo developer or small team on a new project: start with Biome (linting + formatting) and TypeScript strict mode. Add npm audit for dependency security.
For a medium-sized team building a production web application: ESLint with typescript-eslint, Prettier, TypeScript strict mode, Semgrep in CI for security, and Knip for dead code detection.
For an enterprise team with compliance and security requirements: SonarQube for quality gates and trend tracking, CodeQL for deep vulnerability scanning, Snyk Code for developer-facing security feedback, and SMART TS XL if the JavaScript application interacts with legacy or multi-language systems.
For a team evaluating ESLint alternatives due to performance in a monorepo: OxcLint as a speed-focused drop-in, or Biome for a complete linter-formatter replacement.