@nudojs/service 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # @nudojs/service
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Conceptual design and basic implementation.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - nudo@0.1.0
13
+ - @nudojs/core@0.1.0
14
+ - @nudojs/parser@0.1.0
@@ -0,0 +1,77 @@
1
+ import { Node } from '@babel/types';
2
+ import { TypeValue } from '@nudojs/core';
3
+
4
+ type SourceLocation = {
5
+ start: {
6
+ line: number;
7
+ column: number;
8
+ };
9
+ end: {
10
+ line: number;
11
+ column: number;
12
+ };
13
+ };
14
+ type DiagnosticSeverity = "error" | "warning" | "info";
15
+ type DiagnosticTag = "unnecessary";
16
+ type Diagnostic = {
17
+ range: SourceLocation;
18
+ severity: DiagnosticSeverity;
19
+ message: string;
20
+ tags?: DiagnosticTag[];
21
+ };
22
+ type CaseResult = {
23
+ name: string;
24
+ args: TypeValue[];
25
+ result: TypeValue;
26
+ throws: TypeValue;
27
+ throwLoc?: SourceLocation;
28
+ };
29
+ type FunctionAnalysis = {
30
+ name: string;
31
+ loc: SourceLocation;
32
+ cases: CaseResult[];
33
+ combined?: TypeValue;
34
+ assertionErrors?: string[];
35
+ };
36
+ type BindingInfo = {
37
+ type: TypeValue;
38
+ loc?: SourceLocation;
39
+ };
40
+ type CaseHint = {
41
+ line: number;
42
+ label: string;
43
+ ok: boolean;
44
+ };
45
+ type AnalysisResult = {
46
+ functions: FunctionAnalysis[];
47
+ diagnostics: Diagnostic[];
48
+ bindings: Map<string, BindingInfo>;
49
+ nodeTypeMap: Map<Node, TypeValue>;
50
+ caseHints: CaseHint[];
51
+ };
52
+ type CompletionItem = {
53
+ label: string;
54
+ kind: "property" | "method" | "variable";
55
+ detail?: string;
56
+ };
57
+ declare function analyzeFile(filePath: string, source: string, activeCases?: Map<string, number>): AnalysisResult;
58
+ type CaseInfo = {
59
+ functionName: string;
60
+ caseName: string;
61
+ caseIndex: number;
62
+ };
63
+ declare function getCasesForFile(filePath: string, source: string): {
64
+ functionName: string;
65
+ cases: {
66
+ name: string;
67
+ index: number;
68
+ }[];
69
+ loc: SourceLocation;
70
+ }[];
71
+ declare function getTypeAtPosition(filePath: string, source: string, line: number, column: number, activeCases?: Map<string, number>): TypeValue | null;
72
+ declare function getCompletionsAtPosition(filePath: string, source: string, line: number, column: number): CompletionItem[];
73
+
74
+ declare function typeValueToTSType(tv: TypeValue): string;
75
+ declare function generateDts(result: AnalysisResult): string;
76
+
77
+ export { type AnalysisResult, type BindingInfo, type CaseHint, type CaseInfo, type CaseResult, type CompletionItem, type Diagnostic, type DiagnosticSeverity, type DiagnosticTag, type FunctionAnalysis, type SourceLocation, analyzeFile, generateDts, getCasesForFile, getCompletionsAtPosition, getTypeAtPosition, typeValueToTSType };
package/dist/index.js ADDED
@@ -0,0 +1,523 @@
1
+ // src/analyzer.ts
2
+ import { readFileSync, existsSync } from "fs";
3
+ import { resolve, dirname } from "path";
4
+ import traverse from "@babel/traverse";
5
+ import {
6
+ T,
7
+ typeValueToString,
8
+ simplifyUnion,
9
+ createEnvironment,
10
+ isSubtypeOf
11
+ } from "@nudojs/core";
12
+ import { parse, extractDirectives, parseTypeValueExpr } from "@nudojs/parser";
13
+ import {
14
+ evaluateFunction,
15
+ evaluateFunctionFull,
16
+ evaluateProgram,
17
+ setModuleResolver,
18
+ setCurrentFileDir,
19
+ resetMemo,
20
+ getUnreachableRanges,
21
+ resetUnreachableRanges,
22
+ setNodeTypeCollector
23
+ } from "@nudojs/cli/evaluator";
24
+ function resolveModule(source, fromDir) {
25
+ const extensions = [".js", ".ts", ".mjs"];
26
+ const basePath = resolve(fromDir, source);
27
+ for (const ext of ["", ...extensions]) {
28
+ const candidate = basePath + ext;
29
+ if (existsSync(candidate)) {
30
+ const src = readFileSync(candidate, "utf-8");
31
+ return { ast: parse(src), filePath: candidate };
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ function applyMocks(directives, env, filePath) {
37
+ for (const d of directives) {
38
+ if (d.kind !== "mock") continue;
39
+ if (d.expression) {
40
+ env.bind(d.name, parseTypeValueExpr(d.expression));
41
+ } else if (d.fromPath) {
42
+ const mockPath = resolve(dirname(filePath), d.fromPath);
43
+ const mockSource = readFileSync(mockPath, "utf-8");
44
+ const mockAst = parse(mockSource);
45
+ const mockEnv = createEnvironment();
46
+ evaluateProgram(mockAst, mockEnv);
47
+ const mockVal = mockEnv.lookup(d.name);
48
+ env.bind(d.name, mockVal);
49
+ }
50
+ }
51
+ }
52
+ function locFromNode(node) {
53
+ return {
54
+ start: { line: node.loc?.start.line ?? 1, column: node.loc?.start.column ?? 0 },
55
+ end: { line: node.loc?.end.line ?? 1, column: node.loc?.end.column ?? 0 }
56
+ };
57
+ }
58
+ function analyzeFile(filePath, source, activeCases) {
59
+ const ast = parse(source);
60
+ const functions = extractDirectives(ast);
61
+ const diagnostics = [];
62
+ const bindings = /* @__PURE__ */ new Map();
63
+ const nodeTypeMap = /* @__PURE__ */ new Map();
64
+ const functionResults = [];
65
+ const caseHints = [];
66
+ resetMemo();
67
+ resetUnreachableRanges();
68
+ setModuleResolver(resolveModule);
69
+ setCurrentFileDir(dirname(filePath));
70
+ const globalEnv = createEnvironment();
71
+ evaluateProgram(ast, globalEnv);
72
+ const unreachableRanges = getUnreachableRanges();
73
+ for (const ur of unreachableRanges) {
74
+ diagnostics.push({
75
+ range: ur,
76
+ severity: "info",
77
+ message: "Unreachable code",
78
+ tags: ["unnecessary"]
79
+ });
80
+ }
81
+ collectBindings(ast, globalEnv, bindings);
82
+ for (const fn of functions) {
83
+ applyMocks(fn.directives, globalEnv, filePath);
84
+ const isPure = fn.directives.some((d) => d.kind === "pure");
85
+ const skipDirective = fn.directives.find((d) => d.kind === "skip");
86
+ const returnsDirective = fn.directives.find((d) => d.kind === "returns");
87
+ const fnLoc = locFromNode(fn.node);
88
+ const analysis = { name: fn.name, loc: fnLoc, cases: [] };
89
+ if (skipDirective && skipDirective.kind === "skip") {
90
+ if (skipDirective.returns) {
91
+ analysis.combined = skipDirective.returns;
92
+ }
93
+ functionResults.push(analysis);
94
+ continue;
95
+ }
96
+ if (isPure) {
97
+ const fnVal = globalEnv.has(fn.name) ? globalEnv.lookup(fn.name) : null;
98
+ if (fnVal && fnVal.kind === "function") {
99
+ fnVal._memoize = fn.name;
100
+ }
101
+ }
102
+ const caseDirectives = fn.directives.filter((d) => d.kind === "case");
103
+ const activeCaseIdx = activeCases?.get(fn.name) ?? 0;
104
+ for (let ci = 0; ci < caseDirectives.length; ci++) {
105
+ const directive = caseDirectives[ci];
106
+ resetUnreachableRanges();
107
+ const fullResult = evaluateFunctionFull(fn.node, directive.args, globalEnv);
108
+ const caseUnreachable = [...getUnreachableRanges()];
109
+ analysis.cases.push({
110
+ name: directive.name,
111
+ args: directive.args,
112
+ result: fullResult.value,
113
+ throws: fullResult.throws,
114
+ throwLoc: fullResult.throwLoc
115
+ });
116
+ if (directive.commentLine) {
117
+ const hasThrow = fullResult.throws.kind !== "never";
118
+ const resultStr = fullResult.value.kind !== "never" ? typeValueToString(fullResult.value) : "";
119
+ const throwStr = hasThrow ? `throws ${typeValueToString(fullResult.throws)}` : "";
120
+ const label = [resultStr, throwStr].filter(Boolean).join(" ");
121
+ const hintLabel = `=> ${label}`;
122
+ let ok = true;
123
+ if (directive.expected) {
124
+ ok = isSubtypeOf(fullResult.value, directive.expected);
125
+ if (!ok) {
126
+ diagnostics.push({
127
+ range: { start: { line: directive.commentLine, column: 0 }, end: { line: directive.commentLine, column: 999 } },
128
+ severity: "error",
129
+ message: `Case "${directive.name}": expected ${typeValueToString(directive.expected)}, got ${typeValueToString(fullResult.value)}`
130
+ });
131
+ }
132
+ }
133
+ caseHints.push({ line: directive.commentLine, label: hintLabel, ok });
134
+ }
135
+ const isActive = ci === Math.min(activeCaseIdx, caseDirectives.length - 1);
136
+ if (isActive) {
137
+ if (fullResult.throws.kind !== "never") {
138
+ const throwRange = fullResult.throwLoc ?? fnLoc;
139
+ diagnostics.push({
140
+ range: throwRange,
141
+ severity: "warning",
142
+ message: `Function "${fn.name}" case "${directive.name}" may throw: ${typeValueToString(fullResult.throws)}`
143
+ });
144
+ }
145
+ for (const ur of caseUnreachable) {
146
+ diagnostics.push({
147
+ range: ur,
148
+ severity: "info",
149
+ message: "Unreachable code",
150
+ tags: ["unnecessary"]
151
+ });
152
+ }
153
+ }
154
+ }
155
+ if (analysis.cases.length > 1) {
156
+ analysis.combined = simplifyUnion(analysis.cases.map((c) => c.result));
157
+ } else if (analysis.cases.length === 1) {
158
+ analysis.combined = analysis.cases[0].result;
159
+ }
160
+ if (returnsDirective && returnsDirective.kind === "returns") {
161
+ analysis.assertionErrors = [];
162
+ for (const directive of caseDirectives) {
163
+ const result = evaluateFunction(fn.node, directive.args, globalEnv);
164
+ const matches = isSubtypeOf(result, returnsDirective.expected);
165
+ if (!matches) {
166
+ const msg = `@nudo:returns assertion failed for case "${directive.name}": expected ${typeValueToString(returnsDirective.expected)}, got ${typeValueToString(result)}`;
167
+ analysis.assertionErrors.push(msg);
168
+ diagnostics.push({
169
+ range: fnLoc,
170
+ severity: "error",
171
+ message: msg
172
+ });
173
+ }
174
+ }
175
+ }
176
+ functionResults.push(analysis);
177
+ }
178
+ buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
179
+ setModuleResolver(null);
180
+ return {
181
+ functions: functionResults,
182
+ diagnostics,
183
+ bindings,
184
+ nodeTypeMap,
185
+ caseHints
186
+ };
187
+ }
188
+ function collectBindings(ast, env, bindings) {
189
+ if (ast.type !== "File") return;
190
+ for (const stmt of ast.program.body) {
191
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
192
+ const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T.unknown;
193
+ bindings.set(stmt.id.name, { type: val, loc: locFromNode(stmt) });
194
+ }
195
+ if (stmt.type === "VariableDeclaration") {
196
+ for (const decl of stmt.declarations) {
197
+ if (decl.id.type === "Identifier") {
198
+ const val = env.has(decl.id.name) ? env.lookup(decl.id.name) : T.unknown;
199
+ bindings.set(decl.id.name, { type: val, loc: locFromNode(decl) });
200
+ }
201
+ }
202
+ }
203
+ if (stmt.type === "ClassDeclaration" && stmt.id) {
204
+ const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T.unknown;
205
+ bindings.set(stmt.id.name, { type: val, loc: locFromNode(stmt) });
206
+ }
207
+ if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
208
+ collectBindings({ type: "File", program: { type: "Program", body: [stmt.declaration] } }, env, bindings);
209
+ }
210
+ }
211
+ }
212
+ function buildNodeTypeMap(ast, env, nodeTypeMap) {
213
+ const traverseFn = typeof traverse === "function" ? traverse : traverse.default;
214
+ try {
215
+ traverseFn(ast, {
216
+ enter(path) {
217
+ const node = path.node;
218
+ try {
219
+ if (node.type === "Identifier" && node.name !== "undefined" && path.parentPath?.node.type !== "FunctionDeclaration" && path.parentPath?.node.type !== "VariableDeclarator") {
220
+ if (env.has(node.name)) {
221
+ nodeTypeMap.set(node, env.lookup(node.name));
222
+ }
223
+ }
224
+ if (node.type === "NumericLiteral") {
225
+ nodeTypeMap.set(node, T.literal(node.value));
226
+ }
227
+ if (node.type === "StringLiteral") {
228
+ nodeTypeMap.set(node, T.literal(node.value));
229
+ }
230
+ if (node.type === "BooleanLiteral") {
231
+ nodeTypeMap.set(node, T.literal(node.value));
232
+ }
233
+ if (node.type === "NullLiteral") {
234
+ nodeTypeMap.set(node, T.null);
235
+ }
236
+ } catch {
237
+ }
238
+ }
239
+ });
240
+ } catch {
241
+ }
242
+ }
243
+ function getCasesForFile(filePath, source) {
244
+ const ast = parse(source);
245
+ const functions = extractDirectives(ast);
246
+ return functions.map((fn) => {
247
+ const cases = fn.directives.filter((d) => d.kind === "case").map((d, i) => ({ name: d.name, index: i }));
248
+ return { functionName: fn.name, cases, loc: locFromNode(fn.node) };
249
+ });
250
+ }
251
+ function getTypeAtPosition(filePath, source, line, column, activeCases) {
252
+ const ast = parse(source);
253
+ resetMemo();
254
+ setModuleResolver(resolveModule);
255
+ setCurrentFileDir(dirname(filePath));
256
+ const globalEnv = createEnvironment();
257
+ evaluateProgram(ast, globalEnv);
258
+ const nodeTypeMap = /* @__PURE__ */ new Map();
259
+ buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
260
+ const functions = extractDirectives(ast);
261
+ const enclosingFn = findEnclosingFunction(functions, line);
262
+ if (enclosingFn) {
263
+ const caseDirectives = enclosingFn.directives.filter((d) => d.kind === "case");
264
+ if (caseDirectives.length > 0) {
265
+ const caseIndex = activeCases?.get(enclosingFn.name) ?? 0;
266
+ const directive = caseDirectives[Math.min(caseIndex, caseDirectives.length - 1)];
267
+ const fnNodeTypeMap = /* @__PURE__ */ new Map();
268
+ setNodeTypeCollector((node, tv) => fnNodeTypeMap.set(node, tv));
269
+ evaluateFunctionFull(enclosingFn.node, directive.args, globalEnv);
270
+ setNodeTypeCollector(null);
271
+ for (const [node, tv] of fnNodeTypeMap) {
272
+ nodeTypeMap.set(node, tv);
273
+ }
274
+ }
275
+ }
276
+ setModuleResolver(null);
277
+ return findBestTypeAtPosition(nodeTypeMap, globalEnv, ast, line, column);
278
+ }
279
+ function findEnclosingFunction(functions, line) {
280
+ for (const fn of functions) {
281
+ const loc = fn.node.loc;
282
+ if (!loc) continue;
283
+ if (loc.start.line <= line && loc.end.line >= line) {
284
+ return fn;
285
+ }
286
+ }
287
+ return null;
288
+ }
289
+ function findBestTypeAtPosition(nodeTypeMap, globalEnv, ast, line, column) {
290
+ let bestMatch = null;
291
+ let bestSize = Infinity;
292
+ for (const [node, tv] of nodeTypeMap) {
293
+ const loc = node.loc;
294
+ if (!loc) continue;
295
+ if (loc.start.line <= line && loc.end.line >= line && (loc.start.line < line || loc.start.column <= column) && (loc.end.line > line || loc.end.column >= column)) {
296
+ const size = (loc.end.line - loc.start.line) * 1e4 + (loc.end.column - loc.start.column);
297
+ if (size < bestSize) {
298
+ bestSize = size;
299
+ bestMatch = tv;
300
+ }
301
+ }
302
+ }
303
+ if (!bestMatch) {
304
+ const identAtPos = findIdentifierAtPosition(ast, line, column);
305
+ if (identAtPos && globalEnv.has(identAtPos)) {
306
+ bestMatch = globalEnv.lookup(identAtPos);
307
+ }
308
+ }
309
+ return bestMatch;
310
+ }
311
+ function findIdentifierAtPosition(ast, line, column) {
312
+ let found = null;
313
+ const traverseFn = typeof traverse === "function" ? traverse : traverse.default;
314
+ try {
315
+ traverseFn(ast, {
316
+ Identifier(path) {
317
+ const loc = path.node.loc;
318
+ if (!loc) return;
319
+ if (loc.start.line === line && loc.start.column <= column && loc.end.column >= column) {
320
+ found = path.node.name;
321
+ path.stop();
322
+ }
323
+ }
324
+ });
325
+ } catch {
326
+ }
327
+ return found;
328
+ }
329
+ function getCompletionsAtPosition(filePath, source, line, column) {
330
+ const textBefore = getTextBeforePosition(source, line, column);
331
+ const dotMatch = textBefore.match(/(\w+)\.\s*\w*$/);
332
+ if (!dotMatch) return getVariableCompletions(filePath, source);
333
+ const objName = dotMatch[1];
334
+ const safeSource = sanitizeSourceForParsing(source);
335
+ let ast;
336
+ try {
337
+ ast = parse(safeSource);
338
+ } catch {
339
+ try {
340
+ ast = parse(source);
341
+ } catch {
342
+ return [];
343
+ }
344
+ }
345
+ resetMemo();
346
+ setModuleResolver(resolveModule);
347
+ setCurrentFileDir(dirname(filePath));
348
+ const globalEnv = createEnvironment();
349
+ evaluateProgram(ast, globalEnv);
350
+ if (!globalEnv.has(objName)) {
351
+ setModuleResolver(null);
352
+ return [];
353
+ }
354
+ const objType = globalEnv.lookup(objName);
355
+ const completions = getCompletionsForType(objType);
356
+ setModuleResolver(null);
357
+ return completions;
358
+ }
359
+ function sanitizeSourceForParsing(source) {
360
+ return source.replace(/(\w+)\.\s*$/gm, "$1._ ");
361
+ }
362
+ function getTextBeforePosition(source, line, column) {
363
+ const lines = source.split("\n");
364
+ if (line < 1 || line > lines.length) return "";
365
+ return lines[line - 1].slice(0, column);
366
+ }
367
+ function getVariableCompletions(filePath, source) {
368
+ const ast = parse(source);
369
+ resetMemo();
370
+ setModuleResolver(resolveModule);
371
+ setCurrentFileDir(dirname(filePath));
372
+ const globalEnv = createEnvironment();
373
+ evaluateProgram(ast, globalEnv);
374
+ const ownBindings = globalEnv.getOwnBindings();
375
+ const completions = [];
376
+ for (const [name, tv] of Object.entries(ownBindings)) {
377
+ if (name.startsWith("__export_")) continue;
378
+ completions.push({
379
+ label: name,
380
+ kind: tv.kind === "function" ? "method" : "variable",
381
+ detail: typeValueToString(tv)
382
+ });
383
+ }
384
+ setModuleResolver(null);
385
+ return completions;
386
+ }
387
+ function getCompletionsForType(tv) {
388
+ const completions = [];
389
+ if (tv.kind === "object") {
390
+ for (const [key, val] of Object.entries(tv.properties)) {
391
+ completions.push({
392
+ label: key,
393
+ kind: val.kind === "function" ? "method" : "property",
394
+ detail: typeValueToString(val)
395
+ });
396
+ }
397
+ return completions;
398
+ }
399
+ if (tv.kind === "instance") {
400
+ for (const [key, val] of Object.entries(tv.properties)) {
401
+ completions.push({
402
+ label: key,
403
+ kind: val.kind === "function" ? "method" : "property",
404
+ detail: typeValueToString(val)
405
+ });
406
+ }
407
+ return completions;
408
+ }
409
+ if (tv.kind === "array" || tv.kind === "tuple") {
410
+ const arrayMethods = [
411
+ { label: "map", detail: "map(callback)" },
412
+ { label: "filter", detail: "filter(callback)" },
413
+ { label: "reduce", detail: "reduce(callback, init)" },
414
+ { label: "find", detail: "find(callback)" },
415
+ { label: "some", detail: "some(callback)" },
416
+ { label: "every", detail: "every(callback)" },
417
+ { label: "forEach", detail: "forEach(callback)" },
418
+ { label: "flatMap", detail: "flatMap(callback)" },
419
+ { label: "includes", detail: "includes(value)" },
420
+ { label: "indexOf", detail: "indexOf(value)" },
421
+ { label: "join", detail: "join(separator)" },
422
+ { label: "slice", detail: "slice(start, end)" },
423
+ { label: "concat", detail: "concat(other)" },
424
+ { label: "push", detail: "push(value)" },
425
+ { label: "length", detail: tv.kind === "tuple" ? `${tv.elements.length}` : "number" }
426
+ ];
427
+ for (const m of arrayMethods) {
428
+ completions.push({ label: m.label, kind: "method", detail: m.detail });
429
+ }
430
+ return completions;
431
+ }
432
+ if (tv.kind === "promise") {
433
+ completions.push({ label: "then", kind: "method", detail: "then(callback)" });
434
+ completions.push({ label: "catch", kind: "method", detail: "catch(callback)" });
435
+ completions.push({ label: "finally", kind: "method", detail: "finally(callback)" });
436
+ return completions;
437
+ }
438
+ if (tv.kind === "primitive" && tv.type === "string") {
439
+ const stringMethods = ["toUpperCase", "toLowerCase", "trim", "split", "slice", "includes", "indexOf", "replace", "startsWith", "endsWith", "charAt", "length"];
440
+ for (const m of stringMethods) {
441
+ completions.push({ label: m, kind: "method", detail: `string.${m}` });
442
+ }
443
+ return completions;
444
+ }
445
+ return completions;
446
+ }
447
+
448
+ // src/dts-generator.ts
449
+ function typeValueToTSType(tv) {
450
+ switch (tv.kind) {
451
+ case "literal": {
452
+ const v = tv.value;
453
+ if (v === null) return "null";
454
+ if (v === void 0) return "undefined";
455
+ if (typeof v === "string") return JSON.stringify(v);
456
+ if (typeof v === "boolean") return String(v);
457
+ return String(v);
458
+ }
459
+ case "primitive":
460
+ return tv.type;
461
+ case "object": {
462
+ const entries = Object.entries(tv.properties);
463
+ if (entries.length === 0) return "{}";
464
+ const inner = entries.map(([k, v]) => `${k}: ${typeValueToTSType(v)}`).join("; ");
465
+ return `{ ${inner} }`;
466
+ }
467
+ case "array":
468
+ return `${wrapComplexType(tv.element)}[]`;
469
+ case "tuple": {
470
+ const inner = tv.elements.map(typeValueToTSType).join(", ");
471
+ return `[${inner}]`;
472
+ }
473
+ case "function": {
474
+ const params = tv.params.map((p, i) => `${p}: unknown`).join(", ");
475
+ return `(${params}) => unknown`;
476
+ }
477
+ case "promise":
478
+ return `Promise<${typeValueToTSType(tv.value)}>`;
479
+ case "instance":
480
+ return tv.className;
481
+ case "union":
482
+ return tv.members.map(typeValueToTSType).join(" | ");
483
+ case "never":
484
+ return "never";
485
+ case "unknown":
486
+ return "unknown";
487
+ }
488
+ }
489
+ function wrapComplexType(tv) {
490
+ const ts = typeValueToTSType(tv);
491
+ if (tv.kind === "union") return `(${ts})`;
492
+ return ts;
493
+ }
494
+ function generateDts(result) {
495
+ const lines = [];
496
+ for (const fn of result.functions) {
497
+ if (fn.cases.length === 0 && fn.combined) {
498
+ lines.push(`export declare function ${fn.name}(...args: unknown[]): ${typeValueToTSType(fn.combined)};`);
499
+ continue;
500
+ }
501
+ if (fn.cases.length === 1) {
502
+ const c = fn.cases[0];
503
+ const params = c.args.map((a, i) => `arg${i}: ${typeValueToTSType(a)}`).join(", ");
504
+ const ret = typeValueToTSType(c.result);
505
+ lines.push(`export declare function ${fn.name}(${params}): ${ret};`);
506
+ continue;
507
+ }
508
+ for (const c of fn.cases) {
509
+ const params = c.args.map((a, i) => `arg${i}: ${typeValueToTSType(a)}`).join(", ");
510
+ const ret = typeValueToTSType(c.result);
511
+ lines.push(`export declare function ${fn.name}(${params}): ${ret};`);
512
+ }
513
+ }
514
+ return lines.join("\n") + "\n";
515
+ }
516
+ export {
517
+ analyzeFile,
518
+ generateDts,
519
+ getCasesForFile,
520
+ getCompletionsAtPosition,
521
+ getTypeAtPosition,
522
+ typeValueToTSType
523
+ };
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@nudojs/service",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "dependencies": {
7
+ "@babel/traverse": "^7.29.0",
8
+ "@babel/types": "^7.29.0",
9
+ "@nudojs/core": "0.1.0",
10
+ "@nudojs/parser": "0.1.0",
11
+ "@nudojs/cli": "0.1.0"
12
+ },
13
+ "scripts": {
14
+ "build": "tsup src/index.ts --format esm --dts"
15
+ }
16
+ }