feed-the-machine 1.3.1 → 1.4.0
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/ftm-git/SKILL.md +0 -1
- package/ftm-map/SKILL.md +46 -14
- package/ftm-map/scripts/db.py +439 -118
- package/ftm-map/scripts/index.py +128 -54
- package/ftm-map/scripts/parser.py +89 -320
- package/ftm-map/scripts/queries/go-tags.scm +20 -0
- package/ftm-map/scripts/queries/javascript-tags.scm +19 -7
- package/ftm-map/scripts/queries/python-tags.scm +22 -8
- package/ftm-map/scripts/queries/ruby-tags.scm +19 -0
- package/ftm-map/scripts/queries/rust-tags.scm +37 -0
- package/ftm-map/scripts/queries/typescript-tags.scm +20 -8
- package/ftm-map/scripts/query.py +176 -24
- package/ftm-map/scripts/ranker.py +377 -0
- package/ftm-map/scripts/requirements.txt +3 -0
- package/ftm-map/scripts/setup.sh +11 -0
- package/ftm-map/scripts/test_db.py +355 -115
- package/ftm-map/scripts/test_parser.py +169 -101
- package/ftm-map/scripts/test_query.py +178 -61
- package/ftm-map/scripts/test_ranker.py +199 -0
- package/ftm-map/scripts/views.py +107 -61
- package/ftm-mind/references/event-registry.md +0 -10
- package/hooks/ftm-blackboard-enforcer.sh +1 -4
- package/package.json +1 -1
- package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/__pycache__/generator.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/planner/__pycache__/schema.cpython-314.pyc +0 -0
- package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
- package/ftm-map/scripts/tests/fixtures/__init__.py +0 -0
- package/ftm-map/scripts/tests/fixtures/sample_project/api.ts +0 -16
- package/ftm-map/scripts/tests/fixtures/sample_project/auth.py +0 -15
- package/ftm-map/scripts/tests/fixtures/sample_project/utils.js +0 -16
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Tree-sitter based parser for extracting
|
|
3
|
-
|
|
2
|
+
Tree-sitter based parser for extracting definition and reference tags from source code.
|
|
3
|
+
|
|
4
|
+
Uses Aider-style @name.definition.* / @name.reference.* capture convention in
|
|
5
|
+
per-language .scm query files for structured tag extraction. Falls back to Pygments
|
|
6
|
+
lexer for reference extraction when tree-sitter queries lack reference patterns.
|
|
4
7
|
"""
|
|
5
8
|
import hashlib
|
|
6
9
|
import os
|
|
7
10
|
import sys
|
|
8
|
-
from
|
|
11
|
+
from collections import namedtuple
|
|
9
12
|
from pathlib import Path
|
|
10
13
|
from typing import Optional
|
|
11
14
|
|
|
12
15
|
import tree_sitter as ts
|
|
13
16
|
from tree_sitter_language_pack import get_language, get_parser
|
|
14
17
|
|
|
18
|
+
# Tag namedtuple: the single output type for all extraction
|
|
19
|
+
# kind is "def" or "ref"
|
|
20
|
+
# rel_fname is relative path, fname is absolute path
|
|
21
|
+
Tag = namedtuple("Tag", ["rel_fname", "fname", "line", "name", "kind"])
|
|
22
|
+
|
|
15
23
|
QUERIES_DIR = os.path.join(os.path.dirname(__file__), "queries")
|
|
16
24
|
|
|
17
25
|
# Map file extensions to tree-sitter language names
|
|
@@ -35,67 +43,6 @@ EXTENSION_MAP = {
|
|
|
35
43
|
".sh": "bash",
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
# Node types that represent definitions in generic AST walk, keyed by node type
|
|
39
|
-
DEFINITION_TYPES = {
|
|
40
|
-
# TypeScript / JavaScript
|
|
41
|
-
"function_declaration": "function",
|
|
42
|
-
"method_definition": "method",
|
|
43
|
-
"class_declaration": "class",
|
|
44
|
-
"arrow_function": "function",
|
|
45
|
-
"lexical_declaration": "variable",
|
|
46
|
-
"variable_declaration": "variable",
|
|
47
|
-
"interface_declaration": "class",
|
|
48
|
-
"type_alias_declaration": "type",
|
|
49
|
-
"enum_declaration": "class",
|
|
50
|
-
# Python
|
|
51
|
-
"function_definition": "function",
|
|
52
|
-
"class_definition": "class",
|
|
53
|
-
"decorated_definition": None, # unwrap to inner definition
|
|
54
|
-
# Imports
|
|
55
|
-
"import_statement": "import",
|
|
56
|
-
"import_from_statement": "import",
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
# Node types that carry a symbol name field
|
|
60
|
-
NAME_TYPES = frozenset({
|
|
61
|
-
"identifier",
|
|
62
|
-
"property_identifier",
|
|
63
|
-
"type_identifier",
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
# Node types representing call expressions per language
|
|
67
|
-
CALL_TYPES = frozenset({
|
|
68
|
-
"call_expression", # JS/TS/Go/Rust
|
|
69
|
-
"call", # Ruby
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
# Node types representing import statements
|
|
73
|
-
IMPORT_TYPES = frozenset({
|
|
74
|
-
"import_statement",
|
|
75
|
-
"import_from_statement",
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@dataclass
|
|
80
|
-
class Symbol:
|
|
81
|
-
name: str
|
|
82
|
-
kind: str # function, class, method, variable, import, type
|
|
83
|
-
file_path: str
|
|
84
|
-
start_line: int
|
|
85
|
-
end_line: int
|
|
86
|
-
signature: str = ""
|
|
87
|
-
doc_comment: str = ""
|
|
88
|
-
content_hash: str = ""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@dataclass
|
|
92
|
-
class Relationship:
|
|
93
|
-
source_name: str
|
|
94
|
-
target_name: str
|
|
95
|
-
kind: str # calls, imports, extends, implements, uses
|
|
96
|
-
source_file: str
|
|
97
|
-
target_file: str = "" # may be unknown for cross-file refs
|
|
98
|
-
|
|
99
46
|
|
|
100
47
|
# ---------------------------------------------------------------------------
|
|
101
48
|
# Public API
|
|
@@ -112,55 +59,44 @@ def compute_content_hash(content: str) -> str:
|
|
|
112
59
|
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
113
60
|
|
|
114
61
|
|
|
115
|
-
def
|
|
116
|
-
"""
|
|
62
|
+
def get_tags(fname: str, rel_fname: str = None) -> list[Tag]:
|
|
63
|
+
"""Extract definition and reference tags from a source file.
|
|
64
|
+
|
|
65
|
+
Uses tree-sitter query files with @name.definition.* and @name.reference.*
|
|
66
|
+
capture naming convention (Aider-style).
|
|
117
67
|
|
|
118
|
-
Returns
|
|
119
|
-
returning partial results and logging warnings to stderr.
|
|
68
|
+
Returns list of Tag namedtuples with kind="def" or kind="ref".
|
|
120
69
|
"""
|
|
121
|
-
|
|
70
|
+
if rel_fname is None:
|
|
71
|
+
rel_fname = fname
|
|
72
|
+
|
|
73
|
+
lang = detect_language(fname)
|
|
122
74
|
if not lang:
|
|
123
75
|
return []
|
|
124
76
|
|
|
125
|
-
source = _read_source(
|
|
77
|
+
source = _read_source(fname)
|
|
126
78
|
if source is None:
|
|
127
79
|
return []
|
|
128
80
|
|
|
129
|
-
tree = _parse_source(source, lang,
|
|
81
|
+
tree = _parse_source(source, lang, fname)
|
|
130
82
|
if tree is None:
|
|
131
83
|
return []
|
|
132
84
|
|
|
133
85
|
scm_path = os.path.join(QUERIES_DIR, f"{lang}-tags.scm")
|
|
134
|
-
if os.path.exists(scm_path):
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if symbols:
|
|
138
|
-
return symbols
|
|
139
|
-
|
|
140
|
-
return _extract_generic(tree, source, file_path)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def extract_relationships(file_path: str) -> list[Relationship]:
|
|
144
|
-
"""Extract relationships (calls, imports) from a source file.
|
|
145
|
-
|
|
146
|
-
Returns a list of Relationship objects.
|
|
147
|
-
"""
|
|
148
|
-
lang = detect_language(file_path)
|
|
149
|
-
if not lang:
|
|
150
|
-
return []
|
|
86
|
+
if not os.path.exists(scm_path):
|
|
87
|
+
# No query file -- use Pygments fallback for refs only
|
|
88
|
+
return _pygments_ref_fallback(source, fname, rel_fname)
|
|
151
89
|
|
|
152
|
-
|
|
153
|
-
if source is None:
|
|
154
|
-
return []
|
|
90
|
+
tags = _extract_tags(tree, source, fname, rel_fname, lang, scm_path)
|
|
155
91
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
92
|
+
# If we got defs but no refs, supplement with Pygments fallback for refs
|
|
93
|
+
has_defs = any(t.kind == "def" for t in tags)
|
|
94
|
+
has_refs = any(t.kind == "ref" for t in tags)
|
|
95
|
+
if has_defs and not has_refs:
|
|
96
|
+
ref_tags = _pygments_ref_fallback(source, fname, rel_fname)
|
|
97
|
+
tags.extend(ref_tags)
|
|
159
98
|
|
|
160
|
-
|
|
161
|
-
_extract_calls(tree.root_node, source, file_path, relationships)
|
|
162
|
-
_extract_imports(tree.root_node, source, file_path, relationships)
|
|
163
|
-
return relationships
|
|
99
|
+
return tags
|
|
164
100
|
|
|
165
101
|
|
|
166
102
|
# ---------------------------------------------------------------------------
|
|
@@ -188,23 +124,15 @@ def _parse_source(source: str, lang: str, file_path: str):
|
|
|
188
124
|
|
|
189
125
|
|
|
190
126
|
# ---------------------------------------------------------------------------
|
|
191
|
-
#
|
|
127
|
+
# Tag extraction via tree-sitter queries
|
|
192
128
|
# ---------------------------------------------------------------------------
|
|
193
129
|
|
|
194
|
-
def
|
|
195
|
-
"""Extract
|
|
196
|
-
|
|
197
|
-
Uses tree-sitter QueryCursor.matches() which returns per-pattern match dicts.
|
|
198
|
-
Each match dict contains both the @definition.X node and the @name node so
|
|
199
|
-
they are already correlated — no post-hoc joining needed.
|
|
200
|
-
|
|
201
|
-
Falls back to empty list on any error so callers can use generic extraction.
|
|
202
|
-
"""
|
|
130
|
+
def _extract_tags(tree, source: str, fname: str, rel_fname: str, lang: str, scm_path: str) -> list[Tag]:
|
|
131
|
+
"""Extract tags using tree-sitter query with @name.definition.* / @name.reference.* convention."""
|
|
203
132
|
try:
|
|
204
133
|
with open(scm_path) as fh:
|
|
205
134
|
query_text = fh.read()
|
|
206
|
-
except (IOError, OSError)
|
|
207
|
-
print(f"Warning: Cannot read query {scm_path}: {exc}", file=sys.stderr)
|
|
135
|
+
except (IOError, OSError):
|
|
208
136
|
return []
|
|
209
137
|
|
|
210
138
|
try:
|
|
@@ -212,211 +140,76 @@ def _extract_with_query(tree, source: str, file_path: str, lang: str, scm_path:
|
|
|
212
140
|
query = ts.Query(language, query_text)
|
|
213
141
|
cursor = ts.QueryCursor(query)
|
|
214
142
|
matches = list(cursor.matches(tree.root_node))
|
|
215
|
-
except Exception
|
|
216
|
-
print(f"Warning: Query execution failed for {file_path}: {exc}", file=sys.stderr)
|
|
143
|
+
except Exception: # noqa: BLE001
|
|
217
144
|
return []
|
|
218
145
|
|
|
219
|
-
|
|
146
|
+
tags = []
|
|
147
|
+
seen = {} # (name, start_byte, end_byte) -> Tag for dedup
|
|
220
148
|
|
|
149
|
+
for _pattern_idx, capture_dict in matches:
|
|
150
|
+
for capture_name, nodes in capture_dict.items():
|
|
151
|
+
# Only process @name.definition.* and @name.reference.* captures
|
|
152
|
+
if capture_name.startswith("name.definition."):
|
|
153
|
+
kind = "def"
|
|
154
|
+
elif capture_name.startswith("name.reference."):
|
|
155
|
+
kind = "ref"
|
|
156
|
+
else:
|
|
157
|
+
continue
|
|
221
158
|
|
|
222
|
-
|
|
223
|
-
|
|
159
|
+
for node in nodes:
|
|
160
|
+
name_text = source[node.start_byte:node.end_byte].strip()
|
|
161
|
+
if not name_text:
|
|
162
|
+
continue
|
|
224
163
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"""
|
|
229
|
-
symbols: list[Symbol] = []
|
|
164
|
+
key = (name_text, node.start_byte, node.end_byte)
|
|
165
|
+
if key in seen:
|
|
166
|
+
continue
|
|
230
167
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if not def_key:
|
|
238
|
-
continue
|
|
239
|
-
|
|
240
|
-
kind = def_key[len("definition."):]
|
|
241
|
-
def_nodes = capture_dict[def_key]
|
|
242
|
-
name_nodes = capture_dict.get("name", [])
|
|
243
|
-
|
|
244
|
-
if not def_nodes:
|
|
245
|
-
continue
|
|
246
|
-
|
|
247
|
-
def_node = def_nodes[0]
|
|
248
|
-
|
|
249
|
-
# Prefer the @name capture; fall back to identifier child walk
|
|
250
|
-
if name_nodes:
|
|
251
|
-
sym_name = source[name_nodes[0].start_byte:name_nodes[0].end_byte].strip()
|
|
252
|
-
else:
|
|
253
|
-
sym_name = _find_name(def_node, source)
|
|
254
|
-
|
|
255
|
-
if not sym_name:
|
|
256
|
-
continue
|
|
257
|
-
|
|
258
|
-
body = source[def_node.start_byte:def_node.end_byte]
|
|
259
|
-
sig = _first_line(body)
|
|
260
|
-
doc = _find_doc_comment(def_node, source)
|
|
261
|
-
|
|
262
|
-
symbols.append(Symbol(
|
|
263
|
-
name=sym_name,
|
|
264
|
-
kind=kind,
|
|
265
|
-
file_path=file_path,
|
|
266
|
-
start_line=def_node.start_point[0] + 1,
|
|
267
|
-
end_line=def_node.end_point[0] + 1,
|
|
268
|
-
signature=sig,
|
|
269
|
-
doc_comment=doc,
|
|
270
|
-
content_hash=compute_content_hash(body),
|
|
271
|
-
))
|
|
272
|
-
|
|
273
|
-
return symbols
|
|
168
|
+
line = node.start_point[0] + 1
|
|
169
|
+
tag = Tag(rel_fname=rel_fname, fname=fname, line=line, name=name_text, kind=kind)
|
|
170
|
+
seen[key] = tag
|
|
171
|
+
tags.append(tag)
|
|
172
|
+
|
|
173
|
+
return tags
|
|
274
174
|
|
|
275
175
|
|
|
276
176
|
# ---------------------------------------------------------------------------
|
|
277
|
-
#
|
|
177
|
+
# Pygments fallback for references
|
|
278
178
|
# ---------------------------------------------------------------------------
|
|
279
179
|
|
|
280
|
-
def
|
|
281
|
-
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def _walk_node(node, source: str, file_path: str, symbols: list[Symbol]) -> None:
|
|
288
|
-
"""Recursively walk AST nodes looking for definition nodes."""
|
|
289
|
-
node_type = node.type
|
|
290
|
-
|
|
291
|
-
if node_type in DEFINITION_TYPES:
|
|
292
|
-
kind = DEFINITION_TYPES[node_type]
|
|
293
|
-
|
|
294
|
-
if kind is None:
|
|
295
|
-
# Decorated definition — unwrap inner nodes only
|
|
296
|
-
for child in node.children:
|
|
297
|
-
_walk_node(child, source, file_path, symbols)
|
|
298
|
-
return
|
|
299
|
-
|
|
300
|
-
# Skip bare arrow functions without a variable name context
|
|
301
|
-
if node_type == "arrow_function":
|
|
302
|
-
for child in node.children:
|
|
303
|
-
_walk_node(child, source, file_path, symbols)
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
name = _find_name(node, source)
|
|
307
|
-
if not name:
|
|
308
|
-
for child in node.children:
|
|
309
|
-
_walk_node(child, source, file_path, symbols)
|
|
310
|
-
return
|
|
311
|
-
|
|
312
|
-
body = source[node.start_byte:node.end_byte]
|
|
313
|
-
sig = _first_line(body)
|
|
314
|
-
doc = _find_doc_comment(node, source)
|
|
315
|
-
|
|
316
|
-
symbols.append(Symbol(
|
|
317
|
-
name=name,
|
|
318
|
-
kind=kind,
|
|
319
|
-
file_path=file_path,
|
|
320
|
-
start_line=node.start_point[0] + 1,
|
|
321
|
-
end_line=node.end_point[0] + 1,
|
|
322
|
-
signature=sig,
|
|
323
|
-
doc_comment=doc,
|
|
324
|
-
content_hash=compute_content_hash(body),
|
|
325
|
-
))
|
|
326
|
-
|
|
327
|
-
# Always recurse (definitions can be nested)
|
|
328
|
-
for child in node.children:
|
|
329
|
-
_walk_node(child, source, file_path, symbols)
|
|
180
|
+
def _pygments_ref_fallback(source: str, fname: str, rel_fname: str) -> list[Tag]:
|
|
181
|
+
"""Use Pygments to extract reference-like tokens when tree-sitter refs are missing."""
|
|
182
|
+
try:
|
|
183
|
+
from pygments.lexers import get_lexer_for_filename
|
|
184
|
+
from pygments.token import Token
|
|
185
|
+
except ImportError:
|
|
186
|
+
return []
|
|
330
187
|
|
|
188
|
+
try:
|
|
189
|
+
lexer = get_lexer_for_filename(fname)
|
|
190
|
+
except Exception: # noqa: BLE001
|
|
191
|
+
return []
|
|
331
192
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
193
|
+
tags = []
|
|
194
|
+
line = 1
|
|
195
|
+
for token_type, value in lexer.get_tokens(source):
|
|
196
|
+
# Count newlines for line tracking
|
|
197
|
+
newlines = value.count('\n')
|
|
198
|
+
if token_type in Token.Name and value.strip():
|
|
199
|
+
tags.append(Tag(rel_fname=rel_fname, fname=fname, line=line, name=value.strip(), kind="ref"))
|
|
200
|
+
line += newlines
|
|
335
201
|
|
|
336
|
-
|
|
337
|
-
"""Recursively extract function call relationships."""
|
|
338
|
-
if node.type in CALL_TYPES:
|
|
339
|
-
func_node = node.children[0] if node.children else None
|
|
340
|
-
if func_node:
|
|
341
|
-
callee_text = source[func_node.start_byte:func_node.end_byte]
|
|
342
|
-
# Simplify dotted paths to last component
|
|
343
|
-
callee_name = callee_text.split(".")[-1].split("(")[0].strip()
|
|
344
|
-
caller_name = _find_enclosing_function(node, source)
|
|
345
|
-
if callee_name and caller_name:
|
|
346
|
-
rels.append(Relationship(
|
|
347
|
-
source_name=caller_name,
|
|
348
|
-
target_name=callee_name,
|
|
349
|
-
kind="calls",
|
|
350
|
-
source_file=file_path,
|
|
351
|
-
))
|
|
352
|
-
|
|
353
|
-
for child in node.children:
|
|
354
|
-
_extract_calls(child, source, file_path, rels)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
def _extract_imports(node, source: str, file_path: str, rels: list[Relationship]) -> None:
|
|
358
|
-
"""Recursively extract import relationships."""
|
|
359
|
-
if node.type in IMPORT_TYPES:
|
|
360
|
-
module_stem = Path(file_path).stem
|
|
361
|
-
_collect_import_names(node, source, module_stem, file_path, rels)
|
|
362
|
-
|
|
363
|
-
for child in node.children:
|
|
364
|
-
_extract_imports(child, source, file_path, rels)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
def _collect_import_names(
|
|
368
|
-
node,
|
|
369
|
-
source: str,
|
|
370
|
-
module_stem: str,
|
|
371
|
-
file_path: str,
|
|
372
|
-
rels: list[Relationship],
|
|
373
|
-
) -> None:
|
|
374
|
-
"""Walk an import node and emit Relationship objects for each imported name."""
|
|
375
|
-
for child in node.children:
|
|
376
|
-
child_type = child.type
|
|
377
|
-
|
|
378
|
-
if child_type == "dotted_name":
|
|
379
|
-
imported = source[child.start_byte:child.end_byte]
|
|
380
|
-
rels.append(Relationship(
|
|
381
|
-
source_name=module_stem,
|
|
382
|
-
target_name=imported,
|
|
383
|
-
kind="imports",
|
|
384
|
-
source_file=file_path,
|
|
385
|
-
))
|
|
386
|
-
|
|
387
|
-
elif child_type in ("import_clause", "named_imports", "import_specifier"):
|
|
388
|
-
for grandchild in child.children:
|
|
389
|
-
if grandchild.type in NAME_TYPES:
|
|
390
|
-
name = source[grandchild.start_byte:grandchild.end_byte].strip()
|
|
391
|
-
if name and name not in ("{", "}", ","):
|
|
392
|
-
rels.append(Relationship(
|
|
393
|
-
source_name=module_stem,
|
|
394
|
-
target_name=name,
|
|
395
|
-
kind="imports",
|
|
396
|
-
source_file=file_path,
|
|
397
|
-
))
|
|
398
|
-
|
|
399
|
-
elif child_type == "string":
|
|
400
|
-
# import ... from "module-path"
|
|
401
|
-
raw = source[child.start_byte:child.end_byte].strip("'\"")
|
|
402
|
-
rels.append(Relationship(
|
|
403
|
-
source_name=module_stem,
|
|
404
|
-
target_name=raw,
|
|
405
|
-
kind="imports",
|
|
406
|
-
source_file=file_path,
|
|
407
|
-
))
|
|
202
|
+
return tags
|
|
408
203
|
|
|
409
204
|
|
|
410
205
|
# ---------------------------------------------------------------------------
|
|
411
|
-
# Small
|
|
206
|
+
# Small utilities (kept for potential downstream use)
|
|
412
207
|
# ---------------------------------------------------------------------------
|
|
413
208
|
|
|
414
|
-
def
|
|
415
|
-
"""
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
return source[child.start_byte:child.end_byte]
|
|
419
|
-
return ""
|
|
209
|
+
def _first_line(text: str, max_len: int = 200) -> str:
|
|
210
|
+
"""Return the first non-empty line of text, truncated to max_len."""
|
|
211
|
+
line = text.split("\n")[0].strip()
|
|
212
|
+
return line[:max_len] + "..." if len(line) > max_len else line
|
|
420
213
|
|
|
421
214
|
|
|
422
215
|
def _find_doc_comment(node, source: str) -> str:
|
|
@@ -429,27 +222,3 @@ def _find_doc_comment(node, source: str) -> str:
|
|
|
429
222
|
text = text.strip(marker)
|
|
430
223
|
return text.strip()[:500]
|
|
431
224
|
return ""
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
def _first_line(text: str, max_len: int = 200) -> str:
|
|
435
|
-
"""Return the first non-empty line of text, truncated to max_len."""
|
|
436
|
-
line = text.split("\n")[0].strip()
|
|
437
|
-
return line[:max_len] + "..." if len(line) > max_len else line
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
def _find_enclosing_function(node, source: str) -> str:
|
|
441
|
-
"""Walk up the AST to find the name of the nearest enclosing function."""
|
|
442
|
-
enclosing_types = {
|
|
443
|
-
"function_declaration",
|
|
444
|
-
"function_definition",
|
|
445
|
-
"method_definition",
|
|
446
|
-
"arrow_function",
|
|
447
|
-
}
|
|
448
|
-
current = node.parent
|
|
449
|
-
while current:
|
|
450
|
-
if current.type in enclosing_types:
|
|
451
|
-
name = _find_name(current, source)
|
|
452
|
-
if name:
|
|
453
|
-
return name
|
|
454
|
-
current = current.parent
|
|
455
|
-
return ""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
; Functions
|
|
2
|
+
(function_declaration
|
|
3
|
+
name: (identifier) @name.definition.function) @definition.function
|
|
4
|
+
|
|
5
|
+
; Methods
|
|
6
|
+
(method_declaration
|
|
7
|
+
name: (field_identifier) @name.definition.method) @definition.method
|
|
8
|
+
|
|
9
|
+
; Type declarations
|
|
10
|
+
(type_declaration
|
|
11
|
+
(type_spec
|
|
12
|
+
name: (type_identifier) @name.definition.type)) @definition.type
|
|
13
|
+
|
|
14
|
+
; Call references
|
|
15
|
+
(call_expression
|
|
16
|
+
function: [
|
|
17
|
+
(identifier) @name.reference.call
|
|
18
|
+
(selector_expression
|
|
19
|
+
field: (field_identifier) @name.reference.call)
|
|
20
|
+
]) @reference.call
|
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
; Functions
|
|
2
2
|
(function_declaration
|
|
3
|
-
name: (identifier) @name) @definition.function
|
|
3
|
+
name: (identifier) @name.definition.function) @definition.function
|
|
4
4
|
|
|
5
5
|
; Methods
|
|
6
6
|
(method_definition
|
|
7
|
-
name: (property_identifier) @name) @definition.method
|
|
7
|
+
name: (property_identifier) @name.definition.method) @definition.method
|
|
8
8
|
|
|
9
9
|
; Classes
|
|
10
10
|
(class_declaration
|
|
11
|
-
name: (identifier) @name) @definition.class
|
|
11
|
+
name: (identifier) @name.definition.class) @definition.class
|
|
12
12
|
|
|
13
|
-
; Arrow functions assigned to const/let
|
|
13
|
+
; Arrow functions assigned to const/let
|
|
14
14
|
(lexical_declaration
|
|
15
15
|
(variable_declarator
|
|
16
|
-
name: (identifier) @name
|
|
16
|
+
name: (identifier) @name.definition.function
|
|
17
17
|
value: (arrow_function))) @definition.function
|
|
18
18
|
|
|
19
|
-
; Arrow functions assigned to var
|
|
19
|
+
; Arrow functions assigned to var
|
|
20
20
|
(variable_declaration
|
|
21
21
|
(variable_declarator
|
|
22
|
-
name: (identifier) @name
|
|
22
|
+
name: (identifier) @name.definition.function
|
|
23
23
|
value: (arrow_function))) @definition.function
|
|
24
|
+
|
|
25
|
+
; Call references
|
|
26
|
+
(call_expression
|
|
27
|
+
function: [
|
|
28
|
+
(identifier) @name.reference.call
|
|
29
|
+
(member_expression
|
|
30
|
+
property: (property_identifier) @name.reference.call)
|
|
31
|
+
]) @reference.call
|
|
32
|
+
|
|
33
|
+
; New expressions
|
|
34
|
+
(new_expression
|
|
35
|
+
constructor: (identifier) @name.reference.class) @reference.class
|
|
@@ -1,17 +1,31 @@
|
|
|
1
|
-
;
|
|
2
|
-
(
|
|
3
|
-
|
|
1
|
+
; Module-level constants
|
|
2
|
+
(module
|
|
3
|
+
(expression_statement
|
|
4
|
+
(assignment
|
|
5
|
+
left: (identifier) @name.definition.constant) @definition.constant))
|
|
4
6
|
|
|
5
7
|
; Classes
|
|
6
8
|
(class_definition
|
|
7
|
-
name: (identifier) @name) @definition.class
|
|
9
|
+
name: (identifier) @name.definition.class) @definition.class
|
|
10
|
+
|
|
11
|
+
; Functions
|
|
12
|
+
(function_definition
|
|
13
|
+
name: (identifier) @name.definition.function) @definition.function
|
|
8
14
|
|
|
9
|
-
; Decorated functions
|
|
15
|
+
; Decorated definitions (functions)
|
|
10
16
|
(decorated_definition
|
|
11
17
|
definition: (function_definition
|
|
12
|
-
name: (identifier) @name) @definition.function
|
|
18
|
+
name: (identifier) @name.definition.function)) @definition.function
|
|
13
19
|
|
|
14
|
-
; Decorated classes
|
|
20
|
+
; Decorated definitions (classes)
|
|
15
21
|
(decorated_definition
|
|
16
22
|
definition: (class_definition
|
|
17
|
-
name: (identifier) @name) @definition.class
|
|
23
|
+
name: (identifier) @name.definition.class)) @definition.class
|
|
24
|
+
|
|
25
|
+
; Call references (direct function calls and attribute method calls)
|
|
26
|
+
(call
|
|
27
|
+
function: [
|
|
28
|
+
(identifier) @name.reference.call
|
|
29
|
+
(attribute
|
|
30
|
+
attribute: (identifier) @name.reference.call)
|
|
31
|
+
]) @reference.call
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
; Methods
|
|
2
|
+
(method
|
|
3
|
+
name: (identifier) @name.definition.method) @definition.method
|
|
4
|
+
|
|
5
|
+
; Singleton methods
|
|
6
|
+
(singleton_method
|
|
7
|
+
name: (identifier) @name.definition.method) @definition.method
|
|
8
|
+
|
|
9
|
+
; Classes
|
|
10
|
+
(class
|
|
11
|
+
name: (constant) @name.definition.class) @definition.class
|
|
12
|
+
|
|
13
|
+
; Modules
|
|
14
|
+
(module
|
|
15
|
+
name: (constant) @name.definition.module) @definition.module
|
|
16
|
+
|
|
17
|
+
; Call references
|
|
18
|
+
(call
|
|
19
|
+
method: (identifier) @name.reference.call) @reference.call
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
; Functions
|
|
2
|
+
(function_item
|
|
3
|
+
name: (identifier) @name.definition.function) @definition.function
|
|
4
|
+
|
|
5
|
+
; Structs
|
|
6
|
+
(struct_item
|
|
7
|
+
name: (type_identifier) @name.definition.struct) @definition.struct
|
|
8
|
+
|
|
9
|
+
; Enums
|
|
10
|
+
(enum_item
|
|
11
|
+
name: (type_identifier) @name.definition.enum) @definition.enum
|
|
12
|
+
|
|
13
|
+
; Traits
|
|
14
|
+
(trait_item
|
|
15
|
+
name: (type_identifier) @name.definition.trait) @definition.trait
|
|
16
|
+
|
|
17
|
+
; Impl blocks
|
|
18
|
+
(impl_item
|
|
19
|
+
trait: (type_identifier) @name.definition.impl) @definition.impl
|
|
20
|
+
|
|
21
|
+
; Modules
|
|
22
|
+
(mod_item
|
|
23
|
+
name: (identifier) @name.definition.module) @definition.module
|
|
24
|
+
|
|
25
|
+
; Macro definitions
|
|
26
|
+
(macro_definition
|
|
27
|
+
name: (identifier) @name.definition.macro) @definition.macro
|
|
28
|
+
|
|
29
|
+
; Call references
|
|
30
|
+
(call_expression
|
|
31
|
+
function: [
|
|
32
|
+
(identifier) @name.reference.call
|
|
33
|
+
(field_expression
|
|
34
|
+
field: (field_identifier) @name.reference.call)
|
|
35
|
+
(scoped_identifier
|
|
36
|
+
name: (identifier) @name.reference.call)
|
|
37
|
+
]) @reference.call
|