Skip to content

Latest commit

 

History

History
201 lines (171 loc) · 9.47 KB

File metadata and controls

201 lines (171 loc) · 9.47 KB

analyze

askr analyze performs workspace-aware static checks for current Askr APIs. It uses the TypeScript compiler API with each selected workspace's tsconfig.json, when present, and also scans JavaScript and TypeScript source files that are not listed by that config.

askr analyze
askr analyze --workspace "@example/web"
askr analyze --workspace "apps-*" --workspace "shared-*"
askr analyze --check
askr analyze --json --check
askr analyze --cwd ./apps/web

The command discovers the containing npm or pnpm workspace. It scans the root and every declared workspace by default. Repeat --workspace to select workspace names with minimatch patterns.

Diagnostics

Every diagnostic has a stable rule ID, category, severity, message, workspace, workspace-relative file, one-based line and column, and optional remediation. Output is sorted by workspace, file, position, rule ID, and message.

The analyzer resolves named aliases and namespace imports from @askrjs/askr, @askrjs/ui, @askrjs/themes/components, and their supported public subpaths. A same-named function imported from another package or local module is not treated as an Askr API.

Correctness

  • askr/parse-error reports malformed source before other results can be considered complete.
  • askr/no-hardcoded-theme-token reports --ak-* token names in runtime JavaScript and TypeScript string, template, and JSX attribute literals. The exact @askrjs/themes workspace is exempt because it owns those declarations.
  • askr/stable-render-call enforces stable top-level calls for state, derived values, selectors, resources, lifecycle operations, actions, queries, and mutations where the AST establishes a component render context.
  • askr/stable-control-boundary reports For, Show, Case, and statically resolved defineScope() boundaries created conditionally during rendering.
  • askr/render-scope-required reports render-owned primitives created in statically non-render callbacks such as handlers, timers, Promise continuations, and task bodies. It also reports module-scope or non-render calls to readScope(), getSignal(), routeData(), and ErrorBoundary(); readScope() remains valid in a resource() loader.
  • askr/exhaustive-dependencies compares direct same-component reactive reads with literal resource and stream dependency arrays. Dynamic arrays, spreads, and uninvoked nested functions are deliberately skipped.
  • askr/for-row-closure-capture reports direct reactive reads and one-hop snapshots captured by a For row renderer. Function-valued JSX props remain valid.
  • askr/state-access reports state getters used without calling them and setters called without a value or updater.
  • askr/state-render-write reports state mutation during the owning component's render while allowing updates in event callbacks.
  • askr/resource-cancellation and askr/data-cancellation report fetch-based resource, query, and mutation loaders that do not forward their cancellation signal.
  • askr/for-contract requires each, an item renderer, and exactly one of by or byIndex.
  • askr/control-contract validates required Show and Match conditions and direct Case/Match structure; Case otherwise permits only null, false, and whitespace children.
  • askr/stable-module-identity reports lazy() and defineScope() created directly during a proven component render.
  • askr/no-async-component reports async JSX components.
  • askr/route-registry keeps route DSL calls inside a synchronous createRouteRegistry() definition.
  • askr/route-path-syntax mirrors the runtime's static path validation, including leading and duplicate slashes, complete {name} interpolation, non-empty unique parameter names, final named splats, and non-empty page paths.
  • askr/route-scope-structure follows named definitions and groups to report nested pages, duplicate page indexes, and absolute child routes. A proven child-route leading slash has a safe fix.
  • askr/link-contract requires one unambiguous destination, rejects the runtime's unsafe URL schemes, and checks static to(routeRef, { ... }) parameter objects against workspace route declarations. Dynamic objects and spreads are deliberately skipped. Relative links plus http, https, mailto, tel, and sms remain valid.
  • askr/no-slot-style-override rejects non-empty class and className props on fully themed floating-layer content and overlay components. Customize the owning theme tokens instead. Components with a spread are skipped because the analyzer cannot prove the final prop set.
  • askr/block-layout-authority-conflict warns when Block combines a non-empty class or className with direction, align, justify, gap, or wrap. Choose either the class or the component props as the element's flex-layout authority. Components with a spread are skipped.
  • askr/query-key-contract reports directly provable nondeterministic and Symbol query key/scope parts; dynamic values are left alone.
  • askr/import-subpath groups named root imports by their owning public Askr subpath in one transactional fix per declaration while retaining aliases, type modifiers, and valid root specifiers.
  • askr/no-hardcoded-theme-token reports runtime color literals outside tests and the framework/theme owner packages.
  • askr/no-effect-data-loading reports direct fetch-to-component-state flows in task() callbacks; arbitrary service-call inference is intentionally out of scope.
  • askr/testing-contract requires a synchronous flush() or result.flush() between canonical testing dispatch() and the next assertion in a block.
  • askr/boot-registry requires an explicit registry and an observed Promise for createSPA() and hydrateSPA().
  • askr/ssr-browser-global reports unguarded browser globals in SSR and SSG modules.

Performance

  • askr/prefer-for reports JSX .map() only when its receiver is proven to be an Askr state-backed reactive collection. Static array transforms remain valid.
  • askr/stable-key reports index-returning by functions.
  • askr/stable-dependencies reports object, array, function, and constructor allocations in resource dependency arrays.

Configuration

  • askr/framework-config validates the Askr JSX import source and detects a declared @askrjs/vite dependency that is absent from vite.config.*.

The rule catalog is intentionally extensible. Current concepts inventory reactive state, lifecycle operations, queries, mutations, invalidation, control flow, route DSL and registries, SPA and island boot, SSR, SSG, actions, authorization, scopes, refs, and composition. Static analysis reports only patterns it can establish from source and configuration; it does not run the project's lint, tests, or build.

Safe fixes

Without --check, the command applies only fixes whose intent is mechanical:

  • convert route parameters such as :id to {id};
  • add a missing leading slash to a root route or collapse consecutive slashes;
  • strip the leading slash from a statically proven child route;
  • split misplaced named root imports into their documented public subpaths;
  • add the Askr JSX runtime to a plain-JSON tsconfig.json.

All changed files are staged and replaced as one transaction. If any replacement fails, completed replacements are rolled back. JSONC, inherited TypeScript configuration, .map() to <For>, conditional render-scoped calls, invalid keys, and other semantic changes are report-only.

--check performs no writes. Fixable diagnostics remain in the result and appear under skippedFixes with a check-mode reason.

The command exits 1 while error or warning diagnostics remain, and 0 when only informational or no diagnostics remain.

Performance contract

The analyzer intentionally builds lightweight syntax programs: project source and local path aliases are resolved, while standard-library and external package declaration graphs are not loaded. Rules still distinguish canonical Askr imports from unrelated local functions, but analysis does not pay the cost of type-checking dependency declarations it never reports.

npm run bench:analyze runs the analyzer's Vitest benchmark suite. It covers a 50-file workspace, a 250-file workspace, and five workspaces containing 250 files in total. The benchmark reporter enforces mean-time budgets of 100 ms, 250 ms, and 300 ms respectively. The general npm run bench gate also checks a cold installed-CLI scan of the 35-file startkit template against a 350 ms p95 budget.

Configuration

Configure the analyzer in the workspace root package.json:

{
  "askr": {
    "analyze": {
      "exclude": ["fixtures/**", "**/*.generated.ts"],
      "rules": {
        "askr/prefer-for": "error",
        "askr/stable-dependencies": "info",
        "askr/ssr-browser-global": "off"
      }
    }
  }
}

Rule values are error, warning, info, or off. Source discovery honors .gitignore files from the project root through each selected workspace, including nested rules and negation. askr.analyze.exclude adds analyzer-only patterns relative to each workspace. The analyzer also always ignores dependency, VCS, coverage, generated, and common build-output directories by default.

CI

Use check mode so CI cannot change the checkout:

askr analyze --check

JSON output is one deterministic object containing schema version 1, the project root, discovered and selected workspaces, per-workspace program details, applied and skipped fixes, sorted diagnostics, and summary counts.