Static Code Analysis: Tools and Best Practices
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 Type | What It Checks | Examples | Speed |
|---|---|---|---|
| Linter | Style, code smells, potential bugs | ESLint, Pylint, RuboCop | Very fast |
| Type Checker | Type consistency, null safety | TypeScript, mypy, Pyre | Fast |
| Security Scanner | Vulnerabilities, secrets, injections | Bandit, Semgrep, Snyk | Slow |
| Comprehensive | All of the above | SonarQube, CodeClimate | Slowest |
ESLint (JavaScript/TypeScript)
npm init @eslint/configConfigure 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-consolePylint (Python)
pip install pylint
pylint mymodule.pyConfiguration 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=50Expected 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=trueRun in CI:
sonar-scanner -Dsonar.login=$SONAR_TOKENExpected output:
ANALYSIS SUMMARY:
- Bugs: 3 (new: 2)
- Vulnerabilities: 1 (new: 0)
- Code Smells: 42 (new: 5)
- Coverage: 87.3%
- Duplications: 2.1%
- Quality Gate: PASSEDCI 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 auditPre-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: flake8Handling 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
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.
How do you handle a false positive in ESLint? Use
// eslint-disable-next-line rule-namewith a comment explaining why.What quality gate does SonarQube measure? A combined pass/fail check based on bugs, vulnerabilities, code smells, coverage, and duplication.
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.
Why should warnings be treated as errors in CI? Because warnings are otherwise ignored.
--max-warnings 0ensures 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
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| Code Review Best Practices | Human review to complement automated analysis |
| CI/CD Pipeline Setup | Integrating analysis into automated pipelines |
| SonarQube Setup and Configuration | Deploying 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