This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
npm run build # compile CJS + ESM into lib/ (via tsup)
npm run watch # build in watch mode during development
npm run typecheck # tsc --noEmit
npm run lint # eslint --fix
npm run check # lint + typecheck
npm test # node:test via tsx
npm run testWatch # node:test watch mode via tsxRun a single test file: node --import tsx --test tests/query.test.ts
Consumers import from lib/, not src/ — always run npm run build before testing integration or publishing.
Purpose: query(code, query) and multiQuery(code, namedQueries) parse JavaScript source and run a compact XPath-inspired query language over the resulting AST.
Execution flow:
parseSource(insrc/index.ts) parses JS viameriyah— triesmodule: true, falls back tomodule: false, webcompat: true.createTraverser()walks the AST once, buildingNodePathwrappers (viacreateNodePath, backed by a WeakMap) and registering scopes/bindings.- The query string is tokenized and parsed by
src/parseQuery.tsinto a tree ofQNodes. createQuerier()evaluates QNodes against NodePaths, memoizing subquery results by(QNode, NodePath)pair.
Key source files:
- src/index.ts — traversal, binding registration (
registerBindings,getBinding), querier, built-infunctionsmap (join,concat,first,nthchild), public API - src/parseQuery.ts — tokenizer and parser for the query DSL; defines
AvailableFunctiontype - src/nodeutils.ts —
VISITOR_KEYS, type predicates (isIdentifier,isScopable, etc.),NodePathhelpers - src/utils.ts — small generic utilities
Build output: tsup produces both CJS (lib/index.js) and ESM (lib/index.mjs) plus .d.ts/.d.mts files. The exports map in package.json routes require/import to the right build.
Adding a query language function (e.g. /fn:uppercase(sel)):
- Add implementation to the
functionsmap insrc/index.ts. - The
AvailableFunctiontype inparseQuery.tsis derived fromtypeof functions— no manual update needed unless you change the type structure. - Add tests in
tests/.
Bindings and scopes: Scope IDs are stored on AST nodes as a flat scopeId property (a nested wrapper object would cost an allocation per node). The nodePathMap WeakMap ties AST nodes to their NodePath; during traversal a NodePath is only materialized when a selector actually matches. Do not mutate node identity or add per-node state — this silently breaks memoization and binding lookups.
Performance (large/minified files):
- Use
multiQueryinstead of repeatedquerycalls — the traverser is designed for a single pass over multiple queries. - Prefer child selectors (
/Type) over descendant (//Type) when the depth is known. - Avoid
../(parent) filters in hot paths — they cause extra traversal and defeat memoization.