Skip to content

Releases: DrSkillIssue/Ganko

v0.2.7

18 Mar 22:22

Choose a tag to compare

feat: replace binary logger.enabled with per-level isLevelEnabled(Lev…

v0.2.61

18 Mar 00:21

Choose a tag to compare

What's Changed

  • feat: --accessibility-policy <name> CLI flag for ganko lint — enables accessibility policy enforcement with no default (rules are silent unless the flag is provided). Valid values: wcag-aa, wcag-aaa, mobile-first, dense-ui, large-text
  • fix: Sync state.vscodePolicy in handleConfigurationChange — policy changes via VS Code settings were being overwritten on the next ESLint config reload
  • fix: Resolve lint violations in --accessibility-policy flag parsing — replaced .find() in loop with a module-scope Map for O(1) lookup, replaced conditional spread patterns with explicit object assignment
  • fix: Set active policy in tests broken by null-default change — policy rule tests now use beforeAll/afterAll to set wcag-aa for the duration of each policy suite

v0.2.6

17 Mar 20:29
6badee0

Choose a tag to compare

Core

  • update lazyRuleBatch to use Map for createTestProgram compatibility
  • resolve ESLint warnings in test utilities and cross-file test suites

v0.2.5

17 Mar 18:53

Choose a tag to compare

TypeScript Diagnostic Push

Push TypeScript diagnostics through ganko's tiered LSP architecture:

  • Tier 1 — syntactic diagnostics at startup
  • Tier 2 — semantic diagnostics with debounce
  • Phase 5 — async propagation via setImmediate with keystroke cancellation

Enable with --enable-ts CLI flag.

Co-located CSS Scope Resolution

Automatically includes foo.css in scope when foo.tsx exists in the same directory. Fixes false positives in CSS aggregation architectures that use central @import chains instead of per-component JS imports.

Three-valued Selector Matching

Dynamic JSX attribute values (e.g. data-variant={props.variant}) now produce conditional matches instead of no-match. CSS selectors like [data-component="text-field"][data-variant="normal"] correctly apply layout signals through the cascade.

Render Callback Parent Resolution

JSX elements inside callback props (e.g. <Select itemComponent={(p) => <Item />}>) are no longer parented to the outer element, preventing false sibling alignment detections in components like Kobalte's Select.

contain:layout CLS Exemption

Elements with contain: layout, contain: strict, or contain: content are exempt from CLS detection since layout containment isolates geometry changes.

Trace Logging

--log-level trace now instruments the cascade builder, context classification, and alignment rule evaluation for full diagnostic visibility.

v0.2.1

17 Mar 00:12

Choose a tag to compare

Problem

The v0.2.0 three-phase LSP startup introduced a race condition in Phase C workspace enrichment. Cross-file CSS diagnostics reported false positives in VSCode — classes like gap-inset-xs flagged as "not defined in project CSS files" even though ganko lint CLI produced correct results with zero false positives.

enrichWorkspace exposed context.fileIndex before the async resolveTailwindValidator call completed. Any didOpen event firing during that async yield saw a non-null file index and triggered cross-file analysis with tailwindValidator === null. The false-positive results were cached via setCachedCrossFileResults, and the stale cache persisted through Phase C re-diagnosis because the generation counters had not changed — the fast path returned cached results without consulting the now-resolved tailwind validator.

The re-diagnosis loop at the end of Phase C reused a stale openPaths snapshot captured after Phase B. Files opened during the 5-10s Phase B→C window were never re-diagnosed with cross-file results.

setCachedCrossFileResults only wrote entries for files WITH diagnostics, never clearing entries for files whose issues were resolved. When editing file B resolved file A's cross-file issues, A's stale diagnostics persisted in the per-file cache until save.

What changed

Deferred context.fileIndex assignment until after tailwind resolution and external property scanning complete. The file index and tailwind validator now appear atomically from the perspective of concurrent didOpen handlers. Any cross-file analysis triggered before enrichment completes is gated by fileIndex === null and skips, getting correct results when Phase C re-diagnosis runs.

Added graphCache.invalidateAll() after enrichment completes, forcing the re-diagnosis loop to rebuild cross-file results with the fully-enriched context rather than hitting the stale cache fast path.

Recaptured open document paths after Phase C completes instead of reusing the Phase B snapshot. Files opened during the enrichment window are now included in the re-diagnosis loop.

Fixed setCachedCrossFileResults to clear crossFileDiagnostics before repopulating, ensuring files whose cross-file issues were resolved do not retain phantom diagnostics.

Upgraded file index and tailwind validator log lines from debug to info for visibility in production logs.

v0.2.0

16 Mar 21:47

Choose a tag to compare

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.

v0.1.26

15 Mar 21:27

Choose a tag to compare

Changes

  • refactor: Separate cause/fix in alignment signal findings — diagnostic messages now show a single actionable fix instead of dumping internal scoring metadata
  • fix: CLI formatter includes filename on each diagnostic line (src/App.tsx:222:8 instead of 222:8)

v0.1.25

15 Mar 20:33

Choose a tag to compare

Performance

Daemon pre-warm. prewarmDaemon() runs after server.listen(), eagerly building the TS ProjectService and triggering program creation via a sentinel file. First ganko lint after ganko daemon start drops from 20-30s to <3s. Only config-stable resources are pre-warmed; file index, Tailwind, and dependency scan are deferred to the first request (they depend on --exclude CLI params unknown at daemon start).

updateFile content equality check. ScriptInfo.editContent() unconditionally bumps script version even when content is identical. Added snapshot comparison before calling editContent(), preventing spurious graph rebuilds on unchanged files (particularly the pre-warm sentinel).

Bug Fixes

@drskillissue/ganko moved from devDependencies to dependencies. Users running ganko lint without ganko as a project devDependency had their ESLint config's import solid from "@drskillissue/ganko/eslint-plugin" fail silently. loadESLintConfig returned EMPTY_ESLINT_RESULT, all rules fired at manifest defaults, producing 3k+ false diagnostics instead of ~25.

Daemon ESLint config cache consumed once after pre-warm, then cleared. Previous implementation reused the pre-warmed config on every request, masking config file changes between lint invocations.

v0.1.24

15 Mar 00:53

Choose a tag to compare

New Features

resource-implicit-suspense missingErrorBoundary diagnostic now provides an auto-fix that wraps the nearest <Suspense> ancestor's children with <ErrorBoundary fallback={...}>, inserting the import if missing.

Bug Fixes

css-layout-sibling-alignment-outlier suppressed false positives when parent containers use flex-direction: column or grid-auto-flow: column. Added crossAxisIsBlockAxis discriminant to AlignmentContext and flex-flow shorthand expansion to detect column variants through the shorthand.

Architecture

6-phase layout subsystem refactor across 34 files (51 files changed, +1382 -1029):

Enumeration Reduction: Converted 11 string literal unions to const enum with numeric values. Replaced kindRank and combineCertainty with numeric max comparisons.

Dead Code & Consolidation: Deleted dead buildContainingBlockFactsByElementKey. Consolidated INTRINSIC_REPLACED_TAGS and WHITESPACE_RE to single sources of truth.

Signature Compression: Extracted SelectorMatchContext (5 params to 1 object). Converted 9 string-keyed *ByElementKey Maps to object-identity-keyed *ByNode Maps.

Hot Path Optimization: Single-pass computeHotSignals switch dispatch (42 Map lookups to N). Inlined overflow parsing with indexOf/slice. O(n) quickselect for P95. Allocation-free selectTopFactors.

Memory Reduction: Slimmed LayoutSignalSnapshot (11 to 5 fields), LayoutMatchEdge (6 to 3 fields), LayoutElementNode (removed 2 derivable fields). Removed raw from signal values. Unified LayoutGuardProvenance with LayoutRuleGuard (pass by reference, no copy). Reservoir sampling for posteriorWidths (O(cases) to O(200)).

Consolidation: Extracted deriveAlignmentContext helper. Merged resolveCompositionDivergenceStrength + resolveMajorityClassification into single resolveCompositionDivergence.

v0.1.23

14 Mar 11:13
1e6e4a0

Choose a tag to compare

Rule Changes

resource-implicit-suspense conditionalSuspense ERROR now fires regardless of initialValue. Verified against solid-js source: initialValue does NOT prevent Suspense activation. The SuspenseContext increment path fires whenever the fetcher's Promise is pending. initialValue only prevents the accessor from returning undefined.

Rule Improvements

resource-implicit-suspense consolidated JSX parent chain walks into a single analyzeComponentBoundaries pass per component (cached per component name), collecting conditional mount context, Suspense distance, and ErrorBoundary presence in one walk. Shared fetcherCanThrow visited set across all createResource calls in the file.

Documentation

resource-implicit-suspense corrected mechanism descriptions based on solid-js source verification: SolidJS uses SuspenseContext increment/decrement counters (not React-style Promise throwing), and Suspense has zero error handling (handleError walks the ownership chain for ErrorBoundary's catchError as the only interception mechanism).