Skip to content

Repository files navigation

codeanalyzer

CI demo license

Analizzatore statico di codice sorgente, senza dipendenze esterne. Passa la cartella di un progetto e ti dice quali file non servono più, quale codice è duplicato, quali dipendenze sono inutilizzate o obsolete e quali errori comuni ci sono dentro.

Supporta Python (analisi via AST, alta precisione) e JavaScript / TypeScript / Vue / Svelte (parser interno, nessun Node richiesto). Legge package.json, requirements.txt, pyproject.toml, Pipfile, setup.py e setup.cfg.

python -m codeanalyzer .          # report a terminale
python -m codeanalyzer . --serve  # web app locale

Demo pubblica: https://codeanalyzer-demo.fly.dev — provala senza installare niente, scegliendo un progetto di esempio o caricando uno .zip del tuo codice.


Installazione

Serve solo Python 3.11+. Nessuna dipendenza a runtime.

git clone <questo-repo> && cd code_analyze
python -m codeanalyzer --help

Installazione come comando globale (opzionale):

pip install -e .

Dopo l'installazione il comando codeanalyzer è disponibile nella shell.


Cosa trova

1. File inutilizzati

Costruisce il grafo delle importazioni del progetto, individua i punti di ingresso (main.py, __main__.py, index.js, main e bin di package.json, script di CI, Dockerfile, file di test, routing per convenzione tipo pages/ e routes/) e segnala tutto ciò che non è raggiungibile.

Regola Cosa significa
unused-file Nessuno importa né referenzia questo file
unreachable-module Importato solo da altri file a loro volta morti
orphan-asset Immagine / font / media mai citato da nessuna parte
broken-relative-import Import relativo che non risolve su nessun file
empty-file, no-code-file File vuoto o solo commenti

Gli import dinamici (importlib, __import__, require(variabile)) abbassano automaticamente la confidenza del risultato invece di produrre falsi positivi silenziosi.

2. Funzioni e blocchi duplicati

Ogni funzione viene ridotta a un flusso di token con le variabili locali rinominate posizionalmente: due copie che differiscono solo nei nomi delle variabili collidono comunque.

Regola Cosa significa
duplicate-function Stessa identica logica in più punti
similar-function Quasi identiche (soglia Jaccard configurabile)
identical-file Due file con contenuto identico
duplicate-block Sequenza di righe ripetuta
repeated-literal Stessa stringa scritta a mano decine di volte

I quasi-duplicati usano bottom-k shingle sketches con indice invertito: il costo resta quasi lineare anche su progetti grandi, e solo le coppie candidate pagano il confronto Jaccard completo.

3. Dipendenze

Regola Cosa significa
unused-dependency Dichiarata nel manifest ma mai importata
undeclared-dependency Importata ma assente dal manifest (funziona solo per fortuna)
deprecated-dependency Pacchetto obsoleto, abbandonato o compromesso
removed-stdlib-module Modulo tolto dalla stdlib (distutils, imp, cgi, telnetlib…)
conflicting-dependency Versioni diverse in manifest diversi
dev-dependency-in-production devDependency importata da codice non di test
unpinned-dependency Nessun vincolo di versione

Conosce la mappa nome-pacchetto → nome-modulo (PillowPIL, PyYAMLyaml, beautifulsoup4bs4, …) e non segnala i tool di build (pytest, black, eslint, webpack…) come inutilizzati.

4. Errori comuni e code smell

Python — argomenti default mutabili, except: nudo, except: pass, chiave duplicata in un dizionario, funzione/metodo definito due volte nello stesso scope, codice irraggiungibile, closure sulla variabile di ciclo, metodo senza self, attributo di classe mutabile, == None, is con letterale, type(x) == T, confronto di una variabile con sé stessa, import/variabili/ funzioni private inutilizzate, import *, f-string senza placeholder, concatenazione di stringhe in ciclo, open() fuori da with, return incoerenti, funzioni troppo lunghe / troppi parametri / complessità ciclomatica / annidamento eccessivo.

JavaScript / TypeScript== invece di ===, var, console.log dimenticati, debugger, catch {} vuoto, eval, assegnazione dentro un if, auto-assegnazione, parseInt senza radix, promise senza .catch(), await dentro un ciclo, import inutilizzati, : any espliciti, @ts-ignore.

Trasversali — marcatori di conflitto di merge non risolti, indentazione mista tab/spazi, file sovradimensionati, righe troppo lunghe, inventario di TODO / FIXME / HACK.

5. Sicurezza

Credenziali in chiaro (chiavi AWS, token GitHub/Slack/Stripe, chiavi private, JWT, DSN con password, segreti generici verificati per entropia), API pericolose (shell=True, eval, pickle.loads, yaml.load, SQL costruito con f-string, verify=False, debug=True, innerHTML, new Function, Math.random per generare token) e file .env non coperti da .gitignore.

I valori sospetti vengono oscurati nel report: il tool non stampa mai il segreto per intero.

6. Salute del progetto

Assenza di test, suite di test sottile, README/LICENSE/.gitignore mancanti, e una classifica di hotspot (dimensione × complessità) che indica dove i bug tendono ad accumularsi.


Uso

python -m codeanalyzer .                            # analizza il progetto corrente
python -m codeanalyzer ../app -s medium             # solo da severità media in su
python -m codeanalyzer . --only duplicates,security # solo due analizzatori
python -m codeanalyzer . -f html -o report.html     # report navigabile da condividere
python -m codeanalyzer . -f json | jq '.findings[0]'
python -m codeanalyzer . --fail-on high             # exit code 1 per la CI
python -m codeanalyzer --list-rules                 # elenco completo delle regole

Opzioni principali

Opzione Effetto
-f, --format text, json, markdown, html, csv, sarif (anche più di uno, separati da virgola)
-o, --output Scrive su file; con più formati diventa il nome base
-s, --min-severity info | low | medium | high | critical
--only / --skip Attiva o disattiva categorie
--ignore-rule Silenzia una regola (ripetibile)
--exclude / --include Glob di percorsi (ripetibili)
--no-gitignore Ignora i file .gitignore
--include-hidden Analizza anche file e cartelle nascosti
--similarity Soglia per i quasi-duplicati (default 0.80)
--max-function-lines, --max-complexity, --max-file-lines Soglie di manutenibilità
--max-per-rule Quante occorrenze mostrare per regola (0 = tutte)
--fail-on Exit code 1 se emerge qualcosa a quella severità
-v, --verbose Mostra ogni occorrenza e ogni suggerimento

Exit code

Codice Significato
0 Nessun risultato a livello --fail-on
1 Trovato qualcosa alla soglia indicata
2 Errore di invocazione
130 Interrotto

Configurazione

Il tool legge, in ordine, .codeanalyzer.toml, .codeanalyzer.json, oppure la sezione [tool.codeanalyzer] di pyproject.toml.

[tool.codeanalyzer]
exclude-globs = ["demo", "demo/*", "*.generated.ts"]
ignore-rules  = ["repeated-literal", "long-lines"]
min-severity  = "low"
max-function-lines = 90
max-complexity = 18
similarity-threshold = 0.85

Le esclusioni si sommano ai default (node_modules, .venv, dist, lock file, bundle minificati…): un file di configurazione non può riattivarle per sbaglio.


Integrazione in CI

- name: Analisi statica
  run: |
    python -m codeanalyzer . --fail-on high -f sarif -o results.sarif.json
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: results.sarif.json

L'output SARIF 2.1.0 è accettato da GitHub code scanning e dalla maggior parte delle dashboard di qualità.


Come è fatto

codeanalyzer/
├── cli.py            interfaccia a riga di comando
├── engine.py         orchestrazione: scan → parse → analizzatori → report
├── config.py         default, tabelle dei linguaggi, file di configurazione
├── scanner.py        attraversamento del progetto, .gitignore, conteggio righe
├── manifests.py      lettura di package.json / requirements / pyproject / Pipfile
├── models.py         Finding, Severity, SourceFile, Report
├── parsers/
│   ├── python_parser.py   AST: import, funzioni, impronte, complessità
│   └── js_parser.py       parser JS/TS senza dipendenze (blanking + brace matching)
├── analyzers/
│   ├── unused_files.py    grafo delle importazioni e raggiungibilità
│   ├── duplicates.py      cloni esatti, quasi-cloni, blocchi ripetuti
│   ├── dependencies.py    igiene delle dipendenze
│   ├── python_smells.py   errori comuni Python (su AST)
│   ├── js_smells.py       errori comuni JS/TS
│   ├── generic_smells.py  controlli indipendenti dal linguaggio
│   ├── security.py        credenziali e API pericolose
│   └── metrics.py         salute del progetto e hotspot
├── reporters/
│   ├── terminal.py        output a terminale (ANSI, fallback ASCII)
│   ├── structured.py      JSON, Markdown, CSV, SARIF
│   └── html_reporter.py   report HTML interattivo autoconsistente
└── web/
    ├── server.py          server HTTP (stdlib), API JSON, token di sessione
    ├── jobs.py            analisi in thread separati, cronologia, opzioni
    ├── demo.py            restrizioni per l'esposizione pubblica
    └── static/            index.html, app.js, style.css (nessun build step)

Ogni finding porta con sé una confidenza: le regole euristiche (asset orfani, segreti generici, dipendenze usate solo da plugin) dichiarano esplicitamente quanto sono sicure invece di fingere certezza.

Il parser JavaScript lavora su una copia del sorgente in cui commenti, contenuto delle stringhe e letterali regex sono sostituiti da spazi mantenendo tutti gli offset: nessun pattern può scattare dentro una stringa. Lo stesso vale per le regole Python sulle API pericolose, che girano su una copia prodotta con tokenize.


Progetto di esempio

demo/messy_project/ contiene un progetto minuscolo con problemi voluti (credenziali in chiaro, SQL injection, funzioni duplicate, file morti, dipendenze obsolete) che serve a vedere il tool in azione:

python -m codeanalyzer demo/messy_project

Web app

Lo stesso motore, dietro un'interfaccia locale:

python -m codeanalyzer . --serve

Si apre il browser su http://127.0.0.1:8770. Nessuna dipendenza aggiuntiva: il server è http.server della libreria standard, il frontend è HTML/CSS/JS senza build step e senza CDN.

Cosa ci puoi fare:

  • Scegliere il progetto scrivendo il percorso o navigando le cartelle; il campo segnala se ha riconosciuto un manifest (pyproject.toml, package.json…).
  • Trascinare uno .zip per analizzare codice che non sta su questa macchina.
  • Regolare le opzioni — quali analizzatori attivare, severità minima, soglie di complessità, glob da escludere — senza ricordare i flag da riga di comando.
  • Filtrare i risultati per severità, per testo, o mostrando solo i risultati ad alta confidenza.
  • Aprire il codice cliccando un risultato: si apre il file alla riga esatta, con la riga incriminata evidenziata e la sintassi colorata.
  • Esportare in HTML, JSON, Markdown, CSV, SARIF o testo.
  • Rileggere le analisi precedenti dalla cronologia laterale.

Opzioni

Opzione Effetto
--serve Avvia la web app invece di stampare un report
--port N Porta (default 8770)
--host ADDR Indirizzo di bind (default 127.0.0.1, solo loopback)
--no-browser Non aprire automaticamente il browser

Il percorso passato come argomento posizionale diventa la cartella di partenza: codeanalyzer ~/progetti --serve.

Sicurezza

La web app può leggere i file del progetto che analizzi, quindi:

  • si lega solo a 127.0.0.1 salvo --host esplicito (che stampa un avviso);
  • ogni chiamata /api/ richiede un token di sessione generato all'avvio e consegnato solo dentro l'HTML dell'app: una pagina di un altro sito non può leggerlo e quindi non può pilotare il server;
  • il visualizzatore di codice risolve i percorsi dentro la radice analizzata e rifiuta qualsiasi tentativo di uscirne;
  • gli zip caricati vengono estratti rifiutando percorsi assoluti e traversal (zip-slip), con un tetto di 80 MB;
  • non viene mai eseguito nulla del codice analizzato.

Demo pubblica e deploy

La demo gira su fly.io: https://codeanalyzer-demo.fly.dev

Perché serve una modalità separata

La web app locale è uno strumento da sviluppatore: naviga il filesystem e analizza qualunque cartella le indichi. Esposta su un URL pubblico, quello stesso comportamento è lettura remota di file arbitrari per chiunque passi di lì. Per questo la demo gira con --demo, che toglie proprio quelle capacità:

Locale --demo
Navigazione del filesystem (/api/browse) disabilitata (403)
Analisi di un percorso arbitrario rifiutata (403)
Progetti analizzabili qualsiasi cartella solo gli esempi inclusi e gli .zip caricati
Upload massimo 80 MB 8 MB, max 2.000 file, protezione zip-bomb
File per analisi illimitati 1.500 (max_files)
Limite di frequenza nessuno 12 analisi / 5 min per IP, 3 concorrenti
Ritenzione degli upload fino a chiusura cancellati dopo 30 minuti

Il visualizzatore di codice resta confinato alla radice analizzata in entrambe le modalità, e in nessuna delle due viene mai eseguito il codice analizzato.

Avviarla altrove

python -m codeanalyzer . --serve --demo --samples demo --host 0.0.0.0 --port 8080

Ogni sottocartella di --samples diventa un progetto selezionabile.

Docker

docker build -t codeanalyzer-demo .
docker run --rm -p 8080:8080 codeanalyzer-demo

L'immagine è python:3.13-slim più il pacchetto: nessuna dipendenza da installare, processo non-root, healthcheck HTTP.

fly.io

flyctl apps create codeanalyzer-demo
flyctl deploy --remote-only

fly.toml tiene la macchina a min_machines_running = 0 con auto-stop: la demo dorme quando nessuno la usa e si sveglia alla prima richiesta. Il deploy si ripete a ogni push su main tramite .github/workflows/deploy.yml (richiede il secret FLY_API_TOKEN, generabile con flyctl tokens create deploy).


Prestazioni

Misurate su Windows, Python 3.14, senza parallelismo:

Progetto File Righe Tempo
questo repository 37 8.800 ~2 s
site-packages completo 3.360 1.006.000 ~4 min 40 s

Il costo cresce linearmente con le righe (~3.600 righe/s). Le due parti che potevano degenerare in comportamento quadratico — "questo nome è citato altrove?" e la ricerca di coppie candidate per i quasi-duplicati — usano rispettivamente un indice invertito costruito una sola volta e uno sketch bottom-k con tetto sulle coppie tracciate.

Disattivare un analizzatore ne evita davvero il costo: con --only security il fingerprinting dei cloni non viene nemmeno calcolato.


Test

pip install pytest
python -m pytest tests/ -q

148 test coprono scanner, parser, ogni analizzatore, i reporter e la CLI — inclusi i casi negativi (codice pulito che non deve produrre segnalazioni) il livello web (autenticazione, sandbox dei percorsi, zip-slip) e ogni restrizione della modalità demo.


Limiti dichiarati

  • L'analisi è statica: reflection, import dinamici, plugin caricati per nome e bundler con alias personalizzati possono far apparire "inutilizzato" del codice che invece viene usato. Per questo la confidenza è sempre esplicita.
  • Il parser JS non è un vero parser: copre import, funzioni e i pattern d'errore comuni, non la semantica completa del linguaggio.
  • Il controllo dipendenze legge i manifest, non il registro dei pacchetti: sa che request è deprecato perché lo ha in tabella, non perché interroghi npm.
  • Le regole di sicurezza sono un linter, non un audit: segnalano schemi che meritano uno sguardo umano.

About

Static analyzer for unused files, duplicated code, stale dependencies and common bugs — CLI + local web app. Zero dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages