dsh-plugin-codegraph-tree-sitter 0.1.5 → 0.1.7
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/lib/index.d.ts +14 -0
- package/lib/index.js +5 -1
- package/lib/minified.d.ts +52 -0
- package/lib/minified.js +58 -0
- package/lib/resolve.d.ts +13 -6
- package/lib/resolve.js +50 -13
- package/lib/walk.d.ts +10 -0
- package/lib/walk.js +23 -6
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export { loadGitignore, matchesGitignore, parseGitignore } from './gitignore.ts'
|
|
|
25
25
|
export type { GitignoreRule } from './gitignore.ts';
|
|
26
26
|
export { resolveWorkspace } from './resolve.ts';
|
|
27
27
|
export type { ExtractedFile, GraphEdge, GraphNode, ResolvedGraph, UnresolvedRef } from './resolve.ts';
|
|
28
|
+
export { isMinifiedName, looksMinified, measureText, MINIFIED_AVERAGE_LINE_LENGTH } from './minified.ts';
|
|
29
|
+
export type { TextShape } from './minified.ts';
|
|
28
30
|
export { SCHEMA_VERSION, writeGraph } from './schema.ts';
|
|
29
31
|
export { isExcluded, walkAndExtract } from './walk.ts';
|
|
30
32
|
export type { WalkConfig, WalkResult } from './walk.ts';
|
|
@@ -80,6 +82,18 @@ export interface Config {
|
|
|
80
82
|
* see `./gitignore.ts`. A project with no root `.gitignore` is unaffected either way.
|
|
81
83
|
*/
|
|
82
84
|
respectGitignore?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Leave generated (bundled or minified) artifacts out of the index instead of parsing them
|
|
87
|
+
* (default true). A workspace that commits its build output — `dist/` in version control so a
|
|
88
|
+
* server can serve it — hands the walk megabytes of bundler output declaring thousands of short
|
|
89
|
+
* mangled names; indexing it both dominates the run's time and collides with real source symbols,
|
|
90
|
+
* making "one unique workspace-wide name wins" refuse calls the duplicates made ambiguous.
|
|
91
|
+
*
|
|
92
|
+
* Two signals, one structural: a `.min.` path segment, or an average line length past
|
|
93
|
+
* {@link MINIFIED_AVERAGE_LINE_LENGTH}. Set false to index such files anyway, or name their
|
|
94
|
+
* directory in `exclude`.
|
|
95
|
+
*/
|
|
96
|
+
skipMinified?: boolean;
|
|
83
97
|
/** Files larger than this are skipped and counted in the report, never parsed (default 2000000). */
|
|
84
98
|
maxFileBytes?: number;
|
|
85
99
|
/** The walk stops discovering new files once this many have been found (default 50000). */
|
package/lib/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export { LANGUAGE_TABLE, languageFor } from "./languages.js";
|
|
|
29
29
|
export { extractFile } from "./extract.js";
|
|
30
30
|
export { loadGitignore, matchesGitignore, parseGitignore } from "./gitignore.js";
|
|
31
31
|
export { resolveWorkspace } from "./resolve.js";
|
|
32
|
+
export { isMinifiedName, looksMinified, measureText, MINIFIED_AVERAGE_LINE_LENGTH } from "./minified.js";
|
|
32
33
|
export { SCHEMA_VERSION, writeGraph } from "./schema.js";
|
|
33
34
|
export { isExcluded, walkAndExtract } from "./walk.js";
|
|
34
35
|
export { createWatcher, nodeWatch } from "./watcher.js";
|
|
@@ -64,6 +65,7 @@ export const Config = z.object({
|
|
|
64
65
|
languages: z.array(z.string()).default([...new Set(LANGUAGE_TABLE.map(spec => spec.language))]),
|
|
65
66
|
exclude: z.array(z.string()).default(DEFAULT_EXCLUDE),
|
|
66
67
|
respectGitignore: z.boolean().default(true),
|
|
68
|
+
skipMinified: z.boolean().default(true),
|
|
67
69
|
maxFileBytes: z.number().default(DEFAULT_MAX_FILE_BYTES),
|
|
68
70
|
maxFiles: z.number().default(DEFAULT_MAX_FILES),
|
|
69
71
|
concurrency: z.number().default(DEFAULT_CONCURRENCY),
|
|
@@ -167,9 +169,10 @@ async function runSync(projectRoot, config) {
|
|
|
167
169
|
* @returns the run's report.
|
|
168
170
|
*/
|
|
169
171
|
async function runIndex(projectRoot, config, signal) {
|
|
170
|
-
const { files, filesSkipped } = await walkAndExtract(projectRoot, {
|
|
172
|
+
const { files, filesSkipped, filesMinified } = await walkAndExtract(projectRoot, {
|
|
171
173
|
exclude: config.exclude,
|
|
172
174
|
respectGitignore: config.respectGitignore,
|
|
175
|
+
skipMinified: config.skipMinified,
|
|
173
176
|
maxFileBytes: config.maxFileBytes,
|
|
174
177
|
maxFiles: config.maxFiles,
|
|
175
178
|
concurrency: config.concurrency,
|
|
@@ -191,6 +194,7 @@ async function runIndex(projectRoot, config, signal) {
|
|
|
191
194
|
projectRoot,
|
|
192
195
|
filesIndexed: files.length,
|
|
193
196
|
filesSkipped,
|
|
197
|
+
filesMinified,
|
|
194
198
|
nodeCount: graph.nodes.length,
|
|
195
199
|
edgeCount: graph.edges.length,
|
|
196
200
|
unresolvedCount: graph.unresolved.length,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of generated (bundled or minified) code, so an index can leave it out.
|
|
3
|
+
*
|
|
4
|
+
* A workspace's build output is not always gitignored: a deployment that ships committed artifacts
|
|
5
|
+
* (`dist/` committed so a server can serve it as-is) hands the walk megabytes of bundler output that
|
|
6
|
+
* is neither source nor hidden from version control. Indexing it is not merely wasted time — those
|
|
7
|
+
* files declare thousands of short, mangled names (`e`, `t`, `n`) that collide with real source
|
|
8
|
+
* symbols, and "one unique workspace-wide name wins" then refuses to resolve every call that the
|
|
9
|
+
* duplicate makes ambiguous.
|
|
10
|
+
*
|
|
11
|
+
* Two signals, both structural rather than name-based on a build tool:
|
|
12
|
+
* - a `.min.` path segment (`bundle.min.js`) — the conventional marker, checked before any read;
|
|
13
|
+
* - an average source line longer than {@link MINIFIED_AVERAGE_LINE_LENGTH} characters, which
|
|
14
|
+
* hand-written code does not reach. Measured on real workspaces: the longest average found in
|
|
15
|
+
* hand-written source is ~70 characters, while bundled output runs from thousands to tens of
|
|
16
|
+
* thousands.
|
|
17
|
+
* @module dsh-plugin-codegraph-tree-sitter/minified
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Average line length at or above which a file is treated as generated output. Chosen with roughly
|
|
21
|
+
* 7× headroom over the widest real source line measured (~70 characters), and far below the narrowest
|
|
22
|
+
* generated sample seen (~5 000).
|
|
23
|
+
*/
|
|
24
|
+
export declare const MINIFIED_AVERAGE_LINE_LENGTH = 500;
|
|
25
|
+
/** A file's size expressed in lines, without allocating one array element per line. */
|
|
26
|
+
export interface TextShape {
|
|
27
|
+
/** Number of newline-separated lines, matching `text.split('\n').length`. */
|
|
28
|
+
readonly lineCount: number;
|
|
29
|
+
/** Mean characters per line, counting the trailing partial line. */
|
|
30
|
+
readonly averageLineLength: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Measure a file's line structure in one pass over its text.
|
|
34
|
+
* @param text - the file's decoded contents.
|
|
35
|
+
* @returns its line count and mean line length. An empty file is one empty line, matching
|
|
36
|
+
* `''.split('\n').length`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function measureText(text: string): TextShape;
|
|
39
|
+
/**
|
|
40
|
+
* Whether a path carries the conventional `.min.` marker, which needs no content read to decide.
|
|
41
|
+
* @param path - project-relative path of the file.
|
|
42
|
+
* @returns whether its own name marks it as minified.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isMinifiedName(path: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Whether a file is generated output the index should leave out.
|
|
47
|
+
* @param path - project-relative path of the file.
|
|
48
|
+
* @param shape - the file's measured line structure.
|
|
49
|
+
* @returns whether its name or its line shape marks it as generated.
|
|
50
|
+
*/
|
|
51
|
+
export declare function looksMinified(path: string, shape: TextShape): boolean;
|
|
52
|
+
//# sourceMappingURL=minified.d.ts.map
|
package/lib/minified.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of generated (bundled or minified) code, so an index can leave it out.
|
|
3
|
+
*
|
|
4
|
+
* A workspace's build output is not always gitignored: a deployment that ships committed artifacts
|
|
5
|
+
* (`dist/` committed so a server can serve it as-is) hands the walk megabytes of bundler output that
|
|
6
|
+
* is neither source nor hidden from version control. Indexing it is not merely wasted time — those
|
|
7
|
+
* files declare thousands of short, mangled names (`e`, `t`, `n`) that collide with real source
|
|
8
|
+
* symbols, and "one unique workspace-wide name wins" then refuses to resolve every call that the
|
|
9
|
+
* duplicate makes ambiguous.
|
|
10
|
+
*
|
|
11
|
+
* Two signals, both structural rather than name-based on a build tool:
|
|
12
|
+
* - a `.min.` path segment (`bundle.min.js`) — the conventional marker, checked before any read;
|
|
13
|
+
* - an average source line longer than {@link MINIFIED_AVERAGE_LINE_LENGTH} characters, which
|
|
14
|
+
* hand-written code does not reach. Measured on real workspaces: the longest average found in
|
|
15
|
+
* hand-written source is ~70 characters, while bundled output runs from thousands to tens of
|
|
16
|
+
* thousands.
|
|
17
|
+
* @module dsh-plugin-codegraph-tree-sitter/minified
|
|
18
|
+
*/
|
|
19
|
+
import { posix } from 'node:path';
|
|
20
|
+
/**
|
|
21
|
+
* Average line length at or above which a file is treated as generated output. Chosen with roughly
|
|
22
|
+
* 7× headroom over the widest real source line measured (~70 characters), and far below the narrowest
|
|
23
|
+
* generated sample seen (~5 000).
|
|
24
|
+
*/
|
|
25
|
+
export const MINIFIED_AVERAGE_LINE_LENGTH = 500;
|
|
26
|
+
/**
|
|
27
|
+
* Measure a file's line structure in one pass over its text.
|
|
28
|
+
* @param text - the file's decoded contents.
|
|
29
|
+
* @returns its line count and mean line length. An empty file is one empty line, matching
|
|
30
|
+
* `''.split('\n').length`.
|
|
31
|
+
*/
|
|
32
|
+
export function measureText(text) {
|
|
33
|
+
let newlines = 0;
|
|
34
|
+
for (let index = 0; index < text.length; index++) {
|
|
35
|
+
if (text.charCodeAt(index) === 10)
|
|
36
|
+
newlines++;
|
|
37
|
+
}
|
|
38
|
+
const lineCount = newlines + 1;
|
|
39
|
+
return { lineCount, averageLineLength: text.length / lineCount };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether a path carries the conventional `.min.` marker, which needs no content read to decide.
|
|
43
|
+
* @param path - project-relative path of the file.
|
|
44
|
+
* @returns whether its own name marks it as minified.
|
|
45
|
+
*/
|
|
46
|
+
export function isMinifiedName(path) {
|
|
47
|
+
return posix.basename(path).includes('.min.');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Whether a file is generated output the index should leave out.
|
|
51
|
+
* @param path - project-relative path of the file.
|
|
52
|
+
* @param shape - the file's measured line structure.
|
|
53
|
+
* @returns whether its name or its line shape marks it as generated.
|
|
54
|
+
*/
|
|
55
|
+
export function looksMinified(path, shape) {
|
|
56
|
+
return isMinifiedName(path) || shape.averageLineLength >= MINIFIED_AVERAGE_LINE_LENGTH;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=minified.js.map
|
package/lib/resolve.d.ts
CHANGED
|
@@ -3,13 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pass 1 collects every definition into a name index. Pass 2 resolves each call site's callee name,
|
|
5
5
|
* in order: (1) the enclosing file imports that name from a relative specifier resolving to a file
|
|
6
|
-
* this run indexed — that file's matching declaration wins; (2) otherwise exactly one definition
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* this run indexed — that file's matching declaration wins; (2) otherwise exactly one definition **in
|
|
7
|
+
* the calling file itself** carries the name — lexical scope settles it before the workspace gets a
|
|
8
|
+
* say, which is what keeps a helper declared in two test files (or a `render` in two components) from
|
|
9
|
+
* being counted as ambiguous; (3) otherwise exactly one definition workspace-wide carries the name —
|
|
10
|
+
* it wins; (4) otherwise the call is recorded in `unresolved` and no `calls` edge is written. An
|
|
11
|
+
* ambiguous edge is worse than a missing one: the model acts on `callers` output, so a confidently
|
|
12
|
+
* wrong caller sends it to edit the wrong file, while a missing caller merely sends it back to text
|
|
13
|
+
* search.
|
|
11
14
|
*
|
|
12
|
-
* Rule
|
|
15
|
+
* Rule 2 is restricted to bare calls. A member call (`x.map()`) names a property of a receiver whose
|
|
16
|
+
* type this resolver does not track, so a same-named local declaration winning it would be a guess
|
|
17
|
+
* rather than a scope resolution — exactly the kind of confidently wrong edge rule 4 exists to avoid.
|
|
18
|
+
*
|
|
19
|
+
* Rule 4's `unresolved` entries are not one uniform signal. Each carries {@link UnresolvedRef.likelyExternal},
|
|
13
20
|
* which is true for a member call (no type information behind its receiver) or a name the file itself
|
|
14
21
|
* imports (a known external origin the specifier just didn't resolve) — both real code that was never a
|
|
15
22
|
* candidate for a workspace edge, as against a bare, undeclared name, which is the actual gap this
|
package/lib/resolve.js
CHANGED
|
@@ -3,13 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pass 1 collects every definition into a name index. Pass 2 resolves each call site's callee name,
|
|
5
5
|
* in order: (1) the enclosing file imports that name from a relative specifier resolving to a file
|
|
6
|
-
* this run indexed — that file's matching declaration wins; (2) otherwise exactly one definition
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* this run indexed — that file's matching declaration wins; (2) otherwise exactly one definition **in
|
|
7
|
+
* the calling file itself** carries the name — lexical scope settles it before the workspace gets a
|
|
8
|
+
* say, which is what keeps a helper declared in two test files (or a `render` in two components) from
|
|
9
|
+
* being counted as ambiguous; (3) otherwise exactly one definition workspace-wide carries the name —
|
|
10
|
+
* it wins; (4) otherwise the call is recorded in `unresolved` and no `calls` edge is written. An
|
|
11
|
+
* ambiguous edge is worse than a missing one: the model acts on `callers` output, so a confidently
|
|
12
|
+
* wrong caller sends it to edit the wrong file, while a missing caller merely sends it back to text
|
|
13
|
+
* search.
|
|
11
14
|
*
|
|
12
|
-
* Rule
|
|
15
|
+
* Rule 2 is restricted to bare calls. A member call (`x.map()`) names a property of a receiver whose
|
|
16
|
+
* type this resolver does not track, so a same-named local declaration winning it would be a guess
|
|
17
|
+
* rather than a scope resolution — exactly the kind of confidently wrong edge rule 4 exists to avoid.
|
|
18
|
+
*
|
|
19
|
+
* Rule 4's `unresolved` entries are not one uniform signal. Each carries {@link UnresolvedRef.likelyExternal},
|
|
13
20
|
* which is true for a member call (no type information behind its receiver) or a name the file itself
|
|
14
21
|
* imports (a known external origin the specifier just didn't resolve) — both real code that was never a
|
|
15
22
|
* candidate for a workspace edge, as against a bare, undeclared name, which is the actual gap this
|
|
@@ -99,6 +106,22 @@ function buildNameIndex(nodesByFile) {
|
|
|
99
106
|
}
|
|
100
107
|
return index;
|
|
101
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Group one file's own nodes by declaration name, for the same-file tier of call resolution.
|
|
111
|
+
* @param nodes - the nodes declared in one file.
|
|
112
|
+
* @returns every name that file declares, mapped to the nodes carrying it.
|
|
113
|
+
*/
|
|
114
|
+
function indexByName(nodes) {
|
|
115
|
+
const index = new Map();
|
|
116
|
+
for (const node of nodes) {
|
|
117
|
+
const existing = index.get(node.name);
|
|
118
|
+
if (existing === undefined)
|
|
119
|
+
index.set(node.name, [node]);
|
|
120
|
+
else
|
|
121
|
+
existing.push(node);
|
|
122
|
+
}
|
|
123
|
+
return index;
|
|
124
|
+
}
|
|
102
125
|
/**
|
|
103
126
|
* A name index restricted to `class`/`interface` nodes, for `extends`/`implements` resolution.
|
|
104
127
|
* `extractHeritage` only ever names a base class or interface, never a function or variable — indexing
|
|
@@ -213,9 +236,10 @@ export function resolveWorkspace(files, now) {
|
|
|
213
236
|
}
|
|
214
237
|
const keyToId = mustGet(defIdByKey, file.path);
|
|
215
238
|
const importedLocalNames = new Set(file.extraction.imports.map(binding => binding.localName));
|
|
239
|
+
const localByName = indexByName(mustGet(nodesByFile, file.path));
|
|
216
240
|
for (const call of file.extraction.calls) {
|
|
217
241
|
const callerId = call.callerKey === null ? mustGet(fileNodeId, file.path) : mustGet(keyToId, call.callerKey);
|
|
218
|
-
const resolved = resolveName(call.calleeName, bindings, nodesByFile, nameIndex);
|
|
242
|
+
const resolved = resolveName(call.calleeName, bindings, nodesByFile, call.isMemberCall ? undefined : localByName, nameIndex);
|
|
219
243
|
if (resolved === undefined) {
|
|
220
244
|
const likelyExternal = call.isMemberCall || importedLocalNames.has(call.calleeName);
|
|
221
245
|
unresolved.push({ source: callerId, filePath: file.path, calleeName: call.calleeName, line: call.line, col: call.column, likelyExternal });
|
|
@@ -228,7 +252,7 @@ export function resolveWorkspace(files, now) {
|
|
|
228
252
|
// on it) is a calibrated signal specifically about call-resolution gaps; folding in a different edge
|
|
229
253
|
// kind would change what that count measures.
|
|
230
254
|
for (const ref of file.extraction.heritage) {
|
|
231
|
-
const resolved = resolveName(ref.targetName, bindings, nodesByFile, typeNameIndex);
|
|
255
|
+
const resolved = resolveName(ref.targetName, bindings, nodesByFile, undefined, typeNameIndex);
|
|
232
256
|
if (resolved === undefined)
|
|
233
257
|
continue;
|
|
234
258
|
edges.push({ source: mustGet(keyToId, ref.sourceKey), target: resolved, kind: ref.relation, provenance: 'tree-sitter' });
|
|
@@ -237,13 +261,21 @@ export function resolveWorkspace(files, now) {
|
|
|
237
261
|
return { nodes, edges, unresolved };
|
|
238
262
|
}
|
|
239
263
|
/**
|
|
240
|
-
* Resolve a bare name to exactly one declaration, or `undefined` per rule
|
|
264
|
+
* Resolve a bare name to exactly one declaration, or `undefined` per rule 4 — shared by call
|
|
241
265
|
* resolution (against the workspace-wide, any-kind `nameIndex`) and `extends`/`implements` resolution
|
|
242
|
-
* (against the `class`/`interface`-only {@link buildTypeNameIndex}); the
|
|
243
|
-
*
|
|
244
|
-
* not depend on which candidate pool the caller
|
|
266
|
+
* (against the `class`/`interface`-only {@link buildTypeNameIndex}); the rule itself (an imported
|
|
267
|
+
* binding wins if unambiguous in its target file, else a same-file declaration when the caller passes
|
|
268
|
+
* one, else a workspace-wide-unique name wins) does not depend on which candidate pool the caller
|
|
269
|
+
* passes in.
|
|
270
|
+
* @param calleeName - the name at the call or heritage site.
|
|
271
|
+
* @param bindings - the calling file's resolved import bindings.
|
|
272
|
+
* @param nodesByFile - every file's declarations, for the import target lookup.
|
|
273
|
+
* @param localByName - the calling file's own declarations, or `undefined` to skip the same-file tier
|
|
274
|
+
* (the caller decides: member calls and heritage references do not get it).
|
|
275
|
+
* @param nameIndex - the workspace-wide candidate pool this reference resolves against.
|
|
276
|
+
* @returns the winning declaration's node id, or `undefined` when nothing settles it.
|
|
245
277
|
*/
|
|
246
|
-
function resolveName(calleeName, bindings, nodesByFile, nameIndex) {
|
|
278
|
+
function resolveName(calleeName, bindings, nodesByFile, localByName, nameIndex) {
|
|
247
279
|
const bound = bindings.byLocalName.get(calleeName);
|
|
248
280
|
if (bound !== undefined) {
|
|
249
281
|
const targetNodes = mustGet(nodesByFile, bound.targetFile);
|
|
@@ -253,6 +285,11 @@ function resolveName(calleeName, bindings, nodesByFile, nameIndex) {
|
|
|
253
285
|
if (candidates.length === 1)
|
|
254
286
|
return candidates[0]?.id;
|
|
255
287
|
}
|
|
288
|
+
// Lexical scope before workspace scope: a name this very file declares is the call's declaration
|
|
289
|
+
// whenever exactly one declaration carries it, no matter how many other files reuse the name.
|
|
290
|
+
const local = localByName?.get(calleeName);
|
|
291
|
+
if (local !== undefined && local.length === 1)
|
|
292
|
+
return local[0]?.id;
|
|
256
293
|
const workspaceWide = nameIndex.get(calleeName) ?? [];
|
|
257
294
|
return workspaceWide.length === 1 ? workspaceWide[0]?.id : undefined;
|
|
258
295
|
}
|
package/lib/walk.d.ts
CHANGED
|
@@ -33,6 +33,12 @@ export interface WalkConfig {
|
|
|
33
33
|
readonly concurrency: number;
|
|
34
34
|
/** Restrict extraction to these seam language labels when set; every loaded grammar otherwise. */
|
|
35
35
|
readonly languages?: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Leave generated (bundled or minified) artifacts out of the index instead of parsing them. See
|
|
38
|
+
* `./minified.ts` for the two signals; a workspace that commits its build output is the case this
|
|
39
|
+
* exists for, since such output is neither gitignored nor declared in `exclude`.
|
|
40
|
+
*/
|
|
41
|
+
readonly skipMinified: boolean;
|
|
36
42
|
/**
|
|
37
43
|
* Also exclude whatever the project root's `.gitignore` names, unioned with `exclude`. A project
|
|
38
44
|
* that compiles to a gitignored directory (`lib`, `dist`, ...) would otherwise hand the same symbol
|
|
@@ -44,7 +50,11 @@ export interface WalkConfig {
|
|
|
44
50
|
/** What the walk produced. */
|
|
45
51
|
export interface WalkResult {
|
|
46
52
|
readonly files: ExtractedFile[];
|
|
53
|
+
/** Candidates skipped for a reason other than being generated output: over `maxFileBytes`, or cut
|
|
54
|
+
* off by `maxFiles`. */
|
|
47
55
|
readonly filesSkipped: number;
|
|
56
|
+
/** Candidates skipped because they are generated output — see {@link WalkConfig.skipMinified}. */
|
|
57
|
+
readonly filesMinified: number;
|
|
48
58
|
}
|
|
49
59
|
/**
|
|
50
60
|
* Walk `projectRoot` and parse every candidate file it finds.
|
package/lib/walk.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createParser } from "./grammar.js";
|
|
|
15
15
|
import { extractFile } from "./extract.js";
|
|
16
16
|
import { loadGitignore, matchesGitignore } from "./gitignore.js";
|
|
17
17
|
import { languageFor } from "./languages.js";
|
|
18
|
+
import { isMinifiedName, looksMinified, measureText } from "./minified.js";
|
|
18
19
|
/**
|
|
19
20
|
* Whether one directory entry is excluded from both the index and (for `watcher.ts`) the watch tree —
|
|
20
21
|
* the single definition both consult, so "what the index covers" and "what gets watched" can never
|
|
@@ -69,8 +70,18 @@ async function discover(projectRoot, config, signal) {
|
|
|
69
70
|
await scan('');
|
|
70
71
|
return { candidates, overflow };
|
|
71
72
|
}
|
|
72
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Parse one candidate.
|
|
75
|
+
* @param candidate - the file to parse.
|
|
76
|
+
* @param config - the walk's bounds.
|
|
77
|
+
* @param signal - aborts the parse.
|
|
78
|
+
* @returns the extracted file, `'minified'` when it was skipped as generated output, or `undefined`
|
|
79
|
+
* when it is over {@link WalkConfig.maxFileBytes}.
|
|
80
|
+
*/
|
|
73
81
|
async function parseCandidate(candidate, config, signal) {
|
|
82
|
+
// Checked before the stat and read: a `.min.` name settles it with no I/O at all.
|
|
83
|
+
if (config.skipMinified && isMinifiedName(candidate.path))
|
|
84
|
+
return 'minified';
|
|
74
85
|
const stats = await stat(candidate.absolute);
|
|
75
86
|
if (stats.size > config.maxFileBytes)
|
|
76
87
|
return undefined;
|
|
@@ -79,6 +90,9 @@ async function parseCandidate(candidate, config, signal) {
|
|
|
79
90
|
if (spec === undefined)
|
|
80
91
|
return undefined;
|
|
81
92
|
const text = await readFile(candidate.absolute, 'utf8');
|
|
93
|
+
const shape = measureText(text);
|
|
94
|
+
if (config.skipMinified && looksMinified(candidate.path, shape))
|
|
95
|
+
return 'minified';
|
|
82
96
|
const parser = await createParser(spec);
|
|
83
97
|
try {
|
|
84
98
|
signal?.throwIfAborted();
|
|
@@ -94,7 +108,7 @@ async function parseCandidate(candidate, config, signal) {
|
|
|
94
108
|
size: stats.size,
|
|
95
109
|
modifiedAt: stats.mtimeMs,
|
|
96
110
|
contentHash: createHash('sha256').update(text).digest('hex'),
|
|
97
|
-
lineCount:
|
|
111
|
+
lineCount: shape.lineCount,
|
|
98
112
|
extraction,
|
|
99
113
|
};
|
|
100
114
|
}
|
|
@@ -135,12 +149,15 @@ export async function walkAndExtract(projectRoot, config, signal) {
|
|
|
135
149
|
const parsed = await mapWithConcurrency(candidates, config.concurrency, candidate => parseCandidate(candidate, config, signal));
|
|
136
150
|
const files = [];
|
|
137
151
|
let filesSkipped = overflow;
|
|
138
|
-
|
|
139
|
-
|
|
152
|
+
let filesMinified = 0;
|
|
153
|
+
for (const result of parsed) {
|
|
154
|
+
if (result === undefined)
|
|
140
155
|
filesSkipped++;
|
|
156
|
+
else if (result === 'minified')
|
|
157
|
+
filesMinified++;
|
|
141
158
|
else
|
|
142
|
-
files.push(
|
|
159
|
+
files.push(result);
|
|
143
160
|
}
|
|
144
|
-
return { files, filesSkipped };
|
|
161
|
+
return { files, filesSkipped, filesMinified };
|
|
145
162
|
}
|
|
146
163
|
//# sourceMappingURL=walk.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-codegraph-tree-sitter",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Self-built code-graph indexer for DeepSeek Harness — parses a workspace with web-tree-sitter and writes schema version 4 to .codegraph/codegraph.db, the same on-disk format the codegraph CLI writes, so no external indexer is required",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
52
52
|
"tree-sitter-wasms": "^0.1.13",
|
|
53
53
|
"web-tree-sitter": "^0.25.10",
|
|
54
|
-
"dsh-plugin-codegraph-service": "^0.1.
|
|
54
|
+
"dsh-plugin-codegraph-service": "^0.1.7"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@deepseek-ai/cordis": ">=4.0.1",
|