invar-tools 1.10.0__py3-none-any.whl → 1.12.0__py3-none-any.whl

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.
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * TypeScript Compiler API query tool.
4
+ * Single-shot execution: runs query, outputs JSON, exits.
5
+ * No persistent process, no orphan risk.
6
+ *
7
+ * Usage:
8
+ * node ts-query.js '{"command": "sig", "file": "src/auth.ts"}'
9
+ * node ts-query.js '{"command": "map", "path": ".", "top": 10}'
10
+ * node ts-query.js '{"command": "refs", "file": "src/auth.ts", "line": 10, "column": 5}'
11
+ *
12
+ * Part of DX-78: TypeScript Compiler Integration.
13
+ */
14
+
15
+ const ts = require('typescript');
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ // Parse query from command line
20
+ let query;
21
+ try {
22
+ query = JSON.parse(process.argv[2] || '{}');
23
+ } catch (e) {
24
+ console.error(JSON.stringify({
25
+ error: 'Invalid JSON input',
26
+ message: e.message
27
+ }));
28
+ process.exit(1);
29
+ }
30
+
31
+ /**
32
+ * Find tsconfig.json and create a TypeScript program.
33
+ */
34
+ function createProgram(projectPath) {
35
+ const configPath = ts.findConfigFile(
36
+ projectPath,
37
+ ts.sys.fileExists,
38
+ 'tsconfig.json'
39
+ );
40
+
41
+ if (!configPath) {
42
+ console.error(JSON.stringify({ error: 'tsconfig.json not found' }));
43
+ process.exit(1);
44
+ }
45
+
46
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
47
+ if (configFile.error) {
48
+ console.error(JSON.stringify({ error: 'Failed to read tsconfig.json' }));
49
+ process.exit(1);
50
+ }
51
+
52
+ const parsed = ts.parseJsonConfigFileContent(
53
+ configFile.config,
54
+ ts.sys,
55
+ path.dirname(configPath)
56
+ );
57
+
58
+ return ts.createProgram(parsed.fileNames, parsed.options);
59
+ }
60
+
61
+ /**
62
+ * Get class/function members recursively.
63
+ */
64
+ function getMembers(node, checker, sourceFile) {
65
+ const members = [];
66
+
67
+ if (ts.isClassDeclaration(node) && node.members) {
68
+ for (const member of node.members) {
69
+ if (ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member)) {
70
+ const name = member.name ? member.name.getText(sourceFile) : '<anonymous>';
71
+ const symbol = checker.getSymbolAtLocation(member.name || member);
72
+ let signature = '';
73
+
74
+ if (symbol) {
75
+ const type = checker.getTypeOfSymbolAtLocation(symbol, member);
76
+ signature = checker.typeToString(type);
77
+ }
78
+
79
+ const pos = sourceFile.getLineAndCharacterOfPosition(member.pos);
80
+ members.push({
81
+ name,
82
+ kind: ts.isMethodDeclaration(member) ? 'method' : 'property',
83
+ signature,
84
+ line: pos.line + 1
85
+ });
86
+ }
87
+ }
88
+ }
89
+
90
+ return members;
91
+ }
92
+
93
+ /**
94
+ * Extract JSDoc @pre/@post comments.
95
+ */
96
+ function extractContracts(node, sourceFile) {
97
+ const contracts = { pre: [], post: [] };
98
+ const jsDocs = ts.getJSDocTags(node);
99
+
100
+ for (const tag of jsDocs) {
101
+ const tagName = tag.tagName.getText();
102
+ const comment = typeof tag.comment === 'string'
103
+ ? tag.comment
104
+ : tag.comment?.map(c => c.text).join('') || '';
105
+
106
+ if (tagName === 'pre') {
107
+ contracts.pre.push(comment);
108
+ } else if (tagName === 'post') {
109
+ contracts.post.push(comment);
110
+ }
111
+ }
112
+
113
+ return contracts;
114
+ }
115
+
116
+ /**
117
+ * Command: sig - Extract signatures from a file.
118
+ */
119
+ function outputSignatures(filePath) {
120
+ const projectPath = path.dirname(filePath);
121
+ const program = createProgram(projectPath);
122
+ const checker = program.getTypeChecker();
123
+ const sourceFile = program.getSourceFile(path.resolve(filePath));
124
+
125
+ if (!sourceFile) {
126
+ console.log(JSON.stringify({ file: filePath, symbols: [], error: 'File not found in program' }));
127
+ return;
128
+ }
129
+
130
+ const symbols = [];
131
+
132
+ function visit(node) {
133
+ if (ts.isFunctionDeclaration(node) && node.name) {
134
+ const name = node.name.getText(sourceFile);
135
+ const symbol = checker.getSymbolAtLocation(node.name);
136
+ const type = symbol ? checker.getTypeOfSymbolAtLocation(symbol, node) : null;
137
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
138
+ const contracts = extractContracts(node, sourceFile);
139
+
140
+ symbols.push({
141
+ name,
142
+ kind: 'function',
143
+ signature: type ? checker.typeToString(type) : '',
144
+ line: pos.line + 1,
145
+ contracts
146
+ });
147
+ } else if (ts.isClassDeclaration(node) && node.name) {
148
+ const name = node.name.getText(sourceFile);
149
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
150
+ const members = getMembers(node, checker, sourceFile);
151
+
152
+ symbols.push({
153
+ name,
154
+ kind: 'class',
155
+ signature: `class ${name}`,
156
+ line: pos.line + 1,
157
+ members
158
+ });
159
+ } else if (ts.isInterfaceDeclaration(node) && node.name) {
160
+ const name = node.name.getText(sourceFile);
161
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
162
+
163
+ symbols.push({
164
+ name,
165
+ kind: 'interface',
166
+ signature: `interface ${name}`,
167
+ line: pos.line + 1
168
+ });
169
+ } else if (ts.isTypeAliasDeclaration(node) && node.name) {
170
+ const name = node.name.getText(sourceFile);
171
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
172
+
173
+ symbols.push({
174
+ name,
175
+ kind: 'type',
176
+ signature: `type ${name}`,
177
+ line: pos.line + 1
178
+ });
179
+ } else if (ts.isVariableStatement(node)) {
180
+ for (const decl of node.declarationList.declarations) {
181
+ if (ts.isIdentifier(decl.name)) {
182
+ const name = decl.name.getText(sourceFile);
183
+ const symbol = checker.getSymbolAtLocation(decl.name);
184
+ const type = symbol ? checker.getTypeOfSymbolAtLocation(symbol, decl) : null;
185
+ const pos = sourceFile.getLineAndCharacterOfPosition(decl.pos);
186
+
187
+ // Only include exported or significant declarations
188
+ const isExported = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
189
+ const isFunctionLike = decl.initializer && (
190
+ ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)
191
+ );
192
+ if (isExported || isFunctionLike) {
193
+ symbols.push({
194
+ name,
195
+ kind: 'const',
196
+ signature: type ? checker.typeToString(type) : '',
197
+ line: pos.line + 1
198
+ });
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ ts.forEachChild(node, visit);
205
+ }
206
+
207
+ visit(sourceFile);
208
+ console.log(JSON.stringify({ file: filePath, symbols }));
209
+ }
210
+
211
+ /**
212
+ * Command: map - Get symbol map with reference counts.
213
+ */
214
+ function outputSymbolMap(projectPath, topN) {
215
+ const program = createProgram(projectPath);
216
+ const checker = program.getTypeChecker();
217
+ const allSymbols = [];
218
+
219
+ // Collect all symbols from all source files
220
+ for (const sourceFile of program.getSourceFiles()) {
221
+ // Skip node_modules and declaration files
222
+ if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) {
223
+ continue;
224
+ }
225
+
226
+ const relativePath = path.relative(projectPath, sourceFile.fileName);
227
+
228
+ function visit(node) {
229
+ let symbolInfo = null;
230
+
231
+ if (ts.isFunctionDeclaration(node) && node.name) {
232
+ const name = node.name.getText(sourceFile);
233
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
234
+ symbolInfo = { name, kind: 'function', file: relativePath, line: pos.line + 1 };
235
+ } else if (ts.isClassDeclaration(node) && node.name) {
236
+ const name = node.name.getText(sourceFile);
237
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.pos);
238
+ symbolInfo = { name, kind: 'class', file: relativePath, line: pos.line + 1 };
239
+ }
240
+
241
+ if (symbolInfo) {
242
+ allSymbols.push(symbolInfo);
243
+ }
244
+
245
+ ts.forEachChild(node, visit);
246
+ }
247
+
248
+ visit(sourceFile);
249
+ }
250
+
251
+ // Sort by kind priority, then name
252
+ const kindOrder = { 'function': 0, 'class': 1, 'interface': 2, 'type': 3, 'const': 4 };
253
+ allSymbols.sort((a, b) => {
254
+ const orderA = kindOrder[a.kind] ?? 99;
255
+ const orderB = kindOrder[b.kind] ?? 99;
256
+ if (orderA !== orderB) return orderA - orderB;
257
+ return a.name.localeCompare(b.name);
258
+ });
259
+
260
+ // Limit to topN
261
+ const result = topN > 0 ? allSymbols.slice(0, topN) : allSymbols;
262
+
263
+ console.log(JSON.stringify({
264
+ path: projectPath,
265
+ total: allSymbols.length,
266
+ symbols: result
267
+ }));
268
+ }
269
+
270
+ /**
271
+ * Command: refs - Find all references to symbol at position.
272
+ */
273
+ function outputReferences(filePath, line, column) {
274
+ const projectPath = path.dirname(filePath);
275
+ const configPath = ts.findConfigFile(projectPath, ts.sys.fileExists, 'tsconfig.json');
276
+
277
+ if (!configPath) {
278
+ console.log(JSON.stringify({ error: 'tsconfig.json not found', references: [] }));
279
+ return;
280
+ }
281
+
282
+ // Create language service for find references
283
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
284
+ const parsed = ts.parseJsonConfigFileContent(
285
+ configFile.config,
286
+ ts.sys,
287
+ path.dirname(configPath)
288
+ );
289
+
290
+ const files = {};
291
+ for (const fileName of parsed.fileNames) {
292
+ try {
293
+ files[fileName] = {
294
+ version: 0,
295
+ text: fs.readFileSync(fileName, 'utf-8')
296
+ };
297
+ } catch (e) {
298
+ // Skip files that can't be read (may be deleted or permissions issue)
299
+ continue;
300
+ }
301
+ }
302
+
303
+ const servicesHost = {
304
+ getScriptFileNames: () => parsed.fileNames,
305
+ getScriptVersion: (fileName) => files[fileName]?.version.toString() || '0',
306
+ getScriptSnapshot: (fileName) => {
307
+ if (!files[fileName]) {
308
+ try {
309
+ const text = fs.readFileSync(fileName, 'utf-8');
310
+ files[fileName] = { version: 0, text };
311
+ } catch (e) {
312
+ // File doesn't exist or can't be read
313
+ return undefined;
314
+ }
315
+ }
316
+ return ts.ScriptSnapshot.fromString(files[fileName].text);
317
+ },
318
+ getCurrentDirectory: () => path.dirname(configPath),
319
+ getCompilationSettings: () => parsed.options,
320
+ getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
321
+ fileExists: ts.sys.fileExists,
322
+ readFile: ts.sys.readFile,
323
+ readDirectory: ts.sys.readDirectory,
324
+ directoryExists: ts.sys.directoryExists,
325
+ getDirectories: ts.sys.getDirectories,
326
+ };
327
+
328
+ const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
329
+
330
+ // Convert line/column to position
331
+ const absolutePath = path.resolve(filePath);
332
+ const sourceFile = services.getProgram()?.getSourceFile(absolutePath);
333
+
334
+ if (!sourceFile) {
335
+ console.log(JSON.stringify({ error: 'File not found', references: [] }));
336
+ return;
337
+ }
338
+
339
+ const position = sourceFile.getPositionOfLineAndCharacter(line - 1, column);
340
+
341
+ // Find references
342
+ const refs = services.findReferences(absolutePath, position);
343
+ const references = [];
344
+
345
+ if (refs) {
346
+ for (const refGroup of refs) {
347
+ for (const ref of refGroup.references) {
348
+ const refFile = services.getProgram()?.getSourceFile(ref.fileName);
349
+ if (refFile) {
350
+ const startPos = refFile.getLineAndCharacterOfPosition(ref.textSpan.start);
351
+ const lineText = refFile.text.split('\n')[startPos.line]?.trim() || '';
352
+
353
+ references.push({
354
+ file: path.relative(projectPath, ref.fileName),
355
+ line: startPos.line + 1,
356
+ column: startPos.character,
357
+ context: lineText,
358
+ isDefinition: ref.isDefinition || false
359
+ });
360
+ }
361
+ }
362
+ }
363
+ }
364
+
365
+ console.log(JSON.stringify({
366
+ file: filePath,
367
+ line,
368
+ column,
369
+ references
370
+ }));
371
+ }
372
+
373
+ // Route to appropriate command
374
+ switch (query.command) {
375
+ case 'sig':
376
+ outputSignatures(query.file);
377
+ break;
378
+ case 'map':
379
+ outputSymbolMap(query.path || '.', query.top || 10);
380
+ break;
381
+ case 'refs':
382
+ outputReferences(query.file, query.line, query.column);
383
+ break;
384
+ default:
385
+ console.error(JSON.stringify({
386
+ error: `Unknown command: ${query.command}`,
387
+ usage: {
388
+ sig: { file: 'path/to/file.ts' },
389
+ map: { path: '.', top: 10 },
390
+ refs: { file: 'path/to/file.ts', line: 10, column: 5 }
391
+ }
392
+ }));
393
+ process.exit(1);
394
+ }
395
+
396
+ process.exit(0);