WcStack IntelliSenseA VS Code extension for @wcstack/state v2. Provides TypeScript language features for The same validator core is shipped headlessly as the FeaturesInline Script Type SupportTypeScript completions work inside
The wrapper's preamble knows the runtime API surface: Attribute Binding CompletionsCompletions for property names, state paths, modifiers, and filter names in
Path candidates are derived from the for-context CompletionsInside
Pattern paths ( Template Syntax SupportCompletions and diagnostics also work in Mustache
wcs-* Tag Completions (HTML Custom Data)The extension ships
Other editors that use the standard HTML language service (and VS Code setups
without this extension) get the same completions by copying the file into the
project and referencing it from the
Hover, Go to Definition, Find References, Inlay HintsBinding paths are runtime identifiers written directly in HTML, so navigation works without source maps. All four features are queries over the same positional reference index that powers diagnostics.
Hover text follows DiagnosticsReal-time validation of Severity policy: error = the runtime raises or the binding can never work; warning = it runs but silently does the wrong thing (the class of bug these checks exist for); info = advisory. Binding expressions (
|
| Code | Detects | Severity |
|---|---|---|
wcs/binding-path-missing |
Path not found in the state (textContent: typo). Skipped when no path candidates can be derived |
⚠ warning |
wcs/path-nonexistent |
Same, but the state is declared in a sidecar stateSchema, so absence is definite |
❌ error |
wcs/path-type-mismatch |
for: on a path the stateSchema proves is not an array |
❌ error |
wcs/binding-type-expectation |
Non-array for: (error); non-boolean if: / class., non-string attr. / style. (warning) |
❌ / ⚠ |
wcs/filter-unknown |
Unknown filter name (count\|fake) |
⚠ warning |
wcs/filter-arity |
Too few / too many filter arguments (count\|mul) |
❌ error |
wcs/filter-arg-type |
Filter argument type mismatch (count\|gt(abc)) |
⚠ warning |
wcs/filter-input-type |
Filter chain input type mismatch (count\|uc) |
⚠ warning |
wcs/token-undeclared |
$command.<name> / eventToken.<prop>: <name> not declared in $commandTokens / $eventTokens |
⚠ warning |
wcs/token-misconfigured |
command.<method>: right-hand side that is not a $command.<name> |
⚠ warning |
wcs/template-syntax |
Structural directive (for / if / elseif / else) combined with other bindings; spread with a filter or without a target (error). Pattern path items.*.name or shorthand .name outside <template for>; numerically resolved path items.0.name; a filter on an event handler (onclick: fn\|gt(10)) (warning). {{ }} outside a <template> (FOUC) and <!--@@:--> visualization (info) |
❌ / ⚠ / ℹ |
wcs/wildcard-rank |
More * ranks (or a higher $N) than the enclosing for nesting provides |
⚠ warning |
wcs/index-arity |
$getAll / $setAll / $resolve index count does not match the * count of the path |
⚠ warning |
wcs/aria-attr-unknown |
attr.aria-* name that does not exist in WAI-ARIA (with a "did you mean" suggestion) |
⚠ warning |
Built-in wcs-* tag contracts
| Code | Detects | Severity |
|---|---|---|
wcs/tag-member-unknown |
Binding to a property / command. / eventToken. key the tag does not declare in wcBindable — silently ignored at runtime |
⚠ warning |
wcs/spread-no-bindable |
...: spread onto a helper tag without wcBindable (wcs-fetch-header, wcs-fetch-body, wcs-infinite-scroll, wcs-voice) — the runtime raises |
❌ error |
wcs/trigger-seeded-truthy |
A trigger slot seeded with true (fires immediately, no edge) |
⚠ warning |
wcs/storage-seed-clobber |
Non-manual <wcs-storage> value bound to an empty seed — the initial write-back overwrites the stored value |
⚠ warning |
<wcs-state> script
| Code | Detects | Severity |
|---|---|---|
wcs/nested-assign |
this.user.name = x, +=, ++, expression-index chains — not reactive; use this["user.name"] = x |
❌ error |
wcs/array-mutation |
push / splice / sort … (9 destructive methods) — not reactive; the message names the non-destructive alternative (concat, toSpliced, toSorted …) |
❌ error |
wcs/array-index-assign |
this.items[0] = x (bracket-only chain, all compound forms) — use this["items.0"] = x or with() |
❌ error |
wcs/getter-cycle |
Path getters that reference each other in a cycle | ⚠ warning |
wcs/updated-callback-unbound |
$updatedCallback tests a path that no binding reads — the branch never runs (the callback is binding-driven) |
⚠ warning |
wcs/watch-declaration-invalid |
$watch key the runtime rejects: $-prefixed, empty segment, non-function handler literal |
❌ error |
wcs/watch-path-missing |
$watch key that does not exist in the state — the handler silently never fires |
⚠ warning |
wcs/type-annotation |
JSDoc @type incompatible with the initial value (see below) |
⚠ warning |
$listKeys declarations are consumed rather than diagnosed: malformed keys (empty path, trailing *, non-flat field) simply produce no path candidates, so a broken declaration is never confirmed by the static side.
Page setup
| Code | Detects | Severity |
|---|---|---|
wcs/script-order |
Another wcstack /auto script loaded after @wcstack/state/auto |
⚠ warning |
wcs/base-href-missing |
router/auto present without <base href> (basename would be mis-derived) |
⚠ warning |
wcs/signals-dual-entry |
@wcstack/signals and @wcstack/signals/dom on the same page (duplicated reactive core) |
❌ error |
v2 migration
| Code | Detects | Severity |
|---|---|---|
wcs/named-state-deprecated |
Named states — <wcs-state name="x">, path@x, {{ path@x }} — removed in v2; the message points to <wcs-state mount="x"> and the prefixed path x.path |
❌ error |
wcs/mount-path-invalid |
mount= value the runtime rejects: empty, empty segment, wildcard, or reserved characters $ # @ |
❌ error |
Sidecar manifest (wcstack.manifest.json)
All wcs/manifest-* and wcs/drift-* findings are errors, except wcs/manifest-namespace-version (warning) and wcs/manifest-override (info — an intentional, declared shadow). Codes: manifest-broken, manifest-schema-version, manifest-kind-invalid, manifest-unknown-keyword, manifest-external-ref, manifest-ref-cycle, manifest-ref-unresolved, manifest-namespace-version, manifest-tag-collision, manifest-filter-collision, manifest-state-collision, manifest-override, drift-missing-member, drift-event-mismatch.
Three further codes (wcs/path-readonly, wcs/path-reserved-name, wcs/path-dynamic-unknown) are reserved in the code table but not emitted yet. The single source of truth for codes is src/core/diagnostics.ts.
JSDoc Type Validation
Validates consistency between @type annotations and initial values (wcs/type-annotation):
/** @type {string} */
label: null, // ⚠ Type "null" is not compatible with @type {string}
/** @type {string|null} */
label: null, // ✅ OK
Sidecar Manifest, stateSchema, and the CLI
Sidecar manifests. wcstack.manifest.json files are validated against the
supported JSON-Schema subset: envelope / kind checks, cross-file package
resolution, same-name tag/filter collision, forbidden override-after-collision, and
drift against the live static wcBindable surface. The sidecar is tooling-only:
it never overrides the runtime static wcBindable declaration, and a missing or stale
file never changes runtime behavior. The normative schema and resolution rules live in
docs/wcstack-manifest-schema.md.
stateSchema. An application manifest may declare the shape of a state
(wcstack.application.states[<name>].stateSchema). The nearest wcstack.manifest.json
above the HTML file is discovered automatically (one file, no merging). For a declared
state, a missing path is reported as wcs/path-nonexistent (error) instead of
wcs/binding-path-missing (warning), and for: on a schema-confirmed non-array is
wcs/path-type-mismatch. Methods, getters, and $listKeys from the script count as
existing even when absent from the schema; beneath a bare {} the validator stays silent.
Write the schema by hand, or generate it from a TypeScript state file with wcs-schema
from @wcstack/typescript — see
docs/typescript.md. The same package's wcs-tsc type-checks
inline <wcs-state> scripts across a project using this extension's language plugin in
tsc mode.
CLI. A single validateDocument entry point drives both the in-editor diagnostics
and the CLI, so the IDE and CI report identically for the same inputs. One deliberate
asymmetry: external state referenced via <wcs-state src="..."> is resolved only by
the CLI (relative to the HTML file) — the IDE analyzes the single HTML file and skips
src. The bundled wcs-validate CLI runs the same checks headlessly — over
wcstack.manifest.json sidecars (any *.manifest.json argument) and/or HTML
data-wcs bindings — and is distributed on npm as
@wcstack/lint, a zero-dependency
wrapper around the exact same CLI bundle:
npx @wcstack/lint [--attr=data-wcs] [--state-tag=wcs-state] [--lang=ja|en] [--errors-only] [--strict] <file> [<file> ...]
| Option | Effect |
|---|---|
--attr=<name> |
Bind attribute name (default data-wcs) |
--state-tag=<name> |
State element tag name (default wcs-state) |
--lang=ja\|en |
Message language. Default: environment locale (LC_ALL / LC_MESSAGES / LANG, then the OS locale); codes and ranges do not depend on it |
--errors-only (alias --quiet) |
Print only error-severity lines; warning / info counts and the exit code are unchanged |
--strict |
Exit 1 on warnings too. Severities are unchanged — only the exit-code threshold moves. Use it to fail CI on a path typo (wcs/binding-path-missing is a warning) |
Exit code: 0 no error (with --strict: no error or warning) · 1 at least one error (with --strict: error or warning) · 2 usage or file-read failure.
When working on the validator itself, or in this repo's CI (the wcs-validate job runs it exactly this way), build from source and invoke the CLI with node:
# one-time build (from the repo root)
cd packages/vscode-wcs && npm ci && npm run build && cd ../..
node packages/vscode-wcs/dist/cli.cjs [--attr=data-wcs] [--state-tag=wcs-state] [--lang=ja|en] [--errors-only] [--strict] <file> [<file> ...]
Settings
| Setting | Default | Description |
|---|---|---|
wcstack.bindAttributeName |
"data-wcs" |
Bind attribute name |
wcstack.stateTagName |
"wcs-state" |
Custom element tag name for state definition |
wcstack.messageLanguage |
"auto" |
Language of diagnostic and hover messages: auto (VS Code display language; non-Japanese locales get English), ja, or en. Codes and ranges are language-independent |
Requirements
- VS Code 1.110+
- HTML files containing
<wcs-state>elements (tag completions and hover forwcs-*elements work in any HTML file)
Reporting Issues
Use the extension issue form — it asks for the extension and VS Code versions and a minimal <wcs-state> reproduction, and files the report under the @wcstack/vscode-wcs label.
License
MIT