Linting & Static Analysis
Linting and static analysis are two closely related but distinct practices that examine your source code without executing it. Both catch problems early — before code reaches testing, staging, or production — but they focus on different types of issues. Linting primarily targets code style, common mistakes, and language-specific pitfalls. Static analysis digs deeper, identifying security vulnerabilities, complex code smells, data flow issues, and patterns that indicate architectural problems. Together, they form a critical layer of automated quality defense.
What Linting Catches
A linter reads your source code and compares it against a set of rules. These rules typically cover three categories:
- Possible errors: Mistakes that are likely bugs. Using a variable before it is defined, calling a function with the wrong number of arguments, unreachable code after a
returnstatement, and accidental assignment in conditionals (if (x = 5)instead ofif (x === 5)). - Best practices: Patterns that are technically valid but commonly lead to problems. Using
==instead of===in JavaScript, leavingconsole.logstatements in production code, declaring variables withvarinstead ofletorconst, and unused variables that indicate incomplete refactoring. - Style consistency: Formatting and naming conventions. Indentation (tabs vs spaces), quote style (single vs double), semicolon usage, maximum line length, and naming conventions (camelCase vs snake_case). While less critical than error detection, style consistency reduces cognitive load during code review and makes the codebase easier to navigate.
The key characteristic of linting is speed. Linters are designed to run in real time inside your editor, providing instant feedback as you type. They are also fast enough to run on every commit in a CI pipeline without meaningfully increasing build time.
Linting Tools by Language
JavaScript and TypeScript: ESLint
ESLint is the dominant linter for the JavaScript ecosystem. It is highly configurable, supports custom rules and plugins, and has an enormous ecosystem of shared configurations. Popular configurations include eslint:recommended (a conservative set of rules catching common errors), airbnb (a comprehensive and opinionated style guide), and @typescript-eslint (rules specific to TypeScript).
ESLint's plugin architecture is particularly powerful. You can add plugins for React (eslint-plugin-react), accessibility (eslint-plugin-jsx-a11y), imports (eslint-plugin-import), and many other concerns. This means a single tool can enforce rules across your entire frontend stack.
CSS: Stylelint
Stylelint does for CSS what ESLint does for JavaScript. It catches errors like invalid property values, duplicate properties, unknown units, and selector patterns that are too specific. It also enforces conventions like property ordering, color format consistency, and naming patterns for classes and IDs. Stylelint supports CSS, SCSS, Less, and CSS-in-JS.
HTML: HTMLHint
HTMLHint is a lightweight linter for HTML that catches common markup issues: missing doctype declarations, empty tags, duplicate IDs, inline styles, and deprecated attributes. While it does not replace full W3C validation (covered in the previous lesson), it provides fast, editor-integrated feedback on HTML quality. It works well as a first line of defense, with W3C validation running in CI as a more thorough check.
Python: Pylint and Ruff
Pylint is a comprehensive Python linter that checks for errors, enforces coding standards, looks for code smells, and even provides refactoring suggestions. It is thorough but can be slow on large codebases. Ruff is a newer, extremely fast Python linter written in Rust that reimplements most Pylint and Flake8 rules with orders-of-magnitude better performance, making it practical for real-time editor integration and large monorepos.
Dart and Flutter: dart analyze
The Dart SDK includes a built-in analysis tool, dart analyze, that checks for errors, warnings, and style issues. The analysis is configured through an analysis_options.yaml file that specifies which rules to enable. For Flutter projects, the flutter_lints or very_good_analysis packages provide curated rule sets. Because the analyzer is built into the Dart toolchain, it integrates seamlessly with IDEs and CI pipelines.
What Static Analysis Catches
Static analysis goes beyond linting by performing deeper inspection of code structure, data flow, and patterns. While a linter checks individual lines or statements against rules, a static analysis tool can trace how data moves through a program, identify complex patterns across multiple files, and detect issues that require understanding the program's control flow.
Security Patterns: Semgrep and OpenGrep
Semgrep (and its open-source fork OpenGrep) is a static analysis tool designed to find security vulnerabilities and dangerous code patterns. Unlike traditional regex-based tools, Semgrep understands the abstract syntax tree (AST) of your code, which means it can match patterns regardless of formatting, variable names, or comment placement.
Semgrep excels at finding:
- Injection vulnerabilities: SQL injection, cross-site scripting (XSS), command injection, and other cases where user input flows into dangerous operations without sanitization.
- Hardcoded secrets: API keys, passwords, and tokens embedded directly in source code.
- Insecure configurations: Disabled TLS verification, permissive CORS headers, debug mode enabled in production, and weak cryptographic settings.
- Dangerous API usage: Functions known to be insecure (like
eval()in JavaScript,pickle.loads()in Python, orRuntime.exec()in Java without input validation).
Semgrep supports over 30 programming languages and comes with thousands of community-contributed rules. You can also write custom rules specific to your project's patterns and conventions. CodeFrog uses Semgrep/OpenGrep for static analysis in its security scanning, integrating the results into the Mega Report alongside other quality dimensions.
Code Smells and Complexity: SonarQube
SonarQube is a comprehensive code quality platform that goes beyond security to analyze code smells, cognitive complexity, technical debt, and test coverage. It provides a dashboard view of your codebase's overall health and tracks quality metrics over time.
SonarQube identifies:
- Code smells: Functions that are too long, classes with too many responsibilities, deeply nested conditionals, duplicated code blocks, and other patterns that make code difficult to understand and maintain.
- Cognitive complexity: A metric that measures how difficult a piece of code is to understand. Unlike cyclomatic complexity (which counts paths), cognitive complexity weights structures based on how much they increase the mental effort required to read the code. Nested conditionals, for example, are penalized more heavily than sequential ones.
- Technical debt: An estimated time to fix all identified issues, helping teams prioritize and plan quality improvement work.
- Security vulnerabilities and hotspots: Similar to Semgrep but with a broader focus that includes code quality alongside security.
Linting vs Static Analysis: A Comparison
Understanding the difference helps you choose the right tools and set appropriate expectations:
- Speed: Linters run in milliseconds and work in real time in your editor. Static analysis tools typically take seconds to minutes and run in CI rather than in the editor.
- Depth: Linters check syntax, style, and simple patterns. Static analysis tools trace data flow, analyze control paths, and understand cross-file relationships.
- False positives: Linters have very low false positive rates because their rules are simple and well-defined. Static analysis tools, especially for security, may produce more false positives because they must be conservative (it is better to flag a potential vulnerability that turns out to be safe than to miss a real one).
- Configuration effort: Linters are relatively easy to configure. Start with a recommended configuration and adjust rules as needed. Static analysis tools often require more tuning to reduce noise and focus on issues relevant to your project.
Configuring Rules for Your Team
Every team needs to make deliberate decisions about which rules to enforce and at what severity level. Here are practical guidelines:
- Start with a recommended configuration. ESLint's
eslint:recommended, Pylint's defaults, or SonarQube's "Sonar way" profile provide well-tested baselines. Do not start from scratch. - Treat errors as errors. Rules that catch actual bugs (unused variables, unreachable code, potential null dereferences) should be errors that break the build. Rules that enforce style preferences should be warnings initially.
- Promote warnings to errors over time. Start with a lenient configuration and gradually tighten it. This avoids overwhelming the team with hundreds of errors on existing code while still moving toward higher quality.
- Document exceptions. When you disable a rule for a specific line or file, always include a comment explaining why.
// eslint-disable-next-line no-console -- intentional logging for debuggingtells future developers the disable is deliberate, not accidental. - Share configuration across projects. Publish your ESLint, Stylelint, and other configurations as shared packages so that all projects in your organization follow the same rules. This reduces onboarding friction and ensures consistency.
Integrating into Editors and CI
The most effective quality strategy layers linting and analysis at multiple points in the development workflow:
- Editor integration: Install linter extensions (ESLint for VS Code, Pylint for PyCharm, etc.) so developers see errors as they type. This is the fastest feedback loop and catches the majority of issues before code is even saved.
- Pre-commit hooks: Use tools like pre-commit or Husky to run linters automatically before every commit. This prevents linting errors from entering the repository. Combine with lint-staged to lint only changed files, keeping the hook fast.
- CI pipeline: Run both linters and static analysis tools in your CI pipeline. The CI check is the definitive quality gate — it catches anything that slipped past editor integration and pre-commit hooks, and it runs the deeper static analysis that is too slow for real-time editor feedback.
- Pull request annotations: Configure your CI to post linting and analysis results as inline comments on pull requests. This makes issues visible in the exact context where they need to be fixed, reducing the back-and-forth between finding and fixing issues.
CodeFrog and Static Analysis
CodeFrog integrates Semgrep/OpenGrep-based static analysis into its security scanning capabilities. When you run a CodeFrog Mega Report, it performs static analysis alongside accessibility, performance, SEO, and HTML validation checks. This means you get a unified view of your project's quality across all dimensions, including security patterns that Semgrep identifies. The results are presented in a single report that helps you prioritize the most impactful fixes across all quality dimensions.
By combining linting for code style and common errors with static analysis for security and complexity, you build a comprehensive automated quality defense that catches problems at every level of severity — from a missing semicolon to a critical SQL injection vulnerability.
Resources
- ESLint Documentation — Configuration, rules, and plugin guides for the JavaScript linter
- Semgrep Documentation — Getting started, writing rules, and running scans for security-focused static analysis
- SonarQube — Code quality and security platform for continuous inspection