ucn 3.7.5 → 3.7.6
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/cli/index.js +178 -97
- package/core/discovery.js +8 -2
- package/core/output.js +158 -0
- package/core/project.js +121 -38
- package/core/shared.js +43 -0
- package/languages/go.js +61 -1
- package/languages/java.js +78 -2
- package/languages/rust.js +71 -2
- package/mcp/server.js +7 -26
- package/package.json +1 -1
- package/test/mcp-edge-cases.js +28 -0
- package/test/parser.test.js +506 -9
package/cli/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const { getParser, getLanguageModule } = require('../languages');
|
|
|
15
15
|
const { ProjectIndex } = require('../core/project');
|
|
16
16
|
const { expandGlob, findProjectRoot, isTestFile } = require('../core/discovery');
|
|
17
17
|
const output = require('../core/output');
|
|
18
|
+
const { pickBestDefinition, addTestExclusions } = require('../core/shared');
|
|
18
19
|
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// ARGUMENT PARSING
|
|
@@ -141,13 +142,6 @@ const positionalArgs = [
|
|
|
141
142
|
* Add test file patterns to exclusion list
|
|
142
143
|
* Used by find/usages when --include-tests is not specified
|
|
143
144
|
*/
|
|
144
|
-
function addTestExclusions(exclude) {
|
|
145
|
-
const testPatterns = ['test', 'spec', '__tests__', '__mocks__', 'fixture', 'mock'];
|
|
146
|
-
const existing = new Set(exclude.map(e => e.toLowerCase()));
|
|
147
|
-
const additions = testPatterns.filter(p => !existing.has(p));
|
|
148
|
-
return [...exclude, ...additions];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
145
|
/**
|
|
152
146
|
* Validate required argument and exit with usage if missing
|
|
153
147
|
* @param {string} arg - The argument to validate
|
|
@@ -740,13 +734,15 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
740
734
|
break;
|
|
741
735
|
}
|
|
742
736
|
|
|
743
|
-
case 'example':
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
737
|
+
case 'example': {
|
|
738
|
+
requireArg(arg, 'Usage: ucn . example <name>');
|
|
739
|
+
const exampleResult = index.example(arg);
|
|
740
|
+
printOutput(exampleResult,
|
|
741
|
+
r => output.formatExampleJson(r, arg),
|
|
742
|
+
r => output.formatExample(r, arg)
|
|
743
|
+
);
|
|
749
744
|
break;
|
|
745
|
+
}
|
|
750
746
|
|
|
751
747
|
case 'context': {
|
|
752
748
|
requireArg(arg, 'Usage: ucn . context <name>');
|
|
@@ -868,7 +864,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
868
864
|
defaultValue: flags.defaultValue,
|
|
869
865
|
file: flags.file
|
|
870
866
|
});
|
|
871
|
-
printOutput(planResult,
|
|
867
|
+
printOutput(planResult, output.formatPlanJson, output.formatPlan);
|
|
872
868
|
break;
|
|
873
869
|
}
|
|
874
870
|
|
|
@@ -885,14 +881,14 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
885
881
|
case 'stack': {
|
|
886
882
|
requireArg(arg, 'Usage: ucn . stacktrace "<stack trace text>"\nExample: ucn . stacktrace "Error: failed\\n at parseFile (core/parser.js:90:5)"');
|
|
887
883
|
const stackResult = index.parseStackTrace(arg);
|
|
888
|
-
printOutput(stackResult,
|
|
884
|
+
printOutput(stackResult, output.formatStackTraceJson, output.formatStackTrace);
|
|
889
885
|
break;
|
|
890
886
|
}
|
|
891
887
|
|
|
892
888
|
case 'verify': {
|
|
893
889
|
requireArg(arg, 'Usage: ucn . verify <name>');
|
|
894
890
|
const verifyResult = index.verify(arg, { file: flags.file });
|
|
895
|
-
printOutput(verifyResult,
|
|
891
|
+
printOutput(verifyResult, output.formatVerifyJson, output.formatVerify);
|
|
896
892
|
break;
|
|
897
893
|
}
|
|
898
894
|
|
|
@@ -986,7 +982,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
986
982
|
in: flags.in || subdirScope
|
|
987
983
|
});
|
|
988
984
|
printOutput(deadcodeResults,
|
|
989
|
-
|
|
985
|
+
output.formatDeadcodeJson,
|
|
990
986
|
r => output.formatDeadcode(r)
|
|
991
987
|
);
|
|
992
988
|
break;
|
|
@@ -1056,15 +1052,16 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
1056
1052
|
}
|
|
1057
1053
|
}
|
|
1058
1054
|
|
|
1059
|
-
function extractFunctionFromProject(index, name) {
|
|
1060
|
-
const
|
|
1055
|
+
function extractFunctionFromProject(index, name, overrideFlags) {
|
|
1056
|
+
const f = overrideFlags || flags;
|
|
1057
|
+
const matches = index.find(name, { file: f.file }).filter(m => m.type === 'function' || m.params !== undefined);
|
|
1061
1058
|
|
|
1062
1059
|
if (matches.length === 0) {
|
|
1063
1060
|
console.error(`Function "${name}" not found`);
|
|
1064
1061
|
return;
|
|
1065
1062
|
}
|
|
1066
1063
|
|
|
1067
|
-
if (matches.length > 1 && !
|
|
1064
|
+
if (matches.length > 1 && !f.file && f.all) {
|
|
1068
1065
|
// Show all definitions
|
|
1069
1066
|
for (let i = 0; i < matches.length; i++) {
|
|
1070
1067
|
const m = matches[i];
|
|
@@ -1073,7 +1070,7 @@ function extractFunctionFromProject(index, name) {
|
|
|
1073
1070
|
const extracted = lines.slice(m.startLine - 1, m.endLine);
|
|
1074
1071
|
const fnCode = cleanHtmlScriptTags(extracted, detectLanguage(m.file)).join('\n');
|
|
1075
1072
|
if (i > 0) console.log('');
|
|
1076
|
-
if (
|
|
1073
|
+
if (f.json) {
|
|
1077
1074
|
console.log(output.formatFunctionJson(m, fnCode));
|
|
1078
1075
|
} else {
|
|
1079
1076
|
console.log(output.formatFn(m, fnCode));
|
|
@@ -1083,7 +1080,7 @@ function extractFunctionFromProject(index, name) {
|
|
|
1083
1080
|
}
|
|
1084
1081
|
|
|
1085
1082
|
let match;
|
|
1086
|
-
if (matches.length > 1 && !
|
|
1083
|
+
if (matches.length > 1 && !f.file) {
|
|
1087
1084
|
// Auto-select best match using same scoring as resolveSymbol
|
|
1088
1085
|
match = pickBestDefinition(matches);
|
|
1089
1086
|
const others = matches.filter(m => m !== match).map(m => `${m.relativePath}:${m.startLine}`).join(', ');
|
|
@@ -1098,15 +1095,16 @@ function extractFunctionFromProject(index, name) {
|
|
|
1098
1095
|
const extracted = lines.slice(match.startLine - 1, match.endLine);
|
|
1099
1096
|
const fnCode = cleanHtmlScriptTags(extracted, detectLanguage(match.file)).join('\n');
|
|
1100
1097
|
|
|
1101
|
-
if (
|
|
1098
|
+
if (f.json) {
|
|
1102
1099
|
console.log(output.formatFunctionJson(match, fnCode));
|
|
1103
1100
|
} else {
|
|
1104
1101
|
console.log(output.formatFn(match, fnCode));
|
|
1105
1102
|
}
|
|
1106
1103
|
}
|
|
1107
1104
|
|
|
1108
|
-
function extractClassFromProject(index, name) {
|
|
1109
|
-
const
|
|
1105
|
+
function extractClassFromProject(index, name, overrideFlags) {
|
|
1106
|
+
const f = overrideFlags || flags;
|
|
1107
|
+
const matches = index.find(name, { file: f.file }).filter(m =>
|
|
1110
1108
|
['class', 'interface', 'type', 'enum', 'struct', 'trait'].includes(m.type)
|
|
1111
1109
|
);
|
|
1112
1110
|
|
|
@@ -1115,7 +1113,7 @@ function extractClassFromProject(index, name) {
|
|
|
1115
1113
|
return;
|
|
1116
1114
|
}
|
|
1117
1115
|
|
|
1118
|
-
if (matches.length > 1 && !
|
|
1116
|
+
if (matches.length > 1 && !f.file && f.all) {
|
|
1119
1117
|
// Show all definitions using index data (no re-parsing)
|
|
1120
1118
|
for (let i = 0; i < matches.length; i++) {
|
|
1121
1119
|
const m = matches[i];
|
|
@@ -1124,7 +1122,7 @@ function extractClassFromProject(index, name) {
|
|
|
1124
1122
|
const extracted = codeLines.slice(m.startLine - 1, m.endLine);
|
|
1125
1123
|
const clsCode = cleanHtmlScriptTags(extracted, detectLanguage(m.file)).join('\n');
|
|
1126
1124
|
if (i > 0) console.log('');
|
|
1127
|
-
if (
|
|
1125
|
+
if (f.json) {
|
|
1128
1126
|
console.log(JSON.stringify({ ...m, code: clsCode }, null, 2));
|
|
1129
1127
|
} else {
|
|
1130
1128
|
console.log(output.formatClass(m, clsCode));
|
|
@@ -1134,7 +1132,7 @@ function extractClassFromProject(index, name) {
|
|
|
1134
1132
|
}
|
|
1135
1133
|
|
|
1136
1134
|
let match;
|
|
1137
|
-
if (matches.length > 1 && !
|
|
1135
|
+
if (matches.length > 1 && !f.file) {
|
|
1138
1136
|
// Auto-select best match using same scoring as resolveSymbol
|
|
1139
1137
|
match = pickBestDefinition(matches);
|
|
1140
1138
|
const others = matches.filter(m => m !== match).map(m => `${m.relativePath}:${m.startLine}`).join(', ');
|
|
@@ -1149,7 +1147,7 @@ function extractClassFromProject(index, name) {
|
|
|
1149
1147
|
const extracted = codeLines.slice(match.startLine - 1, match.endLine);
|
|
1150
1148
|
const clsCode = cleanHtmlScriptTags(extracted, detectLanguage(match.file)).join('\n');
|
|
1151
1149
|
|
|
1152
|
-
if (
|
|
1150
|
+
if (f.json) {
|
|
1153
1151
|
console.log(JSON.stringify({ ...match, code: clsCode }, null, 2));
|
|
1154
1152
|
} else {
|
|
1155
1153
|
console.log(output.formatClass(match, clsCode));
|
|
@@ -1628,30 +1626,6 @@ function printLines(lines, range) {
|
|
|
1628
1626
|
}
|
|
1629
1627
|
}
|
|
1630
1628
|
|
|
1631
|
-
/**
|
|
1632
|
-
* Pick the best definition from multiple matches using same scoring as resolveSymbol.
|
|
1633
|
-
* Prefers lib/src/core over test/examples/vendor directories.
|
|
1634
|
-
*/
|
|
1635
|
-
function pickBestDefinition(matches) {
|
|
1636
|
-
const typeOrder = new Set(['class', 'struct', 'interface', 'type', 'impl']);
|
|
1637
|
-
const scored = matches.map(m => {
|
|
1638
|
-
let score = 0;
|
|
1639
|
-
const rp = m.relativePath || '';
|
|
1640
|
-
// Prefer class/struct/interface types (+1000) - same as resolveSymbol
|
|
1641
|
-
if (typeOrder.has(m.type)) score += 1000;
|
|
1642
|
-
if (isTestFile(rp, detectLanguage(m.file))) score -= 500;
|
|
1643
|
-
if (/^(examples?|docs?|vendor|third[_-]?party|benchmarks?|samples?)\//i.test(rp)) score -= 300;
|
|
1644
|
-
if (/^(lib|src|core|internal|pkg|crates)\//i.test(rp)) score += 200;
|
|
1645
|
-
// Tiebreaker: prefer larger function bodies (more important/complex)
|
|
1646
|
-
if (m.startLine && m.endLine) {
|
|
1647
|
-
score += Math.min(m.endLine - m.startLine, 100);
|
|
1648
|
-
}
|
|
1649
|
-
return { match: m, score };
|
|
1650
|
-
});
|
|
1651
|
-
scored.sort((a, b) => b.score - a.score);
|
|
1652
|
-
return scored[0].match;
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
1629
|
function suggestSimilar(query, names) {
|
|
1656
1630
|
const lower = query.toLowerCase();
|
|
1657
1631
|
const similar = names.filter(n => n.toLowerCase().includes(lower));
|
|
@@ -1814,31 +1788,38 @@ function runInteractive(rootDir) {
|
|
|
1814
1788
|
if (input === 'help') {
|
|
1815
1789
|
console.log(`
|
|
1816
1790
|
Commands:
|
|
1817
|
-
toc Project overview
|
|
1818
|
-
find <name> Find symbol
|
|
1791
|
+
toc Project overview (--detailed)
|
|
1792
|
+
find <name> Find symbol (--exact, --file=)
|
|
1819
1793
|
about <name> Everything about a symbol
|
|
1820
1794
|
usages <name> All usages grouped by type
|
|
1821
1795
|
context <name> Callers + callees
|
|
1822
1796
|
expand <N> Show code for item N from context
|
|
1823
1797
|
smart <name> Function + dependencies
|
|
1824
1798
|
impact <name> What breaks if changed
|
|
1825
|
-
trace <name> Call tree
|
|
1799
|
+
trace <name> Call tree (--depth=N)
|
|
1826
1800
|
example <name> Best usage example
|
|
1827
1801
|
related <name> Sibling functions
|
|
1802
|
+
fn <name> Extract function code (--file=)
|
|
1803
|
+
class <name> Extract class code (--file=)
|
|
1804
|
+
lines <range> Extract lines (--file= required)
|
|
1805
|
+
graph <file> File dependency tree (--direction=, --depth=)
|
|
1806
|
+
file-exports <file> File's exported symbols
|
|
1828
1807
|
imports <file> What file imports
|
|
1829
1808
|
exporters <file> Who imports file
|
|
1830
|
-
tests <name> Find tests
|
|
1831
|
-
search <term> Text search
|
|
1809
|
+
tests <name> Find tests (--calls-only)
|
|
1810
|
+
search <term> Text search (--code-only, --case-sensitive)
|
|
1832
1811
|
typedef <name> Find type definitions
|
|
1833
1812
|
deadcode Find unused functions/classes
|
|
1834
1813
|
verify <name> Check call sites match signature
|
|
1835
|
-
plan <name> Preview refactoring
|
|
1814
|
+
plan <name> Preview refactoring (--add-param=, --remove-param=, --rename-to=)
|
|
1836
1815
|
stacktrace <text> Parse a stack trace
|
|
1837
1816
|
api Show public symbols
|
|
1838
1817
|
diff-impact What changed and who's affected
|
|
1839
1818
|
stats Index statistics
|
|
1840
1819
|
rebuild Rebuild index
|
|
1841
1820
|
quit Exit
|
|
1821
|
+
|
|
1822
|
+
Flags can be added per-command: context myFunc --include-methods
|
|
1842
1823
|
`);
|
|
1843
1824
|
rl.prompt();
|
|
1844
1825
|
return;
|
|
@@ -1852,13 +1833,16 @@ Commands:
|
|
|
1852
1833
|
return;
|
|
1853
1834
|
}
|
|
1854
1835
|
|
|
1855
|
-
// Parse command
|
|
1856
|
-
const
|
|
1857
|
-
const command =
|
|
1858
|
-
const
|
|
1836
|
+
// Parse command, flags, and arg from interactive input
|
|
1837
|
+
const tokens = input.split(/\s+/);
|
|
1838
|
+
const command = tokens[0];
|
|
1839
|
+
const flagTokens = tokens.filter(t => t.startsWith('--'));
|
|
1840
|
+
const argTokens = tokens.slice(1).filter(t => !t.startsWith('--'));
|
|
1841
|
+
const arg = argTokens.join(' ');
|
|
1842
|
+
const iflags = parseInteractiveFlags(flagTokens);
|
|
1859
1843
|
|
|
1860
1844
|
try {
|
|
1861
|
-
executeInteractiveCommand(index, command, arg);
|
|
1845
|
+
executeInteractiveCommand(index, command, arg, iflags);
|
|
1862
1846
|
} catch (e) {
|
|
1863
1847
|
console.error(`Error: ${e.message}`);
|
|
1864
1848
|
}
|
|
@@ -1871,12 +1855,48 @@ Commands:
|
|
|
1871
1855
|
});
|
|
1872
1856
|
}
|
|
1873
1857
|
|
|
1874
|
-
|
|
1858
|
+
/**
|
|
1859
|
+
* Parse flags from interactive command tokens.
|
|
1860
|
+
* Returns a flags object similar to the global flags but scoped to this command.
|
|
1861
|
+
*/
|
|
1862
|
+
function parseInteractiveFlags(tokens) {
|
|
1863
|
+
return {
|
|
1864
|
+
file: tokens.find(a => a.startsWith('--file='))?.split('=')[1] || null,
|
|
1865
|
+
exclude: tokens.find(a => a.startsWith('--exclude=') || a.startsWith('--not='))?.split('=')[1]?.split(',') || [],
|
|
1866
|
+
in: tokens.find(a => a.startsWith('--in='))?.split('=')[1] || null,
|
|
1867
|
+
includeTests: tokens.includes('--include-tests'),
|
|
1868
|
+
includeExported: tokens.includes('--include-exported'),
|
|
1869
|
+
includeDecorated: tokens.includes('--include-decorated'),
|
|
1870
|
+
includeUncertain: tokens.includes('--include-uncertain'),
|
|
1871
|
+
includeMethods: tokens.includes('--include-methods=false') ? false : tokens.includes('--include-methods') ? true : undefined,
|
|
1872
|
+
detailed: tokens.includes('--detailed'),
|
|
1873
|
+
all: tokens.includes('--all'),
|
|
1874
|
+
exact: tokens.includes('--exact'),
|
|
1875
|
+
callsOnly: tokens.includes('--calls-only'),
|
|
1876
|
+
codeOnly: tokens.includes('--code-only'),
|
|
1877
|
+
caseSensitive: tokens.includes('--case-sensitive'),
|
|
1878
|
+
withTypes: tokens.includes('--with-types'),
|
|
1879
|
+
expand: tokens.includes('--expand'),
|
|
1880
|
+
depth: tokens.find(a => a.startsWith('--depth='))?.split('=')[1] || null,
|
|
1881
|
+
top: parseInt(tokens.find(a => a.startsWith('--top='))?.split('=')[1] || '0'),
|
|
1882
|
+
context: parseInt(tokens.find(a => a.startsWith('--context='))?.split('=')[1] || '0'),
|
|
1883
|
+
direction: tokens.find(a => a.startsWith('--direction='))?.split('=')[1] || null,
|
|
1884
|
+
addParam: tokens.find(a => a.startsWith('--add-param='))?.split('=')[1] || null,
|
|
1885
|
+
removeParam: tokens.find(a => a.startsWith('--remove-param='))?.split('=')[1] || null,
|
|
1886
|
+
renameTo: tokens.find(a => a.startsWith('--rename-to='))?.split('=')[1] || null,
|
|
1887
|
+
defaultValue: tokens.find(a => a.startsWith('--default='))?.split('=')[1] || null,
|
|
1888
|
+
base: tokens.find(a => a.startsWith('--base='))?.split('=')[1] || null,
|
|
1889
|
+
staged: tokens.includes('--staged'),
|
|
1890
|
+
maxLines: parseInt(tokens.find(a => a.startsWith('--max-lines='))?.split('=')[1] || '0') || null,
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
function executeInteractiveCommand(index, command, arg, iflags = {}) {
|
|
1875
1895
|
switch (command) {
|
|
1876
1896
|
case 'toc': {
|
|
1877
|
-
const toc = index.getToc();
|
|
1897
|
+
const toc = index.getToc({ detailed: iflags.detailed });
|
|
1878
1898
|
console.log(output.formatToc(toc, {
|
|
1879
|
-
detailedHint: 'Add --detailed to list all functions, or "
|
|
1899
|
+
detailedHint: 'Add --detailed to list all functions, or "about <name>" for full details on a symbol'
|
|
1880
1900
|
}));
|
|
1881
1901
|
break;
|
|
1882
1902
|
}
|
|
@@ -1886,11 +1906,11 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1886
1906
|
console.log('Usage: find <name>');
|
|
1887
1907
|
return;
|
|
1888
1908
|
}
|
|
1889
|
-
const found = index.find(arg, {});
|
|
1909
|
+
const found = index.find(arg, { exact: iflags.exact, exclude: iflags.exclude, in: iflags.in, includeTests: iflags.includeTests });
|
|
1890
1910
|
if (found.length === 0) {
|
|
1891
1911
|
console.log(`No symbols found for "${arg}"`);
|
|
1892
1912
|
} else {
|
|
1893
|
-
printSymbols(found, arg, {});
|
|
1913
|
+
printSymbols(found, arg, { top: iflags.top });
|
|
1894
1914
|
}
|
|
1895
1915
|
break;
|
|
1896
1916
|
}
|
|
@@ -1900,8 +1920,8 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1900
1920
|
console.log('Usage: about <name>');
|
|
1901
1921
|
return;
|
|
1902
1922
|
}
|
|
1903
|
-
const aboutResult = index.about(arg, { includeMethods:
|
|
1904
|
-
console.log(output.formatAbout(aboutResult, { expand:
|
|
1923
|
+
const aboutResult = index.about(arg, { includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain, file: iflags.file, exclude: iflags.exclude });
|
|
1924
|
+
console.log(output.formatAbout(aboutResult, { expand: iflags.expand, root: index.root, showAll: iflags.all }));
|
|
1905
1925
|
break;
|
|
1906
1926
|
}
|
|
1907
1927
|
|
|
@@ -1910,7 +1930,7 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1910
1930
|
console.log('Usage: usages <name>');
|
|
1911
1931
|
return;
|
|
1912
1932
|
}
|
|
1913
|
-
const usages = index.usages(arg, {});
|
|
1933
|
+
const usages = index.usages(arg, { context: iflags.context, codeOnly: iflags.codeOnly, includeTests: iflags.includeTests });
|
|
1914
1934
|
console.log(output.formatUsages(usages, arg));
|
|
1915
1935
|
break;
|
|
1916
1936
|
}
|
|
@@ -1920,13 +1940,13 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1920
1940
|
console.log('Usage: context <name>');
|
|
1921
1941
|
return;
|
|
1922
1942
|
}
|
|
1923
|
-
const ctx = index.context(arg, { includeUncertain:
|
|
1943
|
+
const ctx = index.context(arg, { includeUncertain: iflags.includeUncertain, includeMethods: iflags.includeMethods, file: iflags.file, exclude: iflags.exclude });
|
|
1924
1944
|
if (!ctx) {
|
|
1925
1945
|
console.log(`Symbol "${arg}" not found.`);
|
|
1926
1946
|
} else {
|
|
1927
1947
|
const { text, expandable } = output.formatContext(ctx, {
|
|
1928
1948
|
methodsHint: 'Note: obj.method() calls excluded — use --include-methods to include them',
|
|
1929
|
-
expandHint: 'Use "
|
|
1949
|
+
expandHint: 'Use "expand <N>" to see code for item N',
|
|
1930
1950
|
uncertainHint: 'use --include-uncertain to include all'
|
|
1931
1951
|
});
|
|
1932
1952
|
console.log(text);
|
|
@@ -1940,7 +1960,7 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1940
1960
|
console.log('Usage: smart <name>');
|
|
1941
1961
|
return;
|
|
1942
1962
|
}
|
|
1943
|
-
const smart = index.smart(arg, { file:
|
|
1963
|
+
const smart = index.smart(arg, { file: iflags.file, includeUncertain: iflags.includeUncertain, withTypes: iflags.withTypes });
|
|
1944
1964
|
if (smart) {
|
|
1945
1965
|
console.log(output.formatSmart(smart, {
|
|
1946
1966
|
uncertainHint: 'use --include-uncertain to include all'
|
|
@@ -1956,7 +1976,7 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1956
1976
|
console.log('Usage: impact <name>');
|
|
1957
1977
|
return;
|
|
1958
1978
|
}
|
|
1959
|
-
const impactResult = index.impact(arg);
|
|
1979
|
+
const impactResult = index.impact(arg, { file: iflags.file });
|
|
1960
1980
|
console.log(output.formatImpact(impactResult));
|
|
1961
1981
|
break;
|
|
1962
1982
|
}
|
|
@@ -1966,12 +1986,70 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1966
1986
|
console.log('Usage: trace <name>');
|
|
1967
1987
|
return;
|
|
1968
1988
|
}
|
|
1969
|
-
const
|
|
1989
|
+
const traceDepth = iflags.depth ? parseInt(iflags.depth) : 3;
|
|
1990
|
+
const traceResult = index.trace(arg, { depth: traceDepth, file: iflags.file, all: iflags.all || !!iflags.depth, includeMethods: iflags.includeMethods, includeUncertain: iflags.includeUncertain });
|
|
1970
1991
|
console.log(output.formatTrace(traceResult));
|
|
1971
1992
|
break;
|
|
1972
1993
|
}
|
|
1973
1994
|
|
|
1974
|
-
case '
|
|
1995
|
+
case 'fn': {
|
|
1996
|
+
if (!arg) {
|
|
1997
|
+
console.log('Usage: fn <name> [--file=<pattern>]');
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
extractFunctionFromProject(index, arg, iflags);
|
|
2001
|
+
break;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
case 'class': {
|
|
2005
|
+
if (!arg) {
|
|
2006
|
+
console.log('Usage: class <name> [--file=<pattern>]');
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
extractClassFromProject(index, arg, iflags);
|
|
2010
|
+
break;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
case 'lines': {
|
|
2014
|
+
if (!arg || !iflags.file) {
|
|
2015
|
+
console.log('Usage: lines <range> --file=<file>');
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
const filePath = index.findFile(iflags.file);
|
|
2019
|
+
if (!filePath) {
|
|
2020
|
+
console.log(`File not found: ${iflags.file}`);
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
2024
|
+
printLines(fileContent.split('\n'), arg);
|
|
2025
|
+
break;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
case 'graph': {
|
|
2029
|
+
if (!arg) {
|
|
2030
|
+
console.log('Usage: graph <file> [--direction=imports|importers|both] [--depth=N]');
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
const graphDepth = iflags.depth ? parseInt(iflags.depth) : 2;
|
|
2034
|
+
const graphDirection = iflags.direction || 'both';
|
|
2035
|
+
const graphResult = index.graph(arg, { direction: graphDirection, maxDepth: graphDepth });
|
|
2036
|
+
console.log(output.formatGraph(graphResult, { showAll: iflags.all, maxDepth: graphDepth }));
|
|
2037
|
+
break;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
case 'file-exports':
|
|
2041
|
+
case 'what-exports': {
|
|
2042
|
+
if (!arg) {
|
|
2043
|
+
console.log('Usage: file-exports <file>');
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
const fileExports = index.fileExports(arg);
|
|
2047
|
+
console.log(output.formatFileExports(fileExports, arg));
|
|
2048
|
+
break;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
case 'imports':
|
|
2052
|
+
case 'what-imports': {
|
|
1975
2053
|
if (!arg) {
|
|
1976
2054
|
console.log('Usage: imports <file>');
|
|
1977
2055
|
return;
|
|
@@ -1981,7 +2059,8 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1981
2059
|
break;
|
|
1982
2060
|
}
|
|
1983
2061
|
|
|
1984
|
-
case 'exporters':
|
|
2062
|
+
case 'exporters':
|
|
2063
|
+
case 'who-imports': {
|
|
1985
2064
|
if (!arg) {
|
|
1986
2065
|
console.log('Usage: exporters <file>');
|
|
1987
2066
|
return;
|
|
@@ -1996,7 +2075,7 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
1996
2075
|
console.log('Usage: tests <name>');
|
|
1997
2076
|
return;
|
|
1998
2077
|
}
|
|
1999
|
-
const tests = index.tests(arg, { callsOnly:
|
|
2078
|
+
const tests = index.tests(arg, { callsOnly: iflags.callsOnly });
|
|
2000
2079
|
console.log(output.formatTests(tests, arg));
|
|
2001
2080
|
break;
|
|
2002
2081
|
}
|
|
@@ -2006,7 +2085,7 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
2006
2085
|
console.log('Usage: search <term>');
|
|
2007
2086
|
return;
|
|
2008
2087
|
}
|
|
2009
|
-
const results = index.search(arg, {});
|
|
2088
|
+
const results = index.search(arg, { codeOnly: iflags.codeOnly, caseSensitive: iflags.caseSensitive, context: iflags.context });
|
|
2010
2089
|
console.log(output.formatSearch(results, arg));
|
|
2011
2090
|
break;
|
|
2012
2091
|
}
|
|
@@ -2029,9 +2108,9 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
2029
2108
|
|
|
2030
2109
|
case 'diff-impact': {
|
|
2031
2110
|
const diffResult = index.diffImpact({
|
|
2032
|
-
base:
|
|
2033
|
-
staged:
|
|
2034
|
-
file:
|
|
2111
|
+
base: iflags.base || 'HEAD',
|
|
2112
|
+
staged: iflags.staged,
|
|
2113
|
+
file: iflags.file
|
|
2035
2114
|
});
|
|
2036
2115
|
console.log(output.formatDiffImpact(diffResult));
|
|
2037
2116
|
break;
|
|
@@ -2069,9 +2148,11 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
2069
2148
|
|
|
2070
2149
|
case 'deadcode': {
|
|
2071
2150
|
const deadResult = index.deadcode({
|
|
2072
|
-
includeExported:
|
|
2073
|
-
includeDecorated:
|
|
2074
|
-
includeTests:
|
|
2151
|
+
includeExported: iflags.includeExported,
|
|
2152
|
+
includeDecorated: iflags.includeDecorated,
|
|
2153
|
+
includeTests: iflags.includeTests,
|
|
2154
|
+
exclude: iflags.exclude,
|
|
2155
|
+
in: iflags.in
|
|
2075
2156
|
});
|
|
2076
2157
|
console.log(output.formatDeadcode(deadResult));
|
|
2077
2158
|
break;
|
|
@@ -2082,8 +2163,8 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
2082
2163
|
console.log('Usage: related <name>');
|
|
2083
2164
|
return;
|
|
2084
2165
|
}
|
|
2085
|
-
const relResult = index.related(arg, { file:
|
|
2086
|
-
console.log(output.formatRelated(relResult));
|
|
2166
|
+
const relResult = index.related(arg, { file: iflags.file, all: iflags.all });
|
|
2167
|
+
console.log(output.formatRelated(relResult, { showAll: iflags.all }));
|
|
2087
2168
|
break;
|
|
2088
2169
|
}
|
|
2089
2170
|
|
|
@@ -2098,19 +2179,19 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
2098
2179
|
|
|
2099
2180
|
case 'plan': {
|
|
2100
2181
|
if (!arg) {
|
|
2101
|
-
console.log('Usage: plan <name>
|
|
2182
|
+
console.log('Usage: plan <name> --add-param=x | --remove-param=x | --rename-to=x');
|
|
2102
2183
|
return;
|
|
2103
2184
|
}
|
|
2104
|
-
if (!
|
|
2185
|
+
if (!iflags.addParam && !iflags.removeParam && !iflags.renameTo) {
|
|
2105
2186
|
console.log('Plan requires an operation: --add-param, --remove-param, or --rename-to');
|
|
2106
2187
|
return;
|
|
2107
2188
|
}
|
|
2108
2189
|
const planResult = index.plan(arg, {
|
|
2109
|
-
addParam:
|
|
2110
|
-
removeParam:
|
|
2111
|
-
renameTo:
|
|
2112
|
-
defaultValue:
|
|
2113
|
-
file:
|
|
2190
|
+
addParam: iflags.addParam,
|
|
2191
|
+
removeParam: iflags.removeParam,
|
|
2192
|
+
renameTo: iflags.renameTo,
|
|
2193
|
+
defaultValue: iflags.defaultValue,
|
|
2194
|
+
file: iflags.file
|
|
2114
2195
|
});
|
|
2115
2196
|
console.log(output.formatPlan(planResult));
|
|
2116
2197
|
break;
|
|
@@ -2121,7 +2202,7 @@ function executeInteractiveCommand(index, command, arg) {
|
|
|
2121
2202
|
console.log('Usage: verify <name>');
|
|
2122
2203
|
return;
|
|
2123
2204
|
}
|
|
2124
|
-
const verifyResult = index.verify(arg, { file:
|
|
2205
|
+
const verifyResult = index.verify(arg, { file: iflags.file });
|
|
2125
2206
|
console.log(output.formatVerify(verifyResult));
|
|
2126
2207
|
break;
|
|
2127
2208
|
}
|
package/core/discovery.js
CHANGED
|
@@ -335,11 +335,17 @@ function walkDir(dir, options, depth = 0, visited = new Set()) {
|
|
|
335
335
|
* @param {string[]} ignores - Patterns to always ignore
|
|
336
336
|
* @param {string} [parentDir] - Parent directory path (for conditional checks)
|
|
337
337
|
*/
|
|
338
|
+
const _globRegexCache = new Map();
|
|
339
|
+
|
|
338
340
|
function shouldIgnore(name, ignores, parentDir) {
|
|
339
341
|
// Check unconditional ignores
|
|
340
342
|
for (const pattern of ignores) {
|
|
341
343
|
if (pattern.includes('*')) {
|
|
342
|
-
|
|
344
|
+
let regex = _globRegexCache.get(pattern);
|
|
345
|
+
if (!regex) {
|
|
346
|
+
regex = globToRegex(pattern);
|
|
347
|
+
_globRegexCache.set(pattern, regex);
|
|
348
|
+
}
|
|
343
349
|
if (regex.test(name)) return true;
|
|
344
350
|
} else if (name === pattern) {
|
|
345
351
|
return true;
|
|
@@ -408,7 +414,7 @@ function detectProjectPattern(projectRoot) {
|
|
|
408
414
|
|
|
409
415
|
if (fs.existsSync(path.join(dir, 'pom.xml')) ||
|
|
410
416
|
fs.existsSync(path.join(dir, 'build.gradle'))) {
|
|
411
|
-
extensions.push('java'
|
|
417
|
+
extensions.push('java');
|
|
412
418
|
}
|
|
413
419
|
};
|
|
414
420
|
|