ucn 3.0.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.
Potentially problematic release.
This version of ucn might be problematic. Click here for more details.
- package/.claude/skills/ucn/SKILL.md +77 -0
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/cli/index.js +2437 -0
- package/core/discovery.js +513 -0
- package/core/imports.js +558 -0
- package/core/output.js +1274 -0
- package/core/parser.js +279 -0
- package/core/project.js +3261 -0
- package/index.js +52 -0
- package/languages/go.js +653 -0
- package/languages/index.js +267 -0
- package/languages/java.js +826 -0
- package/languages/javascript.js +1346 -0
- package/languages/python.js +667 -0
- package/languages/rust.js +950 -0
- package/languages/utils.js +457 -0
- package/package.json +42 -0
- package/test/fixtures/go/go.mod +3 -0
- package/test/fixtures/go/main.go +257 -0
- package/test/fixtures/go/service.go +187 -0
- package/test/fixtures/java/DataService.java +279 -0
- package/test/fixtures/java/Main.java +287 -0
- package/test/fixtures/java/Utils.java +199 -0
- package/test/fixtures/java/pom.xml +6 -0
- package/test/fixtures/javascript/main.js +109 -0
- package/test/fixtures/javascript/package.json +1 -0
- package/test/fixtures/javascript/service.js +88 -0
- package/test/fixtures/javascript/utils.js +67 -0
- package/test/fixtures/python/main.py +198 -0
- package/test/fixtures/python/pyproject.toml +3 -0
- package/test/fixtures/python/service.py +166 -0
- package/test/fixtures/python/utils.py +118 -0
- package/test/fixtures/rust/Cargo.toml +3 -0
- package/test/fixtures/rust/main.rs +253 -0
- package/test/fixtures/rust/service.rs +210 -0
- package/test/fixtures/rust/utils.rs +154 -0
- package/test/fixtures/typescript/main.ts +154 -0
- package/test/fixtures/typescript/package.json +1 -0
- package/test/fixtures/typescript/repository.ts +149 -0
- package/test/fixtures/typescript/types.ts +114 -0
- package/test/parser.test.js +3661 -0
- package/test/public-repos-test.js +477 -0
- package/test/systematic-test.js +619 -0
- package/ucn.js +8 -0
package/core/parser.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/parser.js - Unified parsing interface
|
|
3
|
+
*
|
|
4
|
+
* Provides a single entry point for parsing any supported language.
|
|
5
|
+
* AST-only, no regex fallback.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { detectLanguage, getParser, getLanguageModule, isSupported, PARSE_OPTIONS } = require('../languages');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} FunctionDef
|
|
14
|
+
* @property {string} name - Function name
|
|
15
|
+
* @property {string} params - Normalized parameters string
|
|
16
|
+
* @property {Array<{name: string, type?: string, optional?: boolean, default?: string, rest?: boolean}>} paramsStructured
|
|
17
|
+
* @property {string|null} returnType - Return type annotation
|
|
18
|
+
* @property {number} startLine - 1-indexed start line
|
|
19
|
+
* @property {number} endLine - 1-indexed end line
|
|
20
|
+
* @property {number} indent - Indentation level
|
|
21
|
+
* @property {string|null} docstring - First line of documentation
|
|
22
|
+
* @property {string[]} modifiers - ['async', 'static', 'export', etc.]
|
|
23
|
+
* @property {boolean} [isArrow] - Is arrow function (JS/TS)
|
|
24
|
+
* @property {boolean} [isGenerator] - Is generator function
|
|
25
|
+
* @property {boolean} [isMethod] - Is method with receiver (Go)
|
|
26
|
+
* @property {string} [receiver] - Receiver type (Go)
|
|
27
|
+
* @property {string} [generics] - Generic type parameters
|
|
28
|
+
* @property {boolean} [isConstructor] - Is constructor
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} ClassDef
|
|
33
|
+
* @property {string} name - Class/type name
|
|
34
|
+
* @property {number} startLine - 1-indexed start line
|
|
35
|
+
* @property {number} endLine - 1-indexed end line
|
|
36
|
+
* @property {string} type - 'class', 'interface', 'type', 'enum', 'struct', 'trait', 'impl', 'module', 'macro', 'record'
|
|
37
|
+
* @property {Array} members - Class members (methods, fields)
|
|
38
|
+
* @property {string|null} [docstring] - First line of documentation
|
|
39
|
+
* @property {string} [extends] - Parent class/type
|
|
40
|
+
* @property {string[]} [implements] - Implemented interfaces
|
|
41
|
+
* @property {string[]} modifiers - ['public', 'abstract', etc.]
|
|
42
|
+
* @property {string} [generics] - Generic type parameters
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {Object} StateDef
|
|
47
|
+
* @property {string} name - Constant/state object name
|
|
48
|
+
* @property {number} startLine - 1-indexed start line
|
|
49
|
+
* @property {number} endLine - 1-indexed end line
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {Object} ParseResult
|
|
54
|
+
* @property {string} language - Detected language
|
|
55
|
+
* @property {number} totalLines - Total lines in file
|
|
56
|
+
* @property {FunctionDef[]} functions - All functions
|
|
57
|
+
* @property {ClassDef[]} classes - All classes/types
|
|
58
|
+
* @property {StateDef[]} stateObjects - All state objects
|
|
59
|
+
* @property {Array} imports - Import statements (from imports.js)
|
|
60
|
+
* @property {Array} exports - Export statements (from imports.js)
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse source code and return structured result
|
|
65
|
+
* @param {string} code - Source code
|
|
66
|
+
* @param {string} language - Language name or file path for detection
|
|
67
|
+
* @returns {ParseResult}
|
|
68
|
+
*/
|
|
69
|
+
function parse(code, language) {
|
|
70
|
+
// Detect language if file path provided
|
|
71
|
+
if (language.includes('.') || language.includes('/')) {
|
|
72
|
+
language = detectLanguage(language);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!language || !isSupported(language)) {
|
|
76
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const parser = getParser(language);
|
|
80
|
+
const langModule = getLanguageModule(language);
|
|
81
|
+
|
|
82
|
+
return langModule.parse(code, parser);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parse a file from disk
|
|
87
|
+
* @param {string} filePath - Path to file
|
|
88
|
+
* @returns {ParseResult}
|
|
89
|
+
*/
|
|
90
|
+
function parseFile(filePath) {
|
|
91
|
+
const code = fs.readFileSync(filePath, 'utf-8');
|
|
92
|
+
const language = detectLanguage(filePath);
|
|
93
|
+
|
|
94
|
+
if (!language) {
|
|
95
|
+
throw new Error(`Cannot detect language for: ${filePath}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = parse(code, language);
|
|
99
|
+
result.filePath = filePath;
|
|
100
|
+
result.relativePath = filePath; // Will be updated by caller if needed
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract a specific function by name
|
|
106
|
+
* @param {string} code - Source code
|
|
107
|
+
* @param {string} language - Language name
|
|
108
|
+
* @param {string} name - Function name (supports fuzzy matching)
|
|
109
|
+
* @returns {{fn: FunctionDef|null, code: string}}
|
|
110
|
+
*/
|
|
111
|
+
function extractFunction(code, language, name) {
|
|
112
|
+
const result = parse(code, language);
|
|
113
|
+
const lines = code.split('\n');
|
|
114
|
+
|
|
115
|
+
// Exact match first
|
|
116
|
+
let fn = result.functions.find(f => f.name === name);
|
|
117
|
+
|
|
118
|
+
// Fuzzy match if not found
|
|
119
|
+
if (!fn) {
|
|
120
|
+
const lowerName = name.toLowerCase();
|
|
121
|
+
fn = result.functions.find(f =>
|
|
122
|
+
f.name.toLowerCase().includes(lowerName) ||
|
|
123
|
+
lowerName.includes(f.name.toLowerCase())
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!fn) {
|
|
128
|
+
return { fn: null, code: '' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const fnCode = lines.slice(fn.startLine - 1, fn.endLine).join('\n');
|
|
132
|
+
return { fn, code: fnCode };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Extract a specific class by name
|
|
137
|
+
* @param {string} code - Source code
|
|
138
|
+
* @param {string} language - Language name
|
|
139
|
+
* @param {string} name - Class name
|
|
140
|
+
* @returns {{cls: ClassDef|null, code: string}}
|
|
141
|
+
*/
|
|
142
|
+
function extractClass(code, language, name) {
|
|
143
|
+
const result = parse(code, language);
|
|
144
|
+
const lines = code.split('\n');
|
|
145
|
+
|
|
146
|
+
const cls = result.classes.find(c => c.name === name);
|
|
147
|
+
if (!cls) {
|
|
148
|
+
return { cls: null, code: '' };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const clsCode = lines.slice(cls.startLine - 1, cls.endLine).join('\n');
|
|
152
|
+
return { cls, code: clsCode };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get table of contents for source code
|
|
157
|
+
* @param {string} code - Source code
|
|
158
|
+
* @param {string} language - Language name
|
|
159
|
+
* @returns {ParseResult}
|
|
160
|
+
*/
|
|
161
|
+
function getToc(code, language) {
|
|
162
|
+
return parse(code, language);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Find all symbols matching a name
|
|
167
|
+
* @param {ParseResult} result - Parse result
|
|
168
|
+
* @param {string} name - Symbol name to find
|
|
169
|
+
* @returns {Array<{name: string, type: string, startLine: number, endLine: number, params?: string}>}
|
|
170
|
+
*/
|
|
171
|
+
function findSymbol(result, name) {
|
|
172
|
+
const symbols = [];
|
|
173
|
+
const lowerName = name.toLowerCase();
|
|
174
|
+
|
|
175
|
+
// Search functions
|
|
176
|
+
for (const fn of result.functions) {
|
|
177
|
+
if (fn.name.toLowerCase().includes(lowerName)) {
|
|
178
|
+
symbols.push({
|
|
179
|
+
name: fn.name,
|
|
180
|
+
type: 'function',
|
|
181
|
+
startLine: fn.startLine,
|
|
182
|
+
endLine: fn.endLine,
|
|
183
|
+
params: fn.params,
|
|
184
|
+
returnType: fn.returnType,
|
|
185
|
+
modifiers: fn.modifiers
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Search classes
|
|
191
|
+
for (const cls of result.classes) {
|
|
192
|
+
if (cls.name.toLowerCase().includes(lowerName)) {
|
|
193
|
+
symbols.push({
|
|
194
|
+
name: cls.name,
|
|
195
|
+
type: cls.type,
|
|
196
|
+
startLine: cls.startLine,
|
|
197
|
+
endLine: cls.endLine,
|
|
198
|
+
modifiers: cls.modifiers
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Search class members
|
|
203
|
+
if (cls.members) {
|
|
204
|
+
for (const member of cls.members) {
|
|
205
|
+
if (member.name.toLowerCase().includes(lowerName)) {
|
|
206
|
+
symbols.push({
|
|
207
|
+
name: member.name,
|
|
208
|
+
type: member.memberType || 'member',
|
|
209
|
+
startLine: member.startLine,
|
|
210
|
+
endLine: member.endLine,
|
|
211
|
+
params: member.params,
|
|
212
|
+
className: cls.name
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Search state objects
|
|
220
|
+
for (const state of result.stateObjects) {
|
|
221
|
+
if (state.name.toLowerCase().includes(lowerName)) {
|
|
222
|
+
symbols.push({
|
|
223
|
+
name: state.name,
|
|
224
|
+
type: 'state',
|
|
225
|
+
startLine: state.startLine,
|
|
226
|
+
endLine: state.endLine
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return symbols;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get all exported/public symbols
|
|
236
|
+
* @param {ParseResult} result - Parse result
|
|
237
|
+
* @returns {Array}
|
|
238
|
+
*/
|
|
239
|
+
function getExportedSymbols(result) {
|
|
240
|
+
const exported = [];
|
|
241
|
+
|
|
242
|
+
for (const fn of result.functions) {
|
|
243
|
+
if (fn.modifiers && fn.modifiers.includes('export')) {
|
|
244
|
+
exported.push({
|
|
245
|
+
name: fn.name,
|
|
246
|
+
type: 'function',
|
|
247
|
+
startLine: fn.startLine,
|
|
248
|
+
endLine: fn.endLine,
|
|
249
|
+
params: fn.params,
|
|
250
|
+
returnType: fn.returnType
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
for (const cls of result.classes) {
|
|
256
|
+
if (cls.modifiers && (cls.modifiers.includes('export') || cls.modifiers.includes('public'))) {
|
|
257
|
+
exported.push({
|
|
258
|
+
name: cls.name,
|
|
259
|
+
type: cls.type,
|
|
260
|
+
startLine: cls.startLine,
|
|
261
|
+
endLine: cls.endLine
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return exported;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = {
|
|
270
|
+
parse,
|
|
271
|
+
parseFile,
|
|
272
|
+
extractFunction,
|
|
273
|
+
extractClass,
|
|
274
|
+
getToc,
|
|
275
|
+
findSymbol,
|
|
276
|
+
getExportedSymbols,
|
|
277
|
+
detectLanguage,
|
|
278
|
+
isSupported
|
|
279
|
+
};
|