Skip to content
Static Code Analysis: Tools and Best Practices

Static Code Analysis: Tools and Best Practices

DodaTech Updated Jun 19, 2026 6 min read

Static code analysis examines source code without executing it, automatically detecting potential bugs, security vulnerabilities, code smells, and style violations before the code ever runs.

What You’ll Learn

  • The difference between linters, type checkers, and security scanners
  • How to configure and use ESLint, Pylint, and SonarQube
  • Integrating static analysis into your CI/CD pipeline
  • Handling false positives and writing custom rules

Why Static Analysis Matters

Static analysis catches issues at the cheapest possible moment — during development, not after deployment. A bug caught by a linter costs seconds to fix. The same bug caught in production costs hours or days. Google runs static analysis on every code change and prevents 5,000+ bugs per year from reaching production.

Doda Browser uses static analysis in its CI pipeline to enforce code quality standards across its open-source codebase.

Learning Path

    flowchart LR
  A[Code Review] --> B[Code Smells]
  B --> C[Static Analysis<br/>You are here]
  C --> D[CI/CD Integration]
  D --> E[Production Quality Gates]
  style C fill:#f90,color:#fff
  

Linters vs Type Checkers vs Security Scanners

Tool TypeWhat It ChecksExamplesSpeed
LinterStyle, code smells, potential bugsESLint, Pylint, RuboCopVery fast
Type CheckerType consistency, null safetyTypeScript, mypy, PyreFast
Security ScannerVulnerabilities, secrets, injectionsBandit, Semgrep, SnykSlow
ComprehensiveAll of the aboveSonarQube, CodeClimateSlowest

ESLint (JavaScript/TypeScript)

npm init @eslint/config

Configure in eslint.config.js:

import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    plugins: { react: reactPlugin },
    rules: {
      'no-unused-vars': 'error',
      'no-console': 'warn',
      'react/hooks-rules-of-hooks': 'error',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },
];

Expected output:

1:15  error  'unusedVar' is defined but never used    no-unused-vars
4:10  error  'any' is not allowed                     @typescript-eslint/no-explicit-any
7:5   warn   Unexpected console statement              no-console

Pylint (Python)

pip install pylint
pylint mymodule.py

Configuration written to .pylintrc:

[MASTER]
load-plugins=pylint_django

[MESSAGES CONTROL]
disable=C0111,  # missing docstring
        R0903,  # too few public methods
        W0611   # unused import (let mypy handle it)

[DESIGN]
max-args=5
max-locals=15
max-returns=3
max-statements=50

Expected output:

************* Module mymodule
src/mymodule.py:12:4: W0621: Redefining name 'data' from outer scope (redefined-outer-name)
src/mymodule.py:45:0: R0914: Too many local variables (18/15) (too-many-locals)

SonarQube (Multi-Language)

SonarQube is a comprehensive platform that tracks quality metrics across your entire codebase over time.

# sonar-project.properties
sonar.projectKey=myapp
sonar.sources=src
sonar.tests=tests
sonar.python.version=3.11
sonar.python.coveragePlugin=jacoco
sonar.qualitygate.wait=true

Run in CI:

sonar-scanner -Dsonar.login=$SONAR_TOKEN

Expected output:

ANALYSIS SUMMARY:
- Bugs: 3 (new: 2)
- Vulnerabilities: 1 (new: 0)
- Code Smells: 42 (new: 5)
- Coverage: 87.3%
- Duplications: 2.1%
- Quality Gate: PASSED

CI Integration

GitHub Actions

name: Static Analysis
on: [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: npm run lint          # ESLint
      - run: npx tsc --noEmit      # TypeScript
      - run: npm run audit         # Security audit

Pre-commit Hooks

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v9.0.0
    hooks:
      - id: eslint
  - repo: https://github.com/pycqa/flake8
    rev: 7.0.0
    hooks:
      - id: flake8

Handling False Positives

Static analysis tools sometimes flag code that’s actually correct. Handle false positives properly:

# Pylint false positive — flag with inline comment
def get_user(user_id):  # pylint: disable=redefined-builtin
    return database.query(f"SELECT * FROM users WHERE id = {user_id}")
// ESLint false positive — disable for this line
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = JSON.parse(response);

Best practice: Explain why the rule is disabled. Future developers need to know the reasoning.

Custom Rules

When standard rules don’t cover your team’s conventions, write custom rules.

ESLint Custom Rule

// eslint-rules/no-console-error-only.js
export default {
  meta: {
    type: 'suggestion',
    docs: { description: 'Allow console.error only' },
    messages: {
      noConsole: 'Use console.error instead of console.{{ method }}',
    },
  },
  create(context) {
    return {
      CallExpression(node) {
        if (node.callee.object?.name === 'console' &&
            node.callee.property?.name !== 'error') {
          context.report({ node, messageId: 'noConsole' });
        }
      },
    };
  },
};

Common Errors

1. Too Many Rules

Enabling every available rule creates noise that drowns out real issues. Start with recommended presets and add rules gradually.

2. Ignoring Warnings

Treat warnings as errors in CI, or they’ll be ignored. Use --max-warnings 0 to fail the build on any warning.

3. Running Analysis Too Late

Waiting until the CI pipeline means developers see issues only after pushing. Run linters in the editor and pre-commit hooks.

4. Not Configuring for the Project

Default configs don’t know your project’s conventions. Customize rules for your framework, language version, and team preferences.

5. Skipping Security Scanners

Linters catch style issues but miss SQL injection and XSS. Always pair a linter with a security-focused tool.

6. Not Updating Rule Sets

Outdated rule sets miss new vulnerability patterns. Update your analysis tools monthly.

Practice Questions

  1. What is the difference between a linter and a type checker? A linter checks code style and potential bugs. A type checker validates type consistency and null safety.

  2. How do you handle a false positive in ESLint? Use // eslint-disable-next-line rule-name with a comment explaining why.

  3. What quality gate does SonarQube measure? A combined pass/fail check based on bugs, vulnerabilities, code smells, coverage, and duplication.

  4. When should static analysis run in the development workflow? At three levels: in the editor (real-time), in pre-commit hooks, and in CI on every PR.

  5. Why should warnings be treated as errors in CI? Because warnings are otherwise ignored. --max-warnings 0 ensures all issues are addressed.

Challenge: Set up ESLint and SonarQube (or CodeClimate) on a personal project. Configure custom rules for your team’s conventions. Document your configuration and the first 10 issues found.

FAQ

Can static analysis find all bugs?
No. Static analysis finds certain classes of bugs (type errors, unused variables, potential null references) but cannot find logic errors or business rule violations.
Does static analysis make code reviews obsolete?
No. Tools catch mechanical issues; humans catch design and architectural problems. Use both.
How often should I update my linter configs?
Every 1-3 months. New rules are added, old ones deprecated, and security patterns evolve.
What is the best static analysis tool for a new project?
Start with the language’s standard linter (ESLint for JS, Pylint for Python). Add SonarQube when the project grows beyond 10,000 lines.
Does static analysis slow down the build?
Linters add seconds. Security scanners and comprehensive analysis add minutes. Run fast linters on every commit, slower analysis on PRs or nightly.

What’s Next

TutorialWhat You’ll Learn
Code Review Best PracticesHuman review to complement automated analysis
CI/CD Pipeline SetupIntegrating analysis into automated pipelines
SonarQube Setup and ConfigurationDeploying your own SonarQube instance

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-19.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro