lockwire 0.1.33 → 0.1.35
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/README.md +27 -0
- package/dist/blake3.d.ts +9 -0
- package/dist/blake3.d.ts.map +1 -0
- package/dist/blake3.js +128 -0
- package/dist/blake3.js.map +1 -0
- package/dist/extract-v2.d.ts +14 -4
- package/dist/extract-v2.d.ts.map +1 -1
- package/dist/extract-v2.js +224 -40
- package/dist/extract-v2.js.map +1 -1
- package/dist/extract.d.ts +2 -2
- package/dist/extract.js +4 -4
- package/dist/extract.js.map +1 -1
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +2 -4
- package/dist/hash.js.map +1 -1
- package/dist/mcp-protocol.d.ts +5 -5
- package/dist/mcp-protocol.d.ts.map +1 -1
- package/dist/mcp-protocol.js +14 -20
- package/dist/mcp-protocol.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +18 -18
- package/dist/mcp-server.js.map +1 -1
- package/dist/schema.d.ts +44 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +68 -0
- package/dist/schema.js.map +1 -0
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -173,6 +173,33 @@ sequenceDiagram
|
|
|
173
173
|
|
|
174
174
|
## Install
|
|
175
175
|
|
|
176
|
+
**Have your AI do it.** Paste this into Claude Code, Codex, Cursor or any coding agent, in the repository you want to protect:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
Set up lockwire (https://github.com/Reality-Ventures/lockwire) in this repository. It binds
|
|
180
|
+
claims in our docs to the code they describe and flags them when the code changes. It's an npm
|
|
181
|
+
package with one runtime dependency and makes no network calls.
|
|
182
|
+
|
|
183
|
+
1. Install it for the agent you are:
|
|
184
|
+
- Claude Code: add this to .claude/settings.json (merge with what's there), then tell me to
|
|
185
|
+
run /reload-plugins:
|
|
186
|
+
{"extraKnownMarketplaces": {"lockwire": {"source": {"source": "github",
|
|
187
|
+
"repo": "Reality-Ventures/lockwire"}, "autoUpdate": true}},
|
|
188
|
+
"enabledPlugins": {"lockwire@lockwire": true}}
|
|
189
|
+
- Anything else: run `npx skills add Reality-Ventures/lockwire`, and register
|
|
190
|
+
`npx -y lockwire mcp` as a stdio MCP server if you support MCP.
|
|
191
|
+
2. Run `npx lockwire init` at the repository root.
|
|
192
|
+
3. In CLAUDE.md, AGENTS.md or README.md, find 3 to 5 sentences that state something specific about
|
|
193
|
+
our code (a function's parameters, a default, a limit, where something lives). Above each, add a
|
|
194
|
+
marker naming the code and what the sentence depends on, e.g.
|
|
195
|
+
<!-- lockwire src/auth/session.ts#createSession sig -->
|
|
196
|
+
(sig: signature and types; body: behaviour; deps: what it calls; path: where it lives).
|
|
197
|
+
4. Run `npx lockwire link <doc>` for each doc you changed, then `npx lockwire check`, and show me
|
|
198
|
+
the result.
|
|
199
|
+
|
|
200
|
+
Don't commit, push or run `lockwire autofix`; I'll review the changes first.
|
|
201
|
+
```
|
|
202
|
+
|
|
176
203
|
**Claude Code** — hook, MCP server, and skill in one plugin:
|
|
177
204
|
|
|
178
205
|
```bash
|
package/dist/blake3.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLAKE3, the unkeyed hash, written out so lockwire needs no hashing dependency (Node's crypto has no
|
|
3
|
+
* BLAKE3). Lockfiles written by earlier versions hold BLAKE3 fingerprints, so the hash has to stay
|
|
4
|
+
* the same one; the tests check this against the reference implementation (`@noble/hashes`, a
|
|
5
|
+
* development dependency) over every input length around the chunk and tree boundaries.
|
|
6
|
+
*/
|
|
7
|
+
/** The first `length` bytes (at most 32) of the BLAKE3 hash of `input`, as lowercase hex. */
|
|
8
|
+
export declare function blake3Hex(input: Uint8Array, length?: number): string;
|
|
9
|
+
//# sourceMappingURL=blake3.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blake3.d.ts","sourceRoot":"","sources":["../src/blake3.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA8HH,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,SAAK,GAAG,MAAM,CAkBhE"}
|
package/dist/blake3.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLAKE3, the unkeyed hash, written out so lockwire needs no hashing dependency (Node's crypto has no
|
|
3
|
+
* BLAKE3). Lockfiles written by earlier versions hold BLAKE3 fingerprints, so the hash has to stay
|
|
4
|
+
* the same one; the tests check this against the reference implementation (`@noble/hashes`, a
|
|
5
|
+
* development dependency) over every input length around the chunk and tree boundaries.
|
|
6
|
+
*/
|
|
7
|
+
const IV = [
|
|
8
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
9
|
+
];
|
|
10
|
+
const PERMUTATION = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8];
|
|
11
|
+
const CHUNK_START = 1;
|
|
12
|
+
const CHUNK_END = 2;
|
|
13
|
+
const PARENT = 4;
|
|
14
|
+
const ROOT = 8;
|
|
15
|
+
const BLOCK_LEN = 64;
|
|
16
|
+
const CHUNK_LEN = 1024;
|
|
17
|
+
const rotr = (x, n) => (x >>> n) | (x << (32 - n));
|
|
18
|
+
// Scratch space reused by every compression, so hashing allocates nothing per block.
|
|
19
|
+
const state = new Uint32Array(16);
|
|
20
|
+
const msg = new Uint32Array(16);
|
|
21
|
+
const next = new Uint32Array(16);
|
|
22
|
+
function g(a, b, c, d, mx, my) {
|
|
23
|
+
state[a] = state[a] + state[b] + mx;
|
|
24
|
+
state[d] = rotr(state[d] ^ state[a], 16);
|
|
25
|
+
state[c] = state[c] + state[d];
|
|
26
|
+
state[b] = rotr(state[b] ^ state[c], 12);
|
|
27
|
+
state[a] = state[a] + state[b] + my;
|
|
28
|
+
state[d] = rotr(state[d] ^ state[a], 8);
|
|
29
|
+
state[c] = state[c] + state[d];
|
|
30
|
+
state[b] = rotr(state[b] ^ state[c], 7);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The compression function: an 8-word chaining value and a 16-word block in, 16 words out. The result
|
|
34
|
+
* is `state`, which the next call overwrites: read what you need before compressing again.
|
|
35
|
+
*/
|
|
36
|
+
function compress(cv, block, counter, blockLen, flags) {
|
|
37
|
+
for (let i = 0; i < 8; i++)
|
|
38
|
+
state[i] = cv[i];
|
|
39
|
+
state[8] = IV[0];
|
|
40
|
+
state[9] = IV[1];
|
|
41
|
+
state[10] = IV[2];
|
|
42
|
+
state[11] = IV[3];
|
|
43
|
+
state[12] = counter >>> 0;
|
|
44
|
+
state[13] = Math.floor(counter / 0x100000000) >>> 0;
|
|
45
|
+
state[14] = blockLen;
|
|
46
|
+
state[15] = flags;
|
|
47
|
+
for (let i = 0; i < 16; i++)
|
|
48
|
+
msg[i] = block[i];
|
|
49
|
+
for (let round = 0; round < 7; round++) {
|
|
50
|
+
g(0, 4, 8, 12, msg[0], msg[1]);
|
|
51
|
+
g(1, 5, 9, 13, msg[2], msg[3]);
|
|
52
|
+
g(2, 6, 10, 14, msg[4], msg[5]);
|
|
53
|
+
g(3, 7, 11, 15, msg[6], msg[7]);
|
|
54
|
+
g(0, 5, 10, 15, msg[8], msg[9]);
|
|
55
|
+
g(1, 6, 11, 12, msg[10], msg[11]);
|
|
56
|
+
g(2, 7, 8, 13, msg[12], msg[13]);
|
|
57
|
+
g(3, 4, 9, 14, msg[14], msg[15]);
|
|
58
|
+
if (round < 6) {
|
|
59
|
+
for (let i = 0; i < 16; i++)
|
|
60
|
+
next[i] = msg[PERMUTATION[i]];
|
|
61
|
+
msg.set(next);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (let i = 0; i < 8; i++) {
|
|
65
|
+
state[i] = state[i] ^ state[i + 8];
|
|
66
|
+
state[i + 8] = state[i + 8] ^ cv[i];
|
|
67
|
+
}
|
|
68
|
+
return state;
|
|
69
|
+
}
|
|
70
|
+
const chainingValue = (o) => Array.from(compress(o.cv, o.block, o.counter, o.blockLen, o.flags).subarray(0, 8));
|
|
71
|
+
const words = (bytes, start, length) => {
|
|
72
|
+
const block = new Uint32Array(16);
|
|
73
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + start, length);
|
|
74
|
+
for (let i = 0; i < length >> 2; i++)
|
|
75
|
+
block[i] = view.getUint32(i * 4, true);
|
|
76
|
+
// A final partial word is zero-padded, little-endian.
|
|
77
|
+
for (let i = length & ~3; i < length; i++)
|
|
78
|
+
block[i >> 2] = block[i >> 2] | (view.getUint8(i) << (8 * (i & 3)));
|
|
79
|
+
return block;
|
|
80
|
+
};
|
|
81
|
+
/** The output of the chunk starting at `start`, whose last block is left for the caller to finish. */
|
|
82
|
+
function chunkOutput(input, start, index) {
|
|
83
|
+
const length = Math.min(CHUNK_LEN, input.length - start);
|
|
84
|
+
const blocks = Math.max(1, Math.ceil(length / BLOCK_LEN));
|
|
85
|
+
let cv = IV;
|
|
86
|
+
for (let b = 0; b < blocks - 1; b++) {
|
|
87
|
+
const block = words(input, start + b * BLOCK_LEN, BLOCK_LEN);
|
|
88
|
+
cv = Array.from(compress(cv, block, index, BLOCK_LEN, b === 0 ? CHUNK_START : 0).subarray(0, 8));
|
|
89
|
+
}
|
|
90
|
+
const last = (blocks - 1) * BLOCK_LEN;
|
|
91
|
+
const blockLen = length - last;
|
|
92
|
+
return {
|
|
93
|
+
cv,
|
|
94
|
+
block: words(input, start + last, blockLen),
|
|
95
|
+
counter: index,
|
|
96
|
+
blockLen,
|
|
97
|
+
flags: (blocks === 1 ? CHUNK_START : 0) | CHUNK_END,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const parentOutput = (left, right) => ({
|
|
101
|
+
cv: IV,
|
|
102
|
+
block: Uint32Array.from([...left, ...right]),
|
|
103
|
+
counter: 0,
|
|
104
|
+
blockLen: BLOCK_LEN,
|
|
105
|
+
flags: PARENT,
|
|
106
|
+
});
|
|
107
|
+
/** The first `length` bytes (at most 32) of the BLAKE3 hash of `input`, as lowercase hex. */
|
|
108
|
+
export function blake3Hex(input, length = 32) {
|
|
109
|
+
const chunks = Math.max(1, Math.ceil(input.length / CHUNK_LEN));
|
|
110
|
+
// Completed subtrees, merged as soon as two of equal size exist.
|
|
111
|
+
const stack = [];
|
|
112
|
+
for (let i = 0; i < chunks - 1; i++) {
|
|
113
|
+
let cv = chainingValue(chunkOutput(input, i * CHUNK_LEN, i));
|
|
114
|
+
for (let done = i + 1; (done & 1) === 0; done >>= 1)
|
|
115
|
+
cv = chainingValue(parentOutput(stack.pop(), cv));
|
|
116
|
+
stack.push(cv);
|
|
117
|
+
}
|
|
118
|
+
let out = chunkOutput(input, (chunks - 1) * CHUNK_LEN, chunks - 1);
|
|
119
|
+
while (stack.length > 0)
|
|
120
|
+
out = parentOutput(stack.pop(), chainingValue(out));
|
|
121
|
+
const digest = compress(out.cv, out.block, 0, out.blockLen, out.flags | ROOT);
|
|
122
|
+
let hex = "";
|
|
123
|
+
for (let i = 0; i < Math.ceil(length / 4); i++)
|
|
124
|
+
for (let b = 0; b < 4; b++)
|
|
125
|
+
hex += ((digest[i] >>> (8 * b)) & 0xff).toString(16).padStart(2, "0");
|
|
126
|
+
return hex.slice(0, length * 2);
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=blake3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blake3.js","sourceRoot":"","sources":["../src/blake3.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,EAAE,GAAG;IACT,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU;CAC/F,CAAC;AACF,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAE3E,MAAM,WAAW,GAAG,CAAC,CAAC;AACtB,MAAM,SAAS,GAAG,CAAC,CAAC;AACpB,MAAM,MAAM,GAAG,CAAC,CAAC;AACjB,MAAM,IAAI,GAAG,CAAC,CAAC;AACf,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,SAAS,GAAG,IAAI,CAAC;AAEvB,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AAEnE,qFAAqF;AACrF,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;AAClC,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;AAChC,MAAM,IAAI,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;AAEjC,SAAS,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,EAAU,EAAE,EAAU;IAC3E,KAAK,CAAC,CAAC,CAAC,GAAI,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,CAAY,GAAG,EAAE,CAAC;IAC5D,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAE,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,CAAY,EAAE,EAAE,CAAC,CAAC;IACjE,KAAK,CAAC,CAAC,CAAC,GAAI,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,CAAY,CAAC;IACvD,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAE,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,CAAY,EAAE,EAAE,CAAC,CAAC;IACjE,KAAK,CAAC,CAAC,CAAC,GAAI,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,CAAY,GAAG,EAAE,CAAC;IAC5D,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAE,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,CAAY,EAAE,CAAC,CAAC,CAAC;IAChE,KAAK,CAAC,CAAC,CAAC,GAAI,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,CAAY,CAAC;IACvD,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAE,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,CAAY,EAAE,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,SAAS,QAAQ,CACf,EAAqB,EACrB,KAAwB,EACxB,OAAe,EACf,QAAgB,EAChB,KAAa;IAEb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAW,CAAC;IACvD,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAW,CAAC;IAC3B,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAW,CAAC;IAC3B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAW,CAAC;IAC5B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAW,CAAC;IAC5B,KAAK,CAAC,EAAE,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAW,CAAC;IACzD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QACvC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAW,EAAE,GAAG,CAAC,CAAC,CAAW,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAW,EAAE,GAAG,CAAC,CAAC,CAAW,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAW,EAAE,GAAG,CAAC,CAAC,CAAW,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAW,EAAE,GAAG,CAAC,CAAC,CAAW,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAW,EAAE,GAAG,CAAC,CAAC,CAAW,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAW,EAAE,GAAG,CAAC,EAAE,CAAW,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAW,EAAE,GAAG,CAAC,EAAE,CAAW,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAW,EAAE,GAAG,CAAC,EAAE,CAAW,CAAC,CAAC;QACrD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;gBAAE,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC,CAAW,CAAW,CAAC;YAC/E,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,KAAK,CAAC,CAAC,CAAC,GAAI,KAAK,CAAC,CAAC,CAAY,GAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAY,CAAC;QAC3D,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAY,GAAI,EAAE,CAAC,CAAC,CAAY,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAWD,MAAM,aAAa,GAAG,CAAC,CAAS,EAAY,EAAE,CAC5C,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAErF,MAAM,KAAK,GAAG,CAAC,KAAiB,EAAE,KAAa,EAAE,MAAc,EAAe,EAAE;IAC9E,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,EAAE,MAAM,CAAC,CAAC;IAC1E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAC7E,sDAAsD;IACtD,KAAK,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE;QACvC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,GAAI,KAAK,CAAC,CAAC,IAAI,CAAC,CAAY,GAAG,CAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAY,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9F,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,sGAAsG;AACtG,SAAS,WAAW,CAAC,KAAiB,EAAE,KAAa,EAAE,KAAa;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC1D,IAAI,EAAE,GAAsB,EAAE,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,GAAG,SAAS,EAAE,SAAS,CAAC,CAAC;QAC7D,EAAE,GAAG,KAAK,CAAC,IAAI,CACb,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAChF,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC;IACtC,MAAM,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO;QACL,EAAE;QACF,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,CAAC;QAC3C,OAAO,EAAE,KAAK;QACd,QAAQ;QACR,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS;KACpD,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAAG,CAAC,IAAuB,EAAE,KAAwB,EAAU,EAAE,CAAC,CAAC;IACnF,EAAE,EAAE,EAAE;IACN,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC;IAC5C,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,SAAS;IACnB,KAAK,EAAE,MAAM;CACd,CAAC,CAAC;AAEH,6FAA6F;AAC7F,MAAM,UAAU,SAAS,CAAC,KAAiB,EAAE,MAAM,GAAG,EAAE;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAChE,iEAAiE;IACjE,MAAM,KAAK,GAA0B,EAAE,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,EAAE,GAAsB,aAAa,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;QAChF,KAAK,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC;YACjD,EAAE,GAAG,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,EAAuB,EAAE,EAAE,CAAC,CAAC,CAAC;QACzE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,GAAG,GAAG,WAAW,CAAC,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;IACnE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,EAAuB,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;IAClG,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9E,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE;QAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;YACxB,GAAG,IAAI,CAAC,CAAE,MAAM,CAAC,CAAC,CAAY,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtF,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;AAClC,CAAC"}
|
package/dist/extract-v2.d.ts
CHANGED
|
@@ -19,15 +19,25 @@
|
|
|
19
19
|
* isn't part of the body.
|
|
20
20
|
* - `deps` names each call by its structure (`client.get().json`), so reformatting a call chain or
|
|
21
21
|
* renaming a local passed as an argument doesn't move it.
|
|
22
|
+
*
|
|
23
|
+
* Version 3 is version 2 except that what code formatters (Black, Ruff, Prettier) rewrite without
|
|
24
|
+
* changing behaviour reads the same either way, in `sig` and `body` alike: a string's quotes, escaped
|
|
25
|
+
* quotes and prefix case (`'it\'s'`, `"it's"`, `U'a'`); a number's spelling (`0XAB`, `1E5`, and in
|
|
26
|
+
* TS/JS `1.50`, `.5`); Python's optional parentheses (`from m import (a)`, `with (a, b):`,
|
|
27
|
+
* `for (x, y) in`, `return (a, b)`, `-> (int)`); an arrow's single parameter with or without
|
|
28
|
+
* parentheses; and a quoted key that needn't be (`{ 'a': 1 }`). It also stops reading a class's or a
|
|
29
|
+
* whole file's own top-level names (a model's fields, a module's constants) as locals: renaming one
|
|
30
|
+
* is a change (see {@link collectLocals}). Version-2 anchors keep being read the version-2 way (see
|
|
31
|
+
* {@link withVersion}) until a check finds them unchanged and re-stamps them.
|
|
22
32
|
*/
|
|
23
33
|
import type { Node } from "web-tree-sitter";
|
|
24
34
|
import type { LangId } from "./grammar.js";
|
|
25
35
|
import type { FileSymbols } from "./types.js";
|
|
26
36
|
/** A node as its tokens: comments and layout dropped, a trailing comma before a closer dropped. */
|
|
27
|
-
export declare function canon(node: Node): string;
|
|
28
|
-
export declare function extractFileSymbolsV2(root: Node, lang: LangId, normalizeLocals: boolean): FileSymbols;
|
|
29
|
-
/** Whole-file `body` and `deps` for a file-only anchor, read the version-2 way. */
|
|
30
|
-
export declare function computeWholeFileTiersV2(root: Node, lang: LangId, normalizeLocals: boolean): {
|
|
37
|
+
export declare function canon(node: Node, lang?: LangId): string;
|
|
38
|
+
export declare function extractFileSymbolsV2(root: Node, lang: LangId, normalizeLocals: boolean, version?: number): FileSymbols;
|
|
39
|
+
/** Whole-file `body` and `deps` for a file-only anchor, read the version-2 (or 3) way. */
|
|
40
|
+
export declare function computeWholeFileTiersV2(root: Node, lang: LangId, normalizeLocals: boolean, version?: number): {
|
|
31
41
|
bodyNormalized: string;
|
|
32
42
|
deps: string[];
|
|
33
43
|
};
|
package/dist/extract-v2.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-v2.d.ts","sourceRoot":"","sources":["../src/extract-v2.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"extract-v2.d.ts","sourceRoot":"","sources":["../src/extract-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,YAAY,CAAC;AA+G9D,mGAAmG;AACnG,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAoCvD;AA2eD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,OAAO,EACxB,OAAO,SAAI,GACV,WAAW,CAEb;AAyPD,0FAA0F;AAC1F,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,OAAO,EACxB,OAAO,SAAI,GACV;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAS5C"}
|
package/dist/extract-v2.js
CHANGED
|
@@ -1,17 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the extraction under way reads formatting the version-3 way. Set for the length of one
|
|
3
|
+
* (synchronous) extraction by {@link withVersion}, so the helpers below needn't all carry it.
|
|
4
|
+
*/
|
|
5
|
+
let formatProof = false;
|
|
6
|
+
function withVersion(version, run) {
|
|
7
|
+
const saved = formatProof;
|
|
8
|
+
formatProof = version >= 3;
|
|
9
|
+
try {
|
|
10
|
+
return run();
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
formatProof = saved;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** `\'` and `\"` as the quote alone; every other escape as written. */
|
|
17
|
+
const unescapeQuotes = (s) => s.replace(/\\([\s\S])/g, (pair, c) => (c === "'" || c === '"' ? c : pair));
|
|
18
|
+
/**
|
|
19
|
+
* A string literal whichever quotes delimit it: its prefix (lower case, sorted, without Python 3's
|
|
20
|
+
* no-op `u`) and its contents with escaped quotes unescaped. `inner` renders an f-string's
|
|
21
|
+
* interpolations. Null for anything but a plain or f-string literal (a template literal keeps its
|
|
22
|
+
* backticks: there's no other way to write one).
|
|
23
|
+
*/
|
|
24
|
+
function stringLiteral(node, lang, inner) {
|
|
25
|
+
// Named: TS's `string` keyword is an unnamed node of the same type.
|
|
26
|
+
if (node.type !== "string" || !node.isNamed)
|
|
27
|
+
return null;
|
|
28
|
+
if (lang !== "python")
|
|
29
|
+
return JSON.stringify(unescapeQuotes(node.text.slice(1, -1)));
|
|
30
|
+
let prefix = "";
|
|
31
|
+
let quote = 1;
|
|
32
|
+
let contents = "";
|
|
33
|
+
const text = (t) => {
|
|
34
|
+
contents += prefix.includes("r") ? t : unescapeQuotes(t);
|
|
35
|
+
};
|
|
36
|
+
for (const c of node.children) {
|
|
37
|
+
if (!c || c.isExtra)
|
|
38
|
+
continue;
|
|
39
|
+
if (c.type === "string_start") {
|
|
40
|
+
const delimiter = c.text.replace(/^[^'"]*/, "");
|
|
41
|
+
quote = delimiter.length;
|
|
42
|
+
prefix = [...c.text.slice(0, -quote).toLowerCase().replace("u", "")].sort().join("");
|
|
43
|
+
}
|
|
44
|
+
// An f-string's own braces are written doubled (`{{`), so an interpolation can't be mistaken for text.
|
|
45
|
+
else if (c.type === "interpolation")
|
|
46
|
+
contents += inner(c);
|
|
47
|
+
// The grammar ends `r'\''` with a `string_end` of `\''`: what precedes the closing quote is text.
|
|
48
|
+
else if (c.type === "string_end")
|
|
49
|
+
text(c.text.slice(0, -quote));
|
|
50
|
+
else
|
|
51
|
+
text(c.text);
|
|
52
|
+
}
|
|
53
|
+
return `${prefix}${JSON.stringify(contents)}`;
|
|
54
|
+
}
|
|
55
|
+
/** A number literal however it's spelled: case, `_` separators and, in TS/JS, the value it denotes. */
|
|
56
|
+
function numberLiteral(text, lang) {
|
|
57
|
+
const t = text.toLowerCase().replace(/_/g, "");
|
|
58
|
+
// Python keeps the spelling (`1` and `1.0` are different types); so do BigInts and legacy octals.
|
|
59
|
+
if (lang === "python" || t.endsWith("n") || /^0\d/.test(t))
|
|
60
|
+
return t;
|
|
61
|
+
const value = Number(t);
|
|
62
|
+
return Number.isFinite(value) ? String(value) : t;
|
|
63
|
+
}
|
|
64
|
+
const NUMBER_TYPES = new Set(["number", "integer", "float"]);
|
|
65
|
+
/** The node types version 3 reads differently from version 2 (bar optional parentheses). */
|
|
66
|
+
const FORMATTED_TYPES = new Set(["string", ...NUMBER_TYPES, "formal_parameters"]);
|
|
67
|
+
/** Members whose name may be quoted or not, as long as it's an identifier either way. */
|
|
68
|
+
const NAMED_MEMBERS = new Set([
|
|
69
|
+
"pair",
|
|
70
|
+
"property_signature",
|
|
71
|
+
"method_signature",
|
|
72
|
+
"public_field_definition",
|
|
73
|
+
"field_definition",
|
|
74
|
+
"method_definition",
|
|
75
|
+
"abstract_method_signature",
|
|
76
|
+
]);
|
|
77
|
+
/** A quoted member name that needn't be (`{ 'a': 1 }`): the bare name. */
|
|
78
|
+
function unquotedKey(node, lang) {
|
|
79
|
+
if (lang === "python" || node.type !== "string" || !node.isNamed)
|
|
80
|
+
return null;
|
|
81
|
+
const parent = node.parent;
|
|
82
|
+
if (!parent || !NAMED_MEMBERS.has(parent.type))
|
|
83
|
+
return null;
|
|
84
|
+
const field = ["key", "name", "property"]
|
|
85
|
+
.map((f) => parent.childForFieldName(f))
|
|
86
|
+
.find((c) => c?.id === node.id);
|
|
87
|
+
if (!field)
|
|
88
|
+
return null;
|
|
89
|
+
const name = unescapeQuotes(node.text.slice(1, -1));
|
|
90
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : null;
|
|
91
|
+
}
|
|
92
|
+
/** Python's optional parentheses: around an import list, a `with` clause, or a tuple. */
|
|
93
|
+
const OPTIONAL_PARENS = new Set(["import_from_statement", "with_clause", "tuple", "tuple_pattern"]);
|
|
94
|
+
/** A parenthesized tuple is the same tuple without the parentheses. */
|
|
95
|
+
const TUPLE_AS = new Map([
|
|
96
|
+
["tuple", "expression_list"],
|
|
97
|
+
["tuple_pattern", "pattern_list"],
|
|
98
|
+
]);
|
|
99
|
+
/** An arrow function's one plain parameter, when it's written in parentheses (`(x) => x`). */
|
|
100
|
+
function soleArrowParameter(node) {
|
|
101
|
+
if (node.type !== "formal_parameters" || node.parent?.type !== "arrow_function")
|
|
102
|
+
return null;
|
|
103
|
+
const params = node.namedChildren.filter((c) => c !== null && !c.isExtra);
|
|
104
|
+
if (params.length !== 1)
|
|
105
|
+
return null;
|
|
106
|
+
const p = params[0];
|
|
107
|
+
if (p.type === "identifier")
|
|
108
|
+
return p;
|
|
109
|
+
const named = p.namedChildren.filter((c) => c !== null && !c.isExtra);
|
|
110
|
+
return p.type === "required_parameter" && named.length === 1 && named[0]?.type === "identifier"
|
|
111
|
+
? named[0]
|
|
112
|
+
: null;
|
|
113
|
+
}
|
|
1
114
|
/** A node as its tokens: comments and layout dropped, a trailing comma before a closer dropped. */
|
|
2
|
-
export function canon(node) {
|
|
115
|
+
export function canon(node, lang) {
|
|
3
116
|
const tokens = [];
|
|
4
117
|
const walk = (n) => {
|
|
5
118
|
if (n.isExtra)
|
|
6
119
|
return; // comments
|
|
120
|
+
if (formatProof && lang) {
|
|
121
|
+
const key = unquotedKey(n, lang);
|
|
122
|
+
const literal = key ?? stringLiteral(n, lang, (i) => canon(i, lang));
|
|
123
|
+
if (literal !== null) {
|
|
124
|
+
tokens.push(literal);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (n.isNamed && NUMBER_TYPES.has(n.type)) {
|
|
128
|
+
tokens.push(numberLiteral(n.text, lang));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (n.type === "parenthesized_expression" && n.namedChildCount === 1) {
|
|
132
|
+
const inner = n.namedChildren[0];
|
|
133
|
+
if (inner && !inner.isExtra)
|
|
134
|
+
return walk(inner);
|
|
135
|
+
}
|
|
136
|
+
// (An arrow's parameters need nothing here: sigOf writes a bare one in parentheses.)
|
|
137
|
+
}
|
|
7
138
|
if (n.childCount === 0) {
|
|
8
139
|
const t = n.text.trim();
|
|
9
140
|
if (t)
|
|
10
141
|
tokens.push(t);
|
|
11
142
|
return;
|
|
12
143
|
}
|
|
144
|
+
const dropParens = formatProof && lang === "python" && OPTIONAL_PARENS.has(n.type);
|
|
13
145
|
for (const c of n.children)
|
|
14
|
-
if (c)
|
|
146
|
+
if (c && !(dropParens && (c.type === "(" || c.type === ")")))
|
|
15
147
|
walk(c);
|
|
16
148
|
};
|
|
17
149
|
walk(node);
|
|
@@ -101,20 +233,20 @@ function isExported(node) {
|
|
|
101
233
|
return false;
|
|
102
234
|
}
|
|
103
235
|
/** Decorators written before a declaration: Python's `decorated_definition`, TS's on the class or export, or class-body siblings for a member. */
|
|
104
|
-
function decoratorsOf(def, member) {
|
|
236
|
+
function decoratorsOf(def, member, lang) {
|
|
105
237
|
const out = [];
|
|
106
238
|
const parent = member.parent;
|
|
107
239
|
if (parent?.type === "decorated_definition" || parent?.type === "export_statement")
|
|
108
240
|
for (const c of parent.namedChildren)
|
|
109
241
|
if (c?.type === "decorator")
|
|
110
|
-
out.push(canon(c));
|
|
242
|
+
out.push(canon(c, lang));
|
|
111
243
|
for (const c of def.namedChildren)
|
|
112
244
|
if (c?.type === "decorator")
|
|
113
|
-
out.push(canon(c));
|
|
245
|
+
out.push(canon(c, lang));
|
|
114
246
|
// A TS class member's decorators are the class body's children right before it.
|
|
115
247
|
if (parent?.type === "class_body")
|
|
116
248
|
for (let s = member.previousSibling; s?.type === "decorator"; s = s.previousSibling)
|
|
117
|
-
out.push(canon(s));
|
|
249
|
+
out.push(canon(s, lang));
|
|
118
250
|
return out;
|
|
119
251
|
}
|
|
120
252
|
/** Keyword modifiers written before the name: `static`, `async`, `get`, `private`, `readonly`, `*`, `abstract`… */
|
|
@@ -165,23 +297,23 @@ function sigOf(def, name, lang) {
|
|
|
165
297
|
const nameNode = n.childForFieldName("name");
|
|
166
298
|
const body = [...n.children]
|
|
167
299
|
.filter((c) => c !== null && c !== nameNode && !c.isExtra && c.type !== "decorator")
|
|
168
|
-
.map(canon)
|
|
300
|
+
.map((c) => canon(c, lang))
|
|
169
301
|
.join(" ");
|
|
170
302
|
return `${n.type} ${name} ${body}${tail}`;
|
|
171
303
|
}
|
|
172
304
|
const typeParams = n.childForFieldName("type_parameters");
|
|
173
|
-
const tp = typeParams ? canon(typeParams) : "";
|
|
305
|
+
const tp = typeParams ? canon(typeParams, lang) : "";
|
|
174
306
|
if (def.kind === "class") {
|
|
175
307
|
const heritage = lang === "python"
|
|
176
308
|
? n.childForFieldName("superclasses")
|
|
177
309
|
: (n.namedChildren.find((c) => c?.type === "class_heritage") ?? null);
|
|
178
|
-
return `class ${name}${tp}${heritage ? ` ${canon(heritage)}` : ""}${tail}`;
|
|
310
|
+
return `class ${name}${tp}${heritage ? ` ${canon(heritage, lang)}` : ""}${tail}`;
|
|
179
311
|
}
|
|
180
312
|
const params = n.childForFieldName("parameters");
|
|
181
313
|
const single = lang === "python" ? null : n.childForFieldName("parameter");
|
|
182
|
-
const ps = params ? canon(params) : single ? `(${canon(single)})` : "()";
|
|
314
|
+
const ps = params ? canon(params, lang) : single ? `(${canon(single, lang)})` : "()";
|
|
183
315
|
const ret = n.childForFieldName("return_type");
|
|
184
|
-
const rs = ret ? canon(ret).replace(/^:\s*/, "") : "";
|
|
316
|
+
const rs = ret ? canon(ret, lang).replace(/^:\s*/, "") : "";
|
|
185
317
|
return `fn ${name}${tp}${ps}${rs ? `->${rs}` : ""}${tail}`;
|
|
186
318
|
}
|
|
187
319
|
// -------------------------------------------------------------------------------------------------
|
|
@@ -237,25 +369,52 @@ function paramNames(params, lang, out) {
|
|
|
237
369
|
}
|
|
238
370
|
}
|
|
239
371
|
}
|
|
240
|
-
|
|
372
|
+
/**
|
|
373
|
+
* The names `subtree` binds that are local to it, aliased in `body`. For a class or a whole file
|
|
374
|
+
* (`ownNames`), from version 3 the names bound at its own top level, outside any function in it, aren't
|
|
375
|
+
* locals: they are its fields, attributes, constants and exports, and renaming one changes what other
|
|
376
|
+
* code can use (`montly_fee: float` → `monthly_fee: float` renames a model's field and its JSON key).
|
|
377
|
+
*/
|
|
378
|
+
function collectLocals(subtree, outerParams, lang, ownNames = false) {
|
|
241
379
|
const locals = new Set();
|
|
380
|
+
const own = new Set();
|
|
381
|
+
const keepOwn = ownNames && formatProof;
|
|
382
|
+
/** Records the names `pattern` binds at `site`: the scope's own when no function lies between. */
|
|
383
|
+
const bind = (site, pattern) => {
|
|
384
|
+
if (!site)
|
|
385
|
+
return;
|
|
386
|
+
const names = new Set();
|
|
387
|
+
patternNames(pattern, names);
|
|
388
|
+
let inFunction = false;
|
|
389
|
+
if (keepOwn)
|
|
390
|
+
for (let p = site.parent; p && p.id !== subtree.id; p = p.parent)
|
|
391
|
+
if (FUNCTION_LIKE[lang].has(p.type)) {
|
|
392
|
+
inFunction = true;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
for (const name of names)
|
|
396
|
+
(keepOwn && !inFunction ? own : locals).add(name);
|
|
397
|
+
};
|
|
242
398
|
paramNames(outerParams, lang, locals);
|
|
243
399
|
if (lang === "python") {
|
|
244
400
|
for (const fn of subtree.descendantsOfType(["function_definition", "lambda"]))
|
|
245
401
|
paramNames(fn?.childForFieldName("parameters") ?? null, lang, locals);
|
|
246
402
|
for (const n of subtree.descendantsOfType(["assignment", "augmented_assignment"]))
|
|
247
|
-
|
|
248
|
-
for (const n of subtree.descendantsOfType(["for_statement"
|
|
403
|
+
bind(n, n?.childForFieldName("left") ?? null);
|
|
404
|
+
for (const n of subtree.descendantsOfType(["for_statement"]))
|
|
405
|
+
bind(n, n?.childForFieldName("left") ?? null);
|
|
406
|
+
// A comprehension is a scope of its own, wherever it is.
|
|
407
|
+
for (const n of subtree.descendantsOfType(["for_in_clause"]))
|
|
249
408
|
patternNames(n?.childForFieldName("left") ?? null, locals);
|
|
250
409
|
for (const n of subtree.descendantsOfType(["as_pattern_target"]))
|
|
251
410
|
for (const c of n?.namedChildren ?? [])
|
|
252
|
-
|
|
411
|
+
bind(n, c);
|
|
253
412
|
for (const n of subtree.descendantsOfType(["named_expression"]))
|
|
254
|
-
|
|
413
|
+
bind(n, n?.childForFieldName("name") ?? null);
|
|
255
414
|
}
|
|
256
415
|
else {
|
|
257
416
|
for (const n of subtree.descendantsOfType(["variable_declarator"]))
|
|
258
|
-
|
|
417
|
+
bind(n, n?.childForFieldName("name") ?? null);
|
|
259
418
|
for (const n of subtree.descendantsOfType([...FUNCTION_LIKE[lang]])) {
|
|
260
419
|
paramNames(n?.childForFieldName("parameters") ?? null, lang, locals);
|
|
261
420
|
const single = n?.childForFieldName("parameter");
|
|
@@ -263,10 +422,13 @@ function collectLocals(subtree, outerParams, lang) {
|
|
|
263
422
|
patternNames(single, locals);
|
|
264
423
|
}
|
|
265
424
|
for (const n of subtree.descendantsOfType(["for_in_statement"]))
|
|
266
|
-
|
|
425
|
+
bind(n, n?.childForFieldName("left") ?? null);
|
|
267
426
|
for (const n of subtree.descendantsOfType(["catch_clause"]))
|
|
268
|
-
|
|
427
|
+
bind(n, n?.childForFieldName("parameter") ?? null);
|
|
269
428
|
}
|
|
429
|
+
// A name that is also the scope's own is read as written everywhere, so the two never get confused.
|
|
430
|
+
for (const name of own)
|
|
431
|
+
locals.delete(name);
|
|
270
432
|
return locals;
|
|
271
433
|
}
|
|
272
434
|
/** An identifier that names a value, not a property, attribute or keyword argument. */
|
|
@@ -287,13 +449,28 @@ function isValueIdentifier(node, lang) {
|
|
|
287
449
|
function serializeBody(node, locals, normalizeLocals, lang, aliasOf) {
|
|
288
450
|
if (node.isExtra)
|
|
289
451
|
return "";
|
|
452
|
+
// Read once: each read of a node's type is a call into the parser.
|
|
453
|
+
const nodeType = node.type;
|
|
290
454
|
// Grouping parentheses carry no meaning (the tree's shape already has the precedence), and
|
|
291
455
|
// formatters add and remove them to wrap lines.
|
|
292
|
-
if (
|
|
456
|
+
if (nodeType === "parenthesized_expression" && node.namedChildCount === 1) {
|
|
293
457
|
const inner = node.namedChildren[0];
|
|
294
458
|
if (inner && !inner.isExtra)
|
|
295
459
|
return serializeBody(inner, locals, normalizeLocals, lang, aliasOf);
|
|
296
460
|
}
|
|
461
|
+
if (formatProof && FORMATTED_TYPES.has(nodeType)) {
|
|
462
|
+
const key = unquotedKey(node, lang);
|
|
463
|
+
if (key !== null)
|
|
464
|
+
return `property_identifier:${key}`;
|
|
465
|
+
const literal = stringLiteral(node, lang, (i) => serializeBody(i, locals, normalizeLocals, lang, aliasOf));
|
|
466
|
+
if (literal !== null)
|
|
467
|
+
return `string:${literal}`;
|
|
468
|
+
if (node.isNamed && NUMBER_TYPES.has(nodeType))
|
|
469
|
+
return `${nodeType}:${numberLiteral(node.text, lang)}`;
|
|
470
|
+
const sole = soleArrowParameter(node);
|
|
471
|
+
if (sole)
|
|
472
|
+
return serializeBody(sole, locals, normalizeLocals, lang, aliasOf);
|
|
473
|
+
}
|
|
297
474
|
if (node.childCount === 0) {
|
|
298
475
|
if (node.isNamed) {
|
|
299
476
|
if (normalizeLocals && locals.has(node.text) && isValueIdentifier(node, lang)) {
|
|
@@ -304,21 +481,23 @@ function serializeBody(node, locals, normalizeLocals, lang, aliasOf) {
|
|
|
304
481
|
}
|
|
305
482
|
return `$${idx}`;
|
|
306
483
|
}
|
|
307
|
-
return `${
|
|
484
|
+
return `${nodeType}:${node.text}`;
|
|
308
485
|
}
|
|
309
|
-
if (
|
|
486
|
+
if (nodeType === "," || nodeType === ";")
|
|
310
487
|
return "";
|
|
311
|
-
return
|
|
488
|
+
return nodeType;
|
|
312
489
|
}
|
|
490
|
+
const dropParens = formatProof && lang === "python" && OPTIONAL_PARENS.has(nodeType);
|
|
313
491
|
const parts = [];
|
|
314
492
|
for (const child of node.children) {
|
|
315
|
-
if (!child)
|
|
493
|
+
if (!child || (dropParens && (child.type === "(" || child.type === ")")))
|
|
316
494
|
continue;
|
|
317
495
|
const s = serializeBody(child, locals, normalizeLocals, lang, aliasOf);
|
|
318
496
|
if (s)
|
|
319
497
|
parts.push(s);
|
|
320
498
|
}
|
|
321
|
-
|
|
499
|
+
const type = (formatProof && lang === "python" && TUPLE_AS.get(nodeType)) || nodeType;
|
|
500
|
+
return `(${type} ${parts.join(" ")})`;
|
|
322
501
|
}
|
|
323
502
|
/** A Python function's docstring is documentation, not behaviour. */
|
|
324
503
|
function withoutDocstring(body, lang) {
|
|
@@ -450,7 +629,10 @@ function commonJsPath(left) {
|
|
|
450
629
|
const path = parts.filter((p) => p !== "prototype").join(".");
|
|
451
630
|
return path ? { path, exported } : null;
|
|
452
631
|
}
|
|
453
|
-
export function extractFileSymbolsV2(root, lang, normalizeLocals) {
|
|
632
|
+
export function extractFileSymbolsV2(root, lang, normalizeLocals, version = 2) {
|
|
633
|
+
return withVersion(version, () => extractSymbols(root, lang, normalizeLocals));
|
|
634
|
+
}
|
|
635
|
+
function extractSymbols(root, lang, normalizeLocals) {
|
|
454
636
|
const importedNames = collectImportedNames(root, lang);
|
|
455
637
|
const bySymbolPath = new Map();
|
|
456
638
|
const fileExports = [];
|
|
@@ -467,7 +649,7 @@ export function extractFileSymbolsV2(root, lang, normalizeLocals) {
|
|
|
467
649
|
? withoutDocstring(body, lang)
|
|
468
650
|
: [body];
|
|
469
651
|
const locals = def.kind === "class"
|
|
470
|
-
? collectLocals(body ?? n, null, lang)
|
|
652
|
+
? collectLocals(body ?? n, null, lang, true)
|
|
471
653
|
: collectLocals(body ?? n, n.childForFieldName("parameters") ?? n.childForFieldName("parameter"), lang);
|
|
472
654
|
const aliasOf = new Map();
|
|
473
655
|
const bodyNormalized = roots
|
|
@@ -565,7 +747,7 @@ export function extractFileSymbolsV2(root, lang, normalizeLocals) {
|
|
|
565
747
|
outer: prefix ? cls : outerOf(cls),
|
|
566
748
|
exported: prefix ? false : isExported(cls),
|
|
567
749
|
extra: [],
|
|
568
|
-
decorators: decoratorsOf(cls, cls),
|
|
750
|
+
decorators: decoratorsOf(cls, cls, lang),
|
|
569
751
|
});
|
|
570
752
|
if (isType)
|
|
571
753
|
return;
|
|
@@ -594,7 +776,7 @@ export function extractFileSymbolsV2(root, lang, normalizeLocals) {
|
|
|
594
776
|
outer: member.parent?.type === "decorated_definition" ? member.parent : member,
|
|
595
777
|
exported: false,
|
|
596
778
|
extra: [],
|
|
597
|
-
decorators: decoratorsOf(member, member),
|
|
779
|
+
decorators: decoratorsOf(member, member, lang),
|
|
598
780
|
});
|
|
599
781
|
}
|
|
600
782
|
else if (member.type === "public_field_definition" || member.type === "field_definition") {
|
|
@@ -610,7 +792,7 @@ export function extractFileSymbolsV2(root, lang, normalizeLocals) {
|
|
|
610
792
|
outer: member,
|
|
611
793
|
exported: false,
|
|
612
794
|
extra: [...leadingModifiers(member), ...fn.wrappers.map((w) => `wrap:${w}`)],
|
|
613
|
-
decorators: decoratorsOf(member, member),
|
|
795
|
+
decorators: decoratorsOf(member, member, lang),
|
|
614
796
|
});
|
|
615
797
|
}
|
|
616
798
|
}
|
|
@@ -629,7 +811,7 @@ export function extractFileSymbolsV2(root, lang, normalizeLocals) {
|
|
|
629
811
|
outer: outerOf(node),
|
|
630
812
|
exported: lang === "python" ? false : isExported(node),
|
|
631
813
|
extra: [],
|
|
632
|
-
decorators: decoratorsOf(node, node),
|
|
814
|
+
decorators: decoratorsOf(node, node, lang),
|
|
633
815
|
});
|
|
634
816
|
}
|
|
635
817
|
else if (CLASS_LIKE.has(node.type)) {
|
|
@@ -664,7 +846,7 @@ export function extractFileSymbolsV2(root, lang, normalizeLocals) {
|
|
|
664
846
|
outer: outerOf(node),
|
|
665
847
|
exported: isExported(node),
|
|
666
848
|
extra: [
|
|
667
|
-
...(type ? [`:${canon(type).replace(/^:\s*/, "")}`] : []),
|
|
849
|
+
...(type ? [`:${canon(type, lang).replace(/^:\s*/, "")}`] : []),
|
|
668
850
|
...fn.wrappers.map((w) => `wrap:${w}`),
|
|
669
851
|
],
|
|
670
852
|
decorators: [],
|
|
@@ -693,13 +875,15 @@ export function extractFileSymbolsV2(root, lang, normalizeLocals) {
|
|
|
693
875
|
}
|
|
694
876
|
return { bySymbolPath, fileExports };
|
|
695
877
|
}
|
|
696
|
-
/** Whole-file `body` and `deps` for a file-only anchor, read the version-2 way. */
|
|
697
|
-
export function computeWholeFileTiersV2(root, lang, normalizeLocals) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
878
|
+
/** Whole-file `body` and `deps` for a file-only anchor, read the version-2 (or 3) way. */
|
|
879
|
+
export function computeWholeFileTiersV2(root, lang, normalizeLocals, version = 2) {
|
|
880
|
+
return withVersion(version, () => {
|
|
881
|
+
const importedNames = collectImportedNames(root, lang);
|
|
882
|
+
const locals = collectLocals(root, null, lang, true);
|
|
883
|
+
return {
|
|
884
|
+
bodyNormalized: serializeBody(root, locals, normalizeLocals, lang, new Map()),
|
|
885
|
+
deps: collectDeps([root], lang, importedNames, locals),
|
|
886
|
+
};
|
|
887
|
+
});
|
|
704
888
|
}
|
|
705
889
|
//# sourceMappingURL=extract-v2.js.map
|