@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.
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/indexer/detect-workspace.d.ts +18 -0
- package/dist/indexer/detect-workspace.d.ts.map +1 -0
- package/dist/indexer/detect-workspace.js +80 -0
- package/dist/indexer/extract-csharp-file.d.ts +27 -0
- package/dist/indexer/extract-csharp-file.d.ts.map +1 -0
- package/dist/indexer/extract-csharp-file.js +163 -0
- package/dist/indexer/extract-dart-file.d.ts +28 -0
- package/dist/indexer/extract-dart-file.d.ts.map +1 -0
- package/dist/indexer/extract-dart-file.js +167 -0
- package/dist/indexer/extract-elixir-file.d.ts +27 -0
- package/dist/indexer/extract-elixir-file.d.ts.map +1 -0
- package/dist/indexer/extract-elixir-file.js +164 -0
- package/dist/indexer/extract-go-file.d.ts +28 -0
- package/dist/indexer/extract-go-file.d.ts.map +1 -0
- package/dist/indexer/extract-go-file.js +156 -0
- package/dist/indexer/extract-java-file.d.ts +25 -0
- package/dist/indexer/extract-java-file.d.ts.map +1 -0
- package/dist/indexer/extract-java-file.js +140 -0
- package/dist/indexer/extract-kotlin-file.d.ts +20 -0
- package/dist/indexer/extract-kotlin-file.d.ts.map +1 -0
- package/dist/indexer/extract-kotlin-file.js +158 -0
- package/dist/indexer/extract-php-file.d.ts +26 -0
- package/dist/indexer/extract-php-file.d.ts.map +1 -0
- package/dist/indexer/extract-php-file.js +161 -0
- package/dist/indexer/extract-python-file.d.ts +30 -0
- package/dist/indexer/extract-python-file.d.ts.map +1 -0
- package/dist/indexer/extract-python-file.js +196 -0
- package/dist/indexer/extract-ruby-file.d.ts +29 -0
- package/dist/indexer/extract-ruby-file.d.ts.map +1 -0
- package/dist/indexer/extract-ruby-file.js +151 -0
- package/dist/indexer/extract-rust-file.d.ts +27 -0
- package/dist/indexer/extract-rust-file.d.ts.map +1 -0
- package/dist/indexer/extract-rust-file.js +186 -0
- package/dist/indexer/extract-swift-file.d.ts +27 -0
- package/dist/indexer/extract-swift-file.d.ts.map +1 -0
- package/dist/indexer/extract-swift-file.js +168 -0
- package/dist/indexer/extract-ts-file.d.ts +79 -0
- package/dist/indexer/extract-ts-file.d.ts.map +1 -0
- package/dist/indexer/extract-ts-file.js +403 -0
- package/dist/indexer/incremental-updater.d.ts +41 -0
- package/dist/indexer/incremental-updater.d.ts.map +1 -0
- package/dist/indexer/incremental-updater.js +395 -0
- package/dist/indexer/index-builder.d.ts +23 -0
- package/dist/indexer/index-builder.d.ts.map +1 -0
- package/dist/indexer/index-builder.js +289 -0
- package/dist/indexer/resolve-imports.d.ts +36 -0
- package/dist/indexer/resolve-imports.d.ts.map +1 -0
- package/dist/indexer/resolve-imports.js +144 -0
- package/dist/indexer/unresolved-imports.d.ts +20 -0
- package/dist/indexer/unresolved-imports.d.ts.map +1 -0
- package/dist/indexer/unresolved-imports.js +32 -0
- package/dist/query/cycle-detection.d.ts +40 -0
- package/dist/query/cycle-detection.d.ts.map +1 -0
- package/dist/query/cycle-detection.js +135 -0
- package/dist/query/query-api.d.ts +87 -0
- package/dist/query/query-api.d.ts.map +1 -0
- package/dist/query/query-api.js +232 -0
- package/dist/schema/edge-kind.d.ts +31 -0
- package/dist/schema/edge-kind.d.ts.map +1 -0
- package/dist/schema/edge-kind.js +35 -0
- package/dist/schema/edge.d.ts +22 -0
- package/dist/schema/edge.d.ts.map +1 -0
- package/dist/schema/edge.js +1 -0
- package/dist/schema/file-fingerprint.d.ts +22 -0
- package/dist/schema/file-fingerprint.d.ts.map +1 -0
- package/dist/schema/file-fingerprint.js +1 -0
- package/dist/schema/graph-snapshot.d.ts +18 -0
- package/dist/schema/graph-snapshot.d.ts.map +1 -0
- package/dist/schema/graph-snapshot.js +1 -0
- package/dist/schema/manifest.d.ts +47 -0
- package/dist/schema/manifest.d.ts.map +1 -0
- package/dist/schema/manifest.js +1 -0
- package/dist/schema/node-kind.d.ts +21 -0
- package/dist/schema/node-kind.d.ts.map +1 -0
- package/dist/schema/node-kind.js +27 -0
- package/dist/schema/node.d.ts +26 -0
- package/dist/schema/node.d.ts.map +1 -0
- package/dist/schema/node.js +1 -0
- package/dist/schema/schema-version.d.ts +10 -0
- package/dist/schema/schema-version.d.ts.map +1 -0
- package/dist/schema/schema-version.js +8 -0
- package/dist/store/file-fingerprint.d.ts +8 -0
- package/dist/store/file-fingerprint.d.ts.map +1 -0
- package/dist/store/file-fingerprint.js +64 -0
- package/dist/store/graph-store.d.ts +48 -0
- package/dist/store/graph-store.d.ts.map +1 -0
- package/dist/store/graph-store.js +194 -0
- 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"}
|