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.

Files changed (45) hide show
  1. package/.claude/skills/ucn/SKILL.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +135 -0
  4. package/cli/index.js +2437 -0
  5. package/core/discovery.js +513 -0
  6. package/core/imports.js +558 -0
  7. package/core/output.js +1274 -0
  8. package/core/parser.js +279 -0
  9. package/core/project.js +3261 -0
  10. package/index.js +52 -0
  11. package/languages/go.js +653 -0
  12. package/languages/index.js +267 -0
  13. package/languages/java.js +826 -0
  14. package/languages/javascript.js +1346 -0
  15. package/languages/python.js +667 -0
  16. package/languages/rust.js +950 -0
  17. package/languages/utils.js +457 -0
  18. package/package.json +42 -0
  19. package/test/fixtures/go/go.mod +3 -0
  20. package/test/fixtures/go/main.go +257 -0
  21. package/test/fixtures/go/service.go +187 -0
  22. package/test/fixtures/java/DataService.java +279 -0
  23. package/test/fixtures/java/Main.java +287 -0
  24. package/test/fixtures/java/Utils.java +199 -0
  25. package/test/fixtures/java/pom.xml +6 -0
  26. package/test/fixtures/javascript/main.js +109 -0
  27. package/test/fixtures/javascript/package.json +1 -0
  28. package/test/fixtures/javascript/service.js +88 -0
  29. package/test/fixtures/javascript/utils.js +67 -0
  30. package/test/fixtures/python/main.py +198 -0
  31. package/test/fixtures/python/pyproject.toml +3 -0
  32. package/test/fixtures/python/service.py +166 -0
  33. package/test/fixtures/python/utils.py +118 -0
  34. package/test/fixtures/rust/Cargo.toml +3 -0
  35. package/test/fixtures/rust/main.rs +253 -0
  36. package/test/fixtures/rust/service.rs +210 -0
  37. package/test/fixtures/rust/utils.rs +154 -0
  38. package/test/fixtures/typescript/main.ts +154 -0
  39. package/test/fixtures/typescript/package.json +1 -0
  40. package/test/fixtures/typescript/repository.ts +149 -0
  41. package/test/fixtures/typescript/types.ts +114 -0
  42. package/test/parser.test.js +3661 -0
  43. package/test/public-repos-test.js +477 -0
  44. package/test/systematic-test.js +619 -0
  45. 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
+ };