Ganko was built to catch the horrible code that AI vibe-coding produces and steer it in the right direction automatically. Traditional linters walk a single file's AST, so every rule re-derives the same context from scratch. That makes sophisticated analysis impractical once you have 100+ rules.
Ganko builds a typed graph of your program first (reactivity, scopes, JSX, CSS cascade, cross-file layout) and lets every rule query it. The result is a standalone linter, LSP server, and VS Code extension covering Solid.js reactivity, runtime performance, CSS layout stability, and cross-file JSX-to-CSS analysis.
Solid Reactivity & JSX (25 rules) — Signal call enforcement, effect misuse, store reactivity breaks, component lifecycle, resource access, reactive scope detection, JSX nesting validation, and Suspense boundary checks. Catches the mistakes that Solid's run-once model makes easy to introduce.
Solid Idioms (10 rules) — Import consistency, <For>/<Index>/<Show> preferences, batch optimization, style prop validation, and React-to-Solid migration checks.
TypeScript/JavaScript Correctness (14 rules) — Non-null assertion bans, type-casting restrictions, any/unknown guards, JSDoc enforcement, AI-generated comment detection, and import hygiene.
Runtime Performance (51 rules) — V8 deoptimization patterns (hidden class transitions, megamorphic access, delete operator), quadratic complexity (accumulator spreads, nested collection loops), memory leaks (unbounded collections, detached DOM references, leaked timers/observers/listeners), and hot-path allocation (closures in loops, intermediate array copies).
CSS Analysis (33 rules) — Accessibility policy enforcement (contrast ratios, touch targets, typography, reduced motion), animation validation (discrete transitions, empty keyframes, unknown animation names), cascade correctness (specificity conflicts, layer order inversions, redundant overrides), and structural checks (custom property cycles, container queries, z-index positioning).
Cross-File JSX + CSS (30 rules) — Correlates JSX elements with CSS selectors: layout shift detection (CLS-triggering transitions, conditional display collapse, unsized replaced elements, stateful box-model shifts), classList geometry toggles, fill-image parent sizing, undefined CSS class references, and unused custom properties across file boundaries.
npm i -g @drskillissue/ganko-lspThis installs the ganko binary, which serves as both the language server and CLI linter.
Install ganko-vscode from the VS Code marketplace. It bundles the LSP server — no separate install required.
Add to ~/.config/opencode/opencode.json (or opencode.json in project root):
Any editor with LSP support can use ganko. Launch ganko --stdio — the server communicates via JSON-RPC over stdio.
ganko lint runs the same analysis pipeline as the LSP server, but headless. A background daemon keeps the TypeScript project service and graph caches warm between runs, eliminating the ~2-5s startup cost on repeated invocations. The daemon starts automatically on the first ganko lint call and shuts down after 5 minutes of inactivity.
# Lint entire project (uses daemon)
ganko lint
# Lint specific files or globs
ganko lint src/App.tsx "src/**/*.tsx"
# JSON output for CI
ganko lint --format json
# Fail on any warnings
ganko lint --max-warnings 0
# Exclude paths
ganko lint --exclude "backend/**"
# Skip daemon, run analysis in-process
ganko lint --no-daemon
# Manage the daemon manually
ganko daemon start
ganko daemon status
ganko daemon stopSee the ganko-lsp README for the full CLI reference.
Ganko also ships as an ESLint plugin for individuals that want to run it through their existing ESLint config:
// eslint.config.mjs
import solid from "@drskillissue/ganko/eslint-plugin";
export default [
...solid.configs.recommended,
];- Node.js >= 22.0.0
- TypeScript >= 5.9.3
Rule IDs use the solid/<rule-id> namespace.
The tables below are synced with the current generated manifest and include Solid, CSS, and cross-file rules.
| Rule | Description | Recommended |
|---|---|---|
solid/avoid-conditional-spreads |
Disallow conditional spread operators that create empty objects. Patterns like ...(condition ? {...} : {}) are fragile and create unnecessary object creations. |
error |
solid/avoid-non-null-assertions |
Disallow non-null assertion operator (!). Use optional chaining, nullish coalescing, or proper type narrowing instead. |
error |
solid/avoid-object-assign |
Disallow Object.assign(). Prefer object spread syntax or structuredClone() for copying objects. | error |
solid/avoid-object-spread |
Disallow object spread operators that break Solid's fine-grained reactivity. | error |
solid/avoid-type-casting |
Disallow type casting methods that bypass TypeScript's type safety. Includes unnecessary casts, double assertions, casting to any, type predicates, and unsafe generic assertions. | error |
solid/avoid-unsafe-type-annotations |
Disallow any and unknown in value-level type annotation positions (parameters, returns, variables, properties) |
error |
solid/event-handlers |
Enforce naming DOM element event handlers consistently and prevent Solid's analysis from misunderstanding whether a prop should be an event handler. | error |
solid/missing-jsdoc-comments |
Require JSDoc comments on functions with appropriate tags for parameters, return values, and throws. | error |
solid/no-ai-slop-comments |
Disallow comments containing specified forbidden words or phrases. Useful for enforcing comment style guidelines and detecting AI-generated boilerplate. | error |
solid/no-array-handlers |
Disallow array handlers in JSX event properties. | error |
solid/no-banner-comments |
Disallow banner-style comments with repeated separator characters. | error |
solid/no-destructure |
Disallow destructuring props in Solid components. Props must be accessed via property access (props.x) to preserve reactivity. | error |
solid/no-inline-imports |
Disallow inline type imports. Import types at the top of the file for clarity and maintainability. | error |
solid/string-concat-in-loop |
Disallow string concatenation with += inside loops. Use array.push() and .join() instead. | error |
| Rule | Description | Recommended |
|---|---|---|
solid/css-no-outline-none-without-focus-visible |
Disallow removing outline without explicit focus-visible replacement. | error |
solid/css-policy-contrast |
Enforce minimum contrast ratio per accessibility policy. | warn |
solid/css-policy-spacing |
Enforce minimum spacing per accessibility policy. | warn |
solid/css-policy-typography |
Enforce minimum font sizes and line heights per accessibility policy. | warn |
solid/css-require-reduced-motion-override |
Require reduced-motion override for animated selectors. | warn |
solid/jsx-layout-policy-touch-target |
Enforce minimum interactive element sizes per accessibility policy via resolved layout signals. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/css-no-discrete-transition |
Disallow transitions on discrete CSS properties. | error |
solid/css-no-empty-keyframes |
Disallow empty @keyframes rules. | error |
solid/no-layout-property-animation |
Disallow animating layout-affecting properties. | warn |
solid/no-transition-all |
Disallow transition: all. | warn |
solid/no-unknown-animation-name |
Disallow animation names that do not match declared keyframes. | error |
solid/no-unused-keyframes |
Disallow unused @keyframes declarations. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/declaration-no-overridden-within-rule |
Disallow duplicate declarations of the same property within a single rule block. | warn |
solid/media-query-overlap-conflict |
Disallow conflicting declarations in partially overlapping media queries. | warn |
solid/no-descending-specificity-conflict |
Disallow lower-specificity selectors after higher-specificity selectors for the same property. | warn |
solid/no-layer-order-inversion |
Disallow source-order assumptions that are inverted by layer precedence. | warn |
solid/no-redundant-override-pairs |
Disallow declarations that are deterministically overridden in the same selector context. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/css-no-unreferenced-component-class |
Detect CSS classes that are never referenced by static JSX class attributes. | warn |
solid/jsx-classlist-boolean-values |
Require classList values to be boolean-like expressions. | error |
solid/jsx-classlist-no-accessor-reference |
Disallow passing accessor references directly as classList values. | error |
solid/jsx-classlist-no-constant-literals |
Disallow classList entries with constant true/false values. | warn |
solid/jsx-classlist-static-keys |
Require classList keys to be static and non-computed. | error |
solid/jsx-layout-classlist-geometry-toggle |
Flag classList-driven class toggles that map to layout-affecting CSS geometry changes. | warn |
solid/jsx-layout-fill-image-parent-must-be-sized |
Require stable parent size and positioning for fill-image component usage. | warn |
solid/jsx-layout-picture-source-ratio-consistency |
Require consistent intrinsic aspect ratios across |
warn |
solid/jsx-layout-unstable-style-toggle |
Flag dynamic inline style values on layout-sensitive properties that can trigger CLS. | warn |
solid/jsx-no-duplicate-class-token-class-classlist |
Disallow duplicate class tokens between class and classList on the same JSX element. | warn |
solid/jsx-no-undefined-css-class |
Detect undefined CSS class names in JSX | error |
solid/jsx-style-kebab-case-keys |
Require kebab-case keys in JSX style object literals. | error |
solid/jsx-style-no-function-values |
Disallow function values in JSX style objects. | error |
solid/jsx-style-no-unused-custom-prop |
Detect inline style custom properties that are never consumed by CSS var() references. | warn |
solid/jsx-style-policy |
Enforce accessibility policy thresholds on inline JSX style objects. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/css-layout-animation-layout-property |
Disallow keyframe animations that mutate layout-affecting properties and can trigger CLS. | warn |
solid/css-layout-box-sizing-toggle-with-chrome |
Disallow conditional box-sizing mode toggles when box chrome contributes to geometry shifts. | warn |
solid/css-layout-conditional-display-collapse |
Disallow conditional display collapse in flow without reserved geometry. | warn |
solid/css-layout-conditional-offset-shift |
Disallow conditional non-zero block-axis offsets that can trigger layout shifts. | warn |
solid/css-layout-conditional-white-space-wrap-shift |
Disallow conditional white-space wrapping mode toggles that can trigger CLS. | warn |
solid/css-layout-content-visibility-no-intrinsic-size |
Require intrinsic size reservation when using content-visibility auto to avoid late layout shifts. | warn |
solid/css-layout-dynamic-slot-no-reserved-space |
Require reserved block space for dynamic content containers to avoid layout shifts. | warn |
solid/css-layout-font-swap-instability |
Require metric overrides for swapping webfonts to reduce layout shifts during font load. | warn |
solid/css-layout-overflow-anchor-instability |
Disallow overflow-anchor none on dynamic or scrollable containers prone to visible layout shifts. | warn |
solid/css-layout-overflow-mode-toggle-instability |
Disallow conditional overflow mode switches that can introduce scrollbar-induced layout shifts. | warn |
solid/css-layout-scrollbar-gutter-instability |
Require stable scrollbar gutters for scrollable containers to reduce layout shifts. | warn |
solid/css-layout-sibling-alignment-outlier |
Detect vertical alignment outliers between sibling elements in shared layout containers. | warn |
solid/css-layout-stateful-box-model-shift |
Disallow stateful selector changes that alter element geometry and trigger layout shifts. | warn |
solid/css-layout-transition-layout-property |
Disallow transitions that animate layout-affecting geometry properties. | warn |
solid/css-layout-unsized-replaced-element |
Require stable reserved geometry for replaced media elements to prevent layout shifts. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/css-no-custom-property-cycle |
Disallow cycles in custom property references. | error |
solid/css-no-hardcoded-z-index |
Disallow hardcoded positive z-index literals. | warn |
solid/css-no-legacy-vh-100 |
Disallow 100vh in viewport sizing declarations. | warn |
solid/css-prefer-logical-properties |
Prefer logical properties over physical left/right properties. | warn |
solid/css-z-index-requires-positioned-context |
Require positioned context when using z-index. | warn |
solid/no-important |
Disallow !important declarations. | warn |
solid/no-unresolved-custom-properties |
Disallow unresolved custom property references. | error |
solid/no-unused-custom-properties |
Disallow unused CSS custom properties. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/no-complex-selectors |
Disallow deep selectors that are expensive to match. | warn |
solid/no-duplicate-selectors |
Disallow duplicate selector blocks. | warn |
solid/no-id-selectors |
Disallow ID selectors. | warn |
solid/selector-max-attribute-and-universal |
Disallow selectors with attribute or universal selectors. | off |
solid/selector-max-specificity |
Disallow selectors that exceed a specificity threshold. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/css-no-empty-rule |
Disallow empty CSS rules. | warn |
solid/css-no-unknown-container-name |
Disallow unknown named containers in @container queries. | error |
solid/css-no-unused-container-name |
Disallow unused named containers. | warn |
solid/layer-requirement-for-component-rules |
Require style rules to be inside @layer when the file defines layers. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/components-return-once |
Disallow early returns in components. Solid components only run once, and so conditionals should be inside JSX. | error |
solid/jsx-no-duplicate-props |
Disallow passing the same prop twice in JSX. | error |
solid/jsx-no-script-url |
Disallow javascript: URLs. | error |
solid/jsx-no-undef |
Disallow references to undefined variables in JSX. Handles custom directives. | error |
solid/jsx-uses-vars |
Detect imported components and directives that are never used in JSX. | warn |
solid/no-innerhtml |
Disallow usage of the innerHTML attribute, which can lead to security vulnerabilities. | error |
solid/no-unknown-namespaces |
Enforce using only Solid-specific namespaced attribute names (i.e. 'on:' in <div on:click={...} />). |
error |
solid/show-truthy-conversion |
Detect where expr is not explicitly boolean, which may have unexpected truthy/falsy behavior. | error |
solid/suspense-boundary-missing |
Detect missing fallback props on Suspense/ErrorBoundary, and lazy components without Suspense wrapper. | error |
solid/validate-jsx-nesting |
Validates that HTML elements are nested according to the HTML5 specification. | error |
| Rule | Description | Recommended |
|---|---|---|
solid/avoid-arguments-object |
Disallow arguments object (use rest parameters instead). | warn |
solid/avoid-chained-array-methods |
Flags chained array methods creating 3+ intermediate arrays, or filter().map() pattern. | warn |
solid/avoid-defensive-copy-for-scalar-stat |
Disallow defensive array copies passed into scalar statistic calls. | warn |
solid/avoid-delete-operator |
Disallow delete operator on objects (causes V8 deoptimization). | warn |
solid/avoid-function-allocation-in-hot-loop |
Disallow creating closures inside loops. | warn |
solid/avoid-hidden-class-transition |
Suggest consistent object shapes to avoid V8 hidden class transitions. | warn |
solid/avoid-intermediate-map-copy |
Disallow temporary Map allocations that are copied key-for-key into another Map. | warn |
solid/avoid-megamorphic-property-access |
Avoid property access on any or wide union types to prevent V8 deoptimization. |
warn |
solid/avoid-quadratic-pair-comparison |
Disallow nested for-loops over the same collection creating O(n²) pair comparison. | warn |
solid/avoid-quadratic-spread |
Disallow spreading accumulator in reduce callbacks (O(n²) complexity). | error |
solid/avoid-repeated-indexof-check |
Disallow 3+ .indexOf() calls on the same array variable in one function. | warn |
solid/avoid-slice-sort-pattern |
Disallow .slice().sort() and .slice().reverse() chains. Use .toSorted()/.toReversed(). | warn |
solid/avoid-sparse-arrays |
Disallow new Array(n) without fill (creates holey array). | warn |
solid/avoid-spread-sort-map-join-pipeline |
Disallow [...iterable].sort().map().join() pipelines on hot paths. | warn |
solid/bounded-worklist-traversal |
Detect queue/worklist traversals with unbounded growth and no guard. | warn |
solid/closure-captured-scope |
Detect closures returned from scopes containing large allocations that may be retained. | warn |
solid/closure-dom-circular |
Detect event handler property assignments that create closure-DOM circular references. | warn |
solid/create-root-dispose |
Detect createRoot with unused dispose parameter. | warn |
solid/detached-dom-reference |
Detect DOM query results stored in module-scoped variables that may hold detached nodes. | warn |
solid/effect-outside-root |
Detect reactive computations created outside a reactive root (no Owner). | error |
solid/finalization-registry-leak |
Detect FinalizationRegistry.register() where heldValue references the target. | error |
solid/no-char-array-materialization |
Disallow split(""), Array.from(str), or [...str] in parsing loops. | warn |
solid/no-double-pass-delimiter-count |
Disallow split-based delimiter counting followed by additional split passes. | warn |
solid/no-full-split-in-hot-parse |
Disallow full split() materialization inside hot string parsing loops. | warn |
solid/no-heavy-parser-constructor-in-loop |
Disallow constructing heavy parsing helpers inside loops. | warn |
solid/no-leaked-abort-controller |
Detect AbortController in effects without abort() in onCleanup. | warn |
solid/no-leaked-animation-frame |
Detect requestAnimationFrame in effects without cancelAnimationFrame in onCleanup. | warn |
solid/no-leaked-event-listener |
Detect addEventListener in effects without removeEventListener in onCleanup. | warn |
solid/no-leaked-observer |
Detect Observer APIs in effects without disconnect() in onCleanup. | warn |
solid/no-leaked-subscription |
Detect WebSocket/EventSource/BroadcastChannel in effects without close() in onCleanup. | warn |
solid/no-leaked-timer |
Detect setInterval/setTimeout in effects without onCleanup to clear them. | warn |
solid/no-loop-string-plus-equals |
Disallow repeated string += accumulation in parsing loops. | warn |
solid/no-multipass-split-pipeline |
Disallow multipass split/map/filter pipelines in parsing code. | warn |
solid/no-per-char-substring-scan |
Disallow per-character substring/charAt scanning patterns in loops. | warn |
solid/no-repeated-token-normalization |
Disallow repeated trim/lower/upper normalization chains on the same token in one function. | warn |
solid/no-rescan-indexof-loop |
Disallow repeated indexOf/includes scans from start in parsing loops. | warn |
solid/no-rest-slice-loop |
Disallow repeated self-slice reassignment loops in string parsing code. | warn |
solid/no-shift-splice-head-consume |
Disallow shift/splice(0,1) head-consume patterns in loops. | warn |
solid/no-write-only-index |
Detect index structures that are written but never queried by key. | warn |
solid/prefer-charcode-over-regex-test |
Prefer charCodeAt() range checks over regex .test() for single-character classification. | warn |
solid/prefer-index-scan-over-string-iterator |
Prefer index-based string scanning over for-of iteration in ASCII parser code. | warn |
solid/prefer-lazy-property-access |
Suggests moving property access after early returns when not used immediately. | warn |
solid/prefer-map-lookup-over-linear-scan |
Disallow repeated linear scans over fixed literal collections in hot paths. | warn |
solid/prefer-map-over-object-dictionary |
Suggest Map for dictionary-like objects with dynamic keys. | warn |
solid/prefer-precompiled-regex |
Prefer hoisting regex literals to module-level constants to avoid repeated compilation. | warn |
solid/prefer-set-has-over-equality-chain |
Disallow 4+ guard-style equality checks against string literals on the same variable. Use a Set. | warn |
solid/prefer-set-lookup-in-loop |
Disallow linear search methods (.includes/.indexOf) on arrays inside loops. | warn |
solid/recursive-timer |
Detect setTimeout that recursively calls its enclosing function. | warn |
solid/self-referencing-store |
Detect setStore() where the value argument references the store itself. | error |
solid/unbounded-collection |
Detect module-scoped Map/Set/Array that only grow without removal. | warn |
solid/unbounded-signal-accumulation |
Detect signal setters that accumulate data without truncation via spread+append pattern. | warn |
| Rule | Description | Recommended |
|---|---|---|
solid/async-tracked |
Disallow async functions in tracked scopes (createEffect, createMemo, etc.) | error |
solid/children-helper-misuse |
Detect misuse of the children() helper that causes unnecessary re-computation or breaks reactivity | error |
solid/cleanup-scope |
Detect onCleanup called outside of a valid reactive scope | error |
solid/derived-signal |
Detect functions that capture reactive values but are called in untracked contexts | error |
solid/effect-as-memo |
Detect createEffect that only sets a derived signal value, which should be createMemo instead | error |
solid/effect-as-mount |
Detect createEffect/createRenderEffect with no reactive dependencies that should be onMount instead | error |
solid/inline-component |
Detect component functions defined inside other components, which causes remount on every parent update | error |
solid/no-top-level-signal-call |
Disallow calling signals at component top-level (captures stale snapshots) | error |
solid/ref-early-access |
Detect accessing refs before they are assigned (before mount) | error |
solid/resource-access-unchecked |
Detect accessing resource data without checking loading/error state. | error |
solid/resource-implicit-suspense |
Detect createResource that implicitly triggers or permanently breaks Suspense boundaries. | warn |
solid/resource-refetch-loop |
Detect refetch() calls inside createEffect which can cause infinite loops | error |
solid/signal-call |
Require signals to be called as functions when used in tracked contexts | error |
solid/signal-in-loop |
Detect problematic signal usage inside For/Index loop callbacks | error |
solid/store-reactive-break |
Detect patterns that break store reactivity: spreading stores, top-level property extraction, or destructuring | error |
solid/transition-pending-unchecked |
Detect useTransition usage without handling the isPending state | error |
| Rule | Description | Recommended |
|---|---|---|
solid/batch-optimization |
Suggest using batch() when multiple signal setters are called in the same synchronous scope | warn |
solid/imports |
Enforce consistent imports from "solid-js", "solid-js/web", and "solid-js/store". | error |
solid/index-vs-for |
Suggest for object arrays and for primitive arrays. | warn |
solid/no-react-deps |
Disallow usage of dependency arrays in createEffect, createMemo, and createRenderEffect. |
error |
solid/no-react-specific-props |
Disallow usage of React-specific className/htmlFor props, which were deprecated in v1.4.0. |
error |
solid/prefer-for |
Enforce using Solid's <For /> component for mapping an array to JSX elements. |
warn |
solid/prefer-memo-complex-styles |
Enforce extracting complex style computations to createMemo for better approach. Complex inline style objects are rebuilt on every render, which can impact approach. | warn |
solid/prefer-show |
Enforce using Solid's <Show /> component for conditionally showing content. Solid's compiler covers this case, so it's a stylistic rule only. |
warn |
solid/self-closing-comp |
Disallow extra closing tags for components without children. | warn |
solid/style-prop |
Require CSS properties in the style prop to be valid and kebab-cased (ex. 'font-size'), not camel-cased (ex. 'fontSize') like in React, and that property values with dimensions are strings, not numbers with implicit 'px' units. |
warn |
Rule severity can be overridden in three places (highest precedence first):
- VS Code settings —
solid.rules.<rule-id>per-rule overrides (editor-only) - ESLint flat config —
eslint.config.{mjs,js,cjs}rule entries (read by both CLI and LSP) - Built-in defaults — from the rules manifest
The CLI also accepts --exclude patterns and reads global ignores from ESLint config.
See the ganko-lsp README and ganko-vscode README for configuration details.
# Install dependencies
bun install
# Build all packages (shared → ganko → lsp → vscode)
bun run build
# Run tests (1478 ganko tests)
bun run test
# Run LSP tests separately
bun run --cwd packages/lsp vitest --run
# Type-check all packages
bun run tsc
# Lint (zero warnings enforced)
bun run lint
# Full CI pipeline (build + test + lint + tsc + manifest check)
bun run ci
# Run specific test file
bun run --cwd packages/ganko test -- signal-call.test.tsMIT
{ "$schema": "https://opencode.ai/config.json", "lsp": { "ganko": { "command": ["ganko", "--stdio"], "extensions": [".ts", ".tsx", ".js", ".jsx", ".css", ".scss", ".sass", ".less"] } } }