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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* languages/index.js - Language registry and detection
|
|
3
|
+
*
|
|
4
|
+
* Manages language parsers and provides extension-based detection.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
// Lazy-loaded tree-sitter
|
|
10
|
+
let TreeSitter = null;
|
|
11
|
+
|
|
12
|
+
// Cached parser instances
|
|
13
|
+
const parsers = {};
|
|
14
|
+
|
|
15
|
+
// Language configurations
|
|
16
|
+
const LANGUAGES = {
|
|
17
|
+
javascript: {
|
|
18
|
+
name: 'javascript',
|
|
19
|
+
extensions: ['.js', '.jsx', '.mjs', '.cjs'],
|
|
20
|
+
treeSitterLang: 'javascript',
|
|
21
|
+
module: () => require('./javascript')
|
|
22
|
+
},
|
|
23
|
+
typescript: {
|
|
24
|
+
name: 'typescript',
|
|
25
|
+
extensions: ['.ts'],
|
|
26
|
+
treeSitterLang: 'typescript',
|
|
27
|
+
module: () => require('./javascript') // Same module, different parser
|
|
28
|
+
},
|
|
29
|
+
tsx: {
|
|
30
|
+
name: 'tsx',
|
|
31
|
+
extensions: ['.tsx'],
|
|
32
|
+
treeSitterLang: 'tsx',
|
|
33
|
+
module: () => require('./javascript')
|
|
34
|
+
},
|
|
35
|
+
python: {
|
|
36
|
+
name: 'python',
|
|
37
|
+
extensions: ['.py', '.pyi'],
|
|
38
|
+
treeSitterLang: 'python',
|
|
39
|
+
module: () => require('./python')
|
|
40
|
+
},
|
|
41
|
+
go: {
|
|
42
|
+
name: 'go',
|
|
43
|
+
extensions: ['.go'],
|
|
44
|
+
treeSitterLang: 'go',
|
|
45
|
+
module: () => require('./go')
|
|
46
|
+
},
|
|
47
|
+
rust: {
|
|
48
|
+
name: 'rust',
|
|
49
|
+
extensions: ['.rs'],
|
|
50
|
+
treeSitterLang: 'rust',
|
|
51
|
+
module: () => require('./rust')
|
|
52
|
+
},
|
|
53
|
+
java: {
|
|
54
|
+
name: 'java',
|
|
55
|
+
extensions: ['.java'],
|
|
56
|
+
treeSitterLang: 'java',
|
|
57
|
+
module: () => require('./java')
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Extension to language mapping
|
|
62
|
+
const EXT_MAP = {};
|
|
63
|
+
for (const [langName, config] of Object.entries(LANGUAGES)) {
|
|
64
|
+
for (const ext of config.extensions) {
|
|
65
|
+
EXT_MAP[ext] = langName;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load tree-sitter module (lazy)
|
|
71
|
+
* @returns {object} TreeSitter class
|
|
72
|
+
*/
|
|
73
|
+
function loadTreeSitter() {
|
|
74
|
+
if (!TreeSitter) {
|
|
75
|
+
try {
|
|
76
|
+
TreeSitter = require('tree-sitter');
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
'tree-sitter is required but not installed.\n' +
|
|
80
|
+
'Install with: npm install'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return TreeSitter;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get or create parser for a language
|
|
89
|
+
* @param {string} language - Language name
|
|
90
|
+
* @returns {object} Tree-sitter parser instance
|
|
91
|
+
*/
|
|
92
|
+
function getParser(language) {
|
|
93
|
+
if (parsers[language]) return parsers[language];
|
|
94
|
+
|
|
95
|
+
const TS = loadTreeSitter();
|
|
96
|
+
const parser = new TS();
|
|
97
|
+
const config = LANGUAGES[language];
|
|
98
|
+
|
|
99
|
+
if (!config) {
|
|
100
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
let lang;
|
|
105
|
+
switch (language) {
|
|
106
|
+
case 'javascript':
|
|
107
|
+
lang = require('tree-sitter-javascript');
|
|
108
|
+
break;
|
|
109
|
+
case 'typescript':
|
|
110
|
+
lang = require('tree-sitter-typescript').typescript;
|
|
111
|
+
break;
|
|
112
|
+
case 'tsx':
|
|
113
|
+
lang = require('tree-sitter-typescript').tsx;
|
|
114
|
+
break;
|
|
115
|
+
case 'python':
|
|
116
|
+
lang = require('tree-sitter-python');
|
|
117
|
+
break;
|
|
118
|
+
case 'go':
|
|
119
|
+
lang = require('tree-sitter-go');
|
|
120
|
+
break;
|
|
121
|
+
case 'java':
|
|
122
|
+
lang = require('tree-sitter-java');
|
|
123
|
+
break;
|
|
124
|
+
case 'rust':
|
|
125
|
+
lang = require('tree-sitter-rust');
|
|
126
|
+
break;
|
|
127
|
+
default:
|
|
128
|
+
throw new Error(`No tree-sitter grammar for: ${language}`);
|
|
129
|
+
}
|
|
130
|
+
parser.setLanguage(lang);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Failed to load tree-sitter-${language}.\n` +
|
|
134
|
+
`Install with: npm install tree-sitter-${language}\n` +
|
|
135
|
+
`Original error: ${e.message}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
parsers[language] = parser;
|
|
140
|
+
return parser;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Detect language from file path
|
|
145
|
+
* @param {string} filePath - File path
|
|
146
|
+
* @returns {string|null} Language name or null if unsupported
|
|
147
|
+
*/
|
|
148
|
+
function detectLanguage(filePath) {
|
|
149
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
150
|
+
return EXT_MAP[ext] || null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get language module for a language
|
|
155
|
+
* @param {string} language - Language name
|
|
156
|
+
* @returns {object} Language module with parse functions
|
|
157
|
+
*/
|
|
158
|
+
function getLanguageModule(language) {
|
|
159
|
+
const config = LANGUAGES[language];
|
|
160
|
+
if (!config) {
|
|
161
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
162
|
+
}
|
|
163
|
+
return config.module();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if a language is supported
|
|
168
|
+
* @param {string} language - Language name
|
|
169
|
+
* @returns {boolean}
|
|
170
|
+
*/
|
|
171
|
+
function isSupported(language) {
|
|
172
|
+
return language in LANGUAGES;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get all supported extensions
|
|
177
|
+
* @returns {string[]}
|
|
178
|
+
*/
|
|
179
|
+
function getSupportedExtensions() {
|
|
180
|
+
return Object.keys(EXT_MAP);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get all supported languages
|
|
185
|
+
* @returns {string[]}
|
|
186
|
+
*/
|
|
187
|
+
function getSupportedLanguages() {
|
|
188
|
+
return Object.keys(LANGUAGES);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Buffer size for tree-sitter parser (workaround for default 32KB limit)
|
|
192
|
+
// Default 1MB handles most files; can be overridden via UCN_BUFFER_SIZE env var
|
|
193
|
+
const DEFAULT_BUFFER_SIZE = 1024 * 1024; // 1MB
|
|
194
|
+
const MAX_BUFFER_SIZE = 64 * 1024 * 1024; // 64MB cap
|
|
195
|
+
|
|
196
|
+
const PARSE_OPTIONS = {
|
|
197
|
+
bufferSize: parseInt(process.env.UCN_BUFFER_SIZE, 10) || DEFAULT_BUFFER_SIZE
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get parse options with dynamic buffer sizing based on content size
|
|
202
|
+
* @param {number} contentLength - Length of content to parse
|
|
203
|
+
* @returns {object} Parse options with appropriate buffer size
|
|
204
|
+
*/
|
|
205
|
+
function getParseOptions(contentLength = 0) {
|
|
206
|
+
// Start with configured/default size, scale up for large files
|
|
207
|
+
// Buffer needs room for syntax tree which can be 2-3x content size
|
|
208
|
+
const minBuffer = parseInt(process.env.UCN_BUFFER_SIZE, 10) || DEFAULT_BUFFER_SIZE;
|
|
209
|
+
const scaledBuffer = Math.max(minBuffer, contentLength * 3);
|
|
210
|
+
const bufferSize = Math.min(scaledBuffer, MAX_BUFFER_SIZE);
|
|
211
|
+
return { bufferSize };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Safely parse content with automatic buffer retry on failure
|
|
216
|
+
* @param {object} parser - tree-sitter parser instance
|
|
217
|
+
* @param {string} content - Source code to parse
|
|
218
|
+
* @param {object} oldTree - Previous tree for incremental parsing (optional)
|
|
219
|
+
* @param {object} options - Additional parse options
|
|
220
|
+
* @returns {object} Parsed tree
|
|
221
|
+
*/
|
|
222
|
+
function safeParse(parser, content, oldTree = undefined, options = {}) {
|
|
223
|
+
const contentLength = content.length;
|
|
224
|
+
|
|
225
|
+
// Try with escalating buffer sizes
|
|
226
|
+
const bufferSizes = [
|
|
227
|
+
parseInt(process.env.UCN_BUFFER_SIZE, 10) || DEFAULT_BUFFER_SIZE,
|
|
228
|
+
Math.max(DEFAULT_BUFFER_SIZE, contentLength * 2),
|
|
229
|
+
Math.max(4 * 1024 * 1024, contentLength * 3),
|
|
230
|
+
Math.max(16 * 1024 * 1024, contentLength * 4),
|
|
231
|
+
MAX_BUFFER_SIZE
|
|
232
|
+
].filter((size, i, arr) => i === 0 || size > arr[i - 1]); // Remove duplicates
|
|
233
|
+
|
|
234
|
+
let lastError;
|
|
235
|
+
for (const bufferSize of bufferSizes) {
|
|
236
|
+
try {
|
|
237
|
+
return parser.parse(content, oldTree, { ...options, bufferSize });
|
|
238
|
+
} catch (e) {
|
|
239
|
+
lastError = e;
|
|
240
|
+
// Only retry on buffer-related errors
|
|
241
|
+
if (!e.message?.toLowerCase().includes('buffer') &&
|
|
242
|
+
!e.message?.toLowerCase().includes('memory') &&
|
|
243
|
+
!e.message?.toLowerCase().includes('alloc')) {
|
|
244
|
+
throw e; // Non-buffer error, don't retry
|
|
245
|
+
}
|
|
246
|
+
// Continue to next buffer size
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// All attempts failed
|
|
251
|
+
throw lastError;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = {
|
|
255
|
+
detectLanguage,
|
|
256
|
+
getParser,
|
|
257
|
+
getLanguageModule,
|
|
258
|
+
isSupported,
|
|
259
|
+
getSupportedExtensions,
|
|
260
|
+
getSupportedLanguages,
|
|
261
|
+
LANGUAGES,
|
|
262
|
+
PARSE_OPTIONS,
|
|
263
|
+
getParseOptions,
|
|
264
|
+
safeParse,
|
|
265
|
+
DEFAULT_BUFFER_SIZE,
|
|
266
|
+
MAX_BUFFER_SIZE
|
|
267
|
+
};
|