@wrongstack/tools 1.0.11 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtin.js +15 -15
- package/dist/{chunk-GU7TZE6Z.js → chunk-2BCRQAIW.js} +7 -7
- package/dist/{chunk-XRNN44QY.js → chunk-2IRHFHZY.js} +235 -71
- package/dist/{chunk-NEYUPZL6.js → chunk-6D3UIQMF.js} +651 -303
- package/dist/chunk-BJ5LXLKE.js +351 -0
- package/dist/{chunk-C6ZOXNXV.js → chunk-CSS42GRP.js} +2 -2
- package/dist/{chunk-SKRMUHTU.js → chunk-HSLDY55L.js} +41 -8
- package/dist/{chunk-ENE22I5W.js → chunk-K37AKCTN.js} +884 -362
- package/dist/{chunk-DKXMDO5F.js → chunk-K3VX6V5P.js} +3 -3
- package/dist/chunk-OIFY4XNP.js +855 -0
- package/dist/{chunk-7GOE2ZTZ.js → chunk-OMWC2OGT.js} +2 -2
- package/dist/{chunk-IE4KDAOB.js → chunk-OYQE2UNT.js} +3 -3
- package/dist/chunk-R3HR3QI3.js +97 -0
- package/dist/{chunk-XPVYZ2S6.js → chunk-RYLMSGR5.js} +2 -2
- package/dist/{chunk-HYHE4VNA.js → chunk-UMOP3VGK.js} +79 -45
- package/dist/{chunk-6OIEYJM4.js → chunk-VMNWQI4V.js} +2 -2
- package/dist/{chunk-2UVAIS5O.js → chunk-YPSVPCIE.js} +2 -2
- package/dist/{chunk-PWZUHZP7.js → chunk-Z77XMU3U.js} +2 -2
- package/dist/codebase-index/ast-invariant-engine.d.ts +19 -2
- package/dist/codebase-index/atlas-projection.d.ts +19 -0
- package/dist/codebase-index/background-indexer.d.ts +14 -0
- package/dist/codebase-index/circuit-breaker.d.ts +7 -0
- package/dist/codebase-index/codebase-ast-replace-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-invariant-check-tool.d.ts +2 -0
- package/dist/codebase-index/codebase-targeted-test-tool.d.ts +26 -0
- package/dist/codebase-index/dead-code-scan.d.ts +4 -0
- package/dist/codebase-index/go-parser.d.ts +23 -3
- package/dist/codebase-index/graph-rank-pass.d.ts +10 -0
- package/dist/codebase-index/graph-rank.d.ts +8 -0
- package/dist/codebase-index/index.d.ts +1 -1
- package/dist/codebase-index/index.js +9 -9
- package/dist/codebase-index/lsp-kind.d.ts +3 -1
- package/dist/codebase-index/module-resolver.d.ts +21 -1
- package/dist/codebase-index/parser-batch.d.ts +6 -8
- package/dist/codebase-index/parser-output.d.ts +5 -1
- package/dist/codebase-index/parser-worker-script.js +4 -4
- package/dist/codebase-index/project-server-framing.d.ts +1 -17
- package/dist/codebase-index/project-server-watcher.d.ts +6 -0
- package/dist/codebase-index/project-server.js +72 -26
- package/dist/codebase-index/py-parser.d.ts +4 -4
- package/dist/codebase-index/rs-parser.d.ts +7 -0
- package/dist/codebase-index/toolchain-scripts.d.ts +64 -0
- package/dist/codebase-index/tree-sitter/queries.d.ts +14 -0
- package/dist/codebase-index/tree-sitter/util.d.ts +6 -1
- package/dist/codebase-index/tree-sitter-parser.d.ts +7 -0
- package/dist/codebase-index/worker.js +23 -16
- package/dist/codebase-index/writer-refs.d.ts +17 -2
- package/dist/codebase-index/writer-search-helpers.d.ts +9 -0
- package/dist/codebase-index/writer.d.ts +20 -1
- package/dist/edit.js +8 -8
- package/dist/{generic-parser-N2PLGBC2.js → generic-parser-IVWLFKMR.js} +2 -2
- package/dist/go-parser-LWV3L5V7.js +197 -0
- package/dist/index.js +17 -17
- package/dist/json-parser-PERV2JHX.js +204 -0
- package/dist/pack.js +16 -16
- package/dist/parser-dispatch-ZSWCQB6M.js +14 -0
- package/dist/patch.js +8 -8
- package/dist/{py-parser-4PFZYAJ7.js → py-parser-B6D6RGH7.js} +4 -4
- package/dist/read.js +8 -8
- package/dist/replace.js +8 -8
- package/dist/rs-parser-L7SP4GWJ.js +182 -0
- package/dist/tool-tier.js +17 -17
- package/dist/{tree-sitter-parser-GILYQLS4.js → tree-sitter-parser-KU6JF3ZC.js} +6 -4
- package/dist/{ts-parser-7W26YB26.js → ts-parser-PML6QIH7.js} +55 -23
- package/dist/write.js +8 -8
- package/dist/{yaml-parser-DFBC5FWH.js → yaml-parser-7BJN544B.js} +41 -6
- package/package.json +5 -5
- package/dist/chunk-4CE3RKEP.js +0 -390
- package/dist/chunk-KHUCKLOR.js +0 -899
- package/dist/chunk-ZKC5UNGD.js +0 -167
- package/dist/go-parser-Z4H6SR3R.js +0 -474
- package/dist/json-parser-IJD77VL4.js +0 -256
- package/dist/parser-dispatch-WG4DNBHH.js +0 -14
- package/dist/rs-parser-GIWPUA3T.js +0 -81
|
@@ -156,6 +156,14 @@ export interface WiringGraphOptions {
|
|
|
156
156
|
fileOf?: ReadonlyMap<number, string> | undefined;
|
|
157
157
|
/** Files each source file imports, as resolved by the module resolver. */
|
|
158
158
|
importsOf?: ReadonlyMap<string, ReadonlySet<string>> | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* True when the target is visible to the source without a file-level import
|
|
161
|
+
* — the same Go package, Java package or C# namespace directory, or a file
|
|
162
|
+
* of a Go package the source imports (the resolver records ONE
|
|
163
|
+
* representative file per imported package). Those edges are ordinary
|
|
164
|
+
* code, not misresolutions, and must not take the contradicted weight.
|
|
165
|
+
*/
|
|
166
|
+
implicitlyVisible?: ((sourceFile: string, targetFile: string, imports: ReadonlySet<string> | undefined) => boolean) | undefined;
|
|
159
167
|
}
|
|
160
168
|
export declare function buildWiringGraph(refs: readonly ResolvedRefEdge[], options?: WiringGraphOptions): WiringGraph;
|
|
161
169
|
/**
|
|
@@ -17,7 +17,6 @@ export { type InvariantEvaluationResult, type InvariantRuleId, type InvariantVio
|
|
|
17
17
|
export { type MutateSymbolOptions, type MutateSymbolResult, replaceSymbolInFile, } from './ast-symbol-mutator.js';
|
|
18
18
|
export { cancelPendingReindexes, checkCodebaseIndexServerHealth, codebaseIndexStats, enqueueReindex, ensureCodebaseIndexServer, fileGraphService, getIndexState, incomingCallsService, isIndexableFile, isIndexing, isIndexReady, onIndexStateChange, outgoingCallsService, packageGraphService, runStartupIndex, searchCodebaseIndex, shutdownCodebaseIndexHost, shutdownCodebaseIndexServer, symbolGraphService, } from './background-indexer.js';
|
|
19
19
|
export { buildBm25Index, buildIndexableText, tokenise, } from './bm25.js';
|
|
20
|
-
export { getCodebaseIndexPerfSnapshot, resetCodebaseIndexPerfMetrics, type CodebaseIndexPerfSnapshot, } from './perf-metrics.js';
|
|
21
20
|
export type { CircuitSnapshot, CircuitState } from './circuit-breaker.js';
|
|
22
21
|
export { CircuitOpenError, IndexCircuitBreaker, IndexTimeoutError, indexCircuitBreaker, resetIndexCircuitBreaker, } from './circuit-breaker.js';
|
|
23
22
|
export { type CodebaseAstReplaceInput, type CodebaseAstReplaceOutput, codebaseAstReplaceTool, } from './codebase-ast-replace-tool.js';
|
|
@@ -37,6 +36,7 @@ export { compileGitignore, type IgnoreMatcher, loadGitignoreMatcher, } from './g
|
|
|
37
36
|
export { runIndexer } from './indexer.js';
|
|
38
37
|
export { detectLang, INDEXABLE_EXTENSIONS, isIndexablePath } from './languages.js';
|
|
39
38
|
export { internalKindToLspKind, lspKindToInternalKind, } from './lsp-kind.js';
|
|
39
|
+
export { type CodebaseIndexPerfSnapshot, getCodebaseIndexPerfSnapshot, resetCodebaseIndexPerfMetrics, } from './perf-metrics.js';
|
|
40
40
|
export type { ProjectIndexServerClientHealth, ProjectIndexServerConnectionState, ProjectIndexServerConnectionStatus, } from './project-server-client.js';
|
|
41
41
|
export { type ProjectIndexDaemonAvailability, resolveProjectIndexDaemonAvailability, } from './project-server-client.js';
|
|
42
42
|
export { projectIndexServerEndpoint, projectIndexServerMetadataPath, } from './project-server-endpoint.js';
|
|
@@ -51,8 +51,8 @@ import {
|
|
|
51
51
|
setContextQueryEmbedder,
|
|
52
52
|
writeAtlas,
|
|
53
53
|
writeProjectAtlas
|
|
54
|
-
} from "../chunk-
|
|
55
|
-
import "../chunk-
|
|
54
|
+
} from "../chunk-K37AKCTN.js";
|
|
55
|
+
import "../chunk-2IRHFHZY.js";
|
|
56
56
|
import "../chunk-UVYRQKPZ.js";
|
|
57
57
|
import "../chunk-SCMRM6NL.js";
|
|
58
58
|
import "../chunk-HDYUBRMF.js";
|
|
@@ -78,11 +78,11 @@ import {
|
|
|
78
78
|
shutdownCodebaseIndexHost,
|
|
79
79
|
shutdownCodebaseIndexServer,
|
|
80
80
|
symbolGraphService
|
|
81
|
-
} from "../chunk-
|
|
81
|
+
} from "../chunk-UMOP3VGK.js";
|
|
82
82
|
import {
|
|
83
83
|
projectIndexServerEndpoint,
|
|
84
84
|
projectIndexServerMetadataPath
|
|
85
|
-
} from "../chunk-
|
|
85
|
+
} from "../chunk-RYLMSGR5.js";
|
|
86
86
|
import {
|
|
87
87
|
CONCEPT_RELATIONS,
|
|
88
88
|
CONTRADICTED_VISIBILITY_WEIGHT,
|
|
@@ -122,14 +122,14 @@ import {
|
|
|
122
122
|
shouldRefreshRanks,
|
|
123
123
|
toSymbolRankRows,
|
|
124
124
|
tokenise
|
|
125
|
-
} from "../chunk-
|
|
126
|
-
import "../chunk-
|
|
127
|
-
import "../chunk-
|
|
128
|
-
import "../chunk-
|
|
125
|
+
} from "../chunk-6D3UIQMF.js";
|
|
126
|
+
import "../chunk-BJ5LXLKE.js";
|
|
127
|
+
import "../chunk-R3HR3QI3.js";
|
|
128
|
+
import "../chunk-HSLDY55L.js";
|
|
129
129
|
import {
|
|
130
130
|
getCodebaseIndexPerfSnapshot,
|
|
131
131
|
resetCodebaseIndexPerfMetrics
|
|
132
|
-
} from "../chunk-
|
|
132
|
+
} from "../chunk-OIFY4XNP.js";
|
|
133
133
|
import {
|
|
134
134
|
INDEXABLE_EXTENSIONS,
|
|
135
135
|
detectLang,
|
|
@@ -36,8 +36,10 @@ export declare enum LSPSymbolKind {
|
|
|
36
36
|
Operator = 25,
|
|
37
37
|
TypeParameter = 26
|
|
38
38
|
}
|
|
39
|
+
/** All internal kinds an LSP kind number covers; empty when it has no equivalent. */
|
|
40
|
+
export declare function lspKindToInternalKinds(k: number): readonly SymbolKind[];
|
|
39
41
|
/**
|
|
40
|
-
* Maps an LSP kind number to
|
|
42
|
+
* Maps an LSP kind number to its primary internal SymbolKind.
|
|
41
43
|
* Returns null if the LSP kind has no equivalent in the internal taxonomy.
|
|
42
44
|
*/
|
|
43
45
|
export declare function lspKindToInternalKind(k: number): SymbolKind | null;
|
|
@@ -66,8 +66,28 @@ export declare class ModuleResolver {
|
|
|
66
66
|
* leading dots the extractor preserves (`.sibling`, `..parent.mod`).
|
|
67
67
|
*/
|
|
68
68
|
private resolvePython;
|
|
69
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* `use crate::a::b`, `use super::x`, `use self::y`, `use other_crate::z`,
|
|
71
|
+
* and `mod x;` declarations.
|
|
72
|
+
*
|
|
73
|
+
* Paths are resolved from the declaring file's MODULE directory, which is
|
|
74
|
+
* only its filesystem directory for `mod.rs`/`lib.rs`/`main.rs`. A plain
|
|
75
|
+
* `src/net.rs` owns `src/net/`: its `mod http;` is `src/net/http.rs`, its
|
|
76
|
+
* `super` is the crate root. Using the file's directory put every
|
|
77
|
+
* `self::`/`super::`/`mod` path from such a file one level too high, so
|
|
78
|
+
* those edges silently resolved to nothing (or to an unrelated file).
|
|
79
|
+
*/
|
|
70
80
|
private resolveRust;
|
|
81
|
+
/**
|
|
82
|
+
* Longest module prefix of `segments` under `base` that is an indexed file.
|
|
83
|
+
* The trailing segments of a `use` are usually items (`net::http::Client::new`),
|
|
84
|
+
* so each is dropped in turn until a module file matches.
|
|
85
|
+
*/
|
|
86
|
+
private lookupRustPath;
|
|
87
|
+
/** The file defining the module whose children live in `dir`. */
|
|
88
|
+
private lookupRustModuleFile;
|
|
89
|
+
/** A crate's root file: the library root, else the binary root. */
|
|
90
|
+
private lookupRustCrateRoot;
|
|
71
91
|
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
72
92
|
private resolveJvm;
|
|
73
93
|
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
@@ -4,16 +4,15 @@
|
|
|
4
4
|
* The single-file parsers spawn one `go run` / `python` child process per
|
|
5
5
|
* file. For a 400-file Go project that is 400 process spawns, each paying
|
|
6
6
|
* toolchain startup; the spawn gate serializes them, so parse time scales
|
|
7
|
-
* linearly with spawn cost. This module runs the SAME extraction
|
|
8
|
-
* a batch: one child process per chunk of files,
|
|
9
|
-
* JSON array, one JSON envelope out per file.
|
|
7
|
+
* linearly with spawn cost. This module runs the SAME extraction core (see
|
|
8
|
+
* `toolchain-scripts.ts`) over a batch: one child process per chunk of files,
|
|
9
|
+
* sources in over stdin as a JSON array, one JSON envelope out per file.
|
|
10
10
|
*
|
|
11
11
|
* Fallback contract: a file the batch could not parse (per-file error in the
|
|
12
12
|
* envelope, or the chunk itself failing) is simply ABSENT from the returned
|
|
13
13
|
* map — the caller re-parses those files through the single-file parser,
|
|
14
14
|
* which owns the per-language fallback semantics (Go: regex extractor;
|
|
15
|
-
* Python: generic regex only when the runtime is missing).
|
|
16
|
-
* per-file path is therefore by construction, not by reimplementation.
|
|
15
|
+
* Python: generic regex only when the runtime is missing).
|
|
17
16
|
*/
|
|
18
17
|
import type { FileSymbols, SymbolLang } from './schema.js';
|
|
19
18
|
export interface BatchFile {
|
|
@@ -25,9 +24,8 @@ export interface BatchFile {
|
|
|
25
24
|
* Generic so callers' extra per-file fields (e.g. source index) survive. */
|
|
26
25
|
export declare function chunkBatchFiles<T extends BatchFile>(files: readonly T[]): T[][];
|
|
27
26
|
/**
|
|
28
|
-
* Test-only: the cached batch-script paths
|
|
29
|
-
*
|
|
30
|
-
* a missing binary still leaves the (already-created) path cached.
|
|
27
|
+
* Test-only: the cached batch-script paths, so a test can observe WHERE the
|
|
28
|
+
* scripts were written without needing a toolchain.
|
|
31
29
|
*/
|
|
32
30
|
export declare function __batchScriptPathsForTest(): {
|
|
33
31
|
go: string | null;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* it, and a stale script must degrade to "symbols but no edges", never to a
|
|
12
12
|
* hard parse failure that drops the file from the index entirely.
|
|
13
13
|
*/
|
|
14
|
-
import type { Ref, SymbolLang } from './schema.js';
|
|
14
|
+
import type { Symbol as IndexSymbol, Ref, SymbolLang } from './schema.js';
|
|
15
15
|
interface RawParsedSymbol {
|
|
16
16
|
name: string;
|
|
17
17
|
kind: string;
|
|
@@ -19,7 +19,11 @@ interface RawParsedSymbol {
|
|
|
19
19
|
col: number;
|
|
20
20
|
signature: string;
|
|
21
21
|
scope: string;
|
|
22
|
+
/** First line of the declaration's doc comment / docstring. */
|
|
23
|
+
doc: string;
|
|
22
24
|
}
|
|
25
|
+
/** Map decoded parser symbols onto index rows for `file`. */
|
|
26
|
+
export declare function toIndexSymbols(raw: readonly RawParsedSymbol[], file: string, lang: SymbolLang): IndexSymbol[];
|
|
23
27
|
interface ParsedParserOutput {
|
|
24
28
|
symbols: RawParsedSymbol[];
|
|
25
29
|
refs: Ref[];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseFilesContent
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-BJ5LXLKE.js";
|
|
4
|
+
import "../chunk-R3HR3QI3.js";
|
|
5
|
+
import "../chunk-HSLDY55L.js";
|
|
6
|
+
import "../chunk-OIFY4XNP.js";
|
|
7
7
|
import "../chunk-YOHO4DVT.js";
|
|
8
8
|
import "../chunk-XZLVJD56.js";
|
|
9
9
|
|
|
@@ -9,21 +9,5 @@ import type { ClientState } from './project-server-types.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const MAX_CLIENT_WRITE_BUFFER_BYTES: number;
|
|
11
11
|
export declare function sendServerMessage(state: ClientState, message: ProjectServerMessage): void;
|
|
12
|
-
|
|
13
|
-
* Frame reader for one client connection. Each frame is sniffed per first
|
|
14
|
-
* byte: `0x57` ('W') → length-prefixed MessagePack, anything else →
|
|
15
|
-
* newline-delimited JSON text. Bytes stay raw until a complete frame is
|
|
16
|
-
* assembled, so a multibyte UTF-8 sequence split across chunks is only ever
|
|
17
|
-
* decoded as part of a finished line (raw `0x0a` only occurs as the JSON
|
|
18
|
-
* delimiter — inside JSON strings `\n` is escaped — so a complete line is
|
|
19
|
-
* always complete UTF-8).
|
|
20
|
-
*
|
|
21
|
-
* The buffer is consumed with a scan offset rather than re-slicing after
|
|
22
|
-
* every frame: each parsed frame advances `scan` within the same Buffer,
|
|
23
|
-
* and the leftover tail is compacted once at the end of the call. A burst
|
|
24
|
-
* of small frames therefore costs one allocation per chunk, not one
|
|
25
|
-
* per frame (the previous slice-per-frame loop reallocated on every frame
|
|
26
|
-
* while pinning the whole parent buffer).
|
|
27
|
-
*/
|
|
28
|
-
export declare function consumeClientChunk(state: ClientState, chunk: Buffer, onMessage: (state: ClientState, message: ProjectServerClientMessage) => void): void;
|
|
12
|
+
export declare function consumeClientChunk(state: ClientState, chunk: Buffer, onMessage: (state: ClientState, message: ProjectServerClientMessage) => void | Promise<void>): void;
|
|
29
13
|
//# sourceMappingURL=project-server-framing.d.ts.map
|
|
@@ -2,6 +2,12 @@ import type { ClientState } from './project-server-types.js';
|
|
|
2
2
|
export declare const DEFAULT_EXTERNAL_DEBOUNCE_MS = 400;
|
|
3
3
|
export declare const DEFAULT_EXTERNAL_COALESCE_WINDOW_MS = 50;
|
|
4
4
|
export declare function isIgnoredRelativePath(relativePath: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Whether a non-indexable `rename` path may be a directory whose files the
|
|
7
|
+
* index holds: an existing directory, or a vanished path without an
|
|
8
|
+
* extension (a deleted `.png` must not trigger an index write run).
|
|
9
|
+
*/
|
|
10
|
+
export declare function isDirectoryWatchCandidate(absolute: string): Promise<boolean>;
|
|
5
11
|
export interface ProjectServerWatcherOptions {
|
|
6
12
|
projectRoot: string;
|
|
7
13
|
onFilesChanged: (files: string[]) => void;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
projectIndexServerBuildId,
|
|
11
11
|
projectIndexServerEndpoint,
|
|
12
12
|
projectIndexServerMetadataPath
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-RYLMSGR5.js";
|
|
14
14
|
import {
|
|
15
15
|
contextService,
|
|
16
16
|
fileGraphService,
|
|
@@ -24,13 +24,13 @@ import {
|
|
|
24
24
|
statsService,
|
|
25
25
|
symbolGraphService,
|
|
26
26
|
vectorSearchService
|
|
27
|
-
} from "../chunk-
|
|
28
|
-
import "../chunk-
|
|
29
|
-
import "../chunk-
|
|
30
|
-
import "../chunk-
|
|
27
|
+
} from "../chunk-6D3UIQMF.js";
|
|
28
|
+
import "../chunk-BJ5LXLKE.js";
|
|
29
|
+
import "../chunk-R3HR3QI3.js";
|
|
30
|
+
import "../chunk-HSLDY55L.js";
|
|
31
31
|
import {
|
|
32
32
|
recordWriteQueueWait
|
|
33
|
-
} from "../chunk-
|
|
33
|
+
} from "../chunk-OIFY4XNP.js";
|
|
34
34
|
import {
|
|
35
35
|
isIndexablePath
|
|
36
36
|
} from "../chunk-YOHO4DVT.js";
|
|
@@ -39,7 +39,7 @@ import "../chunk-XZLVJD56.js";
|
|
|
39
39
|
|
|
40
40
|
// src/codebase-index/project-server.ts
|
|
41
41
|
import { randomBytes } from "node:crypto";
|
|
42
|
-
import * as
|
|
42
|
+
import * as fs3 from "node:fs";
|
|
43
43
|
import * as net from "node:net";
|
|
44
44
|
import * as path3 from "node:path";
|
|
45
45
|
import { startSharedHeapWatchdog, useDaemonPerfDefaults } from "@wrongstack/core/utils";
|
|
@@ -58,6 +58,29 @@ function sendServerMessage(state, message) {
|
|
|
58
58
|
state.binary ? encodeBinaryFrame(message) : encodeProjectServerMessage(message)
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
|
+
function isClientMessage(value) {
|
|
62
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string";
|
|
63
|
+
}
|
|
64
|
+
function rejectConnection(state, reason) {
|
|
65
|
+
state.buffer = Buffer.alloc(0);
|
|
66
|
+
if (!state.socket.destroyed) state.socket.destroy(new Error(reason));
|
|
67
|
+
}
|
|
68
|
+
function dispatchClientMessage(state, message, onMessage) {
|
|
69
|
+
const fail = (error) => {
|
|
70
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
71
|
+
process.stderr.write(`codebase-index server: client message handler failed: ${detail}
|
|
72
|
+
`);
|
|
73
|
+
rejectConnection(state, "codebase-index client message handler failed");
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
const pending = onMessage(state, message);
|
|
77
|
+
if (pending && typeof pending.then === "function") pending.then(void 0, fail);
|
|
78
|
+
return true;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
fail(error);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
61
84
|
function consumeClientChunk(state, chunk, onMessage) {
|
|
62
85
|
const buffer = state.buffer.length === 0 ? chunk : Buffer.concat([state.buffer, chunk]);
|
|
63
86
|
let scan = 0;
|
|
@@ -73,18 +96,21 @@ function consumeClientChunk(state, chunk, onMessage) {
|
|
|
73
96
|
}
|
|
74
97
|
if (buffer.length - scan < 5 + frameLen) break;
|
|
75
98
|
const payload = buffer.subarray(scan + 5, scan + 5 + frameLen);
|
|
76
|
-
let
|
|
99
|
+
let decoded;
|
|
77
100
|
try {
|
|
78
|
-
|
|
101
|
+
decoded = decodeBinaryFrame(payload);
|
|
79
102
|
} catch {
|
|
80
|
-
state
|
|
81
|
-
|
|
103
|
+
rejectConnection(state, "invalid codebase-index client binary frame");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!isClientMessage(decoded)) {
|
|
107
|
+
rejectConnection(state, "invalid codebase-index client binary frame");
|
|
82
108
|
return;
|
|
83
109
|
}
|
|
84
110
|
scan += 5 + frameLen;
|
|
85
111
|
state.binary = true;
|
|
86
112
|
state.lastSeenAt = Date.now();
|
|
87
|
-
|
|
113
|
+
if (!dispatchClientMessage(state, decoded, onMessage)) return;
|
|
88
114
|
continue;
|
|
89
115
|
}
|
|
90
116
|
const newline = buffer.indexOf(10, scan);
|
|
@@ -104,16 +130,19 @@ function consumeClientChunk(state, chunk, onMessage) {
|
|
|
104
130
|
const line = buffer.subarray(scan, newline).toString("utf8");
|
|
105
131
|
scan = newline + 1;
|
|
106
132
|
if (!line) continue;
|
|
107
|
-
let
|
|
133
|
+
let parsed2;
|
|
108
134
|
try {
|
|
109
|
-
|
|
135
|
+
parsed2 = JSON.parse(line);
|
|
110
136
|
} catch {
|
|
111
|
-
state
|
|
112
|
-
|
|
137
|
+
rejectConnection(state, "invalid codebase-index client message");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!isClientMessage(parsed2)) {
|
|
141
|
+
rejectConnection(state, "invalid codebase-index client message");
|
|
113
142
|
return;
|
|
114
143
|
}
|
|
115
144
|
state.lastSeenAt = Date.now();
|
|
116
|
-
|
|
145
|
+
if (!dispatchClientMessage(state, parsed2, onMessage)) return;
|
|
117
146
|
}
|
|
118
147
|
state.buffer = scan === 0 ? buffer : Buffer.from(buffer.subarray(scan));
|
|
119
148
|
}
|
|
@@ -475,6 +504,7 @@ async function dispatchOperation(ctx, state, message) {
|
|
|
475
504
|
}
|
|
476
505
|
|
|
477
506
|
// src/codebase-index/project-server-watcher.ts
|
|
507
|
+
import * as fs2 from "node:fs/promises";
|
|
478
508
|
import * as path2 from "node:path";
|
|
479
509
|
import {
|
|
480
510
|
DEFAULT_WALK_IGNORE_SET,
|
|
@@ -485,6 +515,14 @@ var DEFAULT_EXTERNAL_COALESCE_WINDOW_MS = 50;
|
|
|
485
515
|
function isIgnoredRelativePath(relativePath) {
|
|
486
516
|
return relativePath.split(/[/\\]/u).some((segment) => DEFAULT_WALK_IGNORE_SET.has(segment));
|
|
487
517
|
}
|
|
518
|
+
async function isDirectoryWatchCandidate(absolute) {
|
|
519
|
+
try {
|
|
520
|
+
return (await fs2.stat(absolute)).isDirectory();
|
|
521
|
+
} catch (err) {
|
|
522
|
+
const code = err?.code;
|
|
523
|
+
return (code === "ENOENT" || code === "ENOTDIR") && path2.extname(absolute) === "";
|
|
524
|
+
}
|
|
525
|
+
}
|
|
488
526
|
var ProjectServerWatcherManager = class {
|
|
489
527
|
constructor(options) {
|
|
490
528
|
this.options = options;
|
|
@@ -525,14 +563,21 @@ var ProjectServerWatcherManager = class {
|
|
|
525
563
|
const projectRoot2 = this.options.projectRoot;
|
|
526
564
|
this.externalWatcher = watchProjectTree(
|
|
527
565
|
projectRoot2,
|
|
528
|
-
({ filename }) => {
|
|
566
|
+
({ eventType, filename }) => {
|
|
529
567
|
if (!filename || isIgnoredRelativePath(filename)) return;
|
|
530
568
|
const absolute = path2.resolve(projectRoot2, filename);
|
|
531
569
|
const relative2 = path2.relative(projectRoot2, absolute);
|
|
532
|
-
if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2)
|
|
570
|
+
if (relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2)) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (isIndexablePath(absolute)) {
|
|
574
|
+
this.enqueueExternalFile(absolute);
|
|
533
575
|
return;
|
|
534
576
|
}
|
|
535
|
-
|
|
577
|
+
if (eventType !== "rename") return;
|
|
578
|
+
void isDirectoryWatchCandidate(absolute).then((candidate) => {
|
|
579
|
+
if (candidate && this.externalWatcher) this.enqueueExternalFile(absolute);
|
|
580
|
+
});
|
|
536
581
|
},
|
|
537
582
|
{
|
|
538
583
|
onError: () => {
|
|
@@ -910,11 +955,12 @@ async function handleMessage(state, message) {
|
|
|
910
955
|
const previousDebounceMs = state.debounceMs;
|
|
911
956
|
const previousCoalesceWindowMs = state.coalesceWindowMs;
|
|
912
957
|
try {
|
|
913
|
-
|
|
914
|
-
state.
|
|
915
|
-
state.
|
|
916
|
-
|
|
917
|
-
message.coalesceWindowMs
|
|
958
|
+
const nonNegative = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : fallback;
|
|
959
|
+
state.watchExternal = message.watchExternal === true;
|
|
960
|
+
state.debounceMs = nonNegative(message.debounceMs, DEFAULT_EXTERNAL_DEBOUNCE_MS);
|
|
961
|
+
state.coalesceWindowMs = nonNegative(
|
|
962
|
+
message.coalesceWindowMs,
|
|
963
|
+
DEFAULT_EXTERNAL_COALESCE_WINDOW_MS
|
|
918
964
|
);
|
|
919
965
|
watcherManager.reconcile(clients);
|
|
920
966
|
send(state, {
|
|
@@ -1054,7 +1100,7 @@ async function stop(_reason) {
|
|
|
1054
1100
|
});
|
|
1055
1101
|
if (process.platform !== "win32") {
|
|
1056
1102
|
try {
|
|
1057
|
-
|
|
1103
|
+
fs3.rmSync(endpoint, { force: true });
|
|
1058
1104
|
} catch {
|
|
1059
1105
|
}
|
|
1060
1106
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Python source symbol extraction using the `ast` module.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Runs the extraction program from `toolchain-scripts.ts` under the resolved
|
|
5
|
+
* interpreter, with the source on stdin, and decodes its JSON.
|
|
6
6
|
*
|
|
7
|
-
* Extracts: class, function,
|
|
7
|
+
* Extracts: class, function, method, property, const, var, type
|
|
8
8
|
*/
|
|
9
9
|
import type { FileSymbols, SymbolLang } from './schema.js';
|
|
10
10
|
/**
|
|
@@ -23,7 +23,7 @@ export declare function parseSymbols(opts: {
|
|
|
23
23
|
export { detectLang } from './languages.js';
|
|
24
24
|
/**
|
|
25
25
|
* The resolved Python binary (or null when no runtime is available).
|
|
26
|
-
* Shared with the
|
|
26
|
+
* Shared with the batch parser so both paths run the same interpreter.
|
|
27
27
|
*/
|
|
28
28
|
export declare function resolvePythonBinary(): Promise<string | null>;
|
|
29
29
|
//# sourceMappingURL=py-parser.d.ts.map
|
|
@@ -27,4 +27,11 @@ export declare function parseSymbols(opts: {
|
|
|
27
27
|
lang: SymbolLang;
|
|
28
28
|
}): Promise<FileSymbols>;
|
|
29
29
|
export { detectLang } from './languages.js';
|
|
30
|
+
/**
|
|
31
|
+
* Replace comments and string/char literal contents with spaces, keeping every
|
|
32
|
+
* offset and newline intact so match positions map straight back to the
|
|
33
|
+
* original source. Quote characters survive, so the lifetime guard
|
|
34
|
+
* (`'static`) still sees its apostrophe.
|
|
35
|
+
*/
|
|
36
|
+
export declare function maskRustNonCode(src: string): string;
|
|
30
37
|
//# sourceMappingURL=rs-parser.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The out-of-process parser programs (Go's `go/ast`, Python's `ast`) and the
|
|
3
|
+
* plumbing that runs them.
|
|
4
|
+
*
|
|
5
|
+
* The single-file parsers (`go-parser.ts`, `py-parser.ts`) and the batch
|
|
6
|
+
* parsers (`parser-batch.ts`) used to embed two hand-maintained copies of each
|
|
7
|
+
* program. The copies drifted: the Go batch reported 1-based columns while the
|
|
8
|
+
* single-file script reported 0-based ones, so the same file indexed with
|
|
9
|
+
* different positions depending on which path parsed it. Each language now has
|
|
10
|
+
* ONE extraction core; the single-file and batch programs differ only in their
|
|
11
|
+
* `main` (one source on stdin vs. a JSON array of sources).
|
|
12
|
+
*/
|
|
13
|
+
/** One Go source on stdin → `{"symbols": [...], "refs": [...]}`. */
|
|
14
|
+
export declare const GO_PARSE_SCRIPT = "\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype Sym struct {\n\tName string `json:\"name\"`\n\tKind string `json:\"kind\"`\n\tLine int `json:\"line\"`\n\tCol int `json:\"col\"`\n\tSignature string `json:\"signature\"`\n\tScope string `json:\"scope\"`\n\tDoc string `json:\"doc\"`\n}\n\n// Ref is a cross-reference emitted alongside the symbols, so one run yields\n// both. Module is the import path for CallType \"import\", else empty.\ntype Ref struct {\n\tToName string `json:\"toName\"`\n\tCallType string `json:\"callType\"`\n\tLine int `json:\"line\"`\n\tModule string `json:\"module\"`\n}\n\nfunc extract(src []byte) ([]Sym, []Ref, error) {\n\tfset := token.NewFileSet()\n\tnode, err := parser.ParseFile(fset, \"src.go\", src, parser.ParseComments)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tpkg := node.Name.Name\n\t// token columns are 1-based; the index schema's are 0-based.\n\tat := func(p token.Pos) (int, int) {\n\t\tpos := fset.Position(p)\n\t\treturn pos.Line, pos.Column - 1\n\t}\n\n\tsyms := []Sym{}\n\tfor _, decl := range node.Decls {\n\t\tswitch d := decl.(type) {\n\t\tcase *ast.FuncDecl:\n\t\t\tline, col := at(d.Pos())\n\t\t\tkind := \"function\"\n\t\t\tscope := pkg + \".\" + d.Name.Name\n\t\t\tif d.Recv != nil && len(d.Recv.List) > 0 {\n\t\t\t\tkind = \"method\"\n\t\t\t\tscope = pkg + \".\" + recvTypeName(d.Recv.List[0].Type) + \".\" + d.Name.Name\n\t\t\t}\n\t\t\tsyms = append(syms, Sym{Name: d.Name.Name, Kind: kind, Line: line, Col: col, Signature: formatFuncSig(d), Scope: scope, Doc: firstDocLine(d.Doc)})\n\n\t\tcase *ast.GenDecl:\n\t\t\t// An ungrouped declaration carries its comment on the GenDecl; a\n\t\t\t// grouped one's doc describes the group, not each member.\n\t\t\tvar declDoc *ast.CommentGroup\n\t\t\tif !d.Lparen.IsValid() {\n\t\t\t\tdeclDoc = d.Doc\n\t\t\t}\n\t\t\tfor _, spec := range d.Specs {\n\t\t\t\tswitch s := spec.(type) {\n\t\t\t\tcase *ast.TypeSpec:\n\t\t\t\t\tline, col := at(s.Pos())\n\t\t\t\t\tsig := \"type \" + s.Name.Name + formatTypeParams(s.TypeParams)\n\t\t\t\t\tif s.Assign.IsValid() {\n\t\t\t\t\t\tsig += \" =\"\n\t\t\t\t\t}\n\t\t\t\t\tsig += \" \" + formatType(s.Type)\n\t\t\t\t\tsyms = append(syms, Sym{Name: s.Name.Name, Kind: \"type\", Line: line, Col: col, Signature: sig, Scope: pkg, Doc: firstDocLine(s.Doc, declDoc)})\n\t\t\t\tcase *ast.ValueSpec:\n\t\t\t\t\tkind := \"var\"\n\t\t\t\t\tif d.Tok == token.CONST {\n\t\t\t\t\t\tkind = \"const\"\n\t\t\t\t\t}\n\t\t\t\t\tfor _, n := range s.Names {\n\t\t\t\t\t\tline, col := at(n.Pos())\n\t\t\t\t\t\tsig := kind + \" \" + n.Name\n\t\t\t\t\t\tif s.Type != nil {\n\t\t\t\t\t\t\tsig += \" \" + formatType(s.Type)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsyms = append(syms, Sym{Name: n.Name, Kind: kind, Line: line, Col: col, Signature: sig, Scope: pkg, Doc: firstDocLine(s.Doc, declDoc)})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\trefs := []Ref{}\n\tast.Inspect(node, func(n ast.Node) bool {\n\t\tswitch expr := n.(type) {\n\t\tcase *ast.CallExpr:\n\t\t\tline := fset.Position(expr.Pos()).Line\n\t\t\tswitch fun := expr.Fun.(type) {\n\t\t\tcase *ast.Ident:\n\t\t\t\trefs = append(refs, Ref{ToName: fun.Name, CallType: \"call\", Line: line})\n\t\t\tcase *ast.SelectorExpr:\n\t\t\t\t// The selected name (`Join` of `filepath.Join`) is the declared\n\t\t\t\t// symbol name, so it resolves like the TS and Python call refs.\n\t\t\t\trefs = append(refs, Ref{ToName: fun.Sel.Name, CallType: \"call\", Line: line})\n\t\t\t}\n\t\tcase *ast.ImportSpec:\n\t\t\tif expr.Path != nil {\n\t\t\t\tif importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {\n\t\t\t\t\tline := fset.Position(expr.Pos()).Line\n\t\t\t\t\t// A Go import names a package; its last path segment is the\n\t\t\t\t\t// name it is referenced by.\n\t\t\t\t\tname := importPath\n\t\t\t\t\tif idx := strings.LastIndex(importPath, \"/\"); idx >= 0 {\n\t\t\t\t\t\tname = importPath[idx+1:]\n\t\t\t\t\t}\n\t\t\t\t\trefs = append(refs, Ref{ToName: name, CallType: \"import\", Line: line, Module: importPath})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn syms, refs, nil\n}\n\nfunc firstDocLine(groups ...*ast.CommentGroup) string {\n\tfor _, g := range groups {\n\t\tif g == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, line := range strings.Split(g.Text(), \"\\n\") {\n\t\t\tif t := strings.TrimSpace(line); t != \"\" {\n\t\t\t\tif r := []rune(t); len(r) > 200 {\n\t\t\t\t\tt = string(r[:200])\n\t\t\t\t}\n\t\t\t\treturn t\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// recvTypeName unwraps pointers, parens and generic instantiation:\n// `(l *List[T])` is a method of List, not of \"?\".\nfunc recvTypeName(t ast.Expr) string {\n\tswitch v := t.(type) {\n\tcase *ast.Ident:\n\t\treturn v.Name\n\tcase *ast.StarExpr:\n\t\treturn recvTypeName(v.X)\n\tcase *ast.ParenExpr:\n\t\treturn recvTypeName(v.X)\n\tcase *ast.IndexExpr:\n\t\treturn recvTypeName(v.X)\n\tcase *ast.IndexListExpr:\n\t\treturn recvTypeName(v.X)\n\tdefault:\n\t\treturn \"?\"\n\t}\n}\n\nfunc formatFuncSig(d *ast.FuncDecl) string {\n\tsig := \"func \"\n\tif d.Recv != nil && len(d.Recv.List) > 0 {\n\t\tsig += formatFieldList(d.Recv.List) + \" \"\n\t}\n\treturn sig + d.Name.Name + formatTypeParams(d.Type.TypeParams) + formatFuncType(d.Type)\n}\n\nfunc fieldsOf(fl *ast.FieldList) []*ast.Field {\n\tif fl == nil {\n\t\treturn nil\n\t}\n\treturn fl.List\n}\n\nfunc formatFuncType(f *ast.FuncType) string {\n\tout := formatFieldList(fieldsOf(f.Params))\n\tresults := fieldsOf(f.Results)\n\tif len(results) == 1 && len(results[0].Names) == 0 {\n\t\tout += \" \" + formatType(results[0].Type)\n\t} else if len(results) > 0 {\n\t\tout += \" \" + formatFieldList(results)\n\t}\n\treturn out\n}\n\n// formatField keeps every name of a shared-type field: `a, b int`.\nfunc formatField(f *ast.Field) string {\n\tt := formatType(f.Type)\n\tif len(f.Names) == 0 {\n\t\treturn t\n\t}\n\tnames := make([]string, len(f.Names))\n\tfor i, n := range f.Names {\n\t\tnames[i] = n.Name\n\t}\n\treturn strings.Join(names, \", \") + \" \" + t\n}\n\nfunc formatFieldList(fields []*ast.Field) string {\n\tparts := make([]string, len(fields))\n\tfor i, f := range fields {\n\t\tparts[i] = formatField(f)\n\t}\n\treturn \"(\" + strings.Join(parts, \", \") + \")\"\n}\n\n// formatFields renders struct fields and interface members.\nfunc formatFields(fields []*ast.Field) string {\n\tparts := make([]string, len(fields))\n\tfor i, f := range fields {\n\t\tif ft, ok := f.Type.(*ast.FuncType); ok && len(f.Names) == 1 {\n\t\t\tparts[i] = f.Names[0].Name + formatFuncType(ft)\n\t\t} else {\n\t\t\tparts[i] = formatField(f)\n\t\t}\n\t}\n\treturn strings.Join(parts, \"; \")\n}\n\nfunc formatTypeParams(tp *ast.FieldList) string {\n\tif tp == nil || len(tp.List) == 0 {\n\t\treturn \"\"\n\t}\n\tparts := make([]string, len(tp.List))\n\tfor i, p := range tp.List {\n\t\tparts[i] = formatField(p)\n\t}\n\treturn \"[\" + strings.Join(parts, \", \") + \"]\"\n}\n\nfunc formatType(t ast.Expr) string {\n\tif t == nil {\n\t\treturn \"?\"\n\t}\n\tswitch v := t.(type) {\n\tcase *ast.Ident:\n\t\treturn v.Name\n\tcase *ast.SelectorExpr:\n\t\treturn formatType(v.X) + \".\" + v.Sel.Name\n\tcase *ast.StarExpr:\n\t\treturn \"*\" + formatType(v.X)\n\tcase *ast.ParenExpr:\n\t\treturn \"(\" + formatType(v.X) + \")\"\n\tcase *ast.Ellipsis:\n\t\tif v.Elt == nil {\n\t\t\treturn \"...\"\n\t\t}\n\t\treturn \"...\" + formatType(v.Elt)\n\tcase *ast.ArrayType:\n\t\tif v.Len == nil {\n\t\t\treturn \"[]\" + formatType(v.Elt)\n\t\t}\n\t\treturn \"[\" + formatType(v.Len) + \"]\" + formatType(v.Elt)\n\tcase *ast.MapType:\n\t\treturn \"map[\" + formatType(v.Key) + \"]\" + formatType(v.Value)\n\tcase *ast.ChanType:\n\t\tswitch v.Dir {\n\t\tcase ast.SEND:\n\t\t\treturn \"chan<- \" + formatType(v.Value)\n\t\tcase ast.RECV:\n\t\t\treturn \"<-chan \" + formatType(v.Value)\n\t\tdefault:\n\t\t\treturn \"chan \" + formatType(v.Value)\n\t\t}\n\tcase *ast.InterfaceType:\n\t\tif len(fieldsOf(v.Methods)) == 0 {\n\t\t\treturn \"interface{}\"\n\t\t}\n\t\treturn \"interface{ \" + formatFields(v.Methods.List) + \" }\"\n\tcase *ast.StructType:\n\t\tif len(fieldsOf(v.Fields)) == 0 {\n\t\t\treturn \"struct{}\"\n\t\t}\n\t\treturn \"struct{ \" + formatFields(v.Fields.List) + \" }\"\n\tcase *ast.FuncType:\n\t\treturn \"func\" + formatFuncType(v)\n\tcase *ast.UnaryExpr:\n\t\treturn v.Op.String() + formatType(v.X)\n\tcase *ast.BinaryExpr:\n\t\treturn formatType(v.X) + \" \" + v.Op.String() + \" \" + formatType(v.Y)\n\tcase *ast.BasicLit:\n\t\treturn v.Value\n\tcase *ast.IndexExpr:\n\t\treturn formatType(v.X) + \"[\" + formatType(v.Index) + \"]\"\n\tcase *ast.IndexListExpr:\n\t\targs := make([]string, len(v.Indices))\n\t\tfor i, idx := range v.Indices {\n\t\t\targs[i] = formatType(idx)\n\t\t}\n\t\treturn formatType(v.X) + \"[\" + strings.Join(args, \", \") + \"]\"\n\tdefault:\n\t\treturn \"?\"\n\t}\n}\n\ntype Result struct {\n\tSymbols []Sym `json:\"symbols\"`\n\tRefs []Ref `json:\"refs\"`\n}\n\nfunc main() {\n\tempty := `{\"symbols\":[],\"refs\":[]}`\n\tsrc, err := io.ReadAll(os.Stdin)\n\tif err != nil {\n\t\tfmt.Print(empty)\n\t\treturn\n\t}\n\tsyms, refs, err := extract(src)\n\tif err != nil {\n\t\tfmt.Print(empty)\n\t\treturn\n\t}\n\tdata, err := json.Marshal(Result{Symbols: syms, Refs: refs})\n\tif err != nil {\n\t\tfmt.Print(empty)\n\t\treturn\n\t}\n\tfmt.Print(string(data))\n}\n";
|
|
15
|
+
/** A JSON array of `{file, content}` on stdin → one envelope with per-file results. */
|
|
16
|
+
export declare const GO_BATCH_SCRIPT = "\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype Sym struct {\n\tName string `json:\"name\"`\n\tKind string `json:\"kind\"`\n\tLine int `json:\"line\"`\n\tCol int `json:\"col\"`\n\tSignature string `json:\"signature\"`\n\tScope string `json:\"scope\"`\n\tDoc string `json:\"doc\"`\n}\n\n// Ref is a cross-reference emitted alongside the symbols, so one run yields\n// both. Module is the import path for CallType \"import\", else empty.\ntype Ref struct {\n\tToName string `json:\"toName\"`\n\tCallType string `json:\"callType\"`\n\tLine int `json:\"line\"`\n\tModule string `json:\"module\"`\n}\n\nfunc extract(src []byte) ([]Sym, []Ref, error) {\n\tfset := token.NewFileSet()\n\tnode, err := parser.ParseFile(fset, \"src.go\", src, parser.ParseComments)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tpkg := node.Name.Name\n\t// token columns are 1-based; the index schema's are 0-based.\n\tat := func(p token.Pos) (int, int) {\n\t\tpos := fset.Position(p)\n\t\treturn pos.Line, pos.Column - 1\n\t}\n\n\tsyms := []Sym{}\n\tfor _, decl := range node.Decls {\n\t\tswitch d := decl.(type) {\n\t\tcase *ast.FuncDecl:\n\t\t\tline, col := at(d.Pos())\n\t\t\tkind := \"function\"\n\t\t\tscope := pkg + \".\" + d.Name.Name\n\t\t\tif d.Recv != nil && len(d.Recv.List) > 0 {\n\t\t\t\tkind = \"method\"\n\t\t\t\tscope = pkg + \".\" + recvTypeName(d.Recv.List[0].Type) + \".\" + d.Name.Name\n\t\t\t}\n\t\t\tsyms = append(syms, Sym{Name: d.Name.Name, Kind: kind, Line: line, Col: col, Signature: formatFuncSig(d), Scope: scope, Doc: firstDocLine(d.Doc)})\n\n\t\tcase *ast.GenDecl:\n\t\t\t// An ungrouped declaration carries its comment on the GenDecl; a\n\t\t\t// grouped one's doc describes the group, not each member.\n\t\t\tvar declDoc *ast.CommentGroup\n\t\t\tif !d.Lparen.IsValid() {\n\t\t\t\tdeclDoc = d.Doc\n\t\t\t}\n\t\t\tfor _, spec := range d.Specs {\n\t\t\t\tswitch s := spec.(type) {\n\t\t\t\tcase *ast.TypeSpec:\n\t\t\t\t\tline, col := at(s.Pos())\n\t\t\t\t\tsig := \"type \" + s.Name.Name + formatTypeParams(s.TypeParams)\n\t\t\t\t\tif s.Assign.IsValid() {\n\t\t\t\t\t\tsig += \" =\"\n\t\t\t\t\t}\n\t\t\t\t\tsig += \" \" + formatType(s.Type)\n\t\t\t\t\tsyms = append(syms, Sym{Name: s.Name.Name, Kind: \"type\", Line: line, Col: col, Signature: sig, Scope: pkg, Doc: firstDocLine(s.Doc, declDoc)})\n\t\t\t\tcase *ast.ValueSpec:\n\t\t\t\t\tkind := \"var\"\n\t\t\t\t\tif d.Tok == token.CONST {\n\t\t\t\t\t\tkind = \"const\"\n\t\t\t\t\t}\n\t\t\t\t\tfor _, n := range s.Names {\n\t\t\t\t\t\tline, col := at(n.Pos())\n\t\t\t\t\t\tsig := kind + \" \" + n.Name\n\t\t\t\t\t\tif s.Type != nil {\n\t\t\t\t\t\t\tsig += \" \" + formatType(s.Type)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsyms = append(syms, Sym{Name: n.Name, Kind: kind, Line: line, Col: col, Signature: sig, Scope: pkg, Doc: firstDocLine(s.Doc, declDoc)})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\trefs := []Ref{}\n\tast.Inspect(node, func(n ast.Node) bool {\n\t\tswitch expr := n.(type) {\n\t\tcase *ast.CallExpr:\n\t\t\tline := fset.Position(expr.Pos()).Line\n\t\t\tswitch fun := expr.Fun.(type) {\n\t\t\tcase *ast.Ident:\n\t\t\t\trefs = append(refs, Ref{ToName: fun.Name, CallType: \"call\", Line: line})\n\t\t\tcase *ast.SelectorExpr:\n\t\t\t\t// The selected name (`Join` of `filepath.Join`) is the declared\n\t\t\t\t// symbol name, so it resolves like the TS and Python call refs.\n\t\t\t\trefs = append(refs, Ref{ToName: fun.Sel.Name, CallType: \"call\", Line: line})\n\t\t\t}\n\t\tcase *ast.ImportSpec:\n\t\t\tif expr.Path != nil {\n\t\t\t\tif importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {\n\t\t\t\t\tline := fset.Position(expr.Pos()).Line\n\t\t\t\t\t// A Go import names a package; its last path segment is the\n\t\t\t\t\t// name it is referenced by.\n\t\t\t\t\tname := importPath\n\t\t\t\t\tif idx := strings.LastIndex(importPath, \"/\"); idx >= 0 {\n\t\t\t\t\t\tname = importPath[idx+1:]\n\t\t\t\t\t}\n\t\t\t\t\trefs = append(refs, Ref{ToName: name, CallType: \"import\", Line: line, Module: importPath})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn syms, refs, nil\n}\n\nfunc firstDocLine(groups ...*ast.CommentGroup) string {\n\tfor _, g := range groups {\n\t\tif g == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, line := range strings.Split(g.Text(), \"\\n\") {\n\t\t\tif t := strings.TrimSpace(line); t != \"\" {\n\t\t\t\tif r := []rune(t); len(r) > 200 {\n\t\t\t\t\tt = string(r[:200])\n\t\t\t\t}\n\t\t\t\treturn t\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// recvTypeName unwraps pointers, parens and generic instantiation:\n// `(l *List[T])` is a method of List, not of \"?\".\nfunc recvTypeName(t ast.Expr) string {\n\tswitch v := t.(type) {\n\tcase *ast.Ident:\n\t\treturn v.Name\n\tcase *ast.StarExpr:\n\t\treturn recvTypeName(v.X)\n\tcase *ast.ParenExpr:\n\t\treturn recvTypeName(v.X)\n\tcase *ast.IndexExpr:\n\t\treturn recvTypeName(v.X)\n\tcase *ast.IndexListExpr:\n\t\treturn recvTypeName(v.X)\n\tdefault:\n\t\treturn \"?\"\n\t}\n}\n\nfunc formatFuncSig(d *ast.FuncDecl) string {\n\tsig := \"func \"\n\tif d.Recv != nil && len(d.Recv.List) > 0 {\n\t\tsig += formatFieldList(d.Recv.List) + \" \"\n\t}\n\treturn sig + d.Name.Name + formatTypeParams(d.Type.TypeParams) + formatFuncType(d.Type)\n}\n\nfunc fieldsOf(fl *ast.FieldList) []*ast.Field {\n\tif fl == nil {\n\t\treturn nil\n\t}\n\treturn fl.List\n}\n\nfunc formatFuncType(f *ast.FuncType) string {\n\tout := formatFieldList(fieldsOf(f.Params))\n\tresults := fieldsOf(f.Results)\n\tif len(results) == 1 && len(results[0].Names) == 0 {\n\t\tout += \" \" + formatType(results[0].Type)\n\t} else if len(results) > 0 {\n\t\tout += \" \" + formatFieldList(results)\n\t}\n\treturn out\n}\n\n// formatField keeps every name of a shared-type field: `a, b int`.\nfunc formatField(f *ast.Field) string {\n\tt := formatType(f.Type)\n\tif len(f.Names) == 0 {\n\t\treturn t\n\t}\n\tnames := make([]string, len(f.Names))\n\tfor i, n := range f.Names {\n\t\tnames[i] = n.Name\n\t}\n\treturn strings.Join(names, \", \") + \" \" + t\n}\n\nfunc formatFieldList(fields []*ast.Field) string {\n\tparts := make([]string, len(fields))\n\tfor i, f := range fields {\n\t\tparts[i] = formatField(f)\n\t}\n\treturn \"(\" + strings.Join(parts, \", \") + \")\"\n}\n\n// formatFields renders struct fields and interface members.\nfunc formatFields(fields []*ast.Field) string {\n\tparts := make([]string, len(fields))\n\tfor i, f := range fields {\n\t\tif ft, ok := f.Type.(*ast.FuncType); ok && len(f.Names) == 1 {\n\t\t\tparts[i] = f.Names[0].Name + formatFuncType(ft)\n\t\t} else {\n\t\t\tparts[i] = formatField(f)\n\t\t}\n\t}\n\treturn strings.Join(parts, \"; \")\n}\n\nfunc formatTypeParams(tp *ast.FieldList) string {\n\tif tp == nil || len(tp.List) == 0 {\n\t\treturn \"\"\n\t}\n\tparts := make([]string, len(tp.List))\n\tfor i, p := range tp.List {\n\t\tparts[i] = formatField(p)\n\t}\n\treturn \"[\" + strings.Join(parts, \", \") + \"]\"\n}\n\nfunc formatType(t ast.Expr) string {\n\tif t == nil {\n\t\treturn \"?\"\n\t}\n\tswitch v := t.(type) {\n\tcase *ast.Ident:\n\t\treturn v.Name\n\tcase *ast.SelectorExpr:\n\t\treturn formatType(v.X) + \".\" + v.Sel.Name\n\tcase *ast.StarExpr:\n\t\treturn \"*\" + formatType(v.X)\n\tcase *ast.ParenExpr:\n\t\treturn \"(\" + formatType(v.X) + \")\"\n\tcase *ast.Ellipsis:\n\t\tif v.Elt == nil {\n\t\t\treturn \"...\"\n\t\t}\n\t\treturn \"...\" + formatType(v.Elt)\n\tcase *ast.ArrayType:\n\t\tif v.Len == nil {\n\t\t\treturn \"[]\" + formatType(v.Elt)\n\t\t}\n\t\treturn \"[\" + formatType(v.Len) + \"]\" + formatType(v.Elt)\n\tcase *ast.MapType:\n\t\treturn \"map[\" + formatType(v.Key) + \"]\" + formatType(v.Value)\n\tcase *ast.ChanType:\n\t\tswitch v.Dir {\n\t\tcase ast.SEND:\n\t\t\treturn \"chan<- \" + formatType(v.Value)\n\t\tcase ast.RECV:\n\t\t\treturn \"<-chan \" + formatType(v.Value)\n\t\tdefault:\n\t\t\treturn \"chan \" + formatType(v.Value)\n\t\t}\n\tcase *ast.InterfaceType:\n\t\tif len(fieldsOf(v.Methods)) == 0 {\n\t\t\treturn \"interface{}\"\n\t\t}\n\t\treturn \"interface{ \" + formatFields(v.Methods.List) + \" }\"\n\tcase *ast.StructType:\n\t\tif len(fieldsOf(v.Fields)) == 0 {\n\t\t\treturn \"struct{}\"\n\t\t}\n\t\treturn \"struct{ \" + formatFields(v.Fields.List) + \" }\"\n\tcase *ast.FuncType:\n\t\treturn \"func\" + formatFuncType(v)\n\tcase *ast.UnaryExpr:\n\t\treturn v.Op.String() + formatType(v.X)\n\tcase *ast.BinaryExpr:\n\t\treturn formatType(v.X) + \" \" + v.Op.String() + \" \" + formatType(v.Y)\n\tcase *ast.BasicLit:\n\t\treturn v.Value\n\tcase *ast.IndexExpr:\n\t\treturn formatType(v.X) + \"[\" + formatType(v.Index) + \"]\"\n\tcase *ast.IndexListExpr:\n\t\targs := make([]string, len(v.Indices))\n\t\tfor i, idx := range v.Indices {\n\t\t\targs[i] = formatType(idx)\n\t\t}\n\t\treturn formatType(v.X) + \"[\" + strings.Join(args, \", \") + \"]\"\n\tdefault:\n\t\treturn \"?\"\n\t}\n}\n\ntype FileResult struct {\n\tFile string `json:\"file\"`\n\tError string `json:\"error,omitempty\"`\n\tSymbols []Sym `json:\"symbols\"`\n\tRefs []Ref `json:\"refs\"`\n}\n\ntype BatchResult struct {\n\tVersion int `json:\"version\"`\n\tResults []FileResult `json:\"results\"`\n}\n\ntype inputFile struct {\n\tFile string `json:\"file\"`\n\tContent string `json:\"content\"`\n}\n\nfunc main() {\n\tvar inputs []inputFile\n\traw, err := io.ReadAll(os.Stdin)\n\tif err == nil {\n\t\terr = json.Unmarshal(raw, &inputs)\n\t}\n\tresults := make([]FileResult, 0, len(inputs))\n\tif err == nil {\n\t\tfor _, in := range inputs {\n\t\t\tres := FileResult{File: in.File, Symbols: []Sym{}, Refs: []Ref{}}\n\t\t\tif syms, refs, perr := extract([]byte(in.Content)); perr != nil {\n\t\t\t\tres.Error = perr.Error()\n\t\t\t} else {\n\t\t\t\tres.Symbols, res.Refs = syms, refs\n\t\t\t}\n\t\t\tresults = append(results, res)\n\t\t}\n\t}\n\tdata, merr := json.Marshal(BatchResult{Version: 1, Results: results})\n\tif merr != nil {\n\t\tfmt.Print(`{\"version\":1,\"results\":[]}`)\n\t\treturn\n\t}\n\tfmt.Print(string(data))\n}\n";
|
|
17
|
+
/** One Python source on stdin (file path in argv[1]) → `{"symbols", "refs"}`. */
|
|
18
|
+
export declare const PY_PARSE_SCRIPT = "import ast, json, sys\n\n# Decorators whose function is an attribute accessor, by last dotted segment.\nPROPERTY_DECORATORS = (\"property\", \"cached_property\", \"abstractproperty\", \"setter\", \"getter\", \"deleter\")\n\ndef get_name(node):\n if isinstance(node, ast.Name):\n return node.id\n if isinstance(node, ast.Attribute):\n return get_name(node.value) + \".\" + node.attr\n if isinstance(node, ast.Subscript):\n return get_name(node.value)\n if isinstance(node, ast.Call):\n return get_name(node.func)\n if isinstance(node, ast.Constant):\n return str(node.value)\n return \"\"\n\ndef leaf_name(node):\n # Declared name of the callee: `join` of `os.path.join`. Matches how the\n # TypeScript and Go extractors record call refs.\n if isinstance(node, ast.Attribute):\n return node.attr\n if isinstance(node, ast.Name):\n return node.id\n return get_name(node).split(\".\")[-1]\n\ndef is_private(name):\n return name.startswith(\"__\") and not name.endswith(\"__\")\n\ndef first_doc_line(node):\n try:\n doc = ast.get_docstring(node)\n except Exception:\n return \"\"\n for line in (doc or \"\").splitlines():\n line = line.strip()\n if line:\n return line[:200]\n return \"\"\n\ndef module_name_of(path):\n base = path.replace(\"\\\\\", \"/\").rsplit(\"/\", 1)[-1]\n return base.rsplit(\".\", 1)[0] if \".\" in base else base\n\ndef read_stdin_text():\n # Decode explicitly: sys.stdin uses the locale code page on Windows\n # (cp1254, cp1252, ...), which mangles or rejects the UTF-8 we are sent.\n return sys.stdin.buffer.read().decode(\"utf-8\", \"replace\")\n\ndef parse_source(text, name):\n # ast.parse rejects a leading U+FEFF in a str source.\n if text.startswith(\"\\ufeff\"):\n text = text[1:]\n return ast.parse(text, filename=name or \"<module>\")\n\ndef format_args(args):\n parts = [a.arg for a in list(getattr(args, \"posonlyargs\", [])) + list(args.args)]\n if args.vararg is not None:\n parts.append(\"*\" + args.vararg.arg)\n elif args.kwonlyargs:\n parts.append(\"*\")\n parts.extend(a.arg for a in args.kwonlyargs)\n if args.kwarg is not None:\n parts.append(\"**\" + args.kwarg.arg)\n return \", \".join(parts)\n\ndef extract(tree, module_name):\n syms = []\n refs = []\n scope = [module_name]\n\n def add(name, kind, line, col, signature, sym_scope, doc=\"\"):\n syms.append({\n \"name\": name, \"kind\": kind, \"line\": line, \"col\": col,\n \"signature\": signature, \"scope\": sym_scope, \"doc\": doc,\n })\n\n def qualified(name):\n return \".\".join(scope) + \".\" + name\n\n # Kinds are the index schema's (class/function/method/property/var/const/\n # type). Imports are refs, not symbols: an import symbol per file made\n # every imported name look like a definition in every importing module.\n class Visitor(ast.NodeVisitor):\n in_class = False\n\n def visit_ClassDef(self, node):\n bases = [get_name(b) for b in node.bases]\n sig = \"class \" + node.name + (\"(\" + \", \".join(bases) + \")\" if bases else \"\") + \": ...\"\n add(node.name, \"class\", node.lineno, node.col_offset, sig, qualified(node.name), first_doc_line(node))\n scope.append(node.name)\n outer = self.in_class\n self.in_class = True\n try:\n self.generic_visit(node)\n finally:\n self.in_class = outer\n scope.pop()\n\n def visit_FunctionDef(self, node):\n kind = \"method\" if self.in_class else \"function\"\n for dec in node.decorator_list:\n if get_name(dec).rsplit(\".\", 1)[-1] in PROPERTY_DECORATORS:\n kind = \"property\"\n prefix = \"async def \" if isinstance(node, ast.AsyncFunctionDef) else \"def \"\n sig = prefix + node.name + \"(\" + format_args(node.args) + \")\"\n returns = get_name(node.returns) if node.returns is not None else \"\"\n if returns:\n sig += \" -> \" + returns\n add(node.name, kind, node.lineno, node.col_offset, sig, qualified(node.name), first_doc_line(node))\n # Bodies are not visited: function locals are not index symbols.\n\n visit_AsyncFunctionDef = visit_FunctionDef\n\n def add_target(self, target, line, annotation=None, has_value=True):\n if isinstance(target, ast.Name):\n if is_private(target.id):\n return\n kind = \"const\" if target.id.isupper() else \"var\"\n if annotation is not None:\n sig = target.id + \": \" + get_name(annotation) + (\" = ...\" if has_value else \"\")\n else:\n sig = target.id + \" = ...\"\n add(target.id, kind, line, target.col_offset, sig, \".\".join(scope))\n elif isinstance(target, (ast.Tuple, ast.List)):\n for elt in target.elts:\n self.add_target(elt, line)\n elif isinstance(target, ast.Starred):\n self.add_target(target.value, line)\n\n def visit_Assign(self, node):\n for target in node.targets:\n self.add_target(target, node.lineno)\n\n def visit_AnnAssign(self, node):\n self.add_target(node.target, node.lineno, node.annotation, node.value is not None)\n\n def visit_TypeAlias(self, node):\n if isinstance(node.name, ast.Name):\n add(node.name.id, \"type\", node.lineno, node.col_offset, \"type \" + node.name.id + \" = ...\", \".\".join(scope))\n\n Visitor().visit(tree)\n\n # Refs need a full walk: the visitor deliberately skips function bodies,\n # which is exactly where the calls are.\n for node in ast.walk(tree):\n if isinstance(node, ast.Call):\n name = leaf_name(node.func)\n if name:\n refs.append({\"toName\": name, \"callType\": \"call\", \"line\": node.lineno})\n elif isinstance(node, ast.Import):\n for alias in node.names:\n refs.append({\n \"toName\": alias.name.split(\".\")[-1], \"callType\": \"import\",\n \"line\": node.lineno, \"module\": alias.name,\n })\n elif isinstance(node, ast.ImportFrom):\n # PEP 328: keep the leading dots so the resolver can walk up from\n # the importing file's package.\n module = (\".\" * (node.level or 0)) + (node.module or \"\")\n for alias in node.names:\n refs.append({\n \"toName\": alias.name, \"callType\": \"import\",\n \"line\": node.lineno, \"module\": module,\n })\n elif isinstance(node, ast.ClassDef):\n for base in node.bases:\n name = leaf_name(base)\n if name:\n refs.append({\"toName\": name, \"callType\": \"inherit\", \"line\": node.lineno})\n\n return syms, refs\n\ndef main():\n name = sys.argv[1] if len(sys.argv) > 1 else \"module.py\"\n try:\n syms, refs = extract(parse_source(read_stdin_text(), name), module_name_of(name))\n except Exception:\n syms, refs = [], []\n print(json.dumps({\"symbols\": syms, \"refs\": refs}))\n\nmain()\n";
|
|
19
|
+
/** A JSON array of `{file, content}` on stdin → one envelope with per-file results. */
|
|
20
|
+
export declare const PY_BATCH_SCRIPT = "import ast, json, sys\n\n# Decorators whose function is an attribute accessor, by last dotted segment.\nPROPERTY_DECORATORS = (\"property\", \"cached_property\", \"abstractproperty\", \"setter\", \"getter\", \"deleter\")\n\ndef get_name(node):\n if isinstance(node, ast.Name):\n return node.id\n if isinstance(node, ast.Attribute):\n return get_name(node.value) + \".\" + node.attr\n if isinstance(node, ast.Subscript):\n return get_name(node.value)\n if isinstance(node, ast.Call):\n return get_name(node.func)\n if isinstance(node, ast.Constant):\n return str(node.value)\n return \"\"\n\ndef leaf_name(node):\n # Declared name of the callee: `join` of `os.path.join`. Matches how the\n # TypeScript and Go extractors record call refs.\n if isinstance(node, ast.Attribute):\n return node.attr\n if isinstance(node, ast.Name):\n return node.id\n return get_name(node).split(\".\")[-1]\n\ndef is_private(name):\n return name.startswith(\"__\") and not name.endswith(\"__\")\n\ndef first_doc_line(node):\n try:\n doc = ast.get_docstring(node)\n except Exception:\n return \"\"\n for line in (doc or \"\").splitlines():\n line = line.strip()\n if line:\n return line[:200]\n return \"\"\n\ndef module_name_of(path):\n base = path.replace(\"\\\\\", \"/\").rsplit(\"/\", 1)[-1]\n return base.rsplit(\".\", 1)[0] if \".\" in base else base\n\ndef read_stdin_text():\n # Decode explicitly: sys.stdin uses the locale code page on Windows\n # (cp1254, cp1252, ...), which mangles or rejects the UTF-8 we are sent.\n return sys.stdin.buffer.read().decode(\"utf-8\", \"replace\")\n\ndef parse_source(text, name):\n # ast.parse rejects a leading U+FEFF in a str source.\n if text.startswith(\"\\ufeff\"):\n text = text[1:]\n return ast.parse(text, filename=name or \"<module>\")\n\ndef format_args(args):\n parts = [a.arg for a in list(getattr(args, \"posonlyargs\", [])) + list(args.args)]\n if args.vararg is not None:\n parts.append(\"*\" + args.vararg.arg)\n elif args.kwonlyargs:\n parts.append(\"*\")\n parts.extend(a.arg for a in args.kwonlyargs)\n if args.kwarg is not None:\n parts.append(\"**\" + args.kwarg.arg)\n return \", \".join(parts)\n\ndef extract(tree, module_name):\n syms = []\n refs = []\n scope = [module_name]\n\n def add(name, kind, line, col, signature, sym_scope, doc=\"\"):\n syms.append({\n \"name\": name, \"kind\": kind, \"line\": line, \"col\": col,\n \"signature\": signature, \"scope\": sym_scope, \"doc\": doc,\n })\n\n def qualified(name):\n return \".\".join(scope) + \".\" + name\n\n # Kinds are the index schema's (class/function/method/property/var/const/\n # type). Imports are refs, not symbols: an import symbol per file made\n # every imported name look like a definition in every importing module.\n class Visitor(ast.NodeVisitor):\n in_class = False\n\n def visit_ClassDef(self, node):\n bases = [get_name(b) for b in node.bases]\n sig = \"class \" + node.name + (\"(\" + \", \".join(bases) + \")\" if bases else \"\") + \": ...\"\n add(node.name, \"class\", node.lineno, node.col_offset, sig, qualified(node.name), first_doc_line(node))\n scope.append(node.name)\n outer = self.in_class\n self.in_class = True\n try:\n self.generic_visit(node)\n finally:\n self.in_class = outer\n scope.pop()\n\n def visit_FunctionDef(self, node):\n kind = \"method\" if self.in_class else \"function\"\n for dec in node.decorator_list:\n if get_name(dec).rsplit(\".\", 1)[-1] in PROPERTY_DECORATORS:\n kind = \"property\"\n prefix = \"async def \" if isinstance(node, ast.AsyncFunctionDef) else \"def \"\n sig = prefix + node.name + \"(\" + format_args(node.args) + \")\"\n returns = get_name(node.returns) if node.returns is not None else \"\"\n if returns:\n sig += \" -> \" + returns\n add(node.name, kind, node.lineno, node.col_offset, sig, qualified(node.name), first_doc_line(node))\n # Bodies are not visited: function locals are not index symbols.\n\n visit_AsyncFunctionDef = visit_FunctionDef\n\n def add_target(self, target, line, annotation=None, has_value=True):\n if isinstance(target, ast.Name):\n if is_private(target.id):\n return\n kind = \"const\" if target.id.isupper() else \"var\"\n if annotation is not None:\n sig = target.id + \": \" + get_name(annotation) + (\" = ...\" if has_value else \"\")\n else:\n sig = target.id + \" = ...\"\n add(target.id, kind, line, target.col_offset, sig, \".\".join(scope))\n elif isinstance(target, (ast.Tuple, ast.List)):\n for elt in target.elts:\n self.add_target(elt, line)\n elif isinstance(target, ast.Starred):\n self.add_target(target.value, line)\n\n def visit_Assign(self, node):\n for target in node.targets:\n self.add_target(target, node.lineno)\n\n def visit_AnnAssign(self, node):\n self.add_target(node.target, node.lineno, node.annotation, node.value is not None)\n\n def visit_TypeAlias(self, node):\n if isinstance(node.name, ast.Name):\n add(node.name.id, \"type\", node.lineno, node.col_offset, \"type \" + node.name.id + \" = ...\", \".\".join(scope))\n\n Visitor().visit(tree)\n\n # Refs need a full walk: the visitor deliberately skips function bodies,\n # which is exactly where the calls are.\n for node in ast.walk(tree):\n if isinstance(node, ast.Call):\n name = leaf_name(node.func)\n if name:\n refs.append({\"toName\": name, \"callType\": \"call\", \"line\": node.lineno})\n elif isinstance(node, ast.Import):\n for alias in node.names:\n refs.append({\n \"toName\": alias.name.split(\".\")[-1], \"callType\": \"import\",\n \"line\": node.lineno, \"module\": alias.name,\n })\n elif isinstance(node, ast.ImportFrom):\n # PEP 328: keep the leading dots so the resolver can walk up from\n # the importing file's package.\n module = (\".\" * (node.level or 0)) + (node.module or \"\")\n for alias in node.names:\n refs.append({\n \"toName\": alias.name, \"callType\": \"import\",\n \"line\": node.lineno, \"module\": module,\n })\n elif isinstance(node, ast.ClassDef):\n for base in node.bases:\n name = leaf_name(base)\n if name:\n refs.append({\"toName\": name, \"callType\": \"inherit\", \"line\": node.lineno})\n\n return syms, refs\n\ndef main():\n try:\n inputs = json.loads(read_stdin_text())\n except Exception:\n print(json.dumps({\"version\": 1, \"results\": []}))\n return\n results = []\n for entry in inputs:\n name = entry.get(\"file\", \"\")\n result = {\"file\": name, \"symbols\": [], \"refs\": []}\n try:\n syms, refs = extract(parse_source(entry.get(\"content\", \"\"), name), module_name_of(name))\n result[\"symbols\"] = syms\n result[\"refs\"] = refs\n except Exception as e:\n result[\"error\"] = str(e) or type(e).__name__\n results.append(result)\n print(json.dumps({\"version\": 1, \"results\": results}))\n\nmain()\n";
|
|
21
|
+
/**
|
|
22
|
+
* Path of `script` written to a PRIVATE, UNPREDICTABLE directory, written once
|
|
23
|
+
* per process.
|
|
24
|
+
*
|
|
25
|
+
* Security: a fixed location such as `os.tmpdir()/ws-py-parse/parse.py` can be
|
|
26
|
+
* pre-created by another local user, who then either symlinks the script path
|
|
27
|
+
* (our write follows the link) or leaves their own `ast.py`/`json.py` next to
|
|
28
|
+
* it — Python puts the script's directory first on `sys.path`, so indexing
|
|
29
|
+
* would run their module. `fs.mkdtemp` creates the directory with a random
|
|
30
|
+
* suffix (0700 on POSIX) and `wx` refuses to write through anything already at
|
|
31
|
+
* the path. The in-flight promise is cached so concurrent first calls share
|
|
32
|
+
* one directory instead of racing to create several.
|
|
33
|
+
*/
|
|
34
|
+
export declare function privateScriptPath(prefix: string, fileName: string, script: string): Promise<string>;
|
|
35
|
+
/** Test-only: every script path written so far (failed writes resolve to ''). */
|
|
36
|
+
export declare function __privateScriptPathsForTest(): Promise<string[]>;
|
|
37
|
+
interface ToolchainSpawnOptions {
|
|
38
|
+
cwd: string;
|
|
39
|
+
env?: NodeJS.ProcessEnv;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run `go run` from the script's own directory, never the indexed project.
|
|
43
|
+
*
|
|
44
|
+
* Launched from the project, the go command reads the project's `go.mod` and
|
|
45
|
+
* `go.work`: a `go`/`toolchain` line newer than the installed Go made every
|
|
46
|
+
* parse fail ("requires go >= …"), dropping the whole repository to the regex
|
|
47
|
+
* fallback — or, with the default `GOTOOLCHAIN=auto`, made indexing download
|
|
48
|
+
* and run whatever toolchain the scanned repository named. The parser only
|
|
49
|
+
* needs the standard library, so it runs outside any module on the local
|
|
50
|
+
* toolchain.
|
|
51
|
+
*/
|
|
52
|
+
export declare function goSpawnOptions(scriptPath: string): ToolchainSpawnOptions;
|
|
53
|
+
/** Python runs from the script directory too, so the project is never on its path. */
|
|
54
|
+
export declare function pySpawnOptions(scriptPath: string): ToolchainSpawnOptions;
|
|
55
|
+
/**
|
|
56
|
+
* Spawn a parser child, feed `stdinPayload`, collect stdout.
|
|
57
|
+
* Resolves `null` on spawn failure or timeout; never rejects.
|
|
58
|
+
*/
|
|
59
|
+
export declare function runToolchainChild(binary: string, args: string[], stdinPayload: string, timeoutMs: number, options: ToolchainSpawnOptions): Promise<{
|
|
60
|
+
code: number | null;
|
|
61
|
+
stdout: string;
|
|
62
|
+
} | null>;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=toolchain-scripts.d.ts.map
|
|
@@ -47,6 +47,20 @@ export interface NodeQueries {
|
|
|
47
47
|
scopeNodes?: ReadonlySet<string>;
|
|
48
48
|
skipNamedChildren?: boolean;
|
|
49
49
|
refRules?: Partial<Record<string, RefRule>>;
|
|
50
|
+
/**
|
|
51
|
+
* Final say on a matched node's kind; `null` skips it. For node types that
|
|
52
|
+
* are only sometimes declarations: C's `declaration` (prototype, global or
|
|
53
|
+
* local), a Ruby `constant` (assignment target or mere reference), an
|
|
54
|
+
* Elixir `call` (`def` or any call at all).
|
|
55
|
+
*/
|
|
56
|
+
resolveKind?: (node: import('web-tree-sitter').Node, kind: SymbolKind) => SymbolKind | null;
|
|
57
|
+
/**
|
|
58
|
+
* Names for nodes that declare several (`int a, b;`) or whose name is not an
|
|
59
|
+
* identifier child. `undefined` falls back to single-name extraction.
|
|
60
|
+
*/
|
|
61
|
+
declaredNames?: (node: import('web-tree-sitter').Node) => readonly string[] | undefined;
|
|
62
|
+
/** Scope membership decided per node; replaces `scopeNodes` when set. */
|
|
63
|
+
isScopeNode?: (node: import('web-tree-sitter').Node) => boolean;
|
|
50
64
|
}
|
|
51
65
|
/** One ref a refRule wants emitted. `callType` defaults to the rule's. */
|
|
52
66
|
export interface RefEmission {
|