vigiles 30.0.2 → 31.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-conformance.js +9 -15
- package/dist/adapters/claude-code/run-scripts.d.ts +5 -3
- package/dist/adapters/claude-code/run-scripts.js +5 -3
- package/dist/claude-code.js +5 -0
- package/dist/cli-main.d.ts +8 -0
- package/dist/cli-main.js +315 -212
- package/dist/codex.js +5 -0
- package/dist/core/compile.d.ts +5 -5
- package/dist/core/compile.js +28 -25
- package/dist/core/config-schema.d.ts +32 -32
- package/dist/core/coverage.d.ts +4 -3
- package/dist/core/coverage.js +4 -3
- package/dist/core/doc-refs.d.ts +3 -1
- package/dist/core/doc-refs.js +2 -1
- package/dist/core/frame.d.ts +90 -0
- package/dist/core/frame.js +58 -0
- package/dist/core/glob-ignore.d.ts +28 -0
- package/dist/core/glob-ignore.js +42 -0
- package/dist/core/orphans.d.ts +5 -3
- package/dist/core/orphans.js +5 -4
- package/dist/core/refs.d.ts +2 -2
- package/dist/core/refs.js +9 -12
- package/dist/core/spec.js +5 -0
- package/dist/core/symbols.d.ts +25 -19
- package/dist/core/symbols.js +51 -74
- package/dist/core/tree-sitter-wasm.d.ts +24 -0
- package/dist/core/tree-sitter-wasm.js +128 -0
- package/dist/eval-surface.js +5 -0
- package/dist/exclude.d.ts +3 -5
- package/dist/exclude.js +3 -3
- package/dist/hook-install.js +23 -10
- package/dist/hook.js +5 -0
- package/dist/jest.js +5 -0
- package/dist/linting.d.ts +11 -30
- package/dist/linting.js +22 -35
- package/dist/scan.d.ts +3 -3
- package/dist/scan.js +8 -7
- package/dist/test-coverage.d.ts +20 -3
- package/dist/test-coverage.js +40 -14
- package/dist/test.js +5 -0
- package/dist/vitest.mjs +5 -0
- package/package.json +4 -7
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.frameFor = frameFor;
|
|
4
|
+
exports.frameAt = frameAt;
|
|
5
|
+
const posix_path_js_1 = require("../posix-path.js");
|
|
6
|
+
/**
|
|
7
|
+
* Node on Windows hands out `C:\\repo`, and `../posix-path.js` recognises only a leading `/`
|
|
8
|
+
* as absolute. The frame works in `/`-separated form, so every path entering it passes here:
|
|
9
|
+
* backslashes become `/`, and a drive-letter path (`C:/…`) counts as absolute.
|
|
10
|
+
*/
|
|
11
|
+
function slashed(p) {
|
|
12
|
+
return p.replaceAll("\\", "/");
|
|
13
|
+
}
|
|
14
|
+
function isAbs(p) {
|
|
15
|
+
return (0, posix_path_js_1.isAbsolute)(p) || /^[A-Za-z]:\//.test(p);
|
|
16
|
+
}
|
|
17
|
+
/** `rel` is `abs`'s position below `root` when it does not climb out. */
|
|
18
|
+
function isBelow(rel) {
|
|
19
|
+
return rel === "" || (rel !== ".." && !rel.startsWith("../"));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The frame for a command run from `cwd` against `target` (absolute).
|
|
23
|
+
*
|
|
24
|
+
* The root is `cwd` — where `loadConfig()` read `.vigilesrc.json` (it does not
|
|
25
|
+
* walk up; measured 2026-09-23) — UNLESS the target lies outside it. A foreign
|
|
26
|
+
* target (`vigiles lint ../other`) is somebody else's repository, and its paths
|
|
27
|
+
* mean something from ITS root; relative to `cwd` they would all start with
|
|
28
|
+
* `../`, and a foreign lint must never let the caller's own files satisfy the
|
|
29
|
+
* target's `sharedDirs` references. That exception was `sharedDirsRootFor` in
|
|
30
|
+
* the CLI, applied to `sharedDirs` alone; it is owned here now, so `include`,
|
|
31
|
+
* `exclude`, `sharedDirs` and every printed path agree on one root.
|
|
32
|
+
*/
|
|
33
|
+
function frameFor(cwd, target) {
|
|
34
|
+
return frameAt(isBelow((0, posix_path_js_1.relative)(slashed(cwd), slashed(target))) ? cwd : target);
|
|
35
|
+
}
|
|
36
|
+
/** The frame rooted at an absolute directory. */
|
|
37
|
+
function frameAt(root) {
|
|
38
|
+
const base = (0, posix_path_js_1.normalize)(slashed(root));
|
|
39
|
+
const repo = (raw) => {
|
|
40
|
+
const abs = slashed(raw);
|
|
41
|
+
if (!isAbs(abs))
|
|
42
|
+
throw new Error(`frame.repo() takes an absolute path, got "${raw}" — a relative string has no frame to convert from`);
|
|
43
|
+
return ((0, posix_path_js_1.relative)(base, abs) || ".");
|
|
44
|
+
};
|
|
45
|
+
const bundle = (raw) => {
|
|
46
|
+
const dir = (0, posix_path_js_1.normalize)(slashed(raw));
|
|
47
|
+
const path = (p) => repo((0, posix_path_js_1.join)(dir, p));
|
|
48
|
+
return {
|
|
49
|
+
abs: dir,
|
|
50
|
+
at: repo(dir),
|
|
51
|
+
path,
|
|
52
|
+
// The ONE place a plain string is taken to be bundle-relative.
|
|
53
|
+
scanned: (p) => (isAbs(slashed(p)) ? repo(p) : path(p)),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
return { root: base, repo, bundle };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=frame.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One `ignore` for `globSync` out of a detector's own string floor plus the
|
|
3
|
+
* repository's exclude — glob takes EITHER a pattern list OR one `IgnoreLike`,
|
|
4
|
+
* never both, so a detector that has both needs this union.
|
|
5
|
+
*
|
|
6
|
+
* 🔴 WHY THE REPO EXCLUDE ARRIVES AS AN `IgnoreLike` AND NOT AS STRINGS (#281).
|
|
7
|
+
* `ExcludeSet` used to hand detectors a string list whose patterns were relative
|
|
8
|
+
* to the repository root, with the precondition "only correct for a glob rooted
|
|
9
|
+
* AT that root" written in a comment. Two callers globbed from a nested bundle
|
|
10
|
+
* and broke it silently: a root-relative `skills/lonely` dropped
|
|
11
|
+
* `plugins/p/skills/lonely`, and the real exclusion never reached the bundle.
|
|
12
|
+
* `ExcludeSet.globIgnore` computes each candidate's position from the repo root
|
|
13
|
+
* itself, so it is correct from ANY glob `cwd`; the string face was retired.
|
|
14
|
+
*
|
|
15
|
+
* A plain string list is still accepted — for a direct library caller whose
|
|
16
|
+
* patterns are, by that caller's own contract, relative to the `cwd` it globs
|
|
17
|
+
* from. That is the one frame a string can safely carry.
|
|
18
|
+
*/
|
|
19
|
+
import { type IgnoreLike } from "glob";
|
|
20
|
+
/** What a detector takes for "also skip these". */
|
|
21
|
+
export type GlobIgnore = readonly string[] | IgnoreLike;
|
|
22
|
+
/**
|
|
23
|
+
* `floor` (patterns relative to the glob's `cwd`) plus `extra`, in the form
|
|
24
|
+
* `globSync`'s `ignore` option accepts. Stays a plain list when both halves are
|
|
25
|
+
* lists, so a string-only caller sees exactly the glob behaviour it always had.
|
|
26
|
+
*/
|
|
27
|
+
export declare function withIgnored(floor: readonly string[], extra: GlobIgnore | undefined): string[] | IgnoreLike;
|
|
28
|
+
//# sourceMappingURL=glob-ignore.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withIgnored = withIgnored;
|
|
4
|
+
/**
|
|
5
|
+
* One `ignore` for `globSync` out of a detector's own string floor plus the
|
|
6
|
+
* repository's exclude — glob takes EITHER a pattern list OR one `IgnoreLike`,
|
|
7
|
+
* never both, so a detector that has both needs this union.
|
|
8
|
+
*
|
|
9
|
+
* 🔴 WHY THE REPO EXCLUDE ARRIVES AS AN `IgnoreLike` AND NOT AS STRINGS (#281).
|
|
10
|
+
* `ExcludeSet` used to hand detectors a string list whose patterns were relative
|
|
11
|
+
* to the repository root, with the precondition "only correct for a glob rooted
|
|
12
|
+
* AT that root" written in a comment. Two callers globbed from a nested bundle
|
|
13
|
+
* and broke it silently: a root-relative `skills/lonely` dropped
|
|
14
|
+
* `plugins/p/skills/lonely`, and the real exclusion never reached the bundle.
|
|
15
|
+
* `ExcludeSet.globIgnore` computes each candidate's position from the repo root
|
|
16
|
+
* itself, so it is correct from ANY glob `cwd`; the string face was retired.
|
|
17
|
+
*
|
|
18
|
+
* A plain string list is still accepted — for a direct library caller whose
|
|
19
|
+
* patterns are, by that caller's own contract, relative to the `cwd` it globs
|
|
20
|
+
* from. That is the one frame a string can safely carry.
|
|
21
|
+
*/
|
|
22
|
+
const glob_1 = require("glob");
|
|
23
|
+
function isPatternList(x) {
|
|
24
|
+
return Array.isArray(x);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `floor` (patterns relative to the glob's `cwd`) plus `extra`, in the form
|
|
28
|
+
* `globSync`'s `ignore` option accepts. Stays a plain list when both halves are
|
|
29
|
+
* lists, so a string-only caller sees exactly the glob behaviour it always had.
|
|
30
|
+
*/
|
|
31
|
+
function withIgnored(floor, extra) {
|
|
32
|
+
if (extra === undefined)
|
|
33
|
+
return [...floor];
|
|
34
|
+
if (isPatternList(extra))
|
|
35
|
+
return [...floor, ...extra];
|
|
36
|
+
const own = new glob_1.Ignore([...floor], {});
|
|
37
|
+
return {
|
|
38
|
+
ignored: (p) => own.ignored(p) || extra.ignored?.(p) === true,
|
|
39
|
+
childrenIgnored: (p) => own.childrenIgnored(p) || extra.childrenIgnored?.(p) === true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=glob-ignore.js.map
|
package/dist/core/orphans.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* references (markdown links and backtick paths). Works against source
|
|
12
12
|
* README plus compiled CLAUDE.md — no spec loading required.
|
|
13
13
|
*/
|
|
14
|
+
import { type GlobIgnore } from "./glob-ignore.js";
|
|
14
15
|
import type { PluginLayout } from "./layout.js";
|
|
15
16
|
export interface OrphanReport {
|
|
16
17
|
/** Include globs that were scanned. */
|
|
@@ -35,12 +36,13 @@ export interface FindOrphansOptions {
|
|
|
35
36
|
/** Glob patterns to exclude within the include scope (orphan CANDIDACY only). */
|
|
36
37
|
readonly exclude?: readonly string[];
|
|
37
38
|
/**
|
|
38
|
-
* The repo-wide `.vigilesrc.json#exclude` (
|
|
39
|
-
* src/exclude.ts
|
|
39
|
+
* The repo-wide `.vigilesrc.json#exclude` (`ExcludeSet.globIgnore`,
|
|
40
|
+
* src/exclude.ts — correct from any glob cwd; a plain list is relative to
|
|
41
|
+
* `basePath`). Applied to BOTH walks — candidates AND the reference scan —
|
|
40
42
|
* so an excluded corpus can neither be an orphan nor keep one alive (#192).
|
|
41
43
|
* The CLI always passes it; a direct library caller may omit it.
|
|
42
44
|
*/
|
|
43
|
-
readonly repoExclude?:
|
|
45
|
+
readonly repoExclude?: GlobIgnore;
|
|
44
46
|
/**
|
|
45
47
|
* Harnesses whose surface files (instruction file, `SKILL.md`, subagents,
|
|
46
48
|
* commands) are load-bearing by location and thus never orphan CANDIDATES
|
package/dist/core/orphans.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.formatOrphanReport = formatOrphanReport;
|
|
|
18
18
|
const node_fs_1 = require("node:fs");
|
|
19
19
|
const node_path_1 = require("node:path");
|
|
20
20
|
const glob_1 = require("glob");
|
|
21
|
+
const glob_ignore_js_1 = require("./glob-ignore.js");
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
// Internals
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
@@ -100,7 +101,7 @@ function isOrphanExempt(absPath) {
|
|
|
100
101
|
function collectDocs(basePath, include, ignore, layouts) {
|
|
101
102
|
const docs = new Set();
|
|
102
103
|
for (const pattern of include) {
|
|
103
|
-
for (const p of (0, glob_1.globSync)(pattern, { cwd: basePath, ignore
|
|
104
|
+
for (const p of (0, glob_1.globSync)(pattern, { cwd: basePath, ignore })) {
|
|
104
105
|
if (isHarnessLoadedFile(p, layouts))
|
|
105
106
|
continue; // harness files are never orphans
|
|
106
107
|
if (isOrphanExempt((0, node_path_1.resolve)(basePath, p)))
|
|
@@ -160,13 +161,13 @@ function findOrphanDocs(options = {}) {
|
|
|
160
161
|
// CANDIDATE nor a SOURCE of references (a link from inside it must not keep a
|
|
161
162
|
// doc alive). The rule's own `exclude` only narrows candidacy: a doc kept out
|
|
162
163
|
// of the orphan list can still reference others. Union, never override.
|
|
163
|
-
const repoExclude = options.repoExclude
|
|
164
|
-
const ignore = [...DEFAULT_IGNORE, ...
|
|
164
|
+
const repoExclude = options.repoExclude;
|
|
165
|
+
const ignore = (0, glob_ignore_js_1.withIgnored)([...DEFAULT_IGNORE, ...userExclude], repoExclude);
|
|
165
166
|
const layouts = options.layouts ?? [];
|
|
166
167
|
const allDocs = collectDocs(basePath, include, ignore, layouts);
|
|
167
168
|
const allMarkdown = (0, glob_1.globSync)("**/*.md", {
|
|
168
169
|
cwd: basePath,
|
|
169
|
-
ignore:
|
|
170
|
+
ignore: (0, glob_ignore_js_1.withIgnored)(DEFAULT_IGNORE, repoExclude),
|
|
170
171
|
});
|
|
171
172
|
const referencedBy = new Map();
|
|
172
173
|
for (const mdPath of allMarkdown) {
|
package/dist/core/refs.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare function symbolRefs(markdown: string): SymbolRef[];
|
|
|
26
26
|
* file must exist and define the named symbol. `basePath` is the directory the
|
|
27
27
|
* paths resolve against (the instruction file's own directory).
|
|
28
28
|
*/
|
|
29
|
-
export declare function verifySymbolRefs(markdown: string, basePath: string): SymbolRefError[]
|
|
29
|
+
export declare function verifySymbolRefs(markdown: string, basePath: string): Promise<SymbolRefError[]>;
|
|
30
30
|
/**
|
|
31
31
|
* Whether a span is a **linter-rule reference** that ought to be marked
|
|
32
32
|
* (`enforce()` / inline `<!-- vigiles:enforce -->`) so the lint can verify the
|
|
@@ -50,7 +50,7 @@ export declare function unmarkedCodeRefs(markdown: string): Span[];
|
|
|
50
50
|
* code-shaped span that ought to be a mark. The shared detector behind both the
|
|
51
51
|
* `vigiles refs` CLI and the PostToolUse refs-hook.
|
|
52
52
|
*/
|
|
53
|
-
export declare function collectRefIssues(markdown: string, basePath: string): string[]
|
|
53
|
+
export declare function collectRefIssues(markdown: string, basePath: string): Promise<string[]>;
|
|
54
54
|
/** What the refs-hook should do given the issue count and configured severity. */
|
|
55
55
|
export type RefsHookAction = "ok" | "nudge" | "block";
|
|
56
56
|
/**
|
package/dist/core/refs.js
CHANGED
|
@@ -70,11 +70,11 @@ function symbolRefs(markdown) {
|
|
|
70
70
|
* file must exist and define the named symbol. `basePath` is the directory the
|
|
71
71
|
* paths resolve against (the instruction file's own directory).
|
|
72
72
|
*/
|
|
73
|
-
function verifySymbolRefs(markdown, basePath) {
|
|
73
|
+
async function verifySymbolRefs(markdown, basePath) {
|
|
74
74
|
const errors = [];
|
|
75
75
|
for (const ref of symbolRefs(markdown)) {
|
|
76
76
|
const full = (0, node_path_1.resolve)(basePath, ref.file);
|
|
77
|
-
const support = (0, symbols_js_1.langForFile)(ref.file);
|
|
77
|
+
const support = await (0, symbols_js_1.langForFile)(ref.file);
|
|
78
78
|
if (!(0, node_fs_1.existsSync)(full)) {
|
|
79
79
|
errors.push({ ...ref, reason: `File not found: "${ref.file}"` });
|
|
80
80
|
}
|
|
@@ -84,15 +84,12 @@ function verifySymbolRefs(markdown, basePath) {
|
|
|
84
84
|
reason: `Unsupported language for symbol check: "${ref.file}"`,
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
-
else if (support.kind === "grammar-
|
|
88
|
-
// NOT "unsupported": the language is one this tool parses,
|
|
89
|
-
//
|
|
90
|
-
errors.push({
|
|
91
|
-
...ref,
|
|
92
|
-
reason: `Symbol not checked: the ${support.id} grammar is not installed (npm i -D ${support.pkg})`,
|
|
93
|
-
});
|
|
87
|
+
else if (support.kind === "grammar-load-failed") {
|
|
88
|
+
// NOT "unsupported" and NOT "not defined": the language is one this tool parses, its
|
|
89
|
+
// grammar failed to load here. Either other wording would report an un-run check as a verdict.
|
|
90
|
+
errors.push({ ...ref, reason: (0, symbols_js_1.notCheckedReason)(support) });
|
|
94
91
|
}
|
|
95
|
-
else if (!(0, symbols_js_1.fileDefinesSymbol)(full, ref.symbol)) {
|
|
92
|
+
else if (!(await (0, symbols_js_1.fileDefinesSymbol)(full, ref.symbol))) {
|
|
96
93
|
errors.push({
|
|
97
94
|
...ref,
|
|
98
95
|
reason: `"${ref.symbol}" is not defined in ${ref.file}`,
|
|
@@ -149,9 +146,9 @@ function unmarkedCodeRefs(markdown) {
|
|
|
149
146
|
* code-shaped span that ought to be a mark. The shared detector behind both the
|
|
150
147
|
* `vigiles refs` CLI and the PostToolUse refs-hook.
|
|
151
148
|
*/
|
|
152
|
-
function collectRefIssues(markdown, basePath) {
|
|
149
|
+
async function collectRefIssues(markdown, basePath) {
|
|
153
150
|
const out = [];
|
|
154
|
-
for (const b of verifySymbolRefs(markdown, basePath)) {
|
|
151
|
+
for (const b of await verifySymbolRefs(markdown, basePath)) {
|
|
155
152
|
out.push(`line ${String(b.line)}: ${b.reason}`);
|
|
156
153
|
}
|
|
157
154
|
for (const u of unmarkedCodeRefs(markdown)) {
|
package/dist/core/spec.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// 🔴 PUBLIC ENTRY POINT `vigiles/spec` — every export here is a promise to users. The default for a
|
|
3
|
+
// symbol is INTERNAL. It is exported only if (a) a NAMED external consumer uses it, or (b) it is
|
|
4
|
+
// a deliberate extension point listed in STABILITY.md (the adapter kit is the example). "Might
|
|
5
|
+
// be useful" is neither. Review point: the diff of `api-surface/vigiles-spec.api.md`, which
|
|
6
|
+
// `npm run api:check` fails on.
|
|
2
7
|
/**
|
|
3
8
|
* vigiles v2 — Executable specification system.
|
|
4
9
|
*
|
package/dist/core/symbols.d.ts
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
import { Lang } from "@ast-grep/napi";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type LangKey = Lang | string;
|
|
2
|
+
import { type WasmLang } from "./tree-sitter-wasm.js";
|
|
3
|
+
/** A language key: an `@ast-grep/napi` built-in, or one of the WASM grammars. */
|
|
4
|
+
type LangKey = Lang | WasmLang;
|
|
6
5
|
/**
|
|
7
6
|
* Whether this file's language can be parsed HERE, and if not, which of the two reasons.
|
|
8
7
|
*
|
|
9
|
-
* 🔴 THE THREE CASES ARE SEPARATE MEMBERS BECAUSE THEY ARE SEPARATE FACTS.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
8
|
+
* 🔴 THE THREE CASES ARE SEPARATE MEMBERS BECAUSE THEY ARE SEPARATE FACTS. "Extension not in
|
|
9
|
+
* the table" and "the language IS ours but its grammar failed to load in this process" must not
|
|
10
|
+
* share one `null`: both callers would print the first sentence, and a check that did not run
|
|
11
|
+
* would read as a verdict. With WASM grammars shipped as a regular dependency there is no
|
|
12
|
+
* install or platform gap left, so `grammar-load-failed` should not happen — but if the runtime
|
|
13
|
+
* genuinely cannot load, the report carries the loader's own error text, never "not defined"
|
|
14
|
+
* and never an install instruction the user cannot act on.
|
|
16
15
|
*/
|
|
17
16
|
export type LangSupport = {
|
|
18
17
|
readonly kind: "ready";
|
|
19
18
|
readonly lang: LangKey;
|
|
20
19
|
} | {
|
|
21
|
-
readonly kind: "grammar-
|
|
22
|
-
readonly
|
|
23
|
-
readonly
|
|
20
|
+
readonly kind: "grammar-load-failed";
|
|
21
|
+
readonly lang: WasmLang;
|
|
22
|
+
readonly error: string;
|
|
24
23
|
} | {
|
|
25
24
|
readonly kind: "unsupported";
|
|
26
25
|
};
|
|
27
|
-
export declare function langForFile(file: string): LangSupport
|
|
26
|
+
export declare function langForFile(file: string): Promise<LangSupport>;
|
|
27
|
+
/** The one sentence both callers print for a symbol reference that could not be checked. */
|
|
28
|
+
export declare function notCheckedReason(support: Extract<LangSupport, {
|
|
29
|
+
kind: "grammar-load-failed";
|
|
30
|
+
}>): string;
|
|
28
31
|
/** A symbol definition found in a file. */
|
|
29
32
|
export interface SymbolDef {
|
|
30
33
|
/** The defined identifier, e.g. "parseConfig". */
|
|
@@ -36,10 +39,13 @@ export interface SymbolDef {
|
|
|
36
39
|
/** 1-based line of the definition. */
|
|
37
40
|
readonly line: number;
|
|
38
41
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Extract the symbols defined in a single file's source. Async because the Python / Ruby / Rust
|
|
44
|
+
* grammars are WebAssembly, whose instantiation is async; the napi languages resolve at once.
|
|
45
|
+
*/
|
|
46
|
+
export declare function definedSymbols(code: string, lang: LangKey): Promise<SymbolDef[]>;
|
|
41
47
|
/** Defined symbols for a file on disk, or [] if unreadable/unsupported. */
|
|
42
|
-
export declare function definedSymbolsInFile(file: string): SymbolDef[]
|
|
48
|
+
export declare function definedSymbolsInFile(file: string): Promise<SymbolDef[]>;
|
|
43
49
|
/**
|
|
44
50
|
* Whether `file` defines a top-level (or scoped) symbol named `name`. This is
|
|
45
51
|
* the whole check for a file-qualified reference (`path#symbol`): we parse the
|
|
@@ -47,6 +53,6 @@ export declare function definedSymbolsInFile(file: string): SymbolDef[];
|
|
|
47
53
|
* fallback we also consult a co-located declaration file (`.rbi` / `.d.ts`), so
|
|
48
54
|
* typed dynamic symbols resolve without running Sorbet / the TS compiler.
|
|
49
55
|
*/
|
|
50
|
-
export declare function fileDefinesSymbol(file: string, name: string): boolean
|
|
56
|
+
export declare function fileDefinesSymbol(file: string, name: string): Promise<boolean>;
|
|
51
57
|
export {};
|
|
52
58
|
//# sourceMappingURL=symbols.d.ts.map
|
package/dist/core/symbols.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.installedGrammars = installedGrammars;
|
|
4
3
|
exports.langForFile = langForFile;
|
|
4
|
+
exports.notCheckedReason = notCheckedReason;
|
|
5
5
|
exports.definedSymbols = definedSymbols;
|
|
6
6
|
exports.definedSymbolsInFile = definedSymbolsInFile;
|
|
7
7
|
exports.fileDefinesSymbol = fileDefinesSymbol;
|
|
@@ -21,60 +21,32 @@ exports.fileDefinesSymbol = fileDefinesSymbol;
|
|
|
21
21
|
* delegated. Ambiguity (a name defined in several files) is reported, not guessed.
|
|
22
22
|
*/
|
|
23
23
|
const node_fs_1 = require("node:fs");
|
|
24
|
-
const
|
|
24
|
+
const promises_1 = require("node:fs/promises");
|
|
25
25
|
const node_path_1 = require("node:path");
|
|
26
26
|
const napi_1 = require("@ast-grep/napi");
|
|
27
|
+
const tree_sitter_wasm_js_1 = require("./tree-sitter-wasm.js");
|
|
27
28
|
/**
|
|
28
|
-
*
|
|
29
|
+
* Python, Ruby and Rust are parsed by WebAssembly builds of their tree-sitter grammars
|
|
30
|
+
* (`./tree-sitter-wasm.ts`); TypeScript, TSX, JavaScript and CSS by the grammars built into
|
|
31
|
+
* `@ast-grep/napi`.
|
|
29
32
|
*
|
|
30
|
-
* 🔴 WHY
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* grammars
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* CommonJS (`"main": "index.js"`, no `exports`), so a synchronous require works and NOTHING in
|
|
40
|
-
* this module's public surface has to become async. Measured, not assumed.
|
|
33
|
+
* 🔴 WHY NOT `@ast-grep/lang-*` ANY MORE (#257). Those native grammars were the only packages in
|
|
34
|
+
* the tree carrying a `postinstall`, and since pnpm 10 an unapproved dependency build script
|
|
35
|
+
* FAILS a consumer's install (`ERR_PNPM_IGNORED_BUILDS`) — for every `pnpm add vigiles`, whether
|
|
36
|
+
* or not the project has a single `.py` file. Making them optional peers stopped the failure but
|
|
37
|
+
* moved the cost onto the user: a `.py` reference then reported "grammar not installed" after an
|
|
38
|
+
* upgrade. The WASM grammars are a plain dependency with no install script and no native
|
|
39
|
+
* binary, so they are simply there. `src/package-install-scripts.e2e.test.ts` holds both halves
|
|
40
|
+
* against the packed tarball: no install script in the tree, and a `.py` lookup that works
|
|
41
|
+
* right after a default install.
|
|
41
42
|
*/
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// mistake that made every hook fail there — and these grammars are OUR optional dependencies,
|
|
50
|
-
// so they resolve from where this file lives. `__filename` rather than `import.meta.url`
|
|
51
|
-
// because this package compiles to CommonJS (`module: Node16`, `main: ./dist/test.js`).
|
|
52
|
-
const require_ = (0, node_module_1.createRequire)(__filename);
|
|
53
|
-
/** Registered grammar ids, populated on first use. `null` until then. */
|
|
54
|
-
let loaded = null;
|
|
55
|
-
function ensureRegistered() {
|
|
56
|
-
if (loaded)
|
|
57
|
-
return loaded;
|
|
58
|
-
const dynamic = {};
|
|
59
|
-
const present = new Set();
|
|
60
|
-
for (const [id, pkg] of Object.entries(OPTIONAL_GRAMMARS)) {
|
|
61
|
-
try {
|
|
62
|
-
dynamic[id] = require_(pkg);
|
|
63
|
-
present.add(id);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// Absent by design: an optional dependency the consumer did not install. The caller is
|
|
67
|
-
// told WHICH id is missing (see `langForFile`), so "not checked" never reads as "clean".
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (present.size > 0)
|
|
71
|
-
(0, napi_1.registerDynamicLanguage)(dynamic);
|
|
72
|
-
loaded = present;
|
|
73
|
-
return loaded;
|
|
74
|
-
}
|
|
75
|
-
/** Which optional grammars this process actually has. Exported so a report can say so. */
|
|
76
|
-
function installedGrammars() {
|
|
77
|
-
return ensureRegistered();
|
|
43
|
+
const WASM_LANGS = new Set([
|
|
44
|
+
"python",
|
|
45
|
+
"ruby",
|
|
46
|
+
"rust",
|
|
47
|
+
]);
|
|
48
|
+
function isWasmLang(key) {
|
|
49
|
+
return typeof key === "string" && WASM_LANGS.has(key);
|
|
78
50
|
}
|
|
79
51
|
const EXT_LANG = {
|
|
80
52
|
".ts": napi_1.Lang.TypeScript,
|
|
@@ -93,28 +65,26 @@ const EXT_LANG = {
|
|
|
93
65
|
".rb": "ruby",
|
|
94
66
|
".rbi": "ruby",
|
|
95
67
|
};
|
|
96
|
-
function langForFile(file) {
|
|
68
|
+
async function langForFile(file) {
|
|
97
69
|
const key = file.endsWith(".d.ts")
|
|
98
70
|
? napi_1.Lang.TypeScript
|
|
99
71
|
: EXT_LANG[(0, node_path_1.extname)(file).toLowerCase()];
|
|
100
72
|
if (key === undefined)
|
|
101
73
|
return { kind: "unsupported" };
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
74
|
+
if (isWasmLang(key)) {
|
|
75
|
+
// Lazy: the first .py/.rb/.rs reference starts the runtime; later ones hit the cache.
|
|
76
|
+
const error = await (0, tree_sitter_wasm_js_1.loadWasmGrammar)(key);
|
|
77
|
+
if (error !== null)
|
|
78
|
+
return { kind: "grammar-load-failed", lang: key, error };
|
|
106
79
|
}
|
|
107
80
|
return { kind: "ready", lang: key };
|
|
108
81
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"interface_declaration",
|
|
116
|
-
"enum_declaration",
|
|
117
|
-
]);
|
|
82
|
+
/** The one sentence both callers print for a symbol reference that could not be checked. */
|
|
83
|
+
function notCheckedReason(support) {
|
|
84
|
+
return `Symbol not checked: the ${support.lang} grammar failed to load: ${support.error}`;
|
|
85
|
+
}
|
|
86
|
+
const ID_KINDS = new Set(tree_sitter_wasm_js_1.ID_KINDS);
|
|
87
|
+
const SCOPE_KINDS = new Set(tree_sitter_wasm_js_1.SCOPE_KINDS);
|
|
118
88
|
function recordNode(node, scope, out) {
|
|
119
89
|
const line = node.range().start.line + 1;
|
|
120
90
|
const nameNode = node.field("name");
|
|
@@ -128,9 +98,13 @@ function recordNode(node, scope, out) {
|
|
|
128
98
|
out.push({ name: left.text(), kind: node.kind(), scope, line });
|
|
129
99
|
}
|
|
130
100
|
}
|
|
131
|
-
/**
|
|
132
|
-
|
|
133
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Extract the symbols defined in a single file's source. Async because the Python / Ruby / Rust
|
|
103
|
+
* grammars are WebAssembly, whose instantiation is async; the napi languages resolve at once.
|
|
104
|
+
*/
|
|
105
|
+
async function definedSymbols(code, lang) {
|
|
106
|
+
if (isWasmLang(lang))
|
|
107
|
+
return (0, tree_sitter_wasm_js_1.wasmDefinedSymbols)(code, lang);
|
|
134
108
|
const out = [];
|
|
135
109
|
const walk = (node, scope) => {
|
|
136
110
|
recordNode(node, scope, out);
|
|
@@ -143,17 +117,20 @@ function definedSymbols(code, lang) {
|
|
|
143
117
|
return out;
|
|
144
118
|
}
|
|
145
119
|
/** Defined symbols for a file on disk, or [] if unreadable/unsupported. */
|
|
146
|
-
function definedSymbolsInFile(file) {
|
|
147
|
-
const support = langForFile(file);
|
|
120
|
+
async function definedSymbolsInFile(file) {
|
|
121
|
+
const support = await langForFile(file);
|
|
148
122
|
if (support.kind !== "ready")
|
|
149
123
|
return [];
|
|
150
|
-
|
|
124
|
+
let code;
|
|
151
125
|
try {
|
|
152
|
-
|
|
126
|
+
code = await (0, promises_1.readFile)(file, "utf-8");
|
|
153
127
|
}
|
|
154
128
|
catch {
|
|
155
129
|
return [];
|
|
156
130
|
}
|
|
131
|
+
// Deliberately OUTSIDE the try: a parser failure is not an empty file. Swallowing it would
|
|
132
|
+
// turn "could not parse" into "is not defined" — a check that did not run, read as a verdict.
|
|
133
|
+
return definedSymbols(code, support.lang);
|
|
157
134
|
}
|
|
158
135
|
// A co-located declaration file that may declare symbols the source defines
|
|
159
136
|
// dynamically (Sorbet `.rbi`, TypeScript `.d.ts`) — checked as a fallback so a
|
|
@@ -173,15 +150,15 @@ const DECL_SIBLING = {
|
|
|
173
150
|
* fallback we also consult a co-located declaration file (`.rbi` / `.d.ts`), so
|
|
174
151
|
* typed dynamic symbols resolve without running Sorbet / the TS compiler.
|
|
175
152
|
*/
|
|
176
|
-
function fileDefinesSymbol(file, name) {
|
|
177
|
-
if (definedSymbolsInFile(file).some((d) => d.name === name))
|
|
153
|
+
async function fileDefinesSymbol(file, name) {
|
|
154
|
+
if ((await definedSymbolsInFile(file)).some((d) => d.name === name))
|
|
178
155
|
return true;
|
|
179
156
|
const ext = (0, node_path_1.extname)(file);
|
|
180
157
|
const decl = DECL_SIBLING[ext];
|
|
181
158
|
if (decl && !file.endsWith(decl)) {
|
|
182
159
|
const sibling = file.slice(0, -ext.length) + decl;
|
|
183
160
|
if ((0, node_fs_1.existsSync)(sibling) &&
|
|
184
|
-
definedSymbolsInFile(sibling).some((d) => d.name === name)) {
|
|
161
|
+
(await definedSymbolsInFile(sibling)).some((d) => d.name === name)) {
|
|
185
162
|
return true;
|
|
186
163
|
}
|
|
187
164
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The grammars this module parses, keyed by the id used in `tree-sitter-<id>.wasm`. */
|
|
2
|
+
export type WasmLang = "python" | "ruby" | "rust";
|
|
3
|
+
/** A definition found by the walk — the same shape `symbols.ts` exports as `SymbolDef`. */
|
|
4
|
+
export interface WasmSymbolDef {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly kind: string;
|
|
7
|
+
readonly scope: string;
|
|
8
|
+
readonly line: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Node kinds that open a scope (their `name` becomes the `scope` of everything inside), and the
|
|
12
|
+
* kinds a `left` field must have to count as an assignment-style definition. ONE list for both
|
|
13
|
+
* walkers — the napi one in `symbols.ts` and the one below — so they cannot drift apart.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SCOPE_KINDS: readonly string[];
|
|
16
|
+
export declare const ID_KINDS: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* Load `lang`'s grammar once per process. Resolves to `null` on success, or the load error's own
|
|
19
|
+
* text — the caller reports it verbatim as "not checked", never as a missing symbol.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadWasmGrammar(lang: WasmLang): Promise<string | null>;
|
|
22
|
+
/** Parse `code` and return its definitions. Rejects on a parse failure — never resolves []. */
|
|
23
|
+
export declare function wasmDefinedSymbols(code: string, lang: WasmLang): Promise<WasmSymbolDef[]>;
|
|
24
|
+
//# sourceMappingURL=tree-sitter-wasm.d.ts.map
|