@chrisdudek/yg 4.2.0 → 5.0.0-alpha.2
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 +4 -2
- package/dist/ast.d.ts +51 -0
- package/dist/ast.js +279 -0
- package/dist/bin.js +17543 -6420
- package/dist/grammars/tree-sitter-c.node-types.json +4615 -0
- package/dist/grammars/tree-sitter-c.wasm +0 -0
- package/dist/grammars/tree-sitter-c_sharp.node-types.json +7233 -0
- package/dist/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/dist/grammars/tree-sitter-cpp.node-types.json +7997 -0
- package/dist/grammars/tree-sitter-cpp.wasm +0 -0
- package/dist/grammars/tree-sitter-go.node-types.json +2995 -0
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/grammars/tree-sitter-java.node-types.json +4586 -0
- package/dist/grammars/tree-sitter-java.wasm +0 -0
- package/dist/grammars/tree-sitter-javascript.node-types.json +3622 -0
- package/dist/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/grammars/tree-sitter-json.node-types.json +183 -0
- package/dist/grammars/tree-sitter-json.wasm +0 -0
- package/dist/grammars/tree-sitter-kotlin.node-types.json +3091 -0
- package/dist/grammars/tree-sitter-kotlin.wasm +0 -0
- package/dist/grammars/tree-sitter-php_only.node-types.json +6077 -0
- package/dist/grammars/tree-sitter-php_only.wasm +0 -0
- package/dist/grammars/tree-sitter-python.node-types.json +3746 -0
- package/dist/grammars/tree-sitter-python.wasm +0 -0
- package/dist/grammars/tree-sitter-ruby.node-types.json +4108 -0
- package/dist/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/grammars/tree-sitter-rust.node-types.json +5554 -0
- package/dist/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/grammars/tree-sitter-toml.node-types.json +356 -0
- package/dist/grammars/tree-sitter-toml.wasm +0 -0
- package/dist/grammars/tree-sitter-tsx.node-types.json +6288 -0
- package/dist/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/grammars/tree-sitter-typescript.node-types.json +6038 -0
- package/dist/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/grammars/tree-sitter-yaml.node-types.json +552 -0
- package/dist/grammars/tree-sitter-yaml.wasm +0 -0
- package/dist/loader-hook-impl.d.ts +9 -0
- package/dist/loader-hook-impl.js +16 -0
- package/dist/structure.d.ts +414 -0
- package/dist/structure.js +1261 -0
- package/graph-schemas/yg-architecture.yaml +39 -6
- package/graph-schemas/yg-aspect.yaml +88 -3
- package/graph-schemas/yg-config.yaml +28 -12
- package/graph-schemas/yg-flow.yaml +5 -3
- package/graph-schemas/yg-node.yaml +19 -5
- package/package.json +56 -19
|
@@ -0,0 +1,1261 @@
|
|
|
1
|
+
// src/structure/runner.ts
|
|
2
|
+
import * as fs4 from "fs";
|
|
3
|
+
import * as path8 from "path";
|
|
4
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5
|
+
|
|
6
|
+
// src/ast/loader-hook.ts
|
|
7
|
+
import { register } from "module";
|
|
8
|
+
import { pathToFileURL } from "url";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
var __dirname = path.dirname(__filename);
|
|
14
|
+
var registered = false;
|
|
15
|
+
function ensureLoaderRegistered() {
|
|
16
|
+
if (registered) return;
|
|
17
|
+
let implPath = path.resolve(__dirname, "./loader-hook-impl.js");
|
|
18
|
+
if (!existsSync(implPath)) {
|
|
19
|
+
const pkgRoot = path.resolve(__dirname, "../../");
|
|
20
|
+
implPath = path.resolve(pkgRoot, "dist/loader-hook-impl.js");
|
|
21
|
+
}
|
|
22
|
+
register(pathToFileURL(implPath));
|
|
23
|
+
registered = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/structure/ctx-fs.ts
|
|
27
|
+
import * as fs from "fs";
|
|
28
|
+
import * as path2 from "path";
|
|
29
|
+
|
|
30
|
+
// src/utils/mapping-path.ts
|
|
31
|
+
function normalizeMappingPath(p) {
|
|
32
|
+
return p.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/utils/posix.ts
|
|
36
|
+
function toPosix(p) {
|
|
37
|
+
return p.replace(/\\/g, "/");
|
|
38
|
+
}
|
|
39
|
+
function toPosixPath(p) {
|
|
40
|
+
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/structure/ctx-fs.ts
|
|
44
|
+
var UndeclaredFsReadError = class extends Error {
|
|
45
|
+
constructor(path9) {
|
|
46
|
+
super(`structure-aspect-undeclared-fs-read: ${path9}`);
|
|
47
|
+
this.path = path9;
|
|
48
|
+
this.name = "UndeclaredFsReadError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function isAllowed(p, set) {
|
|
52
|
+
if (p === "") return false;
|
|
53
|
+
if (set.has(p)) return true;
|
|
54
|
+
for (const a of set) {
|
|
55
|
+
if (a === p) return true;
|
|
56
|
+
if (a.startsWith(p + "/")) return true;
|
|
57
|
+
if (p.startsWith(a + "/")) return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
function assertRealpathContained(abs, projectRoot, rel) {
|
|
62
|
+
let realRoot;
|
|
63
|
+
try {
|
|
64
|
+
realRoot = fs.realpathSync(projectRoot);
|
|
65
|
+
} catch {
|
|
66
|
+
realRoot = projectRoot;
|
|
67
|
+
}
|
|
68
|
+
let probe = abs;
|
|
69
|
+
while (!fs.existsSync(probe)) {
|
|
70
|
+
const parent = path2.dirname(probe);
|
|
71
|
+
if (parent === probe) return;
|
|
72
|
+
probe = parent;
|
|
73
|
+
}
|
|
74
|
+
let realProbe;
|
|
75
|
+
try {
|
|
76
|
+
realProbe = fs.realpathSync(probe);
|
|
77
|
+
} catch {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const relReal = toPosix(path2.relative(realRoot, realProbe));
|
|
81
|
+
if (relReal === ".." || relReal.startsWith("../") || path2.isAbsolute(relReal)) {
|
|
82
|
+
throw new UndeclaredFsReadError(rel);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function resolveAllowedReadPath(raw, allowedSet, projectRoot) {
|
|
86
|
+
const abs = path2.resolve(projectRoot, normalizeMappingPath(raw));
|
|
87
|
+
const rel = toPosix(path2.relative(projectRoot, abs));
|
|
88
|
+
if (rel === "" || rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
89
|
+
throw new UndeclaredFsReadError(normalizeMappingPath(raw));
|
|
90
|
+
}
|
|
91
|
+
if (!isAllowed(rel, allowedSet)) throw new UndeclaredFsReadError(rel);
|
|
92
|
+
assertRealpathContained(abs, projectRoot, rel);
|
|
93
|
+
return rel;
|
|
94
|
+
}
|
|
95
|
+
function createCtxFs(params) {
|
|
96
|
+
const { allowedSet, projectRoot, touchedFiles } = params;
|
|
97
|
+
function assertAllowed(raw) {
|
|
98
|
+
const p = resolveAllowedReadPath(raw, allowedSet, projectRoot);
|
|
99
|
+
touchedFiles.push(p);
|
|
100
|
+
return p;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
exists(raw) {
|
|
104
|
+
const p = assertAllowed(raw);
|
|
105
|
+
const abs = path2.resolve(projectRoot, p);
|
|
106
|
+
try {
|
|
107
|
+
const stat2 = fs.statSync(abs);
|
|
108
|
+
return stat2.isDirectory() ? "dir" : stat2.isFile() ? "file" : false;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
read(raw) {
|
|
114
|
+
const p = assertAllowed(raw);
|
|
115
|
+
const abs = path2.resolve(projectRoot, p);
|
|
116
|
+
return fs.readFileSync(abs, "utf8");
|
|
117
|
+
},
|
|
118
|
+
list(raw) {
|
|
119
|
+
const p = assertAllowed(raw);
|
|
120
|
+
const abs = path2.resolve(projectRoot, p);
|
|
121
|
+
const entries = fs.readdirSync(abs, { withFileTypes: true });
|
|
122
|
+
return entries.map((e) => ({
|
|
123
|
+
name: e.name,
|
|
124
|
+
kind: e.isDirectory() ? "dir" : "file"
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/structure/ctx-graph.ts
|
|
131
|
+
import * as fs2 from "fs";
|
|
132
|
+
import * as path3 from "path";
|
|
133
|
+
|
|
134
|
+
// src/structure/expand-mapping-sync.ts
|
|
135
|
+
function isPathInMapping(candidate, mapping) {
|
|
136
|
+
const c = normalizeMappingPath(candidate);
|
|
137
|
+
if (c === "") return false;
|
|
138
|
+
for (const raw of mapping) {
|
|
139
|
+
const n = normalizeMappingPath(raw);
|
|
140
|
+
if (n === "") continue;
|
|
141
|
+
if (c === n) return true;
|
|
142
|
+
if (c.startsWith(n + "/")) return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/structure/ctx-graph.ts
|
|
148
|
+
var UndeclaredGraphReadError = class extends Error {
|
|
149
|
+
constructor(nodePath) {
|
|
150
|
+
super(`structure-aspect-undeclared-graph-read: ${nodePath}`);
|
|
151
|
+
this.nodePath = nodePath;
|
|
152
|
+
this.name = "UndeclaredGraphReadError";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
function computeAllowedNodePaths(currentPath, graph) {
|
|
156
|
+
const allowed = /* @__PURE__ */ new Set([currentPath]);
|
|
157
|
+
const current = graph.nodes.get(currentPath);
|
|
158
|
+
if (!current) return allowed;
|
|
159
|
+
for (const rel of current.meta.relations ?? []) {
|
|
160
|
+
allowed.add(rel.target);
|
|
161
|
+
const target = graph.nodes.get(rel.target);
|
|
162
|
+
if (!target) continue;
|
|
163
|
+
const relStack = [...target.children];
|
|
164
|
+
while (relStack.length > 0) {
|
|
165
|
+
const n = relStack.pop();
|
|
166
|
+
allowed.add(n.path);
|
|
167
|
+
relStack.push(...n.children);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
let cursor = current.parent;
|
|
171
|
+
while (cursor) {
|
|
172
|
+
allowed.add(cursor.path);
|
|
173
|
+
cursor = cursor.parent;
|
|
174
|
+
}
|
|
175
|
+
const stack = [...current.children];
|
|
176
|
+
while (stack.length) {
|
|
177
|
+
const n = stack.pop();
|
|
178
|
+
allowed.add(n.path);
|
|
179
|
+
stack.push(...n.children);
|
|
180
|
+
}
|
|
181
|
+
return allowed;
|
|
182
|
+
}
|
|
183
|
+
function createCtxGraph(params) {
|
|
184
|
+
const { currentNodePath, graph, projectRoot, touchedFiles } = params;
|
|
185
|
+
const allowed = computeAllowedNodePaths(currentNodePath, graph);
|
|
186
|
+
function assertAllowed(id) {
|
|
187
|
+
if (!allowed.has(id)) throw new UndeclaredGraphReadError(id);
|
|
188
|
+
}
|
|
189
|
+
function toPublicNode(m) {
|
|
190
|
+
const files = [];
|
|
191
|
+
for (const raw of m.meta.mapping ?? []) {
|
|
192
|
+
const p = normalizeMappingPath(raw);
|
|
193
|
+
if (!p) continue;
|
|
194
|
+
const abs = path3.resolve(projectRoot, p);
|
|
195
|
+
try {
|
|
196
|
+
const stat2 = fs2.statSync(abs);
|
|
197
|
+
if (stat2.isFile()) {
|
|
198
|
+
const content = fs2.readFileSync(abs, "utf8");
|
|
199
|
+
files.push({ path: p, content });
|
|
200
|
+
touchedFiles.push(p);
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
id: m.path,
|
|
207
|
+
type: m.meta.type,
|
|
208
|
+
mapping: m.meta.mapping ?? [],
|
|
209
|
+
files,
|
|
210
|
+
ports: m.meta.ports ?? {}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
node(id) {
|
|
215
|
+
assertAllowed(id);
|
|
216
|
+
const m = graph.nodes.get(id);
|
|
217
|
+
return m ? toPublicNode(m) : void 0;
|
|
218
|
+
},
|
|
219
|
+
nodesByType(type) {
|
|
220
|
+
const out = [];
|
|
221
|
+
for (const id of allowed) {
|
|
222
|
+
const m = graph.nodes.get(id);
|
|
223
|
+
if (m && m.meta.type === type) out.push(toPublicNode(m));
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
},
|
|
227
|
+
relationsFrom(node) {
|
|
228
|
+
assertAllowed(node.id);
|
|
229
|
+
const m = graph.nodes.get(node.id);
|
|
230
|
+
return m?.meta.relations ?? [];
|
|
231
|
+
},
|
|
232
|
+
relationsTo(node) {
|
|
233
|
+
const out = [];
|
|
234
|
+
for (const id of allowed) {
|
|
235
|
+
const m = graph.nodes.get(id);
|
|
236
|
+
if (!m) continue;
|
|
237
|
+
for (const rel of m.meta.relations ?? []) {
|
|
238
|
+
if (rel.target === node.id) out.push(rel);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
},
|
|
243
|
+
children(node) {
|
|
244
|
+
assertAllowed(node.id);
|
|
245
|
+
const m = graph.nodes.get(node.id);
|
|
246
|
+
return m ? m.children.map(toPublicNode) : [];
|
|
247
|
+
},
|
|
248
|
+
flowParticipants(flowName) {
|
|
249
|
+
const flow = graph.flows.find((f) => f.name === flowName || f.path === flowName);
|
|
250
|
+
if (!flow) return [];
|
|
251
|
+
const participates = flow.nodes.some((n) => {
|
|
252
|
+
if (n === currentNodePath) return true;
|
|
253
|
+
let cursor = graph.nodes.get(currentNodePath)?.parent;
|
|
254
|
+
while (cursor) {
|
|
255
|
+
if (cursor.path === n) return true;
|
|
256
|
+
cursor = cursor.parent;
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
});
|
|
260
|
+
if (!participates) throw new UndeclaredGraphReadError(`flow:${flowName}`);
|
|
261
|
+
const out = [];
|
|
262
|
+
for (const nodeId of flow.nodes) {
|
|
263
|
+
const m = graph.nodes.get(nodeId);
|
|
264
|
+
if (m) out.push(toPublicNode(m));
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/structure/ctx-parsers.ts
|
|
272
|
+
import * as fs3 from "fs";
|
|
273
|
+
import * as path5 from "path";
|
|
274
|
+
import { extname } from "path";
|
|
275
|
+
import { parse as parseYaml } from "yaml";
|
|
276
|
+
import { parse as parseTomlSmol } from "smol-toml";
|
|
277
|
+
|
|
278
|
+
// src/ast/parser.ts
|
|
279
|
+
import { Parser, Language } from "web-tree-sitter";
|
|
280
|
+
import path4 from "path";
|
|
281
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
282
|
+
import { existsSync as existsSync3 } from "fs";
|
|
283
|
+
import { createRequire } from "module";
|
|
284
|
+
|
|
285
|
+
// src/core/graph/language-registry.ts
|
|
286
|
+
var LANGUAGES = {
|
|
287
|
+
typescript: {
|
|
288
|
+
id: "typescript",
|
|
289
|
+
extensions: [".ts"],
|
|
290
|
+
wasmFile: "tree-sitter-typescript.wasm",
|
|
291
|
+
wasmPackage: "tree-sitter-typescript",
|
|
292
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-typescript",
|
|
293
|
+
grammarCommit: "",
|
|
294
|
+
treeSitterCliVersion: "",
|
|
295
|
+
externalScanner: false,
|
|
296
|
+
commentTypes: ["comment"],
|
|
297
|
+
commentDelimiters: ["//", "/*"]
|
|
298
|
+
},
|
|
299
|
+
tsx: {
|
|
300
|
+
id: "tsx",
|
|
301
|
+
extensions: [".tsx"],
|
|
302
|
+
wasmFile: "tree-sitter-tsx.wasm",
|
|
303
|
+
wasmPackage: "tree-sitter-typescript",
|
|
304
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-typescript",
|
|
305
|
+
grammarCommit: "",
|
|
306
|
+
treeSitterCliVersion: "",
|
|
307
|
+
externalScanner: false,
|
|
308
|
+
commentTypes: ["comment"],
|
|
309
|
+
commentDelimiters: ["//", "/*"]
|
|
310
|
+
},
|
|
311
|
+
javascript: {
|
|
312
|
+
id: "javascript",
|
|
313
|
+
extensions: [".js", ".mjs", ".cjs", ".jsx"],
|
|
314
|
+
wasmFile: "tree-sitter-javascript.wasm",
|
|
315
|
+
wasmPackage: "tree-sitter-javascript",
|
|
316
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-javascript",
|
|
317
|
+
grammarCommit: "",
|
|
318
|
+
treeSitterCliVersion: "",
|
|
319
|
+
externalScanner: false,
|
|
320
|
+
commentTypes: ["comment"],
|
|
321
|
+
commentDelimiters: ["//", "/*"]
|
|
322
|
+
},
|
|
323
|
+
python: {
|
|
324
|
+
id: "python",
|
|
325
|
+
extensions: [".py"],
|
|
326
|
+
wasmFile: "tree-sitter-python.wasm",
|
|
327
|
+
wasmPackage: "tree-sitter-python",
|
|
328
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-python",
|
|
329
|
+
grammarCommit: "",
|
|
330
|
+
treeSitterCliVersion: "",
|
|
331
|
+
externalScanner: false,
|
|
332
|
+
commentTypes: ["comment"],
|
|
333
|
+
commentDelimiters: ["#"]
|
|
334
|
+
},
|
|
335
|
+
go: {
|
|
336
|
+
id: "go",
|
|
337
|
+
extensions: [".go"],
|
|
338
|
+
wasmFile: "tree-sitter-go.wasm",
|
|
339
|
+
wasmPackage: "tree-sitter-go",
|
|
340
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-go",
|
|
341
|
+
grammarCommit: "",
|
|
342
|
+
treeSitterCliVersion: "",
|
|
343
|
+
externalScanner: false,
|
|
344
|
+
commentTypes: ["comment"],
|
|
345
|
+
commentDelimiters: ["//", "/*"]
|
|
346
|
+
},
|
|
347
|
+
rust: {
|
|
348
|
+
id: "rust",
|
|
349
|
+
extensions: [".rs"],
|
|
350
|
+
wasmFile: "tree-sitter-rust.wasm",
|
|
351
|
+
wasmPackage: "tree-sitter-rust",
|
|
352
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-rust",
|
|
353
|
+
grammarCommit: "",
|
|
354
|
+
treeSitterCliVersion: "",
|
|
355
|
+
externalScanner: false,
|
|
356
|
+
commentTypes: ["line_comment", "block_comment"],
|
|
357
|
+
commentDelimiters: ["//", "/*"]
|
|
358
|
+
},
|
|
359
|
+
java: {
|
|
360
|
+
id: "java",
|
|
361
|
+
extensions: [".java"],
|
|
362
|
+
wasmFile: "tree-sitter-java.wasm",
|
|
363
|
+
wasmPackage: "tree-sitter-java",
|
|
364
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-java",
|
|
365
|
+
grammarCommit: "",
|
|
366
|
+
treeSitterCliVersion: "",
|
|
367
|
+
externalScanner: false,
|
|
368
|
+
commentTypes: ["line_comment", "block_comment"],
|
|
369
|
+
commentDelimiters: ["//", "/*"]
|
|
370
|
+
},
|
|
371
|
+
csharp: {
|
|
372
|
+
id: "csharp",
|
|
373
|
+
extensions: [".cs"],
|
|
374
|
+
wasmFile: "tree-sitter-c_sharp.wasm",
|
|
375
|
+
wasmPackage: "tree-sitter-c-sharp",
|
|
376
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-c-sharp",
|
|
377
|
+
grammarCommit: "",
|
|
378
|
+
treeSitterCliVersion: "",
|
|
379
|
+
externalScanner: false,
|
|
380
|
+
commentTypes: ["comment"],
|
|
381
|
+
commentDelimiters: ["//", "/*"]
|
|
382
|
+
},
|
|
383
|
+
c: {
|
|
384
|
+
id: "c",
|
|
385
|
+
extensions: [".c", ".h"],
|
|
386
|
+
wasmFile: "tree-sitter-c.wasm",
|
|
387
|
+
wasmPackage: "tree-sitter-c",
|
|
388
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-c",
|
|
389
|
+
grammarCommit: "",
|
|
390
|
+
treeSitterCliVersion: "",
|
|
391
|
+
externalScanner: false,
|
|
392
|
+
commentTypes: ["comment"],
|
|
393
|
+
commentDelimiters: ["//", "/*"]
|
|
394
|
+
},
|
|
395
|
+
cpp: {
|
|
396
|
+
id: "cpp",
|
|
397
|
+
extensions: [".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx"],
|
|
398
|
+
wasmFile: "tree-sitter-cpp.wasm",
|
|
399
|
+
wasmPackage: "tree-sitter-cpp",
|
|
400
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-cpp",
|
|
401
|
+
grammarCommit: "",
|
|
402
|
+
treeSitterCliVersion: "",
|
|
403
|
+
externalScanner: false,
|
|
404
|
+
commentTypes: ["comment"],
|
|
405
|
+
commentDelimiters: ["//", "/*"]
|
|
406
|
+
},
|
|
407
|
+
php: {
|
|
408
|
+
id: "php",
|
|
409
|
+
extensions: [".php"],
|
|
410
|
+
wasmFile: "tree-sitter-php_only.wasm",
|
|
411
|
+
wasmPackage: "tree-sitter-php",
|
|
412
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-php",
|
|
413
|
+
grammarCommit: "",
|
|
414
|
+
treeSitterCliVersion: "",
|
|
415
|
+
externalScanner: false,
|
|
416
|
+
commentTypes: ["comment"],
|
|
417
|
+
commentDelimiters: ["//", "#", "/*"]
|
|
418
|
+
},
|
|
419
|
+
ruby: {
|
|
420
|
+
id: "ruby",
|
|
421
|
+
extensions: [".rb"],
|
|
422
|
+
wasmFile: "tree-sitter-ruby.wasm",
|
|
423
|
+
wasmPackage: "tree-sitter-ruby",
|
|
424
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-ruby",
|
|
425
|
+
grammarCommit: "",
|
|
426
|
+
treeSitterCliVersion: "",
|
|
427
|
+
externalScanner: false,
|
|
428
|
+
commentTypes: ["comment"],
|
|
429
|
+
commentDelimiters: ["#"]
|
|
430
|
+
},
|
|
431
|
+
json: {
|
|
432
|
+
id: "json",
|
|
433
|
+
extensions: [".json"],
|
|
434
|
+
wasmFile: "tree-sitter-json.wasm",
|
|
435
|
+
wasmPackage: "tree-sitter-json",
|
|
436
|
+
grammarRepo: "https://github.com/tree-sitter/tree-sitter-json",
|
|
437
|
+
grammarCommit: "",
|
|
438
|
+
treeSitterCliVersion: "",
|
|
439
|
+
externalScanner: false,
|
|
440
|
+
commentTypes: [],
|
|
441
|
+
commentDelimiters: []
|
|
442
|
+
},
|
|
443
|
+
kotlin: {
|
|
444
|
+
id: "kotlin",
|
|
445
|
+
extensions: [".kt", ".kts"],
|
|
446
|
+
wasmFile: "tree-sitter-kotlin.wasm",
|
|
447
|
+
wasmPackage: "@tree-sitter-grammars/tree-sitter-kotlin",
|
|
448
|
+
grammarRepo: "https://github.com/tree-sitter-grammars/tree-sitter-kotlin",
|
|
449
|
+
grammarCommit: "",
|
|
450
|
+
treeSitterCliVersion: "",
|
|
451
|
+
externalScanner: false,
|
|
452
|
+
commentTypes: ["line_comment", "block_comment"],
|
|
453
|
+
commentDelimiters: ["//", "/*"]
|
|
454
|
+
},
|
|
455
|
+
yaml: {
|
|
456
|
+
id: "yaml",
|
|
457
|
+
extensions: [".yaml", ".yml"],
|
|
458
|
+
wasmFile: "tree-sitter-yaml.wasm",
|
|
459
|
+
wasmPackage: "@tree-sitter-grammars/tree-sitter-yaml",
|
|
460
|
+
grammarRepo: "https://github.com/tree-sitter-grammars/tree-sitter-yaml",
|
|
461
|
+
grammarCommit: "",
|
|
462
|
+
treeSitterCliVersion: "",
|
|
463
|
+
externalScanner: false,
|
|
464
|
+
commentTypes: ["comment"],
|
|
465
|
+
commentDelimiters: ["#"]
|
|
466
|
+
},
|
|
467
|
+
toml: {
|
|
468
|
+
id: "toml",
|
|
469
|
+
extensions: [".toml"],
|
|
470
|
+
wasmFile: "tree-sitter-toml.wasm",
|
|
471
|
+
wasmPackage: "@tree-sitter-grammars/tree-sitter-toml",
|
|
472
|
+
grammarRepo: "https://github.com/tree-sitter-grammars/tree-sitter-toml",
|
|
473
|
+
grammarCommit: "",
|
|
474
|
+
treeSitterCliVersion: "",
|
|
475
|
+
externalScanner: false,
|
|
476
|
+
commentTypes: ["comment"],
|
|
477
|
+
commentDelimiters: ["#"]
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
var EXTENSION_TO_LANGUAGE = Object.fromEntries(
|
|
481
|
+
Object.values(LANGUAGES).flatMap((def) => def.extensions.map((ext) => [ext, def.id]))
|
|
482
|
+
);
|
|
483
|
+
function getLanguageForExtension(ext, overrides) {
|
|
484
|
+
if (overrides && ext in overrides) return overrides[ext];
|
|
485
|
+
return EXTENSION_TO_LANGUAGE[ext] ?? null;
|
|
486
|
+
}
|
|
487
|
+
function getGrammarForExtension(ext) {
|
|
488
|
+
const lang = getLanguageForExtension(ext.toLowerCase());
|
|
489
|
+
if (lang === null) return null;
|
|
490
|
+
const def = LANGUAGES[lang];
|
|
491
|
+
if (!def) return null;
|
|
492
|
+
return { wasmFile: def.wasmFile, wasmPackage: def.wasmPackage };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/ast/parser.ts
|
|
496
|
+
var _require = createRequire(import.meta.url);
|
|
497
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
498
|
+
var __dirname2 = path4.dirname(__filename2);
|
|
499
|
+
var GRAMMAR_DIRS = [
|
|
500
|
+
path4.resolve(__dirname2, "grammars"),
|
|
501
|
+
path4.resolve(__dirname2, "..", "grammars")
|
|
502
|
+
];
|
|
503
|
+
var initialized = false;
|
|
504
|
+
var langCache = /* @__PURE__ */ new Map();
|
|
505
|
+
async function init() {
|
|
506
|
+
if (initialized) return;
|
|
507
|
+
await Parser.init();
|
|
508
|
+
initialized = true;
|
|
509
|
+
}
|
|
510
|
+
function resolveWasm(filename, pkg) {
|
|
511
|
+
for (const dir of GRAMMAR_DIRS) {
|
|
512
|
+
const p = path4.join(dir, filename);
|
|
513
|
+
if (existsSync3(p)) return p;
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
const pkgDir = path4.dirname(_require.resolve(`${pkg}/package.json`));
|
|
517
|
+
for (const candidate of [path4.join(pkgDir, filename), path4.join(pkgDir, "bindings/node", filename)]) {
|
|
518
|
+
if (existsSync3(candidate)) return candidate;
|
|
519
|
+
}
|
|
520
|
+
} catch {
|
|
521
|
+
}
|
|
522
|
+
throw new Error(`Could not find WASM grammar ${filename} in dist/grammars/ or in the ${pkg} package.`);
|
|
523
|
+
}
|
|
524
|
+
async function getParser(extension) {
|
|
525
|
+
await init();
|
|
526
|
+
const info = getGrammarForExtension(extension);
|
|
527
|
+
if (!info) {
|
|
528
|
+
throw new Error(`no parser for extension '${extension}'`);
|
|
529
|
+
}
|
|
530
|
+
const cacheKey = info.wasmFile;
|
|
531
|
+
let lang = langCache.get(cacheKey);
|
|
532
|
+
if (!lang) {
|
|
533
|
+
const wasmPath = resolveWasm(info.wasmFile, info.wasmPackage);
|
|
534
|
+
lang = await Language.load(wasmPath);
|
|
535
|
+
langCache.set(cacheKey, lang);
|
|
536
|
+
}
|
|
537
|
+
const parser = new Parser();
|
|
538
|
+
parser.setLanguage(lang);
|
|
539
|
+
return parser;
|
|
540
|
+
}
|
|
541
|
+
async function parseFile(filePath, content) {
|
|
542
|
+
const ext = path4.extname(filePath);
|
|
543
|
+
const parser = await getParser(ext);
|
|
544
|
+
const tree = parser.parse(content);
|
|
545
|
+
if (tree === null) {
|
|
546
|
+
throw new Error(`tree-sitter failed to parse file: ${filePath}`);
|
|
547
|
+
}
|
|
548
|
+
return tree;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/structure/ctx-parsers.ts
|
|
552
|
+
var ParseAstNotPrewarmedError = class extends Error {
|
|
553
|
+
constructor(filePath) {
|
|
554
|
+
super(
|
|
555
|
+
`structure-aspect-parseast-not-prewarmed: ${filePath}. The dispatcher did not prewarm this file. Either (i) add a declared relation to the node owning this file, or (ii) use ctx.parseYaml/Json/Toml if AST is not required.`
|
|
556
|
+
);
|
|
557
|
+
this.filePath = filePath;
|
|
558
|
+
this.name = "ParseAstNotPrewarmedError";
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
function createCtxParsers(params) {
|
|
562
|
+
const { allowedSet, projectRoot, touchedFiles, astCache } = params;
|
|
563
|
+
function asFile(input) {
|
|
564
|
+
if (typeof input !== "string") {
|
|
565
|
+
touchedFiles.push(input.path);
|
|
566
|
+
return input;
|
|
567
|
+
}
|
|
568
|
+
const p = resolveAllowedReadPath(input, allowedSet, projectRoot);
|
|
569
|
+
const abs = path5.resolve(projectRoot, p);
|
|
570
|
+
const content = fs3.readFileSync(abs, "utf8");
|
|
571
|
+
touchedFiles.push(p);
|
|
572
|
+
return { path: p, content };
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
parseAst(file, language) {
|
|
576
|
+
void language;
|
|
577
|
+
const f = asFile(file);
|
|
578
|
+
const cached = astCache.get(f.path);
|
|
579
|
+
if (cached && cached.content === f.content) return cached.ast;
|
|
580
|
+
throw new ParseAstNotPrewarmedError(f.path);
|
|
581
|
+
},
|
|
582
|
+
parseYaml(file) {
|
|
583
|
+
return parseYaml(asFile(file).content);
|
|
584
|
+
},
|
|
585
|
+
parseJson(file) {
|
|
586
|
+
return JSON.parse(asFile(file).content);
|
|
587
|
+
},
|
|
588
|
+
parseToml(file) {
|
|
589
|
+
return parseTomlSmol(asFile(file).content);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
async function prewarmupAstCache(params) {
|
|
594
|
+
const { astCache, files } = params;
|
|
595
|
+
for (const f of files) {
|
|
596
|
+
if (!isAstLanguageExtension(f.path)) continue;
|
|
597
|
+
const existing = astCache.get(f.path);
|
|
598
|
+
if (existing && existing.content === f.content) continue;
|
|
599
|
+
const tree = await parseFile(f.path, f.content);
|
|
600
|
+
astCache.set(f.path, { content: f.content, ast: tree });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function isAstLanguageExtension(p) {
|
|
604
|
+
return getLanguageForExtension(extname(p).toLowerCase()) !== null;
|
|
605
|
+
}
|
|
606
|
+
function enrichFilesWithAst(files, astCache) {
|
|
607
|
+
return files.map((f) => {
|
|
608
|
+
const language = getLanguageForExtension(extname(f.path)) ?? void 0;
|
|
609
|
+
const cached = astCache.get(f.path);
|
|
610
|
+
const ast = cached && cached.content === f.content ? cached.ast : void 0;
|
|
611
|
+
return { ...f, ast, language };
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/structure/allowed-reads.ts
|
|
616
|
+
function collectAllowedReadsForAspect(nodePath, graph) {
|
|
617
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
618
|
+
const node = graph.nodes.get(nodePath);
|
|
619
|
+
if (!node) return allowed;
|
|
620
|
+
const addMapping = (n) => {
|
|
621
|
+
const mapping = n.meta.mapping ?? [];
|
|
622
|
+
for (const raw of mapping) {
|
|
623
|
+
const p = normalizeMappingPath(raw);
|
|
624
|
+
if (p) allowed.add(p);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
const childPaths = /* @__PURE__ */ new Set();
|
|
628
|
+
for (const child of node.children) {
|
|
629
|
+
for (const raw of child.meta.mapping ?? []) {
|
|
630
|
+
const p = normalizeMappingPath(raw);
|
|
631
|
+
if (p) childPaths.add(p);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
for (const raw of node.meta.mapping ?? []) {
|
|
635
|
+
const p = normalizeMappingPath(raw);
|
|
636
|
+
if (p && !childPaths.has(p)) allowed.add(p);
|
|
637
|
+
}
|
|
638
|
+
for (const rel of node.meta.relations ?? []) {
|
|
639
|
+
const target = graph.nodes.get(rel.target);
|
|
640
|
+
if (!target) continue;
|
|
641
|
+
addMapping(target);
|
|
642
|
+
const relStack = [...target.children];
|
|
643
|
+
while (relStack.length > 0) {
|
|
644
|
+
const n = relStack.pop();
|
|
645
|
+
addMapping(n);
|
|
646
|
+
relStack.push(...n.children);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
let cursor = node.parent;
|
|
650
|
+
while (cursor) {
|
|
651
|
+
addMapping(cursor);
|
|
652
|
+
cursor = cursor.parent;
|
|
653
|
+
}
|
|
654
|
+
const parentDirs = /* @__PURE__ */ new Set();
|
|
655
|
+
for (const raw of node.meta.mapping ?? []) {
|
|
656
|
+
const p = normalizeMappingPath(raw);
|
|
657
|
+
if (p) {
|
|
658
|
+
const lastSlash = p.lastIndexOf("/");
|
|
659
|
+
if (lastSlash > 0) {
|
|
660
|
+
parentDirs.add(p.substring(0, lastSlash));
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const siblingCarveOut = /* @__PURE__ */ new Set();
|
|
665
|
+
for (const cp of childPaths) {
|
|
666
|
+
const lastSlash = cp.lastIndexOf("/");
|
|
667
|
+
if (lastSlash > 0) {
|
|
668
|
+
const cpDir = cp.substring(0, lastSlash);
|
|
669
|
+
if (parentDirs.has(cpDir)) {
|
|
670
|
+
siblingCarveOut.add(cp);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const stack = [...node.children];
|
|
675
|
+
while (stack.length > 0) {
|
|
676
|
+
const n = stack.pop();
|
|
677
|
+
for (const raw of n.meta.mapping ?? []) {
|
|
678
|
+
const p = normalizeMappingPath(raw);
|
|
679
|
+
if (p && !siblingCarveOut.has(p)) allowed.add(p);
|
|
680
|
+
}
|
|
681
|
+
stack.push(...n.children);
|
|
682
|
+
}
|
|
683
|
+
return allowed;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/io/hash.ts
|
|
687
|
+
import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
|
|
688
|
+
import path7 from "path";
|
|
689
|
+
import { createHash } from "crypto";
|
|
690
|
+
import { createRequire as createRequire3 } from "module";
|
|
691
|
+
|
|
692
|
+
// src/io/repo-scanner.ts
|
|
693
|
+
import { readFile, readdir } from "fs/promises";
|
|
694
|
+
import { join, relative as relative2, sep } from "path";
|
|
695
|
+
import { createRequire as createRequire2 } from "module";
|
|
696
|
+
|
|
697
|
+
// src/utils/debug-log.ts
|
|
698
|
+
import path6 from "path";
|
|
699
|
+
|
|
700
|
+
// src/io/repo-scanner.ts
|
|
701
|
+
var require2 = createRequire2(import.meta.url);
|
|
702
|
+
var ignoreFactory = require2("ignore");
|
|
703
|
+
|
|
704
|
+
// src/io/hash.ts
|
|
705
|
+
var require3 = createRequire3(import.meta.url);
|
|
706
|
+
var ignoreFactory2 = require3("ignore");
|
|
707
|
+
async function loadRootGitignoreStack2(projectRoot) {
|
|
708
|
+
if (!projectRoot) return [];
|
|
709
|
+
try {
|
|
710
|
+
const content = await readFile2(path7.join(projectRoot, ".gitignore"), "utf-8");
|
|
711
|
+
const matcher = ignoreFactory2();
|
|
712
|
+
matcher.add(content);
|
|
713
|
+
return [{ basePath: projectRoot, matcher }];
|
|
714
|
+
} catch {
|
|
715
|
+
return [];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function isIgnoredByStack2(candidatePath, stack) {
|
|
719
|
+
for (const { basePath, matcher } of stack) {
|
|
720
|
+
const relativePath = toPosix(path7.relative(basePath, candidatePath));
|
|
721
|
+
if (relativePath === "" || relativePath.startsWith("..")) continue;
|
|
722
|
+
if (matcher.ignores(relativePath) || matcher.ignores(relativePath + "/")) return true;
|
|
723
|
+
}
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
function hashString(content) {
|
|
727
|
+
return createHash("sha256").update(content).digest("hex");
|
|
728
|
+
}
|
|
729
|
+
var EMPTY_IDENTITY = { ownSubset: hashString(""), ports: {}, aspects: {} };
|
|
730
|
+
async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
|
|
731
|
+
let stack = options.gitignoreStack ?? [];
|
|
732
|
+
try {
|
|
733
|
+
const localContent = await readFile2(path7.join(directoryPath, ".gitignore"), "utf-8");
|
|
734
|
+
const localMatcher = ignoreFactory2();
|
|
735
|
+
localMatcher.add(localContent);
|
|
736
|
+
stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
|
|
737
|
+
} catch {
|
|
738
|
+
}
|
|
739
|
+
const entries = await readdir2(directoryPath, { withFileTypes: true });
|
|
740
|
+
const dirs = [];
|
|
741
|
+
const files = [];
|
|
742
|
+
for (const entry of entries) {
|
|
743
|
+
const absoluteChildPath = path7.join(directoryPath, entry.name);
|
|
744
|
+
if (isIgnoredByStack2(absoluteChildPath, stack)) continue;
|
|
745
|
+
if (entry.isDirectory()) dirs.push(absoluteChildPath);
|
|
746
|
+
else if (entry.isFile()) files.push(absoluteChildPath);
|
|
747
|
+
}
|
|
748
|
+
const [dirResults, fileStats] = await Promise.all([
|
|
749
|
+
Promise.all(dirs.map((d) => collectDirectoryFilePaths(d, rootDirectoryPath, {
|
|
750
|
+
projectRoot: options.projectRoot,
|
|
751
|
+
gitignoreStack: stack
|
|
752
|
+
}))),
|
|
753
|
+
Promise.all(files.map(async (f) => {
|
|
754
|
+
const fileStat = await stat(f);
|
|
755
|
+
return {
|
|
756
|
+
relPath: toPosixPath(path7.relative(rootDirectoryPath, f)),
|
|
757
|
+
absPath: f,
|
|
758
|
+
mtimeMs: fileStat.mtimeMs
|
|
759
|
+
};
|
|
760
|
+
}))
|
|
761
|
+
]);
|
|
762
|
+
const result = [];
|
|
763
|
+
for (const nested of dirResults) result.push(...nested);
|
|
764
|
+
result.push(...fileStats);
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
async function expandMappingPaths(projectRoot, mappingPaths) {
|
|
768
|
+
const gitignoreStack = await loadRootGitignoreStack2(projectRoot);
|
|
769
|
+
const result = [];
|
|
770
|
+
for (const mp of mappingPaths) {
|
|
771
|
+
const absPath = path7.join(projectRoot, mp);
|
|
772
|
+
try {
|
|
773
|
+
const st = await stat(absPath);
|
|
774
|
+
if (st.isDirectory()) {
|
|
775
|
+
const dirEntries = await collectDirectoryFilePaths(absPath, absPath, {
|
|
776
|
+
projectRoot,
|
|
777
|
+
gitignoreStack
|
|
778
|
+
});
|
|
779
|
+
for (const entry of dirEntries) {
|
|
780
|
+
result.push(toPosixPath(path7.join(mp, entry.relPath)));
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
result.push(toPosixPath(mp));
|
|
784
|
+
}
|
|
785
|
+
} catch {
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return result;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/ast/suppress.ts
|
|
793
|
+
import { extname as extname3 } from "path";
|
|
794
|
+
|
|
795
|
+
// src/ast/find-comments.ts
|
|
796
|
+
import { extname as extname2 } from "path";
|
|
797
|
+
function findComments(target) {
|
|
798
|
+
const hasAst = "ast" in target;
|
|
799
|
+
const hasRootNode = "rootNode" in target;
|
|
800
|
+
if (hasAst && hasRootNode) {
|
|
801
|
+
throw new Error("AST_FINDCOMMENTS_AMBIGUOUS_TARGET: pass either ast or rootNode, not both");
|
|
802
|
+
}
|
|
803
|
+
let language = "language" in target ? target.language : void 0;
|
|
804
|
+
if (language === void 0 && "path" in target) {
|
|
805
|
+
language = getLanguageForExtension(extname2(target.path)) ?? void 0;
|
|
806
|
+
}
|
|
807
|
+
if (language === void 0) {
|
|
808
|
+
throw new Error(
|
|
809
|
+
"AST_FINDCOMMENTS_NO_LANGUAGE: pass a SourceFile whose path has a known extension, or an explicit { language }"
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
const def = LANGUAGES[language];
|
|
813
|
+
if (def === void 0) {
|
|
814
|
+
throw new Error(`AST_FINDCOMMENTS_UNKNOWN_LANGUAGE: '${language}' not in registry`);
|
|
815
|
+
}
|
|
816
|
+
const root = hasAst ? target.ast.rootNode : target.rootNode;
|
|
817
|
+
const out = [];
|
|
818
|
+
for (const type of def.commentTypes) {
|
|
819
|
+
out.push(...root.descendantsOfType(type));
|
|
820
|
+
}
|
|
821
|
+
return out;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/ast/suppress.ts
|
|
825
|
+
var SuppressMarkerError = class extends Error {
|
|
826
|
+
constructor(message, file, line) {
|
|
827
|
+
super(message);
|
|
828
|
+
this.file = file;
|
|
829
|
+
this.line = line;
|
|
830
|
+
this.name = "SuppressMarkerError";
|
|
831
|
+
}
|
|
832
|
+
code = "SUPPRESS_MARKER_MISSING_REASON";
|
|
833
|
+
};
|
|
834
|
+
var RE_SINGLE = /\byg-suppress\(\s*([^)]+?)\s*\)\s*(.+)?$/;
|
|
835
|
+
var RE_DISABLE = /\byg-suppress-disable\(\s*([^)]+?)\s*\)\s*(.+)?$/;
|
|
836
|
+
var RE_ENABLE = /\byg-suppress-enable\(\s*([^)]+?)\s*\)/;
|
|
837
|
+
function commentBody(text) {
|
|
838
|
+
if (text.startsWith("//")) return text.slice(2).trim();
|
|
839
|
+
if (text.startsWith("/*")) return text.replace(/^\/\*+/, "").replace(/\*+\/$/, "").trim();
|
|
840
|
+
return text.trim();
|
|
841
|
+
}
|
|
842
|
+
function splitAspectList(raw) {
|
|
843
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
844
|
+
}
|
|
845
|
+
function makeMarker(kind, match, line, file) {
|
|
846
|
+
const aspectIds = splitAspectList(match[1]);
|
|
847
|
+
const reason = (match[2] ?? "").trim();
|
|
848
|
+
if (reason === "") {
|
|
849
|
+
throw new SuppressMarkerError(
|
|
850
|
+
`yg-suppress${kind === "disable" ? "-disable" : ""}(${match[1]}) missing reason at ${file}:${line}`,
|
|
851
|
+
file,
|
|
852
|
+
line
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
return { kind, aspectIds, reason, line };
|
|
856
|
+
}
|
|
857
|
+
function parseMarker(commentText, line, file) {
|
|
858
|
+
const body = commentBody(commentText);
|
|
859
|
+
let m = body.match(RE_DISABLE);
|
|
860
|
+
if (m) return makeMarker("disable", m, line, file);
|
|
861
|
+
m = body.match(RE_ENABLE);
|
|
862
|
+
if (m) {
|
|
863
|
+
return { kind: "enable", aspectIds: splitAspectList(m[1]), reason: "", line };
|
|
864
|
+
}
|
|
865
|
+
m = body.match(RE_SINGLE);
|
|
866
|
+
if (m) return makeMarker("single", m, line, file);
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
function collectSuppressions(tree, file, totalLines) {
|
|
870
|
+
if (getLanguageForExtension(extname3(file)) === null) {
|
|
871
|
+
return [];
|
|
872
|
+
}
|
|
873
|
+
const comments = findComments({ path: file, ast: tree });
|
|
874
|
+
const markers = [];
|
|
875
|
+
for (const c of comments) {
|
|
876
|
+
const m = parseMarker(c.text, c.startPosition.row + 1, file);
|
|
877
|
+
if (m) markers.push(m);
|
|
878
|
+
}
|
|
879
|
+
markers.sort((a, b) => a.line - b.line);
|
|
880
|
+
const ranges = [];
|
|
881
|
+
const openSpecific = /* @__PURE__ */ new Map();
|
|
882
|
+
let openWildcard = null;
|
|
883
|
+
for (const m of markers) {
|
|
884
|
+
if (m.kind === "single") {
|
|
885
|
+
const isWildcard = m.aspectIds.includes("*");
|
|
886
|
+
ranges.push({ aspectIds: new Set(m.aspectIds), startLine: m.line + 1, endLine: m.line + 1, isWildcard });
|
|
887
|
+
} else if (m.kind === "disable") {
|
|
888
|
+
for (const id of m.aspectIds) {
|
|
889
|
+
if (id === "*") {
|
|
890
|
+
if (openWildcard === null) openWildcard = m.line + 1;
|
|
891
|
+
} else {
|
|
892
|
+
if (!openSpecific.has(id)) openSpecific.set(id, m.line + 1);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
} else {
|
|
896
|
+
for (const id of m.aspectIds) {
|
|
897
|
+
if (id === "*") {
|
|
898
|
+
if (openWildcard !== null) {
|
|
899
|
+
ranges.push({ aspectIds: /* @__PURE__ */ new Set(["*"]), startLine: openWildcard, endLine: m.line - 1, isWildcard: true });
|
|
900
|
+
openWildcard = null;
|
|
901
|
+
}
|
|
902
|
+
} else {
|
|
903
|
+
const start = openSpecific.get(id);
|
|
904
|
+
if (start !== void 0) {
|
|
905
|
+
ranges.push({ aspectIds: /* @__PURE__ */ new Set([id]), startLine: start, endLine: m.line - 1, isWildcard: false });
|
|
906
|
+
openSpecific.delete(id);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (openWildcard !== null) ranges.push({ aspectIds: /* @__PURE__ */ new Set(["*"]), startLine: openWildcard, endLine: totalLines, isWildcard: true });
|
|
913
|
+
for (const [id, start] of openSpecific) {
|
|
914
|
+
ranges.push({ aspectIds: /* @__PURE__ */ new Set([id]), startLine: start, endLine: totalLines, isWildcard: false });
|
|
915
|
+
}
|
|
916
|
+
return ranges;
|
|
917
|
+
}
|
|
918
|
+
function isLineSuppressed(ranges, aspectId, line) {
|
|
919
|
+
return ranges.some((r) => {
|
|
920
|
+
if (line < r.startLine || line > r.endLine) return false;
|
|
921
|
+
return r.isWildcard || r.aspectIds.has(aspectId);
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/utils/validate-check-module.ts
|
|
926
|
+
function validateCheckModuleExport(mod, opts) {
|
|
927
|
+
const { codePrefix, runnerLabel } = opts;
|
|
928
|
+
if (mod.check === void 0) {
|
|
929
|
+
const defaultExport = mod.default;
|
|
930
|
+
if (typeof defaultExport === "function" && defaultExport.name === "check") {
|
|
931
|
+
return {
|
|
932
|
+
ok: false,
|
|
933
|
+
code: `${codePrefix}_CHECK_DEFAULT_EXPORT`,
|
|
934
|
+
message: {
|
|
935
|
+
what: `check.mjs exports 'check' as default, but a NAMED export is required (${runnerLabel}).`,
|
|
936
|
+
why: `The runner imports the named export. A default export is invisible to it.`,
|
|
937
|
+
next: `Change 'export default function check(...)' to 'export function check(...)'.`
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
ok: false,
|
|
943
|
+
code: `${codePrefix}_CHECK_NOT_EXPORTED`,
|
|
944
|
+
message: {
|
|
945
|
+
what: `check.mjs does not export a function named 'check' (${runnerLabel}).`,
|
|
946
|
+
why: `The runner expects 'export function check(ctx) { ... }'.`,
|
|
947
|
+
next: `Add a named export 'check' in check.mjs.`
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
if (typeof mod.check !== "function") {
|
|
952
|
+
return {
|
|
953
|
+
ok: false,
|
|
954
|
+
code: `${codePrefix}_CHECK_NOT_FUNCTION`,
|
|
955
|
+
message: {
|
|
956
|
+
what: `'check' is exported but is not a function (got ${typeof mod.check}).`,
|
|
957
|
+
why: `The runner calls check(ctx).`,
|
|
958
|
+
next: `Re-export check as a function.`
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
const checkFn = mod.check;
|
|
963
|
+
if (checkFn.length !== 1) {
|
|
964
|
+
return {
|
|
965
|
+
ok: false,
|
|
966
|
+
code: `${codePrefix}_CHECK_WRONG_ARITY`,
|
|
967
|
+
message: {
|
|
968
|
+
what: `'check' must accept exactly 1 parameter (ctx); declared arity is ${checkFn.length}.`,
|
|
969
|
+
why: `The runner invokes check(ctx).`,
|
|
970
|
+
next: `Change the signature to function check(ctx).`
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
return { ok: true };
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/structure/runner.ts
|
|
978
|
+
var StructureRunnerError = class extends Error {
|
|
979
|
+
constructor(code, data) {
|
|
980
|
+
super(`${code}: ${data.what}
|
|
981
|
+
${data.why}
|
|
982
|
+
${data.next}`);
|
|
983
|
+
this.code = code;
|
|
984
|
+
this.messageData = data;
|
|
985
|
+
this.name = "StructureRunnerError";
|
|
986
|
+
}
|
|
987
|
+
messageData;
|
|
988
|
+
};
|
|
989
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
990
|
+
".gif",
|
|
991
|
+
".png",
|
|
992
|
+
".jpg",
|
|
993
|
+
".jpeg",
|
|
994
|
+
".webp",
|
|
995
|
+
".bmp",
|
|
996
|
+
".ico",
|
|
997
|
+
".svgz",
|
|
998
|
+
".woff",
|
|
999
|
+
".woff2",
|
|
1000
|
+
".ttf",
|
|
1001
|
+
".otf",
|
|
1002
|
+
".eot",
|
|
1003
|
+
".zip",
|
|
1004
|
+
".gz",
|
|
1005
|
+
".tgz",
|
|
1006
|
+
".tar",
|
|
1007
|
+
".bz2",
|
|
1008
|
+
".7z",
|
|
1009
|
+
".pdf",
|
|
1010
|
+
".mp4",
|
|
1011
|
+
".mov",
|
|
1012
|
+
".webm",
|
|
1013
|
+
".mp3",
|
|
1014
|
+
".wav",
|
|
1015
|
+
".wasm",
|
|
1016
|
+
".bin"
|
|
1017
|
+
]);
|
|
1018
|
+
async function buildOwnFiles(node, projectRoot, touchedFiles) {
|
|
1019
|
+
const childMappingEntries = [];
|
|
1020
|
+
for (const child of node.children) {
|
|
1021
|
+
for (const raw of child.meta.mapping ?? []) {
|
|
1022
|
+
const p = normalizeMappingPath(raw);
|
|
1023
|
+
if (p) childMappingEntries.push(p);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const rawMapping = (node.meta.mapping ?? []).map(normalizeMappingPath).filter((p) => p !== "");
|
|
1027
|
+
const expanded = await expandMappingPaths(projectRoot, rawMapping);
|
|
1028
|
+
const result = [];
|
|
1029
|
+
for (const p of expanded) {
|
|
1030
|
+
if (childMappingEntries.length > 0 && isPathInMapping(p, childMappingEntries)) continue;
|
|
1031
|
+
if (BINARY_EXTENSIONS.has(path8.extname(p).toLowerCase())) continue;
|
|
1032
|
+
const abs = path8.resolve(projectRoot, p);
|
|
1033
|
+
let content;
|
|
1034
|
+
try {
|
|
1035
|
+
content = fs4.readFileSync(abs, "utf8");
|
|
1036
|
+
} catch {
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
result.push({ path: p, content });
|
|
1040
|
+
touchedFiles.push(p);
|
|
1041
|
+
}
|
|
1042
|
+
return result;
|
|
1043
|
+
}
|
|
1044
|
+
async function enumerateMappedFilesAsync(mappingPaths, projectRoot) {
|
|
1045
|
+
const normalized = mappingPaths.map(normalizeMappingPath).filter((p) => p !== "");
|
|
1046
|
+
return expandMappingPaths(projectRoot, normalized);
|
|
1047
|
+
}
|
|
1048
|
+
async function runStructureAspect(params) {
|
|
1049
|
+
ensureLoaderRegistered();
|
|
1050
|
+
const { aspectDir, aspectId, nodePath, graph, projectRoot } = params;
|
|
1051
|
+
const astCache = params.parseCache ?? /* @__PURE__ */ new Map();
|
|
1052
|
+
const touchedFiles = [];
|
|
1053
|
+
const node = graph.nodes.get(nodePath);
|
|
1054
|
+
if (!node) {
|
|
1055
|
+
throw new StructureRunnerError("STRUCTURE_NODE_MISSING", {
|
|
1056
|
+
what: `Node '${nodePath}' not in graph.`,
|
|
1057
|
+
why: `The runner resolves the node by path to load its mapped files and aspects.`,
|
|
1058
|
+
next: `Pass an existing node path, or add the node to the graph.`
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
const aspectDirAbs = path8.isAbsolute(aspectDir) ? aspectDir : path8.resolve(projectRoot, aspectDir);
|
|
1062
|
+
const checkPath = path8.join(aspectDirAbs, "check.mjs");
|
|
1063
|
+
let mod;
|
|
1064
|
+
try {
|
|
1065
|
+
mod = await import(pathToFileURL2(checkPath).href);
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
throw new StructureRunnerError("STRUCTURE_LOADER_RESOLVE_FAILED", {
|
|
1068
|
+
what: `Failed to load check.mjs at ${checkPath}: ${err.message}`,
|
|
1069
|
+
why: `The runner dynamically imports the aspect's check.mjs before invoking it.`,
|
|
1070
|
+
next: `Ensure check.mjs exists at the aspect directory and has no unresolved imports.`
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
const exportCheck = validateCheckModuleExport(mod, {
|
|
1074
|
+
codePrefix: "STRUCTURE",
|
|
1075
|
+
runnerLabel: `aspect '${aspectId}'`
|
|
1076
|
+
});
|
|
1077
|
+
if (!exportCheck.ok) {
|
|
1078
|
+
throw new StructureRunnerError(exportCheck.code, exportCheck.message);
|
|
1079
|
+
}
|
|
1080
|
+
const checkFn = mod.check;
|
|
1081
|
+
const allowedSet = collectAllowedReadsForAspect(nodePath, graph);
|
|
1082
|
+
const ctxFs = createCtxFs({ allowedSet, projectRoot, touchedFiles });
|
|
1083
|
+
const ctxGraph = createCtxGraph({ currentNodePath: nodePath, graph, projectRoot, touchedFiles });
|
|
1084
|
+
const parsers = createCtxParsers({ allowedSet, projectRoot, touchedFiles, astCache });
|
|
1085
|
+
const ownFiles = await buildOwnFiles(node, projectRoot, touchedFiles);
|
|
1086
|
+
await prewarmupAstCache({ astCache, projectRoot, files: ownFiles });
|
|
1087
|
+
const ownFilesEnriched = enrichFilesWithAst(ownFiles, astCache);
|
|
1088
|
+
const ctx = {
|
|
1089
|
+
node: {
|
|
1090
|
+
id: node.path,
|
|
1091
|
+
type: node.meta.type,
|
|
1092
|
+
mapping: node.meta.mapping ?? [],
|
|
1093
|
+
files: ownFilesEnriched,
|
|
1094
|
+
ports: node.meta.ports ?? {}
|
|
1095
|
+
},
|
|
1096
|
+
files: ownFilesEnriched,
|
|
1097
|
+
fs: ctxFs,
|
|
1098
|
+
graph: ctxGraph,
|
|
1099
|
+
parseAst: parsers.parseAst,
|
|
1100
|
+
parseYaml: parsers.parseYaml,
|
|
1101
|
+
parseJson: parsers.parseJson,
|
|
1102
|
+
parseToml: parsers.parseToml
|
|
1103
|
+
};
|
|
1104
|
+
const astInputSet = [...ownFiles];
|
|
1105
|
+
for (const rel of node.meta.relations ?? []) {
|
|
1106
|
+
const target = graph.nodes.get(rel.target);
|
|
1107
|
+
if (!target) continue;
|
|
1108
|
+
for (const p of await enumerateMappedFilesAsync(target.meta.mapping ?? [], projectRoot)) {
|
|
1109
|
+
const abs = path8.resolve(projectRoot, p);
|
|
1110
|
+
try {
|
|
1111
|
+
const content = fs4.readFileSync(abs, "utf8");
|
|
1112
|
+
astInputSet.push({ path: p, content });
|
|
1113
|
+
} catch {
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
await prewarmupAstCache({ astCache, projectRoot, files: astInputSet });
|
|
1118
|
+
let raw;
|
|
1119
|
+
try {
|
|
1120
|
+
raw = checkFn(ctx);
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
if (err instanceof UndeclaredFsReadError) {
|
|
1123
|
+
return {
|
|
1124
|
+
violations: [{
|
|
1125
|
+
message: `Aspect tried to read undeclared path '${err.path}'. Add a relation in yg-node.yaml to the node owning this path.`,
|
|
1126
|
+
kind: "structure-aspect-undeclared-fs-read",
|
|
1127
|
+
file: `.yggdrasil/aspects/${aspectId}/check.mjs`
|
|
1128
|
+
}],
|
|
1129
|
+
touchedFiles: [],
|
|
1130
|
+
succeeded: false
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
if (err instanceof UndeclaredGraphReadError) {
|
|
1134
|
+
return {
|
|
1135
|
+
violations: [{
|
|
1136
|
+
message: `Aspect tried to read undeclared graph node '${err.nodePath}'. Add a relation in yg-node.yaml.`,
|
|
1137
|
+
kind: "structure-aspect-undeclared-graph-read",
|
|
1138
|
+
file: `.yggdrasil/aspects/${aspectId}/check.mjs`
|
|
1139
|
+
}],
|
|
1140
|
+
touchedFiles: [],
|
|
1141
|
+
succeeded: false
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
if (err instanceof ParseAstNotPrewarmedError) {
|
|
1145
|
+
return {
|
|
1146
|
+
violations: [{
|
|
1147
|
+
message: `Aspect called ctx.parseAst on '${err.filePath}', which was not pre-warmed by the dispatcher. Add a declared relation to the node owning this file, or use ctx.parseYaml/Json/Toml if AST is not required.`,
|
|
1148
|
+
kind: "structure-aspect-parseast-not-prewarmed",
|
|
1149
|
+
file: `.yggdrasil/model/${nodePath}/yg-node.yaml`
|
|
1150
|
+
}],
|
|
1151
|
+
touchedFiles: [],
|
|
1152
|
+
succeeded: false
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
throw new StructureRunnerError("STRUCTURE_CHECK_THROWN", {
|
|
1156
|
+
what: `check.mjs threw an exception while running (aspect '${aspectId}').`,
|
|
1157
|
+
why: `${err.message}
|
|
1158
|
+
${err.stack ?? ""}`,
|
|
1159
|
+
next: `Fix the bug in check.mjs and re-run yg approve.`
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
if (raw !== null && typeof raw === "object" && typeof raw.then === "function") {
|
|
1163
|
+
throw new StructureRunnerError("STRUCTURE_CHECK_ASYNC", {
|
|
1164
|
+
what: `check.mjs returned a Promise; only synchronous returns are supported.`,
|
|
1165
|
+
why: `The runner does not await check's return value.`,
|
|
1166
|
+
next: `Refactor check to be synchronous.`
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
if (!Array.isArray(raw)) {
|
|
1170
|
+
throw new StructureRunnerError("STRUCTURE_CHECK_RETURN_SHAPE", {
|
|
1171
|
+
what: `check.mjs returned ${typeof raw}, expected Violation[].`,
|
|
1172
|
+
why: `The runner reports violations from the array returned by check.`,
|
|
1173
|
+
next: `Return [] or Violation[] from check.`
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
const contextFiles = new Set(ownFiles.map((f) => f.path));
|
|
1177
|
+
for (const t of touchedFiles) contextFiles.add(t);
|
|
1178
|
+
const violations = [];
|
|
1179
|
+
for (const v of raw) {
|
|
1180
|
+
if (typeof v !== "object" || v === null || typeof v.message !== "string") {
|
|
1181
|
+
throw new StructureRunnerError("STRUCTURE_CHECK_RETURN_SHAPE", {
|
|
1182
|
+
what: `Violation entry must be an object with a string 'message' field.`,
|
|
1183
|
+
why: `The runner renders each violation from its message and optional file/line.`,
|
|
1184
|
+
next: `Return objects shaped { message: string, file?: string, line?: number } from check.`
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
const vv = v;
|
|
1188
|
+
if (typeof vv.file === "string" && !contextFiles.has(normalizeMappingPath(vv.file))) {
|
|
1189
|
+
throw new StructureRunnerError("STRUCTURE_CHECK_FILE_NOT_IN_CONTEXT", {
|
|
1190
|
+
what: `Violation references file '${vv.file}' not in ctx (own mapping or touched via ctx.fs/ctx.graph).`,
|
|
1191
|
+
why: `Author cannot synthesize violations against files they were not given.`,
|
|
1192
|
+
next: `Return only violations for files in ctx, or declare a relation to the node owning '${vv.file}'.`
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
violations.push(vv);
|
|
1196
|
+
}
|
|
1197
|
+
const rangesByFile = /* @__PURE__ */ new Map();
|
|
1198
|
+
function rangesFor(filePath) {
|
|
1199
|
+
const existing = rangesByFile.get(filePath);
|
|
1200
|
+
if (existing !== void 0) return existing;
|
|
1201
|
+
const cached = astCache.get(filePath);
|
|
1202
|
+
const ranges = cached ? collectSuppressions(cached.ast, filePath, cached.content.split("\n").length) : null;
|
|
1203
|
+
rangesByFile.set(filePath, ranges);
|
|
1204
|
+
return ranges;
|
|
1205
|
+
}
|
|
1206
|
+
const visible = violations.filter((v) => {
|
|
1207
|
+
if (typeof v.file !== "string" || typeof v.line !== "number") return true;
|
|
1208
|
+
const ranges = rangesFor(normalizeMappingPath(v.file));
|
|
1209
|
+
if (!ranges) return true;
|
|
1210
|
+
return !isLineSuppressed(ranges, aspectId, v.line);
|
|
1211
|
+
});
|
|
1212
|
+
return { violations: visible, touchedFiles, succeeded: true };
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// src/ast/walk.ts
|
|
1216
|
+
function walk(node, visitor) {
|
|
1217
|
+
const result = visitor(node);
|
|
1218
|
+
if (result === false) return;
|
|
1219
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
1220
|
+
const child = node.child(i);
|
|
1221
|
+
if (child !== null) walk(child, visitor);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
function closest(node, types) {
|
|
1225
|
+
const typeSet = typeof types === "string" ? /* @__PURE__ */ new Set([types]) : new Set(types);
|
|
1226
|
+
let cur = node.parent;
|
|
1227
|
+
while (cur !== null) {
|
|
1228
|
+
if (typeSet.has(cur.type)) return cur;
|
|
1229
|
+
cur = cur.parent;
|
|
1230
|
+
}
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/ast/report.ts
|
|
1235
|
+
function report(file, node, message) {
|
|
1236
|
+
return {
|
|
1237
|
+
file: file.path,
|
|
1238
|
+
line: node.startPosition.row + 1,
|
|
1239
|
+
column: node.startPosition.column,
|
|
1240
|
+
message
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// src/ast/file-path.ts
|
|
1245
|
+
import { minimatch } from "minimatch";
|
|
1246
|
+
function inFile(file, pattern) {
|
|
1247
|
+
if ("glob" in pattern) return minimatch(file.path, pattern.glob);
|
|
1248
|
+
if ("regex" in pattern) return pattern.regex.test(file.path);
|
|
1249
|
+
if ("contains" in pattern) return file.path.includes(pattern.contains);
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
export {
|
|
1253
|
+
StructureRunnerError,
|
|
1254
|
+
closest,
|
|
1255
|
+
findComments,
|
|
1256
|
+
inFile,
|
|
1257
|
+
report,
|
|
1258
|
+
runStructureAspect,
|
|
1259
|
+
walk
|
|
1260
|
+
};
|
|
1261
|
+
//# sourceMappingURL=structure.js.map
|