ucn 5.3.6 → 5.3.8

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/core/graph.js CHANGED
@@ -564,6 +564,8 @@ function api(index, filePath, options = {}) {
564
564
  const results = [];
565
565
  let scopedFiles = 0;
566
566
  let pythonImplicitFiles = 0;
567
+ let excludedTestFiles = 0;
568
+ let explicitFile = false;
567
569
 
568
570
  let fileIterator;
569
571
  if (filePath) {
@@ -573,6 +575,7 @@ function api(index, filePath, options = {}) {
573
575
  const fileEntry = index.files.get(resolved);
574
576
  if (!fileEntry) return { error: 'file-not-found', filePath };
575
577
  fileIterator = [[resolved, fileEntry]];
578
+ explicitFile = true;
576
579
  } else {
577
580
  // Fall back to pattern filter (substring match on relative path)
578
581
  const matches = [];
@@ -596,7 +599,8 @@ function api(index, filePath, options = {}) {
596
599
  }
597
600
 
598
601
  // Skip test files by default (test classes aren't part of public API)
599
- if (!options.includeTests && isTestFile(fileEntry.relativePath, fileEntry.language)) {
602
+ if (!explicitFile && !options.includeTests && isTestFile(fileEntry.relativePath, fileEntry.language)) {
603
+ excludedTestFiles++;
600
604
  continue;
601
605
  }
602
606
  scopedFiles++;
@@ -612,7 +616,7 @@ function api(index, filePath, options = {}) {
612
616
  results.sort((a, b) => codeUnitCompare(a.file, b.file) ||
613
617
  (a.startLine - b.startLine) || codeUnitCompare(a.name, b.name));
614
618
  Object.defineProperty(results, 'apiInfo', {
615
- value: { scopedFiles, pythonImplicitFiles },
619
+ value: { scopedFiles, pythonImplicitFiles, excludedTestFiles },
616
620
  enumerable: false, writable: true, configurable: true,
617
621
  });
618
622
  return results;
@@ -10,13 +10,17 @@ function formatCheck(result) {
10
10
  // The gate could not run — never render this like a clean tree.
11
11
  return `Pre-commit Check (${result.base}${result.staged ? ', staged' : ''})\n${'═'.repeat(60)}\nCHECK DID NOT RUN [${result.status || 'diff-failed'}] — ${result.error || 'git diff failed'}\nThis is not a pass. Fix the git context (run inside a git repository with a valid base ref) and rerun.`;
12
12
  }
13
+ const pathNotes = [];
14
+ if (result.nonSourcePaths > 0) pathNotes.push(`Note: ${result.nonSourcePaths} changed path(s) outside supported source files not analyzed.`);
15
+ if (result.untrackedPaths > 0) pathNotes.push(`Note: ${result.untrackedPaths} untracked source file(s) included as whole-file additions.`);
13
16
  if (result.empty) {
14
- return `Pre-commit Check (${result.base}${result.staged ? ', staged' : ''})\n${'═'.repeat(60)}\nNo changes to analyze${result.reason ? ` (${result.reason})` : ''}.`;
17
+ return [`Pre-commit Check (${result.base}${result.staged ? ', staged' : ''})\n${'═'.repeat(60)}\nNo changes to analyze${result.reason ? ` (${result.reason})` : ''}.`, ...pathNotes].join('\n');
15
18
  }
16
19
 
17
20
  const lines = [];
18
21
  lines.push(`Pre-commit Check vs ${result.base}${result.staged ? ' (staged)' : ''}`);
19
22
  lines.push('═'.repeat(60));
23
+ lines.push(...pathNotes);
20
24
  if (result.trust) {
21
25
  lines.push(`TRUST: ${result.trust.status} — UCN evidence is not semantic proof; compiler and tests are required`);
22
26
  const trustDetails = [];
@@ -33,7 +33,7 @@ function formatDoctor(result, options = {}) {
33
33
  // Cache state
34
34
  if (result.cache) {
35
35
  const state = result.cache.fresh === true ? 'fresh' : result.cache.fresh === false ? 'stale' : 'unknown';
36
- const buildHint = result.cache.buildMs ? `, ${result.cache.buildMs}ms build` : '';
36
+ const buildHint = result.cache.buildMs ? `, ${result.cache.buildMs}ms last index build (excludes cache I/O and query execution)` : '';
37
37
  lines.push(`Cache: ${state}${buildHint}`);
38
38
  }
39
39
  if (result.commandTrust) {
@@ -29,7 +29,8 @@ function record(pathLike, line, text, tag = '') {
29
29
  // Keep unusual filenames from becoming notes or extra physical records.
30
30
  let file = String(pathLike).replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t');
31
31
  if (file.startsWith('# ')) file = './' + file;
32
- return `${file}:${line == null ? 0 : line}:${body}${tag ? `\t# ${tag}` : ''}`;
32
+ const tagText = String(tag || '').replace(/\s+/g, ' ').trim();
33
+ return `${file}:${line == null ? 0 : line}:${body}${tagText ? `\t# ${tagText}` : ''}`;
33
34
  }
34
35
 
35
36
  function commentLines(text) {
@@ -95,9 +96,14 @@ function searchRecords(result) {
95
96
  if (result && !Array.isArray(result) && Array.isArray(result.results)) {
96
97
  for (const item of result.results) {
97
98
  const text = item.params != null ? `${item.name}(${item.params})` : item.name;
98
- out.push(record(item.file, item.line, text, item.kind || item.type));
99
+ const decorators = (item.decorators || []).map(d => `@${String(d).replace(/^@/, '')}`).join(', ');
100
+ const tag = [item.kind || item.type, decorators].filter(Boolean).join('; ');
101
+ out.push(record(item.file, item.line, text, tag));
99
102
  }
100
103
  const meta = result.meta;
104
+ if (meta?.query?.unused) {
105
+ notes.push(...commentLines(require('./search').unusedSearchNote()));
106
+ }
101
107
  if (meta && meta.totalMatched > meta.shown) {
102
108
  notes.push(`# ${meta.totalMatched - meta.shown} more match(es) (--limit=N / --all)`);
103
109
  }
@@ -372,6 +372,11 @@ function formatPublicText(command, result, params = {}, execution = {}) {
372
372
  function formatPublicJson(command, result, params = {}, execution = {}) {
373
373
  let commandMeta = {};
374
374
  let data = result;
375
+ if (Array.isArray(result) && result.limitInfo) {
376
+ commandMeta.total = result.limitInfo.total;
377
+ commandMeta.shown = result.limitInfo.shown;
378
+ commandMeta.truncated = result.limitInfo.shown < result.limitInfo.total;
379
+ }
375
380
 
376
381
  // Ambiguous bare-name resolution is a surface-level trust decision. Keep
377
382
  // it in the common envelope even for array results (notably `tests`, whose
@@ -419,6 +424,9 @@ function formatPublicJson(command, result, params = {}, execution = {}) {
419
424
  if (result.meta.truncatedMatches > 0) commandMeta.truncated = true;
420
425
  if (result.unsupportedMatches) commandMeta.unsupportedMatches = result.unsupportedMatches;
421
426
  }
427
+ if (command === 'api' && result?.apiInfo) {
428
+ commandMeta.apiInfo = result.apiInfo;
429
+ }
422
430
  if (command === 'entrypoints' && result?.filterInfo) {
423
431
  commandMeta.hiddenTestEntrypoints = result.filterInfo.hiddenTests;
424
432
  commandMeta.testsIncluded = result.filterInfo.testsIncluded;
@@ -125,7 +125,7 @@ function formatStats(stats, options = {}) {
125
125
  lines.push(`Files: ${stats.files}`);
126
126
  }
127
127
  lines.push(`Symbols: ${stats.symbols}`);
128
- lines.push(`Build time: ${stats.buildTime}ms`);
128
+ lines.push(`Last index build: ${stats.buildTime}ms (excludes cache I/O and query execution; reused from cache)`);
129
129
 
130
130
  lines.push('\nBy Language:');
131
131
  for (const [lang, info] of Object.entries(stats.byLanguage)) {
@@ -87,6 +87,10 @@ function formatSearchJson(results, term) {
87
87
  /**
88
88
  * Format structural search results (index-based queries)
89
89
  */
90
+ function unusedSearchNote(flag = '--unused') {
91
+ return `${flag} lists callable symbols with no resolved call edge; it does not assess type/field/reference liveness and is not safe-delete proof. Confirm with deadcode and usages.`;
92
+ }
93
+
90
94
  function formatStructuralSearch(result, options = {}) {
91
95
  const { results, meta } = result;
92
96
  const lines = [];
@@ -106,7 +110,7 @@ function formatStructuralSearch(result, options = {}) {
106
110
  lines.push(`Structural search: ${queryStr}`);
107
111
  lines.push('═'.repeat(60));
108
112
  if (meta.query.unused) {
109
- lines.push(`NOTE: ${options.unusedFlag || '--unused'} lists callable symbols with no resolved call edge; it does not assess type/field/reference liveness and is not safe-delete proof. Confirm with deadcode and usages.`);
113
+ lines.push(`NOTE: ${unusedSearchNote(options.unusedFlag)}`);
110
114
  lines.push('');
111
115
  }
112
116
 
@@ -447,6 +451,7 @@ function formatTestsJson(tests, name) {
447
451
  }
448
452
 
449
453
  module.exports = {
454
+ unusedSearchNote,
450
455
  formatSearch,
451
456
  formatSearchJson,
452
457
  formatStructuralSearch,
package/core/project.js CHANGED
@@ -424,6 +424,7 @@ class ProjectIndex {
424
424
  const implicitProjectDiscovery = !Array.isArray(pattern) && !pattern;
425
425
  this.unsupportedFiles = [];
426
426
  this.discoveryIssues = [];
427
+ this.includeBundled = options.includeBundled === true;
427
428
  const discoveryIssueKeys = new Set();
428
429
  const recordDiscoveryIssue = (issue) => {
429
430
  const rel = path.relative(this.root, issue.path || this.root) || '.';
@@ -450,7 +451,8 @@ class ProjectIndex {
450
451
  maxFiles: options.maxFiles || this.config.maxFiles || 50000,
451
452
  maxDepth: options.maxDepth ?? this.config.maxDepth,
452
453
  maxFileSize: options.maxFileSize ?? this.config.maxFileSize,
453
- followSymlinks: options.followSymlinks
454
+ followSymlinks: options.followSymlinks,
455
+ includeBundled: this.includeBundled,
454
456
  };
455
457
 
456
458
  // Merge .gitignore and .ucn.json exclude into file discovery
@@ -22,7 +22,7 @@ function buildPublicParams(command, arg, params = {}) {
22
22
  if (clean.top === 0 && params.topRaw == null) delete clean.top;
23
23
  if (clean.maxLines == null) delete clean.maxLines;
24
24
  // Surface-only values never belong in execute params.
25
- for (const key of ['json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'interactive',
25
+ for (const key of ['json', 'quiet', 'cache', 'clearCache', 'followSymlinks', 'includeBundled', 'interactive',
26
26
  'topRaw', 'limitRaw', 'maxFilesRaw', 'maxLinesRaw', 'maxChars', 'maxCharsRaw',
27
27
  'depthRaw', 'contextRaw', 'workersRaw',
28
28
  '_fileFromFileMode']) delete clean[key];
package/core/registry.js CHANGED
@@ -114,6 +114,7 @@ const PARAM_MAP = {
114
114
  max_files: 'maxFiles',
115
115
  max_chars: 'maxChars',
116
116
  follow_symlinks: 'followSymlinks',
117
+ include_bundled: 'includeBundled',
117
118
  unreachable_only: 'unreachableOnly',
118
119
  server_only: 'serverOnly',
119
120
  client_only: 'clientOnly',
@@ -142,7 +143,7 @@ const FLAG_APPLICABILITY = {
142
143
  impact: ['name', 'file', 'exclude', 'className', 'line', 'includeMethods', 'top', 'unreachableOnly', 'compact', 'base', 'staged', 'limit', 'all', 'lines'],
143
144
  tests: ['name', 'file', 'exclude', 'className', 'line', 'callsOnly', 'depth', 'includeMethods', 'all'],
144
145
  deps: ['file', 'exclude', 'depth', 'direction', 'all', 'detailed', 'cycles'],
145
- api: ['file', 'in', 'limit'],
146
+ api: ['file', 'in', 'limit', 'includeTests'],
146
147
  check: ['name', 'file', 'className', 'line', 'includeMethods', 'base', 'staged', 'limit'],
147
148
  plan: ['name', 'file', 'className', 'line', 'addParam', 'removeParam', 'renameTo', 'defaultValue'],
148
149
  repo: ['file', 'exclude', 'top', 'limit', 'all', 'detailed', 'topLevel', 'in', 'functions', 'hot', 'deep', 'sections'],
@@ -303,7 +304,7 @@ function formatSurfaceMessage(message, surface = 'cli') {
303
304
  const booleanParams = new Set([
304
305
  'includeTests', 'excludeTests', 'includeMethods', 'withTypes',
305
306
  'codeOnly', 'caseSensitive', 'includeExported', 'includeDecorated',
306
- 'showConfidence', 'callsOnly', 'topLevel', 'followSymlinks',
307
+ 'showConfidence', 'callsOnly', 'topLevel', 'followSymlinks', 'includeBundled',
307
308
  'unreachableOnly', 'serverOnly', 'clientOnly', 'hideUncertain',
308
309
  'expandUnverified', 'withSource', 'all', 'compact', 'exact',
309
310
  'regex', 'exported', 'unused', 'staged', 'detailed', 'functions',
@@ -314,7 +315,7 @@ function formatSurfaceMessage(message, surface = 'cli') {
314
315
  rendered = rendered.replace(/--([a-z][a-z0-9-]*)(?:=([^\s,.)]+))?/g,
315
316
  (whole, flag, rawValue) => {
316
317
  const camel = flag.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
317
- if (!knownParams.has(camel) && !['maxChars', 'maxFiles'].includes(camel)) {
318
+ if (!knownParams.has(camel) && !['maxChars', 'maxFiles', 'includeBundled'].includes(camel)) {
318
319
  return whole;
319
320
  }
320
321
  const snake = REVERSE_PARAM_MAP[camel] || camel;
@@ -365,7 +366,7 @@ const CLI_GLOBAL_FLAGS = Object.freeze([
365
366
  '--help', '-h', '--version', '-v', '--mcp',
366
367
  '--json', '--verbose', '--no-quiet', '--quiet',
367
368
  '--interactive', '-i',
368
- '--no-cache', '--clear-cache', '--no-follow-symlinks',
369
+ '--no-cache', '--clear-cache', '--no-follow-symlinks', '--include-bundled',
369
370
  '--max-files', '--max-chars', '--workers',
370
371
  ]);
371
372
 
@@ -408,7 +409,7 @@ function getCliAcceptedFlags() {
408
409
  * One line per command: `show: file, exclude, class_name, ...`
409
410
  */
410
411
  function generateMcpParamSection() {
411
- const lines = ['', 'ACCEPTED FLAGS PER COMMAND (max_chars, max_files, follow_symlinks always accepted; flags not listed below are ignored):'];
412
+ const lines = ['', 'ACCEPTED FLAGS PER COMMAND (max_chars, max_files, follow_symlinks, include_bundled always accepted; flags not listed below are ignored):'];
412
413
  for (const cmd of CANONICAL_COMMANDS) {
413
414
  const flags = FLAG_APPLICABILITY[cmd];
414
415
  if (!flags || flags.length === 0) continue;
package/core/reporting.js CHANGED
@@ -48,6 +48,7 @@ function getStats(index, options = {}) {
48
48
  files: scopedFiles.length,
49
49
  symbols: totalSymbols, // Total symbol count, not unique names
50
50
  buildTime: index.buildTime,
51
+ buildTimeNote: 'Last index build, including discovery and graphs; excludes cache I/O and query execution. Reused when loading a cached index.',
51
52
  byLanguage: {},
52
53
  byType: {},
53
54
  ...(index.truncated && { truncated: index.truncated })
@@ -1044,6 +1045,7 @@ function orient(index, options = {}) {
1044
1045
  files: stats.files,
1045
1046
  symbols: stats.symbols,
1046
1047
  buildTime: stats.buildTime,
1048
+ buildTimeNote: stats.buildTimeNote,
1047
1049
  byLanguage: stats.byLanguage,
1048
1050
  dirs,
1049
1051
  hot: {
package/core/search.js CHANGED
@@ -204,9 +204,19 @@ function _applyFindFilters(index, matches, options) {
204
204
  return filtered;
205
205
  }
206
206
 
207
- // Add per-symbol usage counts for disambiguation
208
- const withCounts = filtered.map(m => {
209
- const counts = index.countSymbolUsages(m);
207
+ // Rank cheaply before adjudicating callers. A broad inventory must never
208
+ // pay for pinned caller resolution on rows the caller will not receive.
209
+ const ranked = filtered.map(symbol => ({
210
+ symbol, counts: index.countSymbolUsages(symbol),
211
+ }));
212
+ if (options.limit > 0 && ranked.length > options.limit) {
213
+ ranked.sort((a, b) => b.counts.total - a.counts.total ||
214
+ codeUnitCompare(a.symbol.relativePath || '', b.symbol.relativePath || '') ||
215
+ a.symbol.startLine - b.symbol.startLine ||
216
+ codeUnitCompare(a.symbol.name, b.symbol.name));
217
+ }
218
+ const survivors = options.limit > 0 ? ranked.slice(0, options.limit) : ranked;
219
+ const withCounts = survivors.map(({ symbol: m, counts }) => {
210
220
  // The fast count supplies cheap definition/import totals, but its
211
221
  // name-only call bucket cannot distinguish json.dumps from a project
212
222
  // dumps, or one class's save from another's. The public `find`
@@ -873,7 +883,10 @@ function structuralSearch(index, options = {}) {
873
883
  // expression position — never "unused" (the deadcode
874
884
  // twin of the bodyScopedName audit skip).
875
885
  if (def.bodyScopedName) continue;
876
- index.buildCalleeIndex();
886
+ // buildCalleeIndex rebuilds the whole project. Reuse
887
+ // the eagerly built (or cache-loaded) index, including
888
+ // across every candidate in this operation.
889
+ if (!index.calleeIndex) index.buildCalleeIndex();
877
890
  // A name whose every call site is its own recursion
878
891
  // has zero callers (fix #253c — the deadcode
879
892
  // carve-out, applied here). Class-kind names are
@@ -13,6 +13,7 @@ const { typeOrigin } = require('./type-evidence');
13
13
 
14
14
  const {
15
15
  traverseTree,
16
+ nodeTextWithoutComments,
16
17
  traverseTreeCached,
17
18
  nodeToLocation,
18
19
  extractJSDocstring,
@@ -989,7 +990,7 @@ function paramTypeText(param, identity) {
989
990
  const defaultValue = param.childForFieldName('default_value');
990
991
  const end = defaultValue ? defaultValue.startIndex : param.endIndex;
991
992
  if (identity.nameNode.startIndex < base || identity.nameNode.endIndex > end) return null;
992
- const text = param.text.slice(0, end - base);
993
+ const text = nodeTextWithoutComments(param).slice(0, end - base);
993
994
  const typeText = (text.slice(0, identity.nameNode.startIndex - base) +
994
995
  text.slice(identity.nameNode.endIndex - base))
995
996
  .replace(/\s+/g, ' ')
@@ -1014,10 +1015,10 @@ function structuredParams(paramsNode) {
1014
1015
  // their type text alone — the type must not double as both name and
1015
1016
  // annotation, and `void *` must not collapse into the `(void)` form.
1016
1017
  const info = {
1017
- name: identity.name || param.text.replace(/\s+/g, ' ').trim(),
1018
+ name: identity.name || nodeTextWithoutComments(param).replace(/\s+/g, ' ').trim(),
1018
1019
  };
1019
1020
  if (typeNode && identity.name) {
1020
- info.type = paramTypeText(param, identity) || typeNode.text;
1021
+ info.type = paramTypeText(param, identity) || nodeTextWithoutComments(typeNode);
1021
1022
  }
1022
1023
  if (param.type === 'optional_parameter_declaration') info.optional = true;
1023
1024
  let declaratorCursor = declarator;
@@ -1164,7 +1165,7 @@ function returnTypeOf(node) {
1164
1165
  const descriptor = (current.namedChildren || []).find(child =>
1165
1166
  child.type === 'type_descriptor') || current.namedChild(0);
1166
1167
  const type = descriptor?.childForFieldName('type') || descriptor;
1167
- return type?.text || null;
1168
+ return nodeTextWithoutComments(type) || null;
1168
1169
  }
1169
1170
  for (const child of current.namedChildren || []) {
1170
1171
  const found = findTrailing(child);
@@ -1193,7 +1194,7 @@ function returnTypeOf(node) {
1193
1194
  current = current.childForFieldName('declarator') ||
1194
1195
  (current.namedChildren || []).find(child => child.type.endsWith('_declarator'));
1195
1196
  }
1196
- return stars > 0 ? `${typeNode.text} ${'*'.repeat(stars)}` : typeNode.text;
1197
+ return stars > 0 ? `${nodeTextWithoutComments(typeNode)} ${'*'.repeat(stars)}` : nodeTextWithoutComments(typeNode);
1197
1198
  }
1198
1199
 
1199
1200
  function memberFromNode(node, className, access, lines, mode) {
@@ -1209,7 +1210,7 @@ function memberFromNode(node, className, access, lines, mode) {
1209
1210
  if (isConstructor && identity.name.startsWith('~')) modifiers.push('destructor');
1210
1211
  return {
1211
1212
  name: identity.name,
1212
- params: paramsNode ? paramsNode.text.replace(/^\(|\)$/g, '').trim() : '...',
1213
+ params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '...',
1213
1214
  paramsStructured: structuredParams(paramsNode),
1214
1215
  returnType: isConstructor ? null :
1215
1216
  (identity.conversionType || returnTypeOf(node)),
@@ -1510,7 +1511,7 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
1510
1511
  : null;
1511
1512
  functions.push({
1512
1513
  name: identity.name,
1513
- params: paramsNode ? paramsNode.text.replace(/^\(|\)$/g, '').trim() : '...',
1514
+ params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '...',
1514
1515
  paramsStructured: structuredParams(paramsNode),
1515
1516
  returnType: isConstructor ? null :
1516
1517
  (identity.conversionType || returnTypeOf(node)),
@@ -1800,7 +1801,7 @@ function findMacrosInTree(tree, lines, parser) {
1800
1801
  startLine,
1801
1802
  endLine,
1802
1803
  indent,
1803
- params: paramsNode ? paramsNode.text.replace(/^\(|\)$/g, '').trim() : undefined,
1804
+ params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : undefined,
1804
1805
  paramsStructured: paramsNode
1805
1806
  ? (paramsNode.namedChildren || [])
1806
1807
  .filter(child => child.type === 'identifier')
@@ -2,6 +2,7 @@
2
2
 
3
3
  const {
4
4
  traverseTree,
5
+ nodeTextWithoutComments,
5
6
  traverseTreeCached,
6
7
  nodeToLocation,
7
8
  extractJSDocstring,
@@ -127,7 +128,7 @@ function structuredParams(paramsNode) {
127
128
  if (nameNode) {
128
129
  recoveredParams.push({
129
130
  name: nameNode.text,
130
- ...(typeNode && { type: typeNode.text }),
131
+ ...(typeNode && { type: nodeTextWithoutComments(typeNode) }),
131
132
  rest: true,
132
133
  });
133
134
  }
@@ -138,14 +139,14 @@ function structuredParams(paramsNode) {
138
139
  const typeNode = param.childForFieldName('type');
139
140
  if (!nameNode) continue;
140
141
  const info = { name: nameNode.text };
141
- if (typeNode) info.type = typeNode.text;
142
+ if (typeNode) info.type = nodeTextWithoutComments(typeNode);
142
143
  if (modifiersOf(param).includes('this')) info.extensionReceiver = true;
143
144
  if (param.type === 'parameter_array') info.rest = true;
144
145
  const value = param.childForFieldName('value') ||
145
146
  param.namedChildren.find(child => child !== nameNode && child !== typeNode &&
146
- !['attribute_list', 'modifier'].includes(child.type));
147
+ !['attribute_list', 'modifier', 'comment'].includes(child.type));
147
148
  if (value) {
148
- info.default = value.text;
149
+ info.default = nodeTextWithoutComments(value);
149
150
  info.optional = true;
150
151
  }
151
152
  params.push(info);
@@ -208,9 +209,9 @@ function memberFromNode(node, className, lines) {
208
209
  const paramsStructured = structuredParams(paramsNode);
209
210
  return {
210
211
  name,
211
- params: paramsNode ? paramsNode.text.replace(/^\(|\)$/g, '').trim() : '...',
212
+ params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '...',
212
213
  paramsStructured,
213
- returnType: isConstructor ? null : returnNode?.text || null,
214
+ returnType: isConstructor ? null : nodeTextWithoutComments(returnNode).trim() || null,
214
215
  startLine,
215
216
  endLine,
216
217
  indent,
@@ -263,9 +264,9 @@ function indexerMember(node, className, lines) {
263
264
  const { startLine, endLine, indent } = nodeToLocation(node, lines);
264
265
  return {
265
266
  name: 'this[]',
266
- params: paramsNode ? paramsNode.text.replace(/^\[|\]$/g, '').trim() : '...',
267
+ params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\[|\]$/g, '').trim() : '...',
267
268
  paramsStructured: structuredParams(paramsNode),
268
- returnType: typeNode?.text || null,
269
+ returnType: nodeTextWithoutComments(typeNode).trim() || null,
269
270
  startLine,
270
271
  endLine,
271
272
  indent,
@@ -495,9 +496,9 @@ function findFunctions(code, parser) {
495
496
  const modifiers = modifiersOf(node);
496
497
  functions.push({
497
498
  name: nameNode.text,
498
- params: paramsNode ? paramsNode.text.replace(/^\(|\)$/g, '').trim() : '...',
499
+ params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '...',
499
500
  paramsStructured: structuredParams(paramsNode),
500
- returnType: returnNode?.text || null,
501
+ returnType: nodeTextWithoutComments(returnNode).trim() || null,
501
502
  startLine,
502
503
  endLine,
503
504
  indent,
package/languages/go.js CHANGED
@@ -10,6 +10,7 @@ const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
10
10
 
11
11
  const {
12
12
  traverseTree,
13
+ nodeTextWithoutComments,
13
14
  traverseTreeCached,
14
15
  nodeToLocation,
15
16
  parseStructuredParams,
@@ -29,7 +30,7 @@ function parseTree(parser, code) {
29
30
  function extractReturnType(node) {
30
31
  const resultNode = node.childForFieldName('result');
31
32
  if (resultNode) {
32
- return resultNode.text.trim() || null;
33
+ return nodeTextWithoutComments(resultNode).trim() || null;
33
34
  }
34
35
  return null;
35
36
  }
@@ -39,7 +40,7 @@ function extractReturnedFunctionResult(node) {
39
40
  const resultNode = node.childForFieldName('result');
40
41
  if (resultNode?.type !== 'function_type') return null;
41
42
  const innerResult = resultNode.childForFieldName('result');
42
- return innerResult?.text.trim() || null;
43
+ return nodeTextWithoutComments(innerResult).trim() || null;
43
44
  }
44
45
 
45
46
  /**
@@ -51,7 +52,7 @@ function extractGoParams(paramsNode) {
51
52
  // unknown signatures in JSON output (fix #238; the shared
52
53
  // utils.extractParams already had this fix).
53
54
  if (!paramsNode) return '...';
54
- const text = paramsNode.text;
55
+ const text = nodeTextWithoutComments(paramsNode);
55
56
  return text.replace(/^\(|\)$/g, '').trim();
56
57
  }
57
58
 
@@ -115,7 +116,7 @@ function _processFunction(node, functions, processedRanges, lines) {
115
116
  indent,
116
117
  modifiers: isExported ? ['export'] : [],
117
118
  isFunctionVariable: true,
118
- ...(resultNode?.text.trim() && { returnType: resultNode.text.trim() }),
119
+ ...(resultNode && { returnType: nodeTextWithoutComments(resultNode).trim() || null }),
119
120
  });
120
121
  }
121
122
  return true;
@@ -491,11 +492,11 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
491
492
  } else if (sub.type === 'parameter_list') {
492
493
  hasParams = true;
493
494
  if (!paramsText) {
494
- paramsText = sub.text.slice(1, -1); // strip parens
495
+ paramsText = nodeTextWithoutComments(sub).slice(1, -1); // strip parens
495
496
  paramsNode = sub;
496
497
  } else {
497
498
  // Second parameter_list is the return type tuple
498
- returnType = sub.text;
499
+ returnType = nodeTextWithoutComments(sub);
499
500
  }
500
501
  }
501
502
  }
@@ -514,7 +515,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
514
515
  for (let j = 0; j < child.namedChildCount; j++) {
515
516
  const sub = child.namedChild(j);
516
517
  if (returnTypeNodes.has(sub.type) && sub.text !== nameText) {
517
- returnType = sub.text;
518
+ returnType = nodeTextWithoutComments(sub);
518
519
  }
519
520
  }
520
521
  }
package/languages/java.js CHANGED
@@ -10,6 +10,7 @@ const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
10
10
 
11
11
  const {
12
12
  traverseTree,
13
+ nodeTextWithoutComments,
13
14
  traverseTreeCached,
14
15
  nodeToLocation,
15
16
  parseStructuredParams,
@@ -32,7 +33,7 @@ function extractJavaParams(paramsNode) {
32
33
  // unknown signatures in JSON output (fix #241; go/rust got this in #238,
33
34
  // the shared utils.extractParams already had it).
34
35
  if (!paramsNode) return '...';
35
- const text = paramsNode.text;
36
+ const text = nodeTextWithoutComments(paramsNode);
36
37
  let params = text.replace(/^\(|\)$/g, '').trim();
37
38
  return params;
38
39
  }
@@ -201,7 +202,7 @@ function stripJavaString(text) {
201
202
  function extractReturnType(node) {
202
203
  const typeNode = node.childForFieldName('type');
203
204
  if (typeNode) {
204
- return typeNode.text;
205
+ return nodeTextWithoutComments(typeNode);
205
206
  }
206
207
  return null;
207
208
  }
@@ -10,6 +10,7 @@ const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
10
10
 
11
11
  const {
12
12
  traverseTree,
13
+ nodeTextWithoutComments,
13
14
  traverseTreeCached,
14
15
  nodeToLocation,
15
16
  extractParams,
@@ -34,7 +35,7 @@ function parseTree(parser, code) {
34
35
  function extractReturnType(node) {
35
36
  const returnTypeNode = node.childForFieldName('return_type');
36
37
  if (returnTypeNode) {
37
- let text = returnTypeNode.text.trim();
38
+ let text = nodeTextWithoutComments(returnTypeNode).trim();
38
39
  if (text.startsWith(':')) {
39
40
  text = text.slice(1).trim();
40
41
  }
@@ -10,6 +10,7 @@ const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
10
10
 
11
11
  const {
12
12
  traverseTree,
13
+ nodeTextWithoutComments,
13
14
  traverseTreeCached,
14
15
  nodeToLocation,
15
16
  parseStructuredParams,
@@ -32,7 +33,7 @@ function parseTree(parser, code) {
32
33
  function extractReturnType(node) {
33
34
  const returnTypeNode = node.childForFieldName('return_type');
34
35
  if (returnTypeNode) {
35
- let text = returnTypeNode.text.trim();
36
+ let text = nodeTextWithoutComments(returnTypeNode).trim();
36
37
  if (text.startsWith('->')) {
37
38
  text = text.slice(2).trim();
38
39
  }
@@ -129,7 +130,7 @@ function extractPythonParams(paramsNode) {
129
130
  // unknown signatures in JSON output (fix #241; go/rust got this in #238,
130
131
  // the shared utils.extractParams already had it).
131
132
  if (!paramsNode) return '...';
132
- const text = paramsNode.text;
133
+ const text = nodeTextWithoutComments(paramsNode);
133
134
  let params = text.replace(/^\(|\)$/g, '').trim();
134
135
  return params;
135
136
  }
package/languages/rust.js CHANGED
@@ -10,6 +10,7 @@ const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
10
10
 
11
11
  const {
12
12
  traverseTree,
13
+ nodeTextWithoutComments,
13
14
  traverseTreeCached,
14
15
  nodeToLocation,
15
16
  parseStructuredParams,
@@ -128,7 +129,7 @@ function declarationTrees(code, parser) {
128
129
  function extractReturnType(node) {
129
130
  const returnTypeNode = node.childForFieldName('return_type');
130
131
  if (returnTypeNode) {
131
- let text = returnTypeNode.text.trim();
132
+ let text = nodeTextWithoutComments(returnTypeNode).trim();
132
133
  if (text.startsWith('->')) {
133
134
  text = text.slice(2).trim();
134
135
  }
@@ -198,7 +199,7 @@ function extractRustParams(paramsNode) {
198
199
  // unknown signatures in JSON output (fix #238; the shared
199
200
  // utils.extractParams already had this fix).
200
201
  if (!paramsNode) return '...';
201
- const text = paramsNode.text;
202
+ const text = nodeTextWithoutComments(paramsNode);
202
203
  return text.replace(/^\(|\)$/g, '').trim();
203
204
  }
204
205