Skip to content

v0.2.0

Choose a tag to compare

@DrSkillIssue DrSkillIssue released this 16 Mar 21:47
· 160 commits to main since this release

Problem

Ganko wrapped ts.server.ProjectService for all TypeScript analysis. ProjectService was designed for interactive editor scenarios: open a file, get completions, open another file, get hover info. It tracks open/close state, schedules deferred work via 250ms timers, reassigns files between projects, and maintains an internal watcher stub. Ganko is a batch analyzer that happens to also run as an LSP. Every file required openClientFile/closeClientFile lifecycle calls, a sentinel warmup hack to pre build the program, deferred timer tracking and teardown, and per file program retrieval that fell through warmup seeds. All of this ceremony exists because ProjectService was never designed for "give me a ts.Program for n files as fast as possible."

The CLI linted every file serially in a single thread: parse, build graph, run rules, repeat n times. The per file loop was embarrassingly parallel but nothing exploited it.

The LSP blocked the editor for 13+ seconds on startup because handleInitialized loaded ESLint config, built the file index, read all CSS, resolved Tailwind, scanned dependencies, created the project with ProjectService, and scheduled TS warmup all before resolving ready. Every keystroke re diagnosed the entire project sequentially.

Graph cache keys were hardcoded to a constant string "0", so every file change rebuilt every graph from scratch regardless of whether the source actually changed. The daemon had its own content hashing but the LSP server and cross file analysis paths did not.

What changed

Replaced @typescript-eslint/project-service with direct ts.createProgram for CLI/daemon and ts.createWatchProgram for the LSP. One call builds the full program. No open/close lifecycle, no sentinel warmup, no timer management, no per file openClientFile.

Parallelized CLI file processing with worker_threads. Workers receive file paths, load their own TypeScript ProjectService, and run both single file and cross file analysis independently. The main thread distributes work proportional to available cores and merges results.

Restructured LSP diagnostics into three progressive tiers. Tier 1: instant single file analysis on keystroke. Tier 2: debounced cross file analysis after edits settle. Tier 3: full project rebuild only on file system events. Each tier cancels stale work from the previous tier.

Replaced hardcoded "0" graph cache keys with contentHash(sourceFile.text) using SHA256. Only graphs whose source text actually changed get rebuilt. The daemon cache hit path pulls the existing SourceFile from the TypeScript program and skips re parsing entirely.

Removed @typescript-eslint/parser, @typescript-eslint/typescript-estree, @typescript-eslint/project-service, and @typescript-eslint/utils from ganko SDK production dependencies. Removed eslint from the LSP bundle.

Eliminated all type assertions and type predicates across the codebase. Every as cast and is predicate replaced with discriminated unions, exhaustive switches, or narrowing that the compiler verifies.

Fixed solid/missing-jsdoc-comments false positives caused by the scanner template literal bug — extractAllComments swallowed all comments after ${...} expressions because the standalone scanner did not re-scan } as TemplateTail.

Fixed solid/no-write-only-index false positives on Maps returned via shorthand properties — { byKey } now resolves via getShorthandAssignmentValueSymbol instead of returning the property symbol.

Replaced O(n²) findScopeForNode linear scan with O(depth) scopeForCache WeakMap lookup. Eliminated the dense position index entirely — replaced O(n) array allocation plus O(Σ spans) per-node writes with O(log n) AST descent via findExpressionAtOffset. CLI lint pays zero cost; the index is only computed lazily on LSP position queries. Eliminated double comment extraction — parseSuppression now accepts pre-extracted comments from the SolidGraph instead of re-scanning the source file.

Restructured the test suite into vitest workspace projects. Unit tests run with isolate: false to share the module graph. Cross-file performance tests run isolated with fileParallelism: false for stable timing. Test infrastructure uses shared ts.Program with oldProgram incremental reuse and module-scoped lib SourceFile caches.

NOTE: Timing metrics were measured on my machine. Results will differ on other hardware.