05

Analizörler

Dosya tarama, glob eşleştirme, JS/TS ve Python analizörleri, tekrar tespiti, gizli bilgi ve isim sezgileri

6 dk okuma12 bölüm

Analizörler kaynak metni bulgulara dönüştürür. src/analyzers/ içinde yer alırlar. Bu doküman nasıl çalıştıklarını (nasıl) anlatır; 06 — Kural başvurusu ise her kuralın neyi denetlediğini anlatır.

Genel bakış

text
                       ┌─▶ jsAnalyzer.analyzeJs()   (.js .jsx .mjs .cjs .ts .tsx)
file text ─ analyzerFor┤
                       └─▶ pyAnalyzer.analyzePy()   (.py)
                                   │
                                   ▼  RawFinding[]
                     duplicateCode.detectDuplicates()
                                   │
                                   ▼
                         model.toViolation()  →  Violation[]

Her analizörün imzası aynıdır:

ts
(relPath: string, text: string, config: CleanCodeConfig) => RawFinding[]

Saf bir fonksiyondur: G/Ç yok, Git yok. Bu yüzden aynı kod diskteki dosyalarda (HEAD taraması), geçmişteki blob'larda (commit yürüyüşü) ve kaydedilmemiş editör içeriğinde (canlı tanılar) çalışır.

runAnalyzers.ts — HEAD taraması

analyzeProject(root, config, excludeGlobs, onProgress):

  1. scanFiles() desteklenen uzantıya sahip ve hariç tutulmamış her dosyayı toplar.
  2. Her dosya için:
    • UTF-8 olarak okunur (okunamazsa atlanır);
    • excludeGenerated false değilse ve başlık dosyayı üretilmiş olarak işaretliyorsa → atlanır ve excludedFiles içinde sayılır;
    • analizör çalıştırılır. Bir ayrıştırma hatası çalıştırmanın tamamını değil, yalnızca o dosyayı atlar.
  3. detectDuplicates() kalan tüm dosyalar üzerinde bir kez çalıştırılır.
  4. Her bulgu Violation'a dönüştürülür ve önem derecesine (critical → low), sonra yola, sonra satıra göre sıralanır.

analyzerFor(relPath) analizörü uzantıya göre seçer veya desteklenmeyen dosyalar için null döndürür.

fileScanner.ts — hangi dosyalar okunur

  • Depo kökünü özyinelemeli olarak gezer.
  • .git, node_modules, .hg, .svn dizinlerini her zaman atlar.
  • Hariç tutma eşleştiricisi dir veya dir/ ile eşleşirse dizini atlar; böylece hariç tutulan bir klasöre hiç girilmez.
  • Bir dosyayı yalnızca uzantısı destekleniyorsa, hariç tutulmamışsa ve ≤ 1.000.000 bayt (MAX_FILE_BYTES) ise tutar. Daha büyük dosyalar neredeyse her zaman üretilmiş veya paketlenmiştir.
  • Yollar her işletim sisteminde / ayıracına normalleştirilir.

glob.ts — hariç tutma kalıp dili

CleanLens tam bir .gitignore motoru değil, kendine ait küçük bir glob lehçesi kullanır.

SözdizimiAnlamı
*/ dışındaki herhangi karakterler
**/ dahil herhangi karakterler (**/ sıfır dizinle de eşleşir)
?/ dışında tek bir karakter
diğer her şeyOlduğu gibi (regex özel karakterleri kaçışlanır)

ExcludeMatcher iki kolaylık ekler:

  • Köke bağlı olmayan kalıplar her derinlikte eşleşir. venv/** kalıbı **/venv/** olur; böylece backend/venv/… da hariç tutulur. / (köke bağlı) veya **/ ile başlayan kalıplar baştaki / kaldırılarak olduğu gibi kullanılır.
  • / içermeyen kalıplar dosyanın yalnızca adıyla da eşleşir. *.min.js, a/b/c.min.js dosyasını hariç tutar.

Desteklenmeyenler: olumsuzlama (!pattern), karakter sınıfları ([abc]), süslü parantezler ({a,b}).

excludes.ts — tam hariç tutma listesi

Son liste mergedExcludeGlobs() tarafından oluşturulur. Kaynaklar bu sırayla eklenir ve hiçbir şey önceki bir girdiyi silmez:

  1. Yerleşik varsayılanlar (DEFAULT_EXCLUDE): **/node_modules/**, **/dist/**, **/build/**, **/coverage/**, **/.next/**, **/vendor/**, **/__pycache__/**, **/staticfiles/**, **/generated/**, *.min.js, *.map, *.generated.*.
  2. Migration dosyaları **/migrations/**, excludeMigrations: false değilse.
  3. Kilit dosyaları package-lock.json, yarn.lock, pnpm-lock.yaml, excludeLockFiles: false değilse. (Bunlar zaten analiz edilemez; bayrak yalnızca hariç tutma listesini ve önbellek hash'ini etkiler.)
  4. .gitattributes içindeki linguist-generated yolları, excludeGenerated: false değilse. / içermeyen bir kalıp **/<pattern> olur.
  5. .gitignore girdileri (elden gelen en iyi şekilde): yorumlar ve ! olumsuzlamaları atlanır; / ile biten bir girdi veya nokta içermeyen bir ad ayrıca <name>/** ekler.
  6. Yapılandırmanın kendi exclude listesi (ön ayarın listesi artı sizin eklemeleriniz, artı yerel geçersiz kılma ve VS Code ayarının girdileri).

Üretilmiş dosya başlıkları

hasGeneratedHeader(text), ilk 5 satırda büyük/küçük harf duyarsız olarak şunlardan herhangi birini arar: @generated, generated file, auto-generated, autogenerated, do not edit, code generated. Eşleşen dosyalar hem HEAD taramasında hem de commit yürüyüşünde atlanır (excludeGenerated: false değilse).

jsAnalyzer.ts — JavaScript ve TypeScript

  • TypeScript derleyici API'si (ts.createSourceFile) ile ayrıştırır; betik türünü uzantıdan seçer (TS, TSX, JSX, JS). Tip denetimi ve tsconfig yoktur; yalnızca sözdizimi.
  • AST'yi iç içelik için bir depth sayacıyla bir kez gezer.
  • "Fonksiyon benzeri" düğümler: fonksiyon bildirimleri ve ifadeleri, ok fonksiyonları, metotlar, yapıcılar (constructor), getter ve setter'lar.
  • Döngüsel karmaşıklık (cyclomatic complexity) 1'den başlar ve her if, ?:, for, for…in, for…of, while, do, catch, boş olmayan case ile &&, ||, ?? için 1 ekler. İç içe fonksiyonlara girmez; onlar ayrıca sayılır.
  • Sembol adları: bir fonksiyon veya sınıfla ilgili bulgular onun adını taşır (symbolName). Bu, parmak izlerini sabit kılar ve medium atama güvenini mümkün kılar.
  • detectUnusedCode için kullanım sayıları: dosyadaki her tanımlayıcı önceden bir kez sayılır; yalnızca bir kez (kendisi) görünen bir bildirim kullanılmıyordur.

pyAnalyzer.ts — Python

Python, satırlar ve girinti kullanılarak tam bir ayrıştırıcı olmadan analiz edilir:

  • Sekmeler 4 boşluğa genişletilir; # yorumları kaldırılır (dize farkındalığıyla).
  • Bir def / async def / class bloğu, girintisi başlığınkine eşit veya daha az olan ilk boş olmayan satırda biter.
  • Parametreler imzadan okunur () karakterine kadar satırlar boyunca); self, cls, *args, **kwargs yok sayılır.
  • Karmaşıklık yaklaşıktır: 1 + if, elif, for, while, except, case ile başlayan satırlar + gövdedeki her and/or.
  • İç içelik derinliği, if, elif, else, for, while, with, try, except ile başlayan satırlarda indent / 4 değeridir. Kapsayan def/class düzeylerini de içerir; bu yüzden bir metot gövdesi 2. derinlikten başlar.
  • Docstring, """ veya ''' ile başlayan ilk gövde satırıdır.

Bu, Python analizörünü hızlı ve bağımlılıksız yapar, ancak JS analizöründen daha az kesindir. Bkz. 17 — Sınırlamalar.

duplicateCode.ts — yinelenen bloklar

  1. Her satırı normalleştir: kırp ve boşlukları daralt. Şu satırları at: 12 karakterden kısa olanlar, yalnızca parantez/noktalama olanlar, yorumlar (//, #, *) veya import / from / export satırları.
  2. Her dosya üzerinde, tutulan minLines satırlık bir pencere kaydır (seçenek, varsayılan 6, en az 3). Satırları 3 × minLines orijinal satırdan fazlasına yayılmış (yeterince bitişik olmayan) pencere atlanır.
  3. Bir pencerenin metni ilk kez görüldüğünde kaydedilir. Tekrar görüldüğünde (aynı dosyada veya başka bir dosyada) sonraki konum bir eşleşmedir.
  4. Çakışan veya bitişik eşleşmeler yinelenen blok başına tek bir bulguda birleştirilir. 40 satırlık bir kopya 35 değil, tek bir ihlaldir.

Notlar:

  • İlk geçiş asla işaretlenmez; yalnızca tarama sırasında daha sonra bulunan kopyalar işaretlenir.
  • HEAD taramasında tespit tüm dosyalar arasında çalışır. Commit yürüyüşünde (analyzeRevision) yalnızca tek bir dosya içinde çalışır, çünkü önce ve sonra yalnızca bir dosya karşılaştırılır.

secrets.ts — koda gömülü gizli bilgi tespiti

İki dil arasında paylaşılan iki denetim:

  • Bir dizenin herhangi bir yerinde bilinen token kalıpları: AWS erişim anahtarı (AKIA…), PEM özel anahtar başlığı, GitHub token'ı (ghp_, gho_, ghu_, ghs_, ghr_), Slack token'ı (xox?-), JWT (eyJ….….…), sk-… API anahtarları.
  • Gizli bilgiye benzeyen ad + kimlik bilgisine benzeyen değer: değişken/özellik adı password, passwd, secret, api_key, access_key, auth_token, token, credential, private_key, client_secret, … ile eşleşir; değer bir yer tutucu değildir (changeme, your_…, <…>, ${…}, example, test, xxx, …); ve bir kimlik bilgisine benzer (boşluk yok, ≥ 8 güvenli karakter ve harf+rakam veya ≥ 20 karakter).

names.ts — belirsiz isimler

  • isUnclearName (yerel değişkenler): izin verilmedikçe (id, db, fs, io, ui, ok, el, fn, cb, x, y, z, i, j, k, n, t, …) ≤ 2 karakterlik adları; ardından rakam gelen belirsiz kelimeleri (data1, tmp2, result3, …); ve kısa harf+rakam birleşimlerini (abc1) işaretler.
  • isVagueName (parametreler, fonksiyon ve sınıf adları): kısa ad denetimi olmadan aynısı; bu yüzden e, x, cb serbesttir.
  • Her zaman serbest: _ ile başlayan adlar ve utf8, sha256, base64, ipv4, oauth2, i18n, h1–h6 gibi teknik adlar.

model.ts — bulgudan ihlale

toViolation(finding, config):

  • severity, config.rules[ruleId].severity değerinden (varsayılan medium);
  • category, RULE_CATEGORY üzerinden;
  • weight, varsayılan SEVERITY_WEIGHT tablosundan (bkz. 08 — Puanlama içindeki not);
  • id = kural + yol + satır + mesajın djb2 hash'i (rapor içinde benzersizdir, düzenlemeler arasında sabit değildir; atama motoru bunun yerine parmak izini kullanır);
  • source = "internal".

Analizörlerin kullandığı yardımcı fonksiyonlar:

  • ruleEnabled(config, id) — yalnızca enabled === true ise true. Yapılandırmada bulunmayan kural kapalıdır.
  • ruleLimit(config, id, fallback), ruleOption(config, id, key, fallback).

analyzeRevision.ts — geçmişteki tek bir dosya sürümü

Commit yürüyüşü tarafından kullanılır. Bir blob üzerinde dosyanın analizörünü ve dosya içi tekrar tespitini çalıştırır, bulguları ihlallere dönüştürür ve her birine bir parmak izi ekler. Şunu döndürür:

  • desteklenmeyen dosya türleri için [];
  • metin eksikse veya analizör hata fırlatırsa null; böylece çağıran taraf etkilenen ihlalleri sessizce kaybetmek yerine unattributed olarak sayabilir.