prinfer 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # prinfer
2
+
3
+ TypeScript type inference inspection tool. Inspect the inferred types of functions and variables in your TypeScript code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install prinfer
9
+ ```
10
+
11
+ ## CLI Usage
12
+
13
+ ```bash
14
+ # Basic usage
15
+ prinfer src/utils.ts myFunction
16
+
17
+ # With custom tsconfig
18
+ prinfer src/utils.ts myFunction --project ./tsconfig.json
19
+
20
+ # Show help
21
+ prinfer --help
22
+ ```
23
+
24
+ ### Output
25
+
26
+ ```
27
+ (x: number, y: string) => boolean
28
+ returns: boolean
29
+ ```
30
+
31
+ ## Programmatic API
32
+
33
+ ```typescript
34
+ import { inferType } from "prinfer";
35
+
36
+ // Basic usage
37
+ const result = inferType("./src/utils.ts", "myFunction");
38
+ console.log(result.signature);
39
+ // => "(x: number, y: string) => boolean"
40
+ console.log(result.returnType);
41
+ // => "boolean"
42
+
43
+ // With custom tsconfig
44
+ const result2 = inferType("./src/utils.ts", "myFunction", "./tsconfig.json");
45
+ ```
46
+
47
+ ### API Reference
48
+
49
+ #### `inferType(file, name, project?)`
50
+
51
+ Infer the type of a function or variable in a TypeScript file.
52
+
53
+ **Parameters:**
54
+ - `file` - Path to the TypeScript file
55
+ - `name` - Name of the function/variable to inspect
56
+ - `project` - Optional path to tsconfig.json
57
+
58
+ **Returns:** `InferredTypeResult`
59
+ - `signature` - The inferred type signature
60
+ - `returnType` - The return type (for functions)
61
+
62
+ #### Types
63
+
64
+ ```typescript
65
+ interface Options {
66
+ file: string;
67
+ name: string;
68
+ project?: string;
69
+ }
70
+
71
+ interface InferredTypeResult {
72
+ signature: string;
73
+ returnType?: string;
74
+ }
75
+ ```
76
+
77
+ ## Requirements
78
+
79
+ - Node.js >= 18.0.0
80
+ - TypeScript >= 4.7.0 (peer dependency)
81
+
82
+ ## License
83
+
84
+ MIT
package/dist/cli.cjs ADDED
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env bun
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_node_fs = __toESM(require("fs"), 1);
28
+ var import_node_path2 = __toESM(require("path"), 1);
29
+
30
+ // src/core.ts
31
+ var import_node_path = __toESM(require("path"), 1);
32
+ var import_typescript = __toESM(require("typescript"), 1);
33
+ function findNearestTsconfig(startDir) {
34
+ return import_typescript.default.findConfigFile(startDir, import_typescript.default.sys.fileExists, "tsconfig.json") ?? void 0;
35
+ }
36
+ function loadProgram(entryFileAbs, project) {
37
+ const fileDir = import_node_path.default.dirname(entryFileAbs);
38
+ const tsconfigPath = project ? import_node_path.default.resolve(process.cwd(), project) : findNearestTsconfig(fileDir);
39
+ if (!tsconfigPath) {
40
+ return import_typescript.default.createProgram([entryFileAbs], {
41
+ target: import_typescript.default.ScriptTarget.ES2022,
42
+ module: import_typescript.default.ModuleKind.ESNext,
43
+ strict: true,
44
+ allowJs: true,
45
+ checkJs: false,
46
+ moduleResolution: import_typescript.default.ModuleResolutionKind.Bundler,
47
+ skipLibCheck: true
48
+ });
49
+ }
50
+ const cfg = import_typescript.default.readConfigFile(tsconfigPath, import_typescript.default.sys.readFile);
51
+ if (cfg.error) {
52
+ throw new Error(
53
+ import_typescript.default.flattenDiagnosticMessageText(cfg.error.messageText, "\n")
54
+ );
55
+ }
56
+ const parsed = import_typescript.default.parseJsonConfigFileContent(
57
+ cfg.config,
58
+ import_typescript.default.sys,
59
+ import_node_path.default.dirname(tsconfigPath)
60
+ );
61
+ return import_typescript.default.createProgram({
62
+ rootNames: parsed.fileNames,
63
+ options: parsed.options
64
+ });
65
+ }
66
+ function isArrowOrFnExpr(n) {
67
+ return !!n && (import_typescript.default.isArrowFunction(n) || import_typescript.default.isFunctionExpression(n));
68
+ }
69
+ function nodeNameText(n) {
70
+ if (!n) return void 0;
71
+ if (import_typescript.default.isIdentifier(n)) return n.text;
72
+ if (import_typescript.default.isStringLiteral(n)) return n.text;
73
+ if (import_typescript.default.isNumericLiteral(n)) return n.text;
74
+ return void 0;
75
+ }
76
+ function isFunctionLikeNamed(node, name) {
77
+ if (import_typescript.default.isFunctionDeclaration(node) && node.name?.text === name) return true;
78
+ if (import_typescript.default.isVariableDeclaration(node) && import_typescript.default.isIdentifier(node.name) && node.name.text === name) {
79
+ return isArrowOrFnExpr(node.initializer);
80
+ }
81
+ if ((import_typescript.default.isMethodDeclaration(node) || import_typescript.default.isMethodSignature(node)) && nodeNameText(node.name) === name) {
82
+ return true;
83
+ }
84
+ if (import_typescript.default.isPropertyAssignment(node) && nodeNameText(node.name) === name) {
85
+ return isArrowOrFnExpr(node.initializer);
86
+ }
87
+ return false;
88
+ }
89
+ function findFirstMatch(sourceFile, name) {
90
+ let found;
91
+ const visit = (node) => {
92
+ if (found) return;
93
+ if (isFunctionLikeNamed(node, name)) {
94
+ found = node;
95
+ return;
96
+ }
97
+ import_typescript.default.forEachChild(node, visit);
98
+ };
99
+ visit(sourceFile);
100
+ return found;
101
+ }
102
+ function getTypeInfo(program, node) {
103
+ const checker = program.getTypeChecker();
104
+ let sig;
105
+ if (import_typescript.default.isFunctionDeclaration(node) || import_typescript.default.isMethodDeclaration(node)) {
106
+ sig = checker.getSignatureFromDeclaration(node) ?? void 0;
107
+ } else if (import_typescript.default.isVariableDeclaration(node)) {
108
+ const init = node.initializer;
109
+ if (isArrowOrFnExpr(init))
110
+ sig = checker.getSignatureFromDeclaration(init) ?? void 0;
111
+ } else if (import_typescript.default.isPropertyAssignment(node)) {
112
+ const init = node.initializer;
113
+ if (isArrowOrFnExpr(init))
114
+ sig = checker.getSignatureFromDeclaration(init) ?? void 0;
115
+ } else if (import_typescript.default.isMethodSignature(node)) {
116
+ sig = checker.getSignatureFromDeclaration(node) ?? void 0;
117
+ }
118
+ const flags = import_typescript.default.TypeFormatFlags.NoTruncation;
119
+ if (sig) {
120
+ const signature = checker.signatureToString(sig, void 0, flags);
121
+ const ret = checker.getReturnTypeOfSignature(sig);
122
+ const returnType = checker.typeToString(ret, void 0, flags);
123
+ return { signature, returnType };
124
+ }
125
+ const nodeWithName = node;
126
+ let nameNode = node;
127
+ if (nodeWithName.name && import_typescript.default.isIdentifier(nodeWithName.name)) {
128
+ nameNode = nodeWithName.name;
129
+ }
130
+ const t = checker.getTypeAtLocation(nameNode);
131
+ return { signature: checker.typeToString(t, void 0, flags) };
132
+ }
133
+
134
+ // src/index.ts
135
+ function inferType(file, name, project) {
136
+ const entryFileAbs = import_node_path2.default.resolve(process.cwd(), file);
137
+ if (!import_node_fs.default.existsSync(entryFileAbs)) {
138
+ throw new Error(`File not found: ${entryFileAbs}`);
139
+ }
140
+ const program = loadProgram(entryFileAbs, project);
141
+ const sourceFile = program.getSourceFile(entryFileAbs);
142
+ if (!sourceFile) {
143
+ throw new Error(
144
+ `Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`
145
+ );
146
+ }
147
+ const node = findFirstMatch(sourceFile, name);
148
+ if (!node) {
149
+ throw new Error(
150
+ `No function-like symbol named "${name}" found in ${entryFileAbs}`
151
+ );
152
+ }
153
+ return getTypeInfo(program, node);
154
+ }
155
+
156
+ // src/cli.ts
157
+ var HELP = `
158
+ prinfer - TypeScript type inference inspection tool
159
+
160
+ Usage:
161
+ prinfer <file.ts> <name> [--project <tsconfig.json>]
162
+
163
+ Arguments:
164
+ file.ts Path to the TypeScript file
165
+ name Name of the function/variable to inspect
166
+
167
+ Options:
168
+ --project, -p Path to tsconfig.json (optional)
169
+ --help, -h Show this help message
170
+
171
+ Examples:
172
+ prinfer src/utils.ts myFunction
173
+ prinfer src/utils.ts myFunction --project ./tsconfig.json
174
+ `.trim();
175
+ function parseArgs(argv) {
176
+ const args = argv.slice(2);
177
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
178
+ console.log(HELP);
179
+ return null;
180
+ }
181
+ const file = args[0];
182
+ const name = args[1];
183
+ if (!file || !name) {
184
+ console.error(
185
+ "Error: Both <file> and <name> arguments are required.\n"
186
+ );
187
+ console.log(HELP);
188
+ process.exit(1);
189
+ }
190
+ let project;
191
+ const projectIdx = args.findIndex((a) => a === "--project" || a === "-p");
192
+ if (projectIdx >= 0) {
193
+ project = args[projectIdx + 1];
194
+ if (!project) {
195
+ console.error("Error: --project requires a path argument.\n");
196
+ console.log(HELP);
197
+ process.exit(1);
198
+ }
199
+ }
200
+ return { file, name, project };
201
+ }
202
+ function main() {
203
+ const options = parseArgs(process.argv);
204
+ if (!options) {
205
+ process.exit(0);
206
+ }
207
+ try {
208
+ const result = inferType(options.file, options.name, options.project);
209
+ console.log(result.signature);
210
+ if (result.returnType) {
211
+ console.log("returns:", result.returnType);
212
+ }
213
+ } catch (error) {
214
+ console.error(error.message);
215
+ process.exit(1);
216
+ }
217
+ }
218
+ main();
219
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findFirstMatch, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param project - Optional path to tsconfig.json\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n * console.log(result.returnType);\n * // => \"boolean\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\tproject?: string,\n): InferredTypeResult {\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findFirstMatch(sourceFile, name);\n\tif (!node) {\n\t\tthrow new Error(\n\t\t\t`No function-like symbol named \"${name}\" found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, options.project);\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags) };\n}\n","#!/usr/bin/env bun\nimport { inferType } from \"./index.js\";\n\nconst HELP = `\nprinfer - TypeScript type inference inspection tool\n\nUsage:\n prinfer <file.ts> <name> [--project <tsconfig.json>]\n\nArguments:\n file.ts Path to the TypeScript file\n name Name of the function/variable to inspect\n\nOptions:\n --project, -p Path to tsconfig.json (optional)\n --help, -h Show this help message\n\nExamples:\n prinfer src/utils.ts myFunction\n prinfer src/utils.ts myFunction --project ./tsconfig.json\n`.trim();\n\ninterface CliOptions {\n\tfile: string;\n\tname: string;\n\tproject?: string;\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n\tconst args = argv.slice(2);\n\n\t// Check for help flag\n\tif (args.includes(\"--help\") || args.includes(\"-h\") || args.length === 0) {\n\t\tconsole.log(HELP);\n\t\treturn null;\n\t}\n\n\tconst file = args[0];\n\tconst name = args[1];\n\n\tif (!file || !name) {\n\t\tconsole.error(\n\t\t\t\"Error: Both <file> and <name> arguments are required.\\n\",\n\t\t);\n\t\tconsole.log(HELP);\n\t\tprocess.exit(1);\n\t}\n\n\t// Find project option\n\tlet project: string | undefined;\n\tconst projectIdx = args.findIndex((a) => a === \"--project\" || a === \"-p\");\n\tif (projectIdx >= 0) {\n\t\tproject = args[projectIdx + 1];\n\t\tif (!project) {\n\t\t\tconsole.error(\"Error: --project requires a path argument.\\n\");\n\t\t\tconsole.log(HELP);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\treturn { file, name, project };\n}\n\nfunction main(): void {\n\tconst options = parseArgs(process.argv);\n\n\tif (!options) {\n\t\tprocess.exit(0);\n\t}\n\n\ttry {\n\t\tconst result = inferType(options.file, options.name, options.project);\n\n\t\tconsole.log(result.signature);\n\t\tif (result.returnType) {\n\t\t\tconsole.log(\"returns:\", result.returnType);\n\t\t}\n\t} catch (error) {\n\t\tconsole.error((error as Error).message);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"mappings":"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","names":["import_node_path","ts","path","path","fs"]}
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env bun
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env bun
package/dist/cli.js ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // src/index.ts
4
+ import fs from "fs";
5
+ import path2 from "path";
6
+
7
+ // src/core.ts
8
+ import path from "path";
9
+ import ts from "typescript";
10
+ function findNearestTsconfig(startDir) {
11
+ return ts.findConfigFile(startDir, ts.sys.fileExists, "tsconfig.json") ?? void 0;
12
+ }
13
+ function loadProgram(entryFileAbs, project) {
14
+ const fileDir = path.dirname(entryFileAbs);
15
+ const tsconfigPath = project ? path.resolve(process.cwd(), project) : findNearestTsconfig(fileDir);
16
+ if (!tsconfigPath) {
17
+ return ts.createProgram([entryFileAbs], {
18
+ target: ts.ScriptTarget.ES2022,
19
+ module: ts.ModuleKind.ESNext,
20
+ strict: true,
21
+ allowJs: true,
22
+ checkJs: false,
23
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
24
+ skipLibCheck: true
25
+ });
26
+ }
27
+ const cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
28
+ if (cfg.error) {
29
+ throw new Error(
30
+ ts.flattenDiagnosticMessageText(cfg.error.messageText, "\n")
31
+ );
32
+ }
33
+ const parsed = ts.parseJsonConfigFileContent(
34
+ cfg.config,
35
+ ts.sys,
36
+ path.dirname(tsconfigPath)
37
+ );
38
+ return ts.createProgram({
39
+ rootNames: parsed.fileNames,
40
+ options: parsed.options
41
+ });
42
+ }
43
+ function isArrowOrFnExpr(n) {
44
+ return !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));
45
+ }
46
+ function nodeNameText(n) {
47
+ if (!n) return void 0;
48
+ if (ts.isIdentifier(n)) return n.text;
49
+ if (ts.isStringLiteral(n)) return n.text;
50
+ if (ts.isNumericLiteral(n)) return n.text;
51
+ return void 0;
52
+ }
53
+ function isFunctionLikeNamed(node, name) {
54
+ if (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;
55
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === name) {
56
+ return isArrowOrFnExpr(node.initializer);
57
+ }
58
+ if ((ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) && nodeNameText(node.name) === name) {
59
+ return true;
60
+ }
61
+ if (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {
62
+ return isArrowOrFnExpr(node.initializer);
63
+ }
64
+ return false;
65
+ }
66
+ function findFirstMatch(sourceFile, name) {
67
+ let found;
68
+ const visit = (node) => {
69
+ if (found) return;
70
+ if (isFunctionLikeNamed(node, name)) {
71
+ found = node;
72
+ return;
73
+ }
74
+ ts.forEachChild(node, visit);
75
+ };
76
+ visit(sourceFile);
77
+ return found;
78
+ }
79
+ function getTypeInfo(program, node) {
80
+ const checker = program.getTypeChecker();
81
+ let sig;
82
+ if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
83
+ sig = checker.getSignatureFromDeclaration(node) ?? void 0;
84
+ } else if (ts.isVariableDeclaration(node)) {
85
+ const init = node.initializer;
86
+ if (isArrowOrFnExpr(init))
87
+ sig = checker.getSignatureFromDeclaration(init) ?? void 0;
88
+ } else if (ts.isPropertyAssignment(node)) {
89
+ const init = node.initializer;
90
+ if (isArrowOrFnExpr(init))
91
+ sig = checker.getSignatureFromDeclaration(init) ?? void 0;
92
+ } else if (ts.isMethodSignature(node)) {
93
+ sig = checker.getSignatureFromDeclaration(node) ?? void 0;
94
+ }
95
+ const flags = ts.TypeFormatFlags.NoTruncation;
96
+ if (sig) {
97
+ const signature = checker.signatureToString(sig, void 0, flags);
98
+ const ret = checker.getReturnTypeOfSignature(sig);
99
+ const returnType = checker.typeToString(ret, void 0, flags);
100
+ return { signature, returnType };
101
+ }
102
+ const nodeWithName = node;
103
+ let nameNode = node;
104
+ if (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {
105
+ nameNode = nodeWithName.name;
106
+ }
107
+ const t = checker.getTypeAtLocation(nameNode);
108
+ return { signature: checker.typeToString(t, void 0, flags) };
109
+ }
110
+
111
+ // src/index.ts
112
+ function inferType(file, name, project) {
113
+ const entryFileAbs = path2.resolve(process.cwd(), file);
114
+ if (!fs.existsSync(entryFileAbs)) {
115
+ throw new Error(`File not found: ${entryFileAbs}`);
116
+ }
117
+ const program = loadProgram(entryFileAbs, project);
118
+ const sourceFile = program.getSourceFile(entryFileAbs);
119
+ if (!sourceFile) {
120
+ throw new Error(
121
+ `Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`
122
+ );
123
+ }
124
+ const node = findFirstMatch(sourceFile, name);
125
+ if (!node) {
126
+ throw new Error(
127
+ `No function-like symbol named "${name}" found in ${entryFileAbs}`
128
+ );
129
+ }
130
+ return getTypeInfo(program, node);
131
+ }
132
+
133
+ // src/cli.ts
134
+ var HELP = `
135
+ prinfer - TypeScript type inference inspection tool
136
+
137
+ Usage:
138
+ prinfer <file.ts> <name> [--project <tsconfig.json>]
139
+
140
+ Arguments:
141
+ file.ts Path to the TypeScript file
142
+ name Name of the function/variable to inspect
143
+
144
+ Options:
145
+ --project, -p Path to tsconfig.json (optional)
146
+ --help, -h Show this help message
147
+
148
+ Examples:
149
+ prinfer src/utils.ts myFunction
150
+ prinfer src/utils.ts myFunction --project ./tsconfig.json
151
+ `.trim();
152
+ function parseArgs(argv) {
153
+ const args = argv.slice(2);
154
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
155
+ console.log(HELP);
156
+ return null;
157
+ }
158
+ const file = args[0];
159
+ const name = args[1];
160
+ if (!file || !name) {
161
+ console.error(
162
+ "Error: Both <file> and <name> arguments are required.\n"
163
+ );
164
+ console.log(HELP);
165
+ process.exit(1);
166
+ }
167
+ let project;
168
+ const projectIdx = args.findIndex((a) => a === "--project" || a === "-p");
169
+ if (projectIdx >= 0) {
170
+ project = args[projectIdx + 1];
171
+ if (!project) {
172
+ console.error("Error: --project requires a path argument.\n");
173
+ console.log(HELP);
174
+ process.exit(1);
175
+ }
176
+ }
177
+ return { file, name, project };
178
+ }
179
+ function main() {
180
+ const options = parseArgs(process.argv);
181
+ if (!options) {
182
+ process.exit(0);
183
+ }
184
+ try {
185
+ const result = inferType(options.file, options.name, options.project);
186
+ console.log(result.signature);
187
+ if (result.returnType) {
188
+ console.log("returns:", result.returnType);
189
+ }
190
+ } catch (error) {
191
+ console.error(error.message);
192
+ process.exit(1);
193
+ }
194
+ }
195
+ main();
196
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findFirstMatch, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param project - Optional path to tsconfig.json\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n * console.log(result.returnType);\n * // => \"boolean\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\tproject?: string,\n): InferredTypeResult {\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findFirstMatch(sourceFile, name);\n\tif (!node) {\n\t\tthrow new Error(\n\t\t\t`No function-like symbol named \"${name}\" found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, options.project);\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags) };\n}\n","#!/usr/bin/env bun\nimport { inferType } from \"./index.js\";\n\nconst HELP = `\nprinfer - TypeScript type inference inspection tool\n\nUsage:\n prinfer <file.ts> <name> [--project <tsconfig.json>]\n\nArguments:\n file.ts Path to the TypeScript file\n name Name of the function/variable to inspect\n\nOptions:\n --project, -p Path to tsconfig.json (optional)\n --help, -h Show this help message\n\nExamples:\n prinfer src/utils.ts myFunction\n prinfer src/utils.ts myFunction --project ./tsconfig.json\n`.trim();\n\ninterface CliOptions {\n\tfile: string;\n\tname: string;\n\tproject?: string;\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n\tconst args = argv.slice(2);\n\n\t// Check for help flag\n\tif (args.includes(\"--help\") || args.includes(\"-h\") || args.length === 0) {\n\t\tconsole.log(HELP);\n\t\treturn null;\n\t}\n\n\tconst file = args[0];\n\tconst name = args[1];\n\n\tif (!file || !name) {\n\t\tconsole.error(\n\t\t\t\"Error: Both <file> and <name> arguments are required.\\n\",\n\t\t);\n\t\tconsole.log(HELP);\n\t\tprocess.exit(1);\n\t}\n\n\t// Find project option\n\tlet project: string | undefined;\n\tconst projectIdx = args.findIndex((a) => a === \"--project\" || a === \"-p\");\n\tif (projectIdx >= 0) {\n\t\tproject = args[projectIdx + 1];\n\t\tif (!project) {\n\t\t\tconsole.error(\"Error: --project requires a path argument.\\n\");\n\t\t\tconsole.log(HELP);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\treturn { file, name, project };\n}\n\nfunction main(): void {\n\tconst options = parseArgs(process.argv);\n\n\tif (!options) {\n\t\tprocess.exit(0);\n\t}\n\n\ttry {\n\t\tconst result = inferType(options.file, options.name, options.project);\n\n\t\tconsole.log(result.signature);\n\t\tif (result.returnType) {\n\t\t\tconsole.log(\"returns:\", result.returnType);\n\t\t}\n\t} catch (error) {\n\t\tconsole.error((error as Error).message);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"mappings":"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","names":["path","path"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ findFirstMatch: () => findFirstMatch,
34
+ findNearestTsconfig: () => findNearestTsconfig,
35
+ getTypeInfo: () => getTypeInfo,
36
+ inferType: () => inferType,
37
+ inferTypeFromOptions: () => inferTypeFromOptions,
38
+ loadProgram: () => loadProgram
39
+ });
40
+ module.exports = __toCommonJS(src_exports);
41
+ var import_node_fs = __toESM(require("fs"), 1);
42
+ var import_node_path2 = __toESM(require("path"), 1);
43
+
44
+ // src/core.ts
45
+ var import_node_path = __toESM(require("path"), 1);
46
+ var import_typescript = __toESM(require("typescript"), 1);
47
+ function findNearestTsconfig(startDir) {
48
+ return import_typescript.default.findConfigFile(startDir, import_typescript.default.sys.fileExists, "tsconfig.json") ?? void 0;
49
+ }
50
+ function loadProgram(entryFileAbs, project) {
51
+ const fileDir = import_node_path.default.dirname(entryFileAbs);
52
+ const tsconfigPath = project ? import_node_path.default.resolve(process.cwd(), project) : findNearestTsconfig(fileDir);
53
+ if (!tsconfigPath) {
54
+ return import_typescript.default.createProgram([entryFileAbs], {
55
+ target: import_typescript.default.ScriptTarget.ES2022,
56
+ module: import_typescript.default.ModuleKind.ESNext,
57
+ strict: true,
58
+ allowJs: true,
59
+ checkJs: false,
60
+ moduleResolution: import_typescript.default.ModuleResolutionKind.Bundler,
61
+ skipLibCheck: true
62
+ });
63
+ }
64
+ const cfg = import_typescript.default.readConfigFile(tsconfigPath, import_typescript.default.sys.readFile);
65
+ if (cfg.error) {
66
+ throw new Error(
67
+ import_typescript.default.flattenDiagnosticMessageText(cfg.error.messageText, "\n")
68
+ );
69
+ }
70
+ const parsed = import_typescript.default.parseJsonConfigFileContent(
71
+ cfg.config,
72
+ import_typescript.default.sys,
73
+ import_node_path.default.dirname(tsconfigPath)
74
+ );
75
+ return import_typescript.default.createProgram({
76
+ rootNames: parsed.fileNames,
77
+ options: parsed.options
78
+ });
79
+ }
80
+ function isArrowOrFnExpr(n) {
81
+ return !!n && (import_typescript.default.isArrowFunction(n) || import_typescript.default.isFunctionExpression(n));
82
+ }
83
+ function nodeNameText(n) {
84
+ if (!n) return void 0;
85
+ if (import_typescript.default.isIdentifier(n)) return n.text;
86
+ if (import_typescript.default.isStringLiteral(n)) return n.text;
87
+ if (import_typescript.default.isNumericLiteral(n)) return n.text;
88
+ return void 0;
89
+ }
90
+ function isFunctionLikeNamed(node, name) {
91
+ if (import_typescript.default.isFunctionDeclaration(node) && node.name?.text === name) return true;
92
+ if (import_typescript.default.isVariableDeclaration(node) && import_typescript.default.isIdentifier(node.name) && node.name.text === name) {
93
+ return isArrowOrFnExpr(node.initializer);
94
+ }
95
+ if ((import_typescript.default.isMethodDeclaration(node) || import_typescript.default.isMethodSignature(node)) && nodeNameText(node.name) === name) {
96
+ return true;
97
+ }
98
+ if (import_typescript.default.isPropertyAssignment(node) && nodeNameText(node.name) === name) {
99
+ return isArrowOrFnExpr(node.initializer);
100
+ }
101
+ return false;
102
+ }
103
+ function findFirstMatch(sourceFile, name) {
104
+ let found;
105
+ const visit = (node) => {
106
+ if (found) return;
107
+ if (isFunctionLikeNamed(node, name)) {
108
+ found = node;
109
+ return;
110
+ }
111
+ import_typescript.default.forEachChild(node, visit);
112
+ };
113
+ visit(sourceFile);
114
+ return found;
115
+ }
116
+ function getTypeInfo(program, node) {
117
+ const checker = program.getTypeChecker();
118
+ let sig;
119
+ if (import_typescript.default.isFunctionDeclaration(node) || import_typescript.default.isMethodDeclaration(node)) {
120
+ sig = checker.getSignatureFromDeclaration(node) ?? void 0;
121
+ } else if (import_typescript.default.isVariableDeclaration(node)) {
122
+ const init = node.initializer;
123
+ if (isArrowOrFnExpr(init))
124
+ sig = checker.getSignatureFromDeclaration(init) ?? void 0;
125
+ } else if (import_typescript.default.isPropertyAssignment(node)) {
126
+ const init = node.initializer;
127
+ if (isArrowOrFnExpr(init))
128
+ sig = checker.getSignatureFromDeclaration(init) ?? void 0;
129
+ } else if (import_typescript.default.isMethodSignature(node)) {
130
+ sig = checker.getSignatureFromDeclaration(node) ?? void 0;
131
+ }
132
+ const flags = import_typescript.default.TypeFormatFlags.NoTruncation;
133
+ if (sig) {
134
+ const signature = checker.signatureToString(sig, void 0, flags);
135
+ const ret = checker.getReturnTypeOfSignature(sig);
136
+ const returnType = checker.typeToString(ret, void 0, flags);
137
+ return { signature, returnType };
138
+ }
139
+ const nodeWithName = node;
140
+ let nameNode = node;
141
+ if (nodeWithName.name && import_typescript.default.isIdentifier(nodeWithName.name)) {
142
+ nameNode = nodeWithName.name;
143
+ }
144
+ const t = checker.getTypeAtLocation(nameNode);
145
+ return { signature: checker.typeToString(t, void 0, flags) };
146
+ }
147
+
148
+ // src/index.ts
149
+ function inferType(file, name, project) {
150
+ const entryFileAbs = import_node_path2.default.resolve(process.cwd(), file);
151
+ if (!import_node_fs.default.existsSync(entryFileAbs)) {
152
+ throw new Error(`File not found: ${entryFileAbs}`);
153
+ }
154
+ const program = loadProgram(entryFileAbs, project);
155
+ const sourceFile = program.getSourceFile(entryFileAbs);
156
+ if (!sourceFile) {
157
+ throw new Error(
158
+ `Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`
159
+ );
160
+ }
161
+ const node = findFirstMatch(sourceFile, name);
162
+ if (!node) {
163
+ throw new Error(
164
+ `No function-like symbol named "${name}" found in ${entryFileAbs}`
165
+ );
166
+ }
167
+ return getTypeInfo(program, node);
168
+ }
169
+ function inferTypeFromOptions(options) {
170
+ return inferType(options.file, options.name, options.project);
171
+ }
172
+ // Annotate the CommonJS export names for ESM import in node:
173
+ 0 && (module.exports = {
174
+ findFirstMatch,
175
+ findNearestTsconfig,
176
+ getTypeInfo,
177
+ inferType,
178
+ inferTypeFromOptions,
179
+ loadProgram
180
+ });
181
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findFirstMatch, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param project - Optional path to tsconfig.json\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n * console.log(result.returnType);\n * // => \"boolean\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\tproject?: string,\n): InferredTypeResult {\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findFirstMatch(sourceFile, name);\n\tif (!node) {\n\t\tthrow new Error(\n\t\t\t`No function-like symbol named \"${name}\" found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, options.project);\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags) };\n}\n"],"mappings":"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","names":["import_node_path","ts","path","path","fs"]}
@@ -0,0 +1,70 @@
1
+ import ts from 'typescript';
2
+
3
+ /**
4
+ * Options for type inference
5
+ */
6
+ interface Options {
7
+ /** Path to the TypeScript file */
8
+ file: string;
9
+ /** Name of the function/variable to inspect */
10
+ name: string;
11
+ /** Optional path to tsconfig.json */
12
+ project?: string;
13
+ }
14
+ /**
15
+ * Result of type inference
16
+ */
17
+ interface InferredTypeResult {
18
+ /** The inferred type signature */
19
+ signature: string;
20
+ /** The return type (for functions) */
21
+ returnType?: string;
22
+ }
23
+
24
+ /**
25
+ * Find the nearest tsconfig.json from a starting directory
26
+ */
27
+ declare function findNearestTsconfig(startDir: string): string | undefined;
28
+ /**
29
+ * Load a TypeScript program from an entry file
30
+ */
31
+ declare function loadProgram(entryFileAbs: string, project?: string): ts.Program;
32
+ /**
33
+ * Find the first function-like node with the given name in a source file
34
+ */
35
+ declare function findFirstMatch(sourceFile: ts.SourceFile, name: string): ts.Node | undefined;
36
+ /**
37
+ * Get type information for a node
38
+ */
39
+ declare function getTypeInfo(program: ts.Program, node: ts.Node): InferredTypeResult;
40
+
41
+ /**
42
+ * Infer the type of a function or variable in a TypeScript file
43
+ *
44
+ * @param file - Path to the TypeScript file
45
+ * @param name - Name of the function/variable to inspect
46
+ * @param project - Optional path to tsconfig.json
47
+ * @returns The inferred type information
48
+ * @throws Error if file not found or symbol not found
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { inferType } from "prinfer";
53
+ *
54
+ * const result = inferType("./src/utils.ts", "myFunction");
55
+ * console.log(result.signature);
56
+ * // => "(x: number, y: string) => boolean"
57
+ * console.log(result.returnType);
58
+ * // => "boolean"
59
+ * ```
60
+ */
61
+ declare function inferType(file: string, name: string, project?: string): InferredTypeResult;
62
+ /**
63
+ * Infer the type using an options object
64
+ *
65
+ * @param options - Options for type inference
66
+ * @returns The inferred type information
67
+ */
68
+ declare function inferTypeFromOptions(options: Options): InferredTypeResult;
69
+
70
+ export { type InferredTypeResult, type Options, findFirstMatch, findNearestTsconfig, getTypeInfo, inferType, inferTypeFromOptions, loadProgram };
@@ -0,0 +1,70 @@
1
+ import ts from 'typescript';
2
+
3
+ /**
4
+ * Options for type inference
5
+ */
6
+ interface Options {
7
+ /** Path to the TypeScript file */
8
+ file: string;
9
+ /** Name of the function/variable to inspect */
10
+ name: string;
11
+ /** Optional path to tsconfig.json */
12
+ project?: string;
13
+ }
14
+ /**
15
+ * Result of type inference
16
+ */
17
+ interface InferredTypeResult {
18
+ /** The inferred type signature */
19
+ signature: string;
20
+ /** The return type (for functions) */
21
+ returnType?: string;
22
+ }
23
+
24
+ /**
25
+ * Find the nearest tsconfig.json from a starting directory
26
+ */
27
+ declare function findNearestTsconfig(startDir: string): string | undefined;
28
+ /**
29
+ * Load a TypeScript program from an entry file
30
+ */
31
+ declare function loadProgram(entryFileAbs: string, project?: string): ts.Program;
32
+ /**
33
+ * Find the first function-like node with the given name in a source file
34
+ */
35
+ declare function findFirstMatch(sourceFile: ts.SourceFile, name: string): ts.Node | undefined;
36
+ /**
37
+ * Get type information for a node
38
+ */
39
+ declare function getTypeInfo(program: ts.Program, node: ts.Node): InferredTypeResult;
40
+
41
+ /**
42
+ * Infer the type of a function or variable in a TypeScript file
43
+ *
44
+ * @param file - Path to the TypeScript file
45
+ * @param name - Name of the function/variable to inspect
46
+ * @param project - Optional path to tsconfig.json
47
+ * @returns The inferred type information
48
+ * @throws Error if file not found or symbol not found
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { inferType } from "prinfer";
53
+ *
54
+ * const result = inferType("./src/utils.ts", "myFunction");
55
+ * console.log(result.signature);
56
+ * // => "(x: number, y: string) => boolean"
57
+ * console.log(result.returnType);
58
+ * // => "boolean"
59
+ * ```
60
+ */
61
+ declare function inferType(file: string, name: string, project?: string): InferredTypeResult;
62
+ /**
63
+ * Infer the type using an options object
64
+ *
65
+ * @param options - Options for type inference
66
+ * @returns The inferred type information
67
+ */
68
+ declare function inferTypeFromOptions(options: Options): InferredTypeResult;
69
+
70
+ export { type InferredTypeResult, type Options, findFirstMatch, findNearestTsconfig, getTypeInfo, inferType, inferTypeFromOptions, loadProgram };
package/dist/index.js ADDED
@@ -0,0 +1,141 @@
1
+ // src/index.ts
2
+ import fs from "fs";
3
+ import path2 from "path";
4
+
5
+ // src/core.ts
6
+ import path from "path";
7
+ import ts from "typescript";
8
+ function findNearestTsconfig(startDir) {
9
+ return ts.findConfigFile(startDir, ts.sys.fileExists, "tsconfig.json") ?? void 0;
10
+ }
11
+ function loadProgram(entryFileAbs, project) {
12
+ const fileDir = path.dirname(entryFileAbs);
13
+ const tsconfigPath = project ? path.resolve(process.cwd(), project) : findNearestTsconfig(fileDir);
14
+ if (!tsconfigPath) {
15
+ return ts.createProgram([entryFileAbs], {
16
+ target: ts.ScriptTarget.ES2022,
17
+ module: ts.ModuleKind.ESNext,
18
+ strict: true,
19
+ allowJs: true,
20
+ checkJs: false,
21
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
22
+ skipLibCheck: true
23
+ });
24
+ }
25
+ const cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
26
+ if (cfg.error) {
27
+ throw new Error(
28
+ ts.flattenDiagnosticMessageText(cfg.error.messageText, "\n")
29
+ );
30
+ }
31
+ const parsed = ts.parseJsonConfigFileContent(
32
+ cfg.config,
33
+ ts.sys,
34
+ path.dirname(tsconfigPath)
35
+ );
36
+ return ts.createProgram({
37
+ rootNames: parsed.fileNames,
38
+ options: parsed.options
39
+ });
40
+ }
41
+ function isArrowOrFnExpr(n) {
42
+ return !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));
43
+ }
44
+ function nodeNameText(n) {
45
+ if (!n) return void 0;
46
+ if (ts.isIdentifier(n)) return n.text;
47
+ if (ts.isStringLiteral(n)) return n.text;
48
+ if (ts.isNumericLiteral(n)) return n.text;
49
+ return void 0;
50
+ }
51
+ function isFunctionLikeNamed(node, name) {
52
+ if (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;
53
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === name) {
54
+ return isArrowOrFnExpr(node.initializer);
55
+ }
56
+ if ((ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) && nodeNameText(node.name) === name) {
57
+ return true;
58
+ }
59
+ if (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {
60
+ return isArrowOrFnExpr(node.initializer);
61
+ }
62
+ return false;
63
+ }
64
+ function findFirstMatch(sourceFile, name) {
65
+ let found;
66
+ const visit = (node) => {
67
+ if (found) return;
68
+ if (isFunctionLikeNamed(node, name)) {
69
+ found = node;
70
+ return;
71
+ }
72
+ ts.forEachChild(node, visit);
73
+ };
74
+ visit(sourceFile);
75
+ return found;
76
+ }
77
+ function getTypeInfo(program, node) {
78
+ const checker = program.getTypeChecker();
79
+ let sig;
80
+ if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
81
+ sig = checker.getSignatureFromDeclaration(node) ?? void 0;
82
+ } else if (ts.isVariableDeclaration(node)) {
83
+ const init = node.initializer;
84
+ if (isArrowOrFnExpr(init))
85
+ sig = checker.getSignatureFromDeclaration(init) ?? void 0;
86
+ } else if (ts.isPropertyAssignment(node)) {
87
+ const init = node.initializer;
88
+ if (isArrowOrFnExpr(init))
89
+ sig = checker.getSignatureFromDeclaration(init) ?? void 0;
90
+ } else if (ts.isMethodSignature(node)) {
91
+ sig = checker.getSignatureFromDeclaration(node) ?? void 0;
92
+ }
93
+ const flags = ts.TypeFormatFlags.NoTruncation;
94
+ if (sig) {
95
+ const signature = checker.signatureToString(sig, void 0, flags);
96
+ const ret = checker.getReturnTypeOfSignature(sig);
97
+ const returnType = checker.typeToString(ret, void 0, flags);
98
+ return { signature, returnType };
99
+ }
100
+ const nodeWithName = node;
101
+ let nameNode = node;
102
+ if (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {
103
+ nameNode = nodeWithName.name;
104
+ }
105
+ const t = checker.getTypeAtLocation(nameNode);
106
+ return { signature: checker.typeToString(t, void 0, flags) };
107
+ }
108
+
109
+ // src/index.ts
110
+ function inferType(file, name, project) {
111
+ const entryFileAbs = path2.resolve(process.cwd(), file);
112
+ if (!fs.existsSync(entryFileAbs)) {
113
+ throw new Error(`File not found: ${entryFileAbs}`);
114
+ }
115
+ const program = loadProgram(entryFileAbs, project);
116
+ const sourceFile = program.getSourceFile(entryFileAbs);
117
+ if (!sourceFile) {
118
+ throw new Error(
119
+ `Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`
120
+ );
121
+ }
122
+ const node = findFirstMatch(sourceFile, name);
123
+ if (!node) {
124
+ throw new Error(
125
+ `No function-like symbol named "${name}" found in ${entryFileAbs}`
126
+ );
127
+ }
128
+ return getTypeInfo(program, node);
129
+ }
130
+ function inferTypeFromOptions(options) {
131
+ return inferType(options.file, options.name, options.project);
132
+ }
133
+ export {
134
+ findFirstMatch,
135
+ findNearestTsconfig,
136
+ getTypeInfo,
137
+ inferType,
138
+ inferTypeFromOptions,
139
+ loadProgram
140
+ };
141
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { findFirstMatch, getTypeInfo, loadProgram } from \"./core.js\";\nimport type { InferredTypeResult, Options } from \"./types.js\";\n\n// Re-export types\nexport type { Options, InferredTypeResult };\n\n// Re-export core utilities\nexport {\n\tfindFirstMatch,\n\tfindNearestTsconfig,\n\tgetTypeInfo,\n\tloadProgram,\n} from \"./core.js\";\n\n/**\n * Infer the type of a function or variable in a TypeScript file\n *\n * @param file - Path to the TypeScript file\n * @param name - Name of the function/variable to inspect\n * @param project - Optional path to tsconfig.json\n * @returns The inferred type information\n * @throws Error if file not found or symbol not found\n *\n * @example\n * ```ts\n * import { inferType } from \"prinfer\";\n *\n * const result = inferType(\"./src/utils.ts\", \"myFunction\");\n * console.log(result.signature);\n * // => \"(x: number, y: string) => boolean\"\n * console.log(result.returnType);\n * // => \"boolean\"\n * ```\n */\nexport function inferType(\n\tfile: string,\n\tname: string,\n\tproject?: string,\n): InferredTypeResult {\n\tconst entryFileAbs = path.resolve(process.cwd(), file);\n\n\tif (!fs.existsSync(entryFileAbs)) {\n\t\tthrow new Error(`File not found: ${entryFileAbs}`);\n\t}\n\n\tconst program = loadProgram(entryFileAbs, project);\n\tconst sourceFile = program.getSourceFile(entryFileAbs);\n\n\tif (!sourceFile) {\n\t\tthrow new Error(\n\t\t\t`Could not load source file into the program (check tsconfig include/exclude): ${entryFileAbs}`,\n\t\t);\n\t}\n\n\tconst node = findFirstMatch(sourceFile, name);\n\tif (!node) {\n\t\tthrow new Error(\n\t\t\t`No function-like symbol named \"${name}\" found in ${entryFileAbs}`,\n\t\t);\n\t}\n\n\treturn getTypeInfo(program, node);\n}\n\n/**\n * Infer the type using an options object\n *\n * @param options - Options for type inference\n * @returns The inferred type information\n */\nexport function inferTypeFromOptions(options: Options): InferredTypeResult {\n\treturn inferType(options.file, options.name, options.project);\n}\n","import path from \"node:path\";\nimport ts from \"typescript\";\nimport type { InferredTypeResult } from \"./types.js\";\n\n/**\n * Find the nearest tsconfig.json from a starting directory\n */\nexport function findNearestTsconfig(startDir: string): string | undefined {\n\treturn (\n\t\tts.findConfigFile(startDir, ts.sys.fileExists, \"tsconfig.json\") ??\n\t\tundefined\n\t);\n}\n\n/**\n * Load a TypeScript program from an entry file\n */\nexport function loadProgram(\n\tentryFileAbs: string,\n\tproject?: string,\n): ts.Program {\n\tconst fileDir = path.dirname(entryFileAbs);\n\tconst tsconfigPath = project\n\t\t? path.resolve(process.cwd(), project)\n\t\t: findNearestTsconfig(fileDir);\n\n\tif (!tsconfigPath) {\n\t\t// Fallback: single-file program\n\t\treturn ts.createProgram([entryFileAbs], {\n\t\t\ttarget: ts.ScriptTarget.ES2022,\n\t\t\tmodule: ts.ModuleKind.ESNext,\n\t\t\tstrict: true,\n\t\t\tallowJs: true,\n\t\t\tcheckJs: false,\n\t\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\t\tskipLibCheck: true,\n\t\t});\n\t}\n\n\tconst cfg = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\tif (cfg.error) {\n\t\tthrow new Error(\n\t\t\tts.flattenDiagnosticMessageText(cfg.error.messageText, \"\\n\"),\n\t\t);\n\t}\n\n\tconst parsed = ts.parseJsonConfigFileContent(\n\t\tcfg.config,\n\t\tts.sys,\n\t\tpath.dirname(tsconfigPath),\n\t);\n\treturn ts.createProgram({\n\t\trootNames: parsed.fileNames,\n\t\toptions: parsed.options,\n\t});\n}\n\nfunction isArrowOrFnExpr(\n\tn: ts.Node | undefined,\n): n is ts.ArrowFunction | ts.FunctionExpression {\n\treturn !!n && (ts.isArrowFunction(n) || ts.isFunctionExpression(n));\n}\n\nfunction nodeNameText(\n\tn: ts.PropertyName | ts.BindingName | undefined,\n): string | undefined {\n\tif (!n) return undefined;\n\tif (ts.isIdentifier(n)) return n.text;\n\tif (ts.isStringLiteral(n)) return n.text;\n\tif (ts.isNumericLiteral(n)) return n.text;\n\treturn undefined; // ignore computed names etc.\n}\n\nfunction isFunctionLikeNamed(node: ts.Node, name: string): boolean {\n\t// function foo() {}\n\tif (ts.isFunctionDeclaration(node) && node.name?.text === name) return true;\n\n\t// const foo = () => {} / function() {}\n\tif (\n\t\tts.isVariableDeclaration(node) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tnode.name.text === name\n\t) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\t// class C { foo() {} } / interface signatures\n\tif (\n\t\t(ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) &&\n\t\tnodeNameText(node.name) === name\n\t) {\n\t\treturn true;\n\t}\n\n\t// object literal { foo() {} } via MethodDeclaration inside object literal is also MethodDeclaration\n\t// property assignment: { foo: () => {} }\n\tif (ts.isPropertyAssignment(node) && nodeNameText(node.name) === name) {\n\t\treturn isArrowOrFnExpr(node.initializer);\n\t}\n\n\treturn false;\n}\n\n/**\n * Find the first function-like node with the given name in a source file\n */\nexport function findFirstMatch(\n\tsourceFile: ts.SourceFile,\n\tname: string,\n): ts.Node | undefined {\n\tlet found: ts.Node | undefined;\n\n\tconst visit = (node: ts.Node) => {\n\t\tif (found) return;\n\t\tif (isFunctionLikeNamed(node, name)) {\n\t\t\tfound = node;\n\t\t\treturn;\n\t\t}\n\t\tts.forEachChild(node, visit);\n\t};\n\n\tvisit(sourceFile);\n\treturn found;\n}\n\n/**\n * Get type information for a node\n */\nexport function getTypeInfo(\n\tprogram: ts.Program,\n\tnode: ts.Node,\n): InferredTypeResult {\n\tconst checker = program.getTypeChecker();\n\n\tlet sig: ts.Signature | undefined;\n\n\t// Prefer getting the signature from a declaration/expression directly\n\tif (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\tconst init = node.initializer;\n\t\tif (isArrowOrFnExpr(init))\n\t\t\tsig = checker.getSignatureFromDeclaration(init) ?? undefined;\n\t} else if (ts.isMethodSignature(node)) {\n\t\t// interfaces/types\n\t\tsig = checker.getSignatureFromDeclaration(node) ?? undefined;\n\t}\n\n\tconst flags = ts.TypeFormatFlags.NoTruncation;\n\n\tif (sig) {\n\t\tconst signature = checker.signatureToString(sig, undefined, flags);\n\t\tconst ret = checker.getReturnTypeOfSignature(sig);\n\t\tconst returnType = checker.typeToString(ret, undefined, flags);\n\t\treturn { signature, returnType };\n\t}\n\n\t// Fallback: type at the name location\n\tconst nodeWithName = node as unknown as { name?: ts.Node };\n\tlet nameNode: ts.Node = node;\n\tif (nodeWithName.name && ts.isIdentifier(nodeWithName.name)) {\n\t\tnameNode = nodeWithName.name;\n\t}\n\n\tconst t = checker.getTypeAtLocation(nameNode);\n\treturn { signature: checker.typeToString(t, undefined, flags) };\n}\n"],"mappings":"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","names":["path","path"]}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "prinfer",
3
+ "version": "0.2.0",
4
+ "description": "TypeScript type inference inspection tool",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "bin": {
22
+ "prinfer": "./dist/cli.js"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "build": "bun run tsup",
29
+ "test": "bun test",
30
+ "ci": "bun run build && bunx biome check . && bun test",
31
+ "changeset": "changeset",
32
+ "version": "changeset version",
33
+ "release": "bun run build && changeset publish",
34
+ "prepublishOnly": "bun run ci",
35
+ "prepare": "simple-git-hooks"
36
+ },
37
+ "simple-git-hooks": {
38
+ "pre-commit": "bunx biome check ."
39
+ },
40
+ "keywords": [
41
+ "typescript",
42
+ "type-inference",
43
+ "types",
44
+ "inspection",
45
+ "cli"
46
+ ],
47
+ "author": "",
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": ""
52
+ },
53
+ "peerDependencies": {
54
+ "typescript": ">=4.7.0"
55
+ },
56
+ "devDependencies": {
57
+ "@changesets/cli": "^2.29.8",
58
+ "bun-types": "latest",
59
+ "simple-git-hooks": "^2.13.1",
60
+ "tsup": "^8.0.0",
61
+ "typescript": "^5.0.0"
62
+ },
63
+ "engines": {
64
+ "bun": ">=1.0.0"
65
+ }
66
+ }