@shrkcrft/graph 0.1.0-alpha.10

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.
Files changed (91) hide show
  1. package/dist/index.d.ts +30 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +32 -0
  4. package/dist/indexer/detect-workspace.d.ts +18 -0
  5. package/dist/indexer/detect-workspace.d.ts.map +1 -0
  6. package/dist/indexer/detect-workspace.js +80 -0
  7. package/dist/indexer/extract-csharp-file.d.ts +27 -0
  8. package/dist/indexer/extract-csharp-file.d.ts.map +1 -0
  9. package/dist/indexer/extract-csharp-file.js +163 -0
  10. package/dist/indexer/extract-dart-file.d.ts +28 -0
  11. package/dist/indexer/extract-dart-file.d.ts.map +1 -0
  12. package/dist/indexer/extract-dart-file.js +167 -0
  13. package/dist/indexer/extract-elixir-file.d.ts +27 -0
  14. package/dist/indexer/extract-elixir-file.d.ts.map +1 -0
  15. package/dist/indexer/extract-elixir-file.js +164 -0
  16. package/dist/indexer/extract-go-file.d.ts +28 -0
  17. package/dist/indexer/extract-go-file.d.ts.map +1 -0
  18. package/dist/indexer/extract-go-file.js +156 -0
  19. package/dist/indexer/extract-java-file.d.ts +25 -0
  20. package/dist/indexer/extract-java-file.d.ts.map +1 -0
  21. package/dist/indexer/extract-java-file.js +140 -0
  22. package/dist/indexer/extract-kotlin-file.d.ts +20 -0
  23. package/dist/indexer/extract-kotlin-file.d.ts.map +1 -0
  24. package/dist/indexer/extract-kotlin-file.js +158 -0
  25. package/dist/indexer/extract-php-file.d.ts +26 -0
  26. package/dist/indexer/extract-php-file.d.ts.map +1 -0
  27. package/dist/indexer/extract-php-file.js +161 -0
  28. package/dist/indexer/extract-python-file.d.ts +30 -0
  29. package/dist/indexer/extract-python-file.d.ts.map +1 -0
  30. package/dist/indexer/extract-python-file.js +196 -0
  31. package/dist/indexer/extract-ruby-file.d.ts +29 -0
  32. package/dist/indexer/extract-ruby-file.d.ts.map +1 -0
  33. package/dist/indexer/extract-ruby-file.js +151 -0
  34. package/dist/indexer/extract-rust-file.d.ts +27 -0
  35. package/dist/indexer/extract-rust-file.d.ts.map +1 -0
  36. package/dist/indexer/extract-rust-file.js +186 -0
  37. package/dist/indexer/extract-swift-file.d.ts +27 -0
  38. package/dist/indexer/extract-swift-file.d.ts.map +1 -0
  39. package/dist/indexer/extract-swift-file.js +168 -0
  40. package/dist/indexer/extract-ts-file.d.ts +79 -0
  41. package/dist/indexer/extract-ts-file.d.ts.map +1 -0
  42. package/dist/indexer/extract-ts-file.js +403 -0
  43. package/dist/indexer/incremental-updater.d.ts +41 -0
  44. package/dist/indexer/incremental-updater.d.ts.map +1 -0
  45. package/dist/indexer/incremental-updater.js +395 -0
  46. package/dist/indexer/index-builder.d.ts +23 -0
  47. package/dist/indexer/index-builder.d.ts.map +1 -0
  48. package/dist/indexer/index-builder.js +289 -0
  49. package/dist/indexer/resolve-imports.d.ts +36 -0
  50. package/dist/indexer/resolve-imports.d.ts.map +1 -0
  51. package/dist/indexer/resolve-imports.js +144 -0
  52. package/dist/indexer/unresolved-imports.d.ts +20 -0
  53. package/dist/indexer/unresolved-imports.d.ts.map +1 -0
  54. package/dist/indexer/unresolved-imports.js +32 -0
  55. package/dist/query/cycle-detection.d.ts +40 -0
  56. package/dist/query/cycle-detection.d.ts.map +1 -0
  57. package/dist/query/cycle-detection.js +135 -0
  58. package/dist/query/query-api.d.ts +87 -0
  59. package/dist/query/query-api.d.ts.map +1 -0
  60. package/dist/query/query-api.js +232 -0
  61. package/dist/schema/edge-kind.d.ts +31 -0
  62. package/dist/schema/edge-kind.d.ts.map +1 -0
  63. package/dist/schema/edge-kind.js +35 -0
  64. package/dist/schema/edge.d.ts +22 -0
  65. package/dist/schema/edge.d.ts.map +1 -0
  66. package/dist/schema/edge.js +1 -0
  67. package/dist/schema/file-fingerprint.d.ts +22 -0
  68. package/dist/schema/file-fingerprint.d.ts.map +1 -0
  69. package/dist/schema/file-fingerprint.js +1 -0
  70. package/dist/schema/graph-snapshot.d.ts +18 -0
  71. package/dist/schema/graph-snapshot.d.ts.map +1 -0
  72. package/dist/schema/graph-snapshot.js +1 -0
  73. package/dist/schema/manifest.d.ts +47 -0
  74. package/dist/schema/manifest.d.ts.map +1 -0
  75. package/dist/schema/manifest.js +1 -0
  76. package/dist/schema/node-kind.d.ts +21 -0
  77. package/dist/schema/node-kind.d.ts.map +1 -0
  78. package/dist/schema/node-kind.js +27 -0
  79. package/dist/schema/node.d.ts +26 -0
  80. package/dist/schema/node.d.ts.map +1 -0
  81. package/dist/schema/node.js +1 -0
  82. package/dist/schema/schema-version.d.ts +10 -0
  83. package/dist/schema/schema-version.d.ts.map +1 -0
  84. package/dist/schema/schema-version.js +8 -0
  85. package/dist/store/file-fingerprint.d.ts +8 -0
  86. package/dist/store/file-fingerprint.d.ts.map +1 -0
  87. package/dist/store/file-fingerprint.js +64 -0
  88. package/dist/store/graph-store.d.ts +48 -0
  89. package/dist/store/graph-store.d.ts.map +1 -0
  90. package/dist/store/graph-store.js +194 -0
  91. package/package.json +54 -0
@@ -0,0 +1,151 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { EdgeKind } from "../schema/edge-kind.js";
4
+ import { NodeKind } from "../schema/node-kind.js";
5
+ export const EXTRACT_RUBY_FILE_SOURCE = 'extract-ruby-file@v1';
6
+ /**
7
+ * Regex-based Ruby extractor.
8
+ *
9
+ * Top-level constructs only (column-0):
10
+ * - `class Name` / `class Name < Base` → class symbol
11
+ * - `module Name` → module symbol
12
+ * - `def name` / `def self.name` → function symbol
13
+ * - `NAME = …` (uppercase identifier) → const symbol
14
+ *
15
+ * Ruby has no public/private declarations at the top level — every
16
+ * top-level symbol is reachable. All emitted symbols have
17
+ * `isExported: true` for graph consumers; the `visibility` data field
18
+ * preserves the explicit modifier (`private` / `protected`) when one
19
+ * precedes the def, otherwise defaults to `export`.
20
+ *
21
+ * Imports: `require '...'`, `require_relative '...'`, `load '...'`.
22
+ * Specifiers are stored exactly as written, without quotes.
23
+ *
24
+ * Out of scope:
25
+ * - Method-body inspection (visibility modifiers inside class bodies
26
+ * toggle subsequent defs; we don't track that state here).
27
+ * - Mixins (`include`, `extend`, `prepend`).
28
+ * - Singleton classes / `class << self` blocks.
29
+ */
30
+ export function extractRubyFile(fingerprint, absPath, content) {
31
+ const text = content ?? readFileSync(absPath, 'utf8');
32
+ const fileNode = makeFileNode(fingerprint);
33
+ const symbolNodes = [];
34
+ const edges = [];
35
+ const lines = text.split('\n');
36
+ for (let i = 0; i < lines.length; i += 1) {
37
+ const raw = lines[i];
38
+ if (raw.length === 0)
39
+ continue;
40
+ if (raw.startsWith(' ') || raw.startsWith('\t'))
41
+ continue;
42
+ const trimmed = raw.trimStart();
43
+ if (trimmed.startsWith('#'))
44
+ continue;
45
+ // class Name [< Base]
46
+ let m = /^class\s+([A-Z][\w:]*)/.exec(trimmed);
47
+ if (m) {
48
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'class', i + 1);
49
+ continue;
50
+ }
51
+ // module Name
52
+ m = /^module\s+([A-Z][\w:]*)/.exec(trimmed);
53
+ if (m) {
54
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'module', i + 1);
55
+ continue;
56
+ }
57
+ // def name / def self.name
58
+ m = /^def\s+(?:self\.)?([A-Za-z_][\w?!=]*)/.exec(trimmed);
59
+ if (m) {
60
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'function', i + 1);
61
+ continue;
62
+ }
63
+ // Top-level CONSTANT assignment.
64
+ m = /^([A-Z][A-Z0-9_]+)\s*=\s*/.exec(trimmed);
65
+ if (m) {
66
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'const', i + 1);
67
+ }
68
+ }
69
+ return {
70
+ fileNode,
71
+ symbolNodes,
72
+ edges,
73
+ rawImportSpecifiers: scanRubyImports(text),
74
+ importBindings: [],
75
+ identifierReferences: [],
76
+ };
77
+ }
78
+ function pushSymbol(fp, nodes, edges, fileId, name, declKind, line) {
79
+ const sym = {
80
+ id: `symbol:${fp.path}#${name}`,
81
+ kind: NodeKind.Symbol,
82
+ label: name,
83
+ path: fp.path,
84
+ line,
85
+ data: { declKind, visibility: 'export', isExported: true, language: 'ruby' },
86
+ };
87
+ nodes.push(sym);
88
+ edges.push({
89
+ id: createHash('sha1').update(`${fileId}|${sym.id}|${EdgeKind.DeclaresSymbol}`).digest('hex'),
90
+ from: fileId,
91
+ to: sym.id,
92
+ kind: EdgeKind.DeclaresSymbol,
93
+ source: EXTRACT_RUBY_FILE_SOURCE,
94
+ data: { visibility: 'export', declKind, line },
95
+ });
96
+ }
97
+ function makeFileNode(fp) {
98
+ const label = fp.path.split('/').pop() ?? fp.path;
99
+ const tags = ['ruby'];
100
+ if (isRubyTestPath(fp.path))
101
+ tags.push('test');
102
+ return {
103
+ id: fp.nodeId,
104
+ kind: NodeKind.File,
105
+ label,
106
+ path: fp.path,
107
+ tags,
108
+ data: {
109
+ language: 'ruby',
110
+ sizeBytes: fp.sizeBytes,
111
+ sha1: fp.sha1,
112
+ hasDefaultExport: false,
113
+ exportCount: 0,
114
+ localCount: 0,
115
+ reExportCount: 0,
116
+ },
117
+ };
118
+ }
119
+ function isRubyTestPath(rel) {
120
+ return (/(?:^|\/)(?:spec|test)\//.test(rel) ||
121
+ /(?:^|\/)[\w-]+_spec\.rb$/.test(rel) ||
122
+ /(?:^|\/)[\w-]+_test\.rb$/.test(rel));
123
+ }
124
+ function scanRubyImports(text) {
125
+ const out = [];
126
+ const re = /^\s*(?:require|require_relative|load|autoload)\s+(?:["']([^"']+)["']|:[A-Za-z_]\w*\s*,\s*["']([^"']+)["'])/gm;
127
+ let m;
128
+ while ((m = re.exec(text)) !== null) {
129
+ const line = lineFromOffset(text, m.index);
130
+ const specifier = m[1] ?? m[2];
131
+ out.push({ specifier, line, kind: 'ruby-require' });
132
+ }
133
+ const seen = new Set();
134
+ const deduped = [];
135
+ for (const it of out) {
136
+ const k = `${it.specifier}|${it.line}`;
137
+ if (seen.has(k))
138
+ continue;
139
+ seen.add(k);
140
+ deduped.push(it);
141
+ }
142
+ return deduped;
143
+ }
144
+ function lineFromOffset(text, offset) {
145
+ let line = 1;
146
+ for (let i = 0; i < offset && i < text.length; i++) {
147
+ if (text.charCodeAt(i) === 10)
148
+ line += 1;
149
+ }
150
+ return line;
151
+ }
@@ -0,0 +1,27 @@
1
+ import type { IFileFingerprint } from '../schema/file-fingerprint.js';
2
+ import type { IExtractedFile } from './extract-ts-file.js';
3
+ export declare const EXTRACT_RUST_FILE_SOURCE = "extract-rust-file@v1";
4
+ /**
5
+ * Regex-based Rust extractor.
6
+ *
7
+ * Top-level items only (column-0). Captured:
8
+ * - `[pub] fn name`, `[pub] async fn name` → function
9
+ * - `[pub] struct Name`, `[pub] enum Name`, `[pub] trait Name` → class-shaped
10
+ * - `[pub] type Name = ...` → type-alias
11
+ * - `[pub] const NAME: T = …`, `[pub] static NAME: T = …` → const
12
+ * - `[pub] mod name { … }` → module
13
+ *
14
+ * Visibility is derived from the presence of a `pub` keyword (incl.
15
+ * `pub(crate)`, `pub(super)`, `pub(in path::to)`). Items with `pub` →
16
+ * `isExported: true`; everything else is local.
17
+ *
18
+ * Imports: `use a::b::{c, d};` → one specifier per fully-qualified
19
+ * path. `use a::b::*` is captured as `a::b::*`.
20
+ *
21
+ * Out of scope:
22
+ * - `impl` blocks (members live inside; we don't walk bodies).
23
+ * - Macros (`macro_rules!`), `cfg(...)`-gated items.
24
+ * - Item-level attributes other than `pub`.
25
+ */
26
+ export declare function extractRustFile(fingerprint: IFileFingerprint, absPath: string, content?: string): IExtractedFile;
27
+ //# sourceMappingURL=extract-rust-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-rust-file.d.ts","sourceRoot":"","sources":["../../src/indexer/extract-rust-file.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGtE,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,gBAAgB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,cAAc,CAsDhB"}
@@ -0,0 +1,186 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { EdgeKind } from "../schema/edge-kind.js";
4
+ import { NodeKind } from "../schema/node-kind.js";
5
+ export const EXTRACT_RUST_FILE_SOURCE = 'extract-rust-file@v1';
6
+ /**
7
+ * Regex-based Rust extractor.
8
+ *
9
+ * Top-level items only (column-0). Captured:
10
+ * - `[pub] fn name`, `[pub] async fn name` → function
11
+ * - `[pub] struct Name`, `[pub] enum Name`, `[pub] trait Name` → class-shaped
12
+ * - `[pub] type Name = ...` → type-alias
13
+ * - `[pub] const NAME: T = …`, `[pub] static NAME: T = …` → const
14
+ * - `[pub] mod name { … }` → module
15
+ *
16
+ * Visibility is derived from the presence of a `pub` keyword (incl.
17
+ * `pub(crate)`, `pub(super)`, `pub(in path::to)`). Items with `pub` →
18
+ * `isExported: true`; everything else is local.
19
+ *
20
+ * Imports: `use a::b::{c, d};` → one specifier per fully-qualified
21
+ * path. `use a::b::*` is captured as `a::b::*`.
22
+ *
23
+ * Out of scope:
24
+ * - `impl` blocks (members live inside; we don't walk bodies).
25
+ * - Macros (`macro_rules!`), `cfg(...)`-gated items.
26
+ * - Item-level attributes other than `pub`.
27
+ */
28
+ export function extractRustFile(fingerprint, absPath, content) {
29
+ const text = content ?? readFileSync(absPath, 'utf8');
30
+ const fileNode = makeFileNode(fingerprint);
31
+ const symbolNodes = [];
32
+ const edges = [];
33
+ const lines = text.split('\n');
34
+ for (let i = 0; i < lines.length; i += 1) {
35
+ const raw = lines[i];
36
+ if (raw.length === 0)
37
+ continue;
38
+ if (raw.startsWith(' ') || raw.startsWith('\t'))
39
+ continue;
40
+ if (raw.trimStart().startsWith('//'))
41
+ continue;
42
+ // Strip a `pub` (with optional restricted `pub(crate)` style) plus
43
+ // any `async`/`unsafe`/`extern "C"` modifiers before the declaration
44
+ // keyword.
45
+ const pubMatch = /^(pub(?:\s*\([^)]+\))?\s+)?/.exec(raw);
46
+ const isExported = !!pubMatch[1];
47
+ const rest = raw.slice(pubMatch[0].length);
48
+ const trimmed = rest.replace(/^(?:async\s+|unsafe\s+|const\s+(?=fn\b)|extern(?:\s+"[^"]+")?\s+)+/, '');
49
+ let m = /^fn\s+([A-Za-z_][\w]*)\s*[<(]/.exec(trimmed);
50
+ if (m) {
51
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'function', i + 1, isExported);
52
+ continue;
53
+ }
54
+ m = /^(struct|enum|trait|union)\s+([A-Za-z_][\w]*)/.exec(trimmed);
55
+ if (m) {
56
+ const kind = m[1] === 'trait' ? 'interface' : 'class';
57
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[2], kind, i + 1, isExported);
58
+ continue;
59
+ }
60
+ m = /^type\s+([A-Za-z_][\w]*)\s*[=<]/.exec(trimmed);
61
+ if (m) {
62
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'type-alias', i + 1, isExported);
63
+ continue;
64
+ }
65
+ m = /^(?:const|static)\s+([A-Za-z_][\w]*)\s*:/.exec(trimmed);
66
+ if (m) {
67
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'const', i + 1, isExported);
68
+ continue;
69
+ }
70
+ m = /^mod\s+([A-Za-z_][\w]*)\s*[{;]/.exec(trimmed);
71
+ if (m) {
72
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'module', i + 1, isExported);
73
+ }
74
+ }
75
+ return {
76
+ fileNode,
77
+ symbolNodes,
78
+ edges,
79
+ rawImportSpecifiers: scanRustImports(text),
80
+ importBindings: [],
81
+ identifierReferences: [],
82
+ };
83
+ }
84
+ function pushSymbol(fp, nodes, edges, fileId, name, declKind, line, isExported) {
85
+ const sym = {
86
+ id: `symbol:${fp.path}#${name}`,
87
+ kind: NodeKind.Symbol,
88
+ label: name,
89
+ path: fp.path,
90
+ line,
91
+ data: { declKind, visibility: isExported ? 'export' : 'local', isExported, language: 'rust' },
92
+ };
93
+ nodes.push(sym);
94
+ edges.push({
95
+ id: createHash('sha1').update(`${fileId}|${sym.id}|${EdgeKind.DeclaresSymbol}`).digest('hex'),
96
+ from: fileId,
97
+ to: sym.id,
98
+ kind: EdgeKind.DeclaresSymbol,
99
+ source: EXTRACT_RUST_FILE_SOURCE,
100
+ data: { visibility: isExported ? 'export' : 'local', declKind, line },
101
+ });
102
+ }
103
+ function makeFileNode(fp) {
104
+ const label = fp.path.split('/').pop() ?? fp.path;
105
+ const tags = ['rust'];
106
+ if (isRustTestPath(fp.path))
107
+ tags.push('test');
108
+ return {
109
+ id: fp.nodeId,
110
+ kind: NodeKind.File,
111
+ label,
112
+ path: fp.path,
113
+ tags,
114
+ data: {
115
+ language: 'rust',
116
+ sizeBytes: fp.sizeBytes,
117
+ sha1: fp.sha1,
118
+ hasDefaultExport: false,
119
+ exportCount: 0,
120
+ localCount: 0,
121
+ reExportCount: 0,
122
+ },
123
+ };
124
+ }
125
+ function isRustTestPath(rel) {
126
+ return /(?:^|\/)tests\//.test(rel) || /(?:^|\/)[\w-]+_test\.rs$/.test(rel);
127
+ }
128
+ /**
129
+ * Parse `use` declarations. Supports:
130
+ * - `use a::b::c;`
131
+ * - `use a::b::{c, d, e as f};`
132
+ * - `use a::b::*;`
133
+ *
134
+ * Each leaf in the brace group emits its own specifier with the prefix
135
+ * folded in. Aliases (`x as y`) are dropped — we keep the imported
136
+ * path.
137
+ */
138
+ function scanRustImports(text) {
139
+ const out = [];
140
+ // Match `use <path>(::{...})?;` allowing newlines inside `{...}`.
141
+ const useRe = /^use\s+([\s\S]+?);/gm;
142
+ let m;
143
+ while ((m = useRe.exec(text)) !== null) {
144
+ // The greedy regex above would also match `use foo;\nuse bar;` as
145
+ // a single block. Detect that and bail.
146
+ const body = m[1];
147
+ if (body.startsWith('use'))
148
+ continue;
149
+ const line = lineFromOffset(text, m.index);
150
+ const brace = body.indexOf('::{');
151
+ if (brace < 0) {
152
+ // Strip optional `as Alias` and any whitespace.
153
+ const path = body.trim().split(/\s+as\s+/)[0].trim();
154
+ out.push({ specifier: path, line, kind: 'rust-use' });
155
+ }
156
+ else {
157
+ const prefix = body.slice(0, brace).trim();
158
+ const closeIdx = body.lastIndexOf('}');
159
+ const inside = body.slice(brace + 3, closeIdx >= 0 ? closeIdx : body.length);
160
+ for (const item of inside.split(',')) {
161
+ const cleaned = item.trim().split(/\s+as\s+/)[0].trim();
162
+ if (cleaned)
163
+ out.push({ specifier: `${prefix}::${cleaned}`, line, kind: 'rust-use' });
164
+ }
165
+ }
166
+ }
167
+ // De-dupe.
168
+ const seen = new Set();
169
+ const deduped = [];
170
+ for (const it of out) {
171
+ const k = `${it.specifier}|${it.line}`;
172
+ if (seen.has(k))
173
+ continue;
174
+ seen.add(k);
175
+ deduped.push(it);
176
+ }
177
+ return deduped;
178
+ }
179
+ function lineFromOffset(text, offset) {
180
+ let line = 1;
181
+ for (let i = 0; i < offset && i < text.length; i++) {
182
+ if (text.charCodeAt(i) === 10)
183
+ line += 1;
184
+ }
185
+ return line;
186
+ }
@@ -0,0 +1,27 @@
1
+ import type { IFileFingerprint } from '../schema/file-fingerprint.js';
2
+ import type { IExtractedFile } from './extract-ts-file.js';
3
+ export declare const EXTRACT_SWIFT_FILE_SOURCE = "extract-swift-file@v1";
4
+ /**
5
+ * Regex-based Swift extractor.
6
+ *
7
+ * Top-level declarations only (column-0). Detected:
8
+ * - `[public|open|internal|fileprivate|private] [final] class Name`
9
+ * - `struct Name`
10
+ * - `enum Name`
11
+ * - `protocol Name`
12
+ * - `extension Name`
13
+ * - `typealias Name = …`
14
+ * - `func name(…)`
15
+ *
16
+ * Visibility: `public` / `open` → exported, otherwise local.
17
+ *
18
+ * Imports: `import Foundation`, `import struct CoreData.NSManagedObject`,
19
+ * `import class UIKit.UIView` (the second form names a kind + path —
20
+ * captured as the bare path).
21
+ *
22
+ * Out of scope:
23
+ * - `@objc` / `@available` annotations.
24
+ * - Class methods (the regex captures only column-0 funcs).
25
+ */
26
+ export declare function extractSwiftFile(fingerprint: IFileFingerprint, absPath: string, content?: string): IExtractedFile;
27
+ //# sourceMappingURL=extract-swift-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-swift-file.d.ts","sourceRoot":"","sources":["../../src/indexer/extract-swift-file.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGtE,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,gBAAgB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,cAAc,CAqEhB"}
@@ -0,0 +1,168 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { EdgeKind } from "../schema/edge-kind.js";
4
+ import { NodeKind } from "../schema/node-kind.js";
5
+ export const EXTRACT_SWIFT_FILE_SOURCE = 'extract-swift-file@v1';
6
+ /**
7
+ * Regex-based Swift extractor.
8
+ *
9
+ * Top-level declarations only (column-0). Detected:
10
+ * - `[public|open|internal|fileprivate|private] [final] class Name`
11
+ * - `struct Name`
12
+ * - `enum Name`
13
+ * - `protocol Name`
14
+ * - `extension Name`
15
+ * - `typealias Name = …`
16
+ * - `func name(…)`
17
+ *
18
+ * Visibility: `public` / `open` → exported, otherwise local.
19
+ *
20
+ * Imports: `import Foundation`, `import struct CoreData.NSManagedObject`,
21
+ * `import class UIKit.UIView` (the second form names a kind + path —
22
+ * captured as the bare path).
23
+ *
24
+ * Out of scope:
25
+ * - `@objc` / `@available` annotations.
26
+ * - Class methods (the regex captures only column-0 funcs).
27
+ */
28
+ export function extractSwiftFile(fingerprint, absPath, content) {
29
+ const text = content ?? readFileSync(absPath, 'utf8');
30
+ const fileNode = makeFileNode(fingerprint);
31
+ const symbolNodes = [];
32
+ const edges = [];
33
+ const lines = text.split('\n');
34
+ for (let i = 0; i < lines.length; i += 1) {
35
+ const raw = lines[i];
36
+ if (raw.length === 0)
37
+ continue;
38
+ if (raw.startsWith(' ') || raw.startsWith('\t'))
39
+ continue;
40
+ const trimmed = raw.trimStart();
41
+ if (trimmed.startsWith('//') || trimmed.startsWith('/*'))
42
+ continue;
43
+ // Strip leading `@Annotation` style attributes.
44
+ const stripped = trimmed.replace(/^(?:@\w+(?:\([^)]*\))?\s+)+/, '');
45
+ const visMatch = /^(public|open|internal|fileprivate|private)\s+/.exec(stripped);
46
+ const isExported = !!visMatch && (visMatch[1] === 'public' || visMatch[1] === 'open');
47
+ // For visibility-less declarations, Swift defaults to `internal`
48
+ // (file-local for the module). Treat as local in graph terms.
49
+ const explicitlyExported = isExported;
50
+ const rest = visMatch ? stripped.slice(visMatch[0].length) : stripped;
51
+ const noModifiers = rest.replace(/^(?:final\s+|indirect\s+|dynamic\s+|static\s+|class\s+(?=func|var|let)|override\s+|mutating\s+|nonisolated\s+|isolated\s+|async\s+|throws\s+)+/, '');
52
+ let m = /^class\s+([A-Za-z_]\w*)/.exec(noModifiers);
53
+ if (m) {
54
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'class', i + 1, explicitlyExported);
55
+ continue;
56
+ }
57
+ m = /^struct\s+([A-Za-z_]\w*)/.exec(noModifiers);
58
+ if (m) {
59
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'class', i + 1, explicitlyExported);
60
+ continue;
61
+ }
62
+ m = /^enum\s+([A-Za-z_]\w*)/.exec(noModifiers);
63
+ if (m) {
64
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'enum', i + 1, explicitlyExported);
65
+ continue;
66
+ }
67
+ m = /^protocol\s+([A-Za-z_]\w*)/.exec(noModifiers);
68
+ if (m) {
69
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'interface', i + 1, explicitlyExported);
70
+ continue;
71
+ }
72
+ m = /^extension\s+([A-Za-z_]\w*)/.exec(noModifiers);
73
+ if (m) {
74
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'class', i + 1, explicitlyExported);
75
+ continue;
76
+ }
77
+ m = /^typealias\s+([A-Za-z_]\w*)/.exec(noModifiers);
78
+ if (m) {
79
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'type-alias', i + 1, explicitlyExported);
80
+ continue;
81
+ }
82
+ m = /^func\s+([A-Za-z_]\w*)/.exec(noModifiers);
83
+ if (m) {
84
+ pushSymbol(fingerprint, symbolNodes, edges, fileNode.id, m[1], 'function', i + 1, explicitlyExported);
85
+ }
86
+ }
87
+ return {
88
+ fileNode,
89
+ symbolNodes,
90
+ edges,
91
+ rawImportSpecifiers: scanSwiftImports(text),
92
+ importBindings: [],
93
+ identifierReferences: [],
94
+ };
95
+ }
96
+ function pushSymbol(fp, nodes, edges, fileId, name, declKind, line, isExported) {
97
+ const sym = {
98
+ id: `symbol:${fp.path}#${name}`,
99
+ kind: NodeKind.Symbol,
100
+ label: name,
101
+ path: fp.path,
102
+ line,
103
+ data: { declKind, visibility: isExported ? 'export' : 'local', isExported, language: 'swift' },
104
+ };
105
+ nodes.push(sym);
106
+ edges.push({
107
+ id: createHash('sha1').update(`${fileId}|${sym.id}|${EdgeKind.DeclaresSymbol}`).digest('hex'),
108
+ from: fileId,
109
+ to: sym.id,
110
+ kind: EdgeKind.DeclaresSymbol,
111
+ source: EXTRACT_SWIFT_FILE_SOURCE,
112
+ data: { visibility: isExported ? 'export' : 'local', declKind, line },
113
+ });
114
+ }
115
+ function makeFileNode(fp) {
116
+ const label = fp.path.split('/').pop() ?? fp.path;
117
+ const tags = ['swift'];
118
+ if (isSwiftTestPath(fp.path))
119
+ tags.push('test');
120
+ return {
121
+ id: fp.nodeId,
122
+ kind: NodeKind.File,
123
+ label,
124
+ path: fp.path,
125
+ tags,
126
+ data: {
127
+ language: 'swift',
128
+ sizeBytes: fp.sizeBytes,
129
+ sha1: fp.sha1,
130
+ hasDefaultExport: false,
131
+ exportCount: 0,
132
+ localCount: 0,
133
+ reExportCount: 0,
134
+ },
135
+ };
136
+ }
137
+ function isSwiftTestPath(rel) {
138
+ return (/(?:^|\/)Tests\//.test(rel) ||
139
+ /(?:^|\/)[\w-]+Tests\.swift$/.test(rel));
140
+ }
141
+ function scanSwiftImports(text) {
142
+ const out = [];
143
+ // `import Foundation`, `import struct CoreData.NSManagedObject`
144
+ const re = /^import\s+(?:(?:struct|class|protocol|enum|typealias|func|let|var)\s+)?([\w.]+)/gm;
145
+ let m;
146
+ while ((m = re.exec(text)) !== null) {
147
+ const line = lineFromOffset(text, m.index);
148
+ out.push({ specifier: m[1], line, kind: 'swift-import' });
149
+ }
150
+ const seen = new Set();
151
+ const deduped = [];
152
+ for (const it of out) {
153
+ const k = `${it.specifier}|${it.line}`;
154
+ if (seen.has(k))
155
+ continue;
156
+ seen.add(k);
157
+ deduped.push(it);
158
+ }
159
+ return deduped;
160
+ }
161
+ function lineFromOffset(text, offset) {
162
+ let line = 1;
163
+ for (let i = 0; i < offset && i < text.length; i++) {
164
+ if (text.charCodeAt(i) === 10)
165
+ line += 1;
166
+ }
167
+ return line;
168
+ }
@@ -0,0 +1,79 @@
1
+ import type { IEdge } from '../schema/edge.js';
2
+ import type { IFileFingerprint } from '../schema/file-fingerprint.js';
3
+ import type { INode } from '../schema/node.js';
4
+ export declare const EXTRACT_TS_FILE_SOURCE = "extract-ts-file@v1";
5
+ export interface IExtractedFile {
6
+ fileNode: INode;
7
+ symbolNodes: readonly INode[];
8
+ edges: readonly IEdge[];
9
+ /** Specifiers we observed. Resolution happens later in the indexer. */
10
+ rawImportSpecifiers: readonly IRawImportSpecifier[];
11
+ /**
12
+ * Named / default import bindings: `localName` is what the file uses
13
+ * to refer to the symbol; resolution to a target file id happens in
14
+ * the indexer post-pass. Namespace imports (`import * as X`) are
15
+ * intentionally skipped — the binder would need cross-file member
16
+ * lookup which is out of scope for the MVP.
17
+ */
18
+ importBindings: readonly IImportBinding[];
19
+ /**
20
+ * Identifier references found in the file body. The indexer post-pass
21
+ * filters these against the resolved bindings + the file's own local
22
+ * symbols and emits `references-symbol` / `calls-symbol` edges.
23
+ */
24
+ identifierReferences: readonly IIdentifierReference[];
25
+ }
26
+ export interface IRawImportSpecifier {
27
+ specifier: string;
28
+ line: number;
29
+ /** Best-effort kind: 'static' | 'side-effect' | 'dynamic' | 'require'. */
30
+ kind: string;
31
+ }
32
+ export interface IImportBinding {
33
+ /** Identifier name used inside this file. */
34
+ localName: string;
35
+ /** Name as exported by the target module. `default` for default imports. */
36
+ importedName: string;
37
+ specifier: string;
38
+ isDefault: boolean;
39
+ line: number;
40
+ }
41
+ export interface IIdentifierReference {
42
+ /** Identifier text at the use site. */
43
+ name: string;
44
+ line: number;
45
+ /** True when the identifier appears as the callee of a call expression. */
46
+ isCall: boolean;
47
+ }
48
+ /**
49
+ * Extract graph entities from a single TS/TSX/JS/JSX file.
50
+ *
51
+ * Re-uses `buildSymbolIndex` for symbol detection (per-file AST, no full
52
+ * program). Import edges carry the literal specifier; resolution to the
53
+ * target file id happens in `resolve-imports.ts` (R64).
54
+ */
55
+ export declare function extractTsFile(fingerprint: IFileFingerprint, absPath: string, content?: string): IExtractedFile;
56
+ /**
57
+ * Stitch identifier references + import bindings into edges.
58
+ *
59
+ * Called by the indexer after per-file extraction + import resolution.
60
+ * Emits `references-symbol` / `calls-symbol` edges from a file to the
61
+ * symbol(s) it uses. Same-file references resolve against the file's
62
+ * own declared symbols; cross-file references resolve via the import
63
+ * bindings + the resolver's spec → targetPath map.
64
+ *
65
+ * Default imports target `symbol:<targetPath>#<defaultExportName>`
66
+ * when the default export name is known (via the file node's
67
+ * `defaultExportName` data), or `#default` as a placeholder when not.
68
+ */
69
+ export declare function stitchPerFileReferences(input: {
70
+ fileNodeId: string;
71
+ extracted: IExtractedFile;
72
+ /** specifier → targetFilePath (POSIX, project-relative). */
73
+ resolvedSpec: ReadonlyMap<string, string | undefined>;
74
+ /** targetPath → defaultExportName (if known). */
75
+ defaultExportNameByPath: ReadonlyMap<string, string | undefined>;
76
+ /** localName → symbol node id, restricted to this file's own symbols. */
77
+ localSymbolNamesInThisFile: ReadonlyMap<string, string>;
78
+ }): readonly IEdge[];
79
+ //# sourceMappingURL=extract-ts-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-ts-file.d.ts","sourceRoot":"","sources":["../../src/indexer/extract-ts-file.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG/C,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAc3D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,KAAK,CAAC;IAChB,WAAW,EAAE,SAAS,KAAK,EAAE,CAAC;IAC9B,KAAK,EAAE,SAAS,KAAK,EAAE,CAAC;IACxB,uEAAuE;IACvE,mBAAmB,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACpD;;;;;;OAMG;IACH,cAAc,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C;;;;OAIG;IACH,oBAAoB,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACvD;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,gBAAgB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,cAAc,CA+EhB;AA6ID;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,4DAA4D;IAC5D,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACtD,iDAAiD;IACjD,uBAAuB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjE,yEAAyE;IACzE,0BAA0B,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzD,GAAG,SAAS,KAAK,EAAE,CA+BnB"}