Skip to content

chore(types): replace explicit any in 20 more files (batch 2) - #45

Merged
carbon-evolution merged 1 commit into
mainfrom
chore/types-batch-2
Oct 18, 2026
Merged

carbon-evolution merged 1 commit into
mainfrom
chore/types-batch-2

Conversation

@carbon-evolution

Copy link
Copy Markdown
Owner

Summary

This is batch 2 of removing explicit any (batch 1 was #40). It replaces every explicit any in the 20 files in scope with real types and takes those files off the no-explicit-any warn list in eslint.config.mjs.

Only types change. tsc is clean, and the emitted logic is the same apart from the small cases under Behaviour notes.

any counts (npx eslint -f json .)

no-explicit-any files with any ESLint errors ESLint warnings (all rules)
Before (main) 387 26 0 398
After 243 6 0 254

That is 144 removed. All 20 files in scope are clean, and none were skipped.

Files cleaned (20)

  • API routes:
    • cctv/us-dot-additional.ts, cyber-intel, flights/airport, malware, news, satellites/enrich, scm-suppliers, threat-intel
    • osint/{bgp,cve,dns,ismalicious,otx,whois}
  • Components: CyberIntelPanel, EntityGraphPanel, LayerPanel, LiveAlerts
  • Lib: lib/feeds/flights.ts, lib/sdk/PolybolosClient.ts

Still on the warn list (out of scope for this batch)

cctv/route.ts, maritime/route.ts (skipped on purpose in #40), page.tsx, OsintPanel.tsx, OsirisMap.tsx, lib/feeds/markets.ts.

How the anys were replaced

  • Parsed external JSON: each source gets a small local interface listing only the fields the code reads. Fields the upstream can omit are optional, and the existing optional chaining does the narrowing. Examples:
    • RipeData, MitreCveRecord, CirclCve, DohResponse
    • RdapDomain (jCard entries typed as VCardProperty tuples), IsMaliciousCheck, OtxGeneral/OtxGeo/OtxMalware/OtxUrlList
    • SatnogsTransmitter, IpApiGeo, AbuseEntry, ThreatFoxIoc, MalwareBazaarSample
    • the per-state DOT camera shapes
    • RawAircraft (readsb field names)
    • OpenSkyState, a positional tuple for indices 0–11
  • otxFetch is now generic (otxFetch<T>), and each call names its section type.
  • Response builders (results: any = {} filled in step by step) are now typed result interfaces: BgpResult, DnsResult, WhoisResult, IsMaliciousResult, SatEnrichResult, CyberIntelResponse, AirportBoard, ThreatEntry, MalwareThreat.
  • Existing types reused:
    • NewsItem and EarthquakesPayload['earthquakes'] in LiveAlerts
    • ReconFinding from OsintPanel in CyberIntelPanel
    • CctvCamera and the earthquake type in PolybolosClient
    • lucide's LucideIcon for the tab icons
  • EntityGraphPanel uses react-force-graph-2d's exported NodeObject, LinkObject and ForceGraphMethods:
    • properties is now Record<string, unknown>;
    • the three URLSearchParams.set calls wrap the value in String(...). The runtime result is the same because set already converts its value to a string.
    • paintLink reads link.source/link.target as NodeObject, with a comment: force-graph has already swapped the ids for node objects by the time it paints. s.y!/t.y! follow the file's existing n.x!/n.y! style.
  • LayerPanel:
    • data is Record<string, unknown>. Rows are narrowed with Array.isArray through a local rows variable.
    • Layers are typed with LayerDef/LayerGroup.
    • setActiveLayers is typed as (update: <L extends ActiveLayers>(prev: L) => L) => void. With this type the page can pass its useState setter for its concrete layer-flag object without a cast. A plain Dispatch<SetStateAction<Record<string, boolean>>> would not accept that setter.
  • lib/feeds/flights.ts: FlightsPayload now uses Flight (the return type of classifyFlight) and JammingZone instead of any[].
  • PolybolosClient: ingestOsirisData takes an OsirisFeedData interface, with one input shape per feed. The position fields use the types PolybolosEntity['position'] expects. This method has no callers in the repo. If it is later fed FlightsPayload directly, speed_knots (number | null in the feed) will need ?? undefined.
  • cyber-intel: cve.summary?.length > 200 and cve.capec?.length > 0 became cve.summary && cve.summary.length > 200 and cve.capec && cve.capec.length > 0. Both give the same result for every input, and no ! is needed. The MITRE block is first built as a typed mitreEnrichment const, then assigned to response, so mitre_nodes can read it without narrowing.
  • Banned patterns: none added: no eslint-disable, no as unknown as, no new any. The single-cast [] as DohAnswer[] (an empty-array literal) follows the existing [] as string[] idiom.

Behaviour notes (tiny, called out explicitly)

  • osint/bgp catch: detail is now String(e) instead of undefined when something other than an Error is thrown. This is the same pattern as batch 1.
  • EntityGraphPanel: graphRef now starts as undefined instead of null. React-force-graph's ref type is MutableRefObject<ForceGraphMethods | undefined>, so a null start does not type-check against it. The component never reads the ref, and the library fills it in on mount.
  • CyberIntelPanel recon findings: onLocate(f.lat, f.lng!). The check guards lat, and the only producer (OsintPanel) always sets lat and lng together. The runtime call is unchanged.

The ratchet (eslint.config.mjs)

  • The warn list was regenerated from ESLint output after the changes. It is sorted, now has 6 entries, and the comment is unchanged.
  • Ratchet check (a temporary edit, reverted): appending export const x: any = 1; to the cleaned src/lib/feeds/flights.ts gives error Unexpected any.

Test plan

  • npx tsc --noEmit: 0 errors
  • npx eslint .: 0 errors (254 warnings, down from 398; no-explicit-any 387 → 243)
  • npx vitest run (excluding the cache/rateLimit/feedStore/postgres suites): 6 files, 66 tests pass
  • npm run build: succeeds
  • Line endings kept:
    • CRLF/LF line counts checked for all 21 changed files against origin/main;
    • the CRLF files (LayerPanel.tsx, and the mostly-CRLF osint/{cve,dns,whois}) keep their layout, with new lines matching the surrounding endings;
    • there is no whole-file churn.
  • Browser smoke test on next start -p 3014:
    • the map canvas loads;
    • after 15s there were 0 console errors and 0 page errors;
    • the layer panel slide-out (NETWORK INTEL, with per-layer counts) renders;
    • the Cyber Intel panel renders its KEV, CVE, NETWORK and MITRE tabs with data (e.g. APT29 under MITRE);
    • still 0 errors after these interactions.
  • API parity: origin/main was built separately and served on port 3015. 22 requests over every touched route (plus /api/flights and /api/cctv) returned the same status codes on both builds:
    • 200 for most;
    • 400/404 for the validation cases;
    • 503 for isMalicious without a key, on both.
    • A body hash with timestamp stripped matched for osint/cve, osint/dns, osint/bgp and the airport 404.

🤖 Generated with Claude Code

Replace every explicit `any` in the next 20 files with real types and
drop them from the no-explicit-any warn list in eslint.config.mjs.
no-explicit-any warnings: 387 -> 243; files with `any`: 26 -> 6.

- Parsed upstream JSON gets small interfaces covering only the fields
  that are read (RIPEstat, MITRE CVE 5.0, CIRCL, Google DoH, RDAP,
  isMalicious, OTX, SatNOGS, OpenSky, ip-api, AbuseIPDB, ThreatFox,
  MalwareBazaar, state DOT camera feeds).
- Response builders are typed result interfaces.
- Components reuse existing types (NewsItem, EarthquakesPayload,
  ReconFinding, CctvCamera) and react-force-graph-2d's NodeObject /
  LinkObject / ForceGraphMethods.
- Types only. No eslint-disable, no `as unknown as`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@carbon-evolution
carbon-evolution merged commit 5cd3246 into main Oct 18, 2026
7 checks passed
@carbon-evolution
carbon-evolution deleted the chore/types-batch-2 branch October 18, 2026 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant