ts-explainer 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tsclear contributors
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,160 @@
1
+ # ts-explainer
2
+
3
+ [![CI](https://github.com/woxlo1/ts-explainer/actions/workflows/ci.yml/badge.svg)](https://github.com/woxlo1/ts-explainer/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/ts-explainer.svg)](https://www.npmjs.com/package/ts-explainer)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ > TypeScript compiler errors, explained in plain English.
8
+
9
+ `ts-explainer` pipes between `tsc` and your terminal and rewrites cryptic TypeScript errors into clear, actionable explanations — without losing any information.
10
+
11
+ ---
12
+
13
+ ## The problem
14
+
15
+ ```
16
+ src/api.ts(23,5): error TS2345: Argument of type '{ id: string; name: string; }' is not
17
+ assignable to parameter of type 'User'. Type '{ id: string; name: string; }' is missing
18
+ the following properties from type 'User': email, createdAt, updatedAt
19
+ ```
20
+
21
+ What does that actually mean? What should you fix first?
22
+
23
+ ## The solution
24
+
25
+ ```
26
+ $ npx tsc --noEmit | npx ts-explainer
27
+
28
+ TS2345 src/api.ts:23:5
29
+ → You're calling a function with an argument of type '{ id: string; name: string; }',
30
+ but it expects type 'User'. Double-check the value you're passing in, or the
31
+ function's parameter type.
32
+ ```
33
+
34
+ Same information, actually readable.
35
+
36
+ ---
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ npm install -D ts-explainer
42
+ ```
43
+
44
+ Or run without installing:
45
+
46
+ ```bash
47
+ npx tsc --noEmit | npx ts-explainer
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ Pipe any `tsc` output into `ts-explainer`:
53
+
54
+ ```bash
55
+ npx tsc --noEmit | npx ts-explainer
56
+ ```
57
+
58
+ Works with any `tsc` flags you're already using:
59
+
60
+ ```bash
61
+ npx tsc --noEmit --strict | npx ts-explainer
62
+ npx tsc -p tsconfig.prod.json | npx ts-explainer
63
+ ```
64
+
65
+ ### Add to package.json
66
+
67
+ ```json
68
+ {
69
+ "scripts": {
70
+ "typecheck": "tsc --noEmit | ts-explainer"
71
+ }
72
+ }
73
+ ```
74
+
75
+ Then run with:
76
+
77
+ ```bash
78
+ npm run typecheck
79
+ ```
80
+
81
+ ### Unknown errors
82
+
83
+ Errors that `ts-explainer` doesn't recognize yet are passed through unchanged — you never lose information from the original `tsc` output.
84
+
85
+ ---
86
+
87
+ ## Use as a library
88
+
89
+ `ts-explainer` also exports its parser and explainer for programmatic use:
90
+
91
+ ```typescript
92
+ import { parseTscOutput, explainAll } from "ts-explainer";
93
+
94
+ const raw = `src/index.ts(1,1): error TS2304: Cannot find name 'fetchUser'.`;
95
+
96
+ const diagnostics = parseTscOutput(raw);
97
+ const explained = explainAll(diagnostics);
98
+
99
+ console.log(explained[0].explanation);
100
+ // → 'fetchUser' isn't defined anywhere TypeScript can see. This is usually
101
+ // a missing import, a typo, or a missing @types package for a third-party library.
102
+ ```
103
+
104
+ Available exports:
105
+
106
+ ```typescript
107
+ import {
108
+ parseTscOutput, // string → ParsedDiagnostic[]
109
+ explainAll, // ParsedDiagnostic[] → ClearedDiagnostic[]
110
+ explainDiagnostic, // ParsedDiagnostic → ClearedDiagnostic
111
+ patterns, // ErrorPattern[] — the full list of known patterns
112
+ } from "ts-explainer";
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Supported error codes
118
+
119
+ | Code | Meaning |
120
+ | -------- | ------------------------------------------------- |
121
+ | TS2304 | Cannot find name (missing import / typo) |
122
+ | TS2307 | Cannot find module or its type declarations |
123
+ | TS2322 | Type is not assignable to target type |
124
+ | TS2339 | Property does not exist on type |
125
+ | TS2345 | Argument type mismatch in a function call |
126
+ | TS2349 | Expression is not callable |
127
+ | TS2352 | Type conversion may be a mistake |
128
+ | TS2366 | Function missing ending return statement |
129
+ | TS2367 | Condition always returns same value (no overlap) |
130
+ | TS2511 | Cannot instantiate an abstract class |
131
+ | TS2531 | Object is possibly `null` |
132
+ | TS2532 | Object is possibly `undefined` |
133
+ | TS2540 | Cannot assign to a read-only property |
134
+ | TS2551 | Property doesn't exist — did you mean X? |
135
+ | TS2554 | Wrong number of arguments in a call |
136
+ | TS2741 | Required property missing on an object |
137
+ | TS2769 | No function overload matches the call |
138
+ | TS7006 | Parameter implicitly has an `any` type |
139
+ | TS7053 | Expression can't be used to index type |
140
+ | TS18048 | Value is possibly `undefined` |
141
+
142
+ More patterns are added with every release. Open an issue or PR if you keep running into an error that isn't covered yet — see [CONTRIBUTING.md](./CONTRIBUTING.md).
143
+
144
+ ---
145
+
146
+ ## Requirements
147
+
148
+ - Node.js 18 or later
149
+
150
+ ## Versioning
151
+
152
+ This project follows [Semantic Versioning](https://semver.org/) and [Conventional Commits](https://www.conventionalcommits.org/). Version numbers are bumped at release time only — not on every commit. See [CHANGELOG.md](./CHANGELOG.md) for release history.
153
+
154
+ ## Contributing
155
+
156
+ Contributions are welcome! The most valuable thing you can add is a new error pattern explanation. See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to add one, run tests, and how releases are cut.
157
+
158
+ ## License
159
+
160
+ MIT © [woxlo1](https://github.com/woxlo1)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import { parseTscOutput } from "./parser.js";
3
+ import { explainAll } from "./explainer.js";
4
+ const RESET = "\x1b[0m";
5
+ const BOLD = "\x1b[1m";
6
+ const DIM = "\x1b[2m";
7
+ const RED = "\x1b[31m";
8
+ const CYAN = "\x1b[36m";
9
+ const GREEN = "\x1b[32m";
10
+ async function readStdin() {
11
+ const chunks = [];
12
+ for await (const chunk of process.stdin) {
13
+ chunks.push(chunk);
14
+ }
15
+ return Buffer.concat(chunks).toString("utf8");
16
+ }
17
+ async function main() {
18
+ const raw = await readStdin();
19
+ if (!raw.trim()) {
20
+ console.error(`${RED}ts-explainer:${RESET} no input received. Pipe tsc's output into this command, e.g.:\n npx tsc --noEmit | npx ts-explainer`);
21
+ process.exit(1);
22
+ }
23
+ const diagnostics = parseTscOutput(raw);
24
+ if (diagnostics.length === 0) {
25
+ console.log(`${GREEN}ts-explainer:${RESET} no recognizable tsc errors found in input.`);
26
+ return;
27
+ }
28
+ const cleared = explainAll(diagnostics);
29
+ for (const d of cleared) {
30
+ console.log(`${BOLD}${RED}${d.code}${RESET} ${DIM}${d.file}:${d.line}:${d.column}${RESET}`);
31
+ console.log(` ${CYAN}→${RESET} ${d.explanation}`);
32
+ if (!d.matchedPatternId) {
33
+ console.log(` ${DIM}(no plain-language pattern for this one yet — raw message shown)${RESET}`);
34
+ }
35
+ console.log("");
36
+ }
37
+ console.log(`${DIM}${cleared.length} error(s) explained.${RESET}`);
38
+ }
39
+ main().catch((err) => {
40
+ console.error(`${RED}ts-explainer: unexpected error${RESET}`, err);
41
+ process.exit(1);
42
+ });
@@ -0,0 +1,3 @@
1
+ import type { ClearedDiagnostic, ParsedDiagnostic } from "./types.js";
2
+ export declare function explainDiagnostic(diag: ParsedDiagnostic): ClearedDiagnostic;
3
+ export declare function explainAll(diags: ParsedDiagnostic[]): ClearedDiagnostic[];
@@ -0,0 +1,22 @@
1
+ import { patterns } from "./patterns.js";
2
+ export function explainDiagnostic(diag) {
3
+ const candidate = patterns.find((p) => p.code === diag.code);
4
+ if (candidate) {
5
+ const matched = diag.rawMessage.match(candidate.match);
6
+ if (matched) {
7
+ return {
8
+ ...diag,
9
+ explanation: candidate.explain(matched),
10
+ matchedPatternId: candidate.id,
11
+ };
12
+ }
13
+ }
14
+ return {
15
+ ...diag,
16
+ explanation: diag.rawMessage,
17
+ matchedPatternId: null,
18
+ };
19
+ }
20
+ export function explainAll(diags) {
21
+ return diags.map(explainDiagnostic);
22
+ }
@@ -0,0 +1,4 @@
1
+ export { parseTscOutput } from "./parser.js";
2
+ export { explainDiagnostic, explainAll } from "./explainer.js";
3
+ export { patterns } from "./patterns.js";
4
+ export type { ErrorPattern, ParsedDiagnostic, ClearedDiagnostic } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { parseTscOutput } from "./parser.js";
2
+ export { explainDiagnostic, explainAll } from "./explainer.js";
3
+ export { patterns } from "./patterns.js";
@@ -0,0 +1,2 @@
1
+ import type { ParsedDiagnostic } from "./types.js";
2
+ export declare function parseTscOutput(raw: string): ParsedDiagnostic[];
package/dist/parser.js ADDED
@@ -0,0 +1,23 @@
1
+ // Matches tsc's default output format, e.g.:
2
+ // src/index.ts(12,5): error TS2322: Type 'string' is not assignable to type 'number'.
3
+ const TSC_LINE = /^(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)$/;
4
+ export function parseTscOutput(raw) {
5
+ const results = [];
6
+ for (const line of raw.split("\n")) {
7
+ const trimmed = line.trimEnd();
8
+ if (!trimmed)
9
+ continue;
10
+ const matched = trimmed.match(TSC_LINE);
11
+ if (!matched)
12
+ continue;
13
+ const [, file, lineStr, colStr, code, message] = matched;
14
+ results.push({
15
+ file,
16
+ line: Number(lineStr),
17
+ column: Number(colStr),
18
+ code,
19
+ rawMessage: message,
20
+ });
21
+ }
22
+ return results;
23
+ }
@@ -0,0 +1,2 @@
1
+ import type { ErrorPattern } from "./types.js";
2
+ export declare const patterns: ErrorPattern[];
@@ -0,0 +1,141 @@
1
+ export const patterns = [
2
+ {
3
+ id: "type-not-assignable",
4
+ code: "TS2322",
5
+ match: /Type '(.+)' is not assignable to type '(.+)'\.?$/,
6
+ explain: (m) => `You're trying to use a value of type '${m[1]}' where type '${m[2]}' is expected. ` +
7
+ `Check the value you're assigning, or update the target type if '${m[1]}' should actually be allowed there.`,
8
+ },
9
+ {
10
+ id: "property-does-not-exist",
11
+ code: "TS2339",
12
+ match: /Property '(.+)' does not exist on type '(.+)'\.?$/,
13
+ explain: (m) => `You're accessing '.${m[1]}' on something typed as '${m[2]}', but that type has no such property. ` +
14
+ `This usually means a typo, a missing type definition, or the object hasn't been narrowed to the right type yet.`,
15
+ },
16
+ {
17
+ id: "possibly-undefined",
18
+ code: "TS18048",
19
+ match: /'(.+)' is possibly 'undefined'\.?$/,
20
+ explain: (m) => `'${m[1]}' might be 'undefined' at this point. ` +
21
+ `Add a check (like 'if (${m[1]})' or optional chaining '?.'), or use a non-null assertion '!' if you're certain it's always defined here.`,
22
+ },
23
+ {
24
+ id: "argument-mismatch",
25
+ code: "TS2345",
26
+ match: /Argument of type '(.+)' is not assignable to parameter of type '(.+)'\.?$/,
27
+ explain: (m) => `You're calling a function with an argument of type '${m[1]}', but it expects type '${m[2]}'. ` +
28
+ `Double-check the value you're passing in, or the function's parameter type.`,
29
+ },
30
+ {
31
+ id: "cannot-find-name",
32
+ code: "TS2304",
33
+ match: /Cannot find name '(.+)'\.?$/,
34
+ explain: (m) => `'${m[1]}' isn't defined anywhere TypeScript can see. ` +
35
+ `This is usually a missing import, a typo, or a missing @types package for a third-party library.`,
36
+ },
37
+ {
38
+ id: "object-possibly-null",
39
+ code: "TS2531",
40
+ match: /Object is possibly 'null'\.?$/,
41
+ explain: () => `This value could be 'null' here. ` +
42
+ `Add a check (like 'if (value !== null)') or optional chaining '?.' before using it, or use a non-null assertion '!' if you're certain it can't be null at this point.`,
43
+ },
44
+ {
45
+ id: "missing-required-properties",
46
+ code: "TS2741",
47
+ match: /Property '(.+)' is missing in type '(.+)' but required in type '(.+)'\.?$/,
48
+ explain: (m) => `The object you're providing (of type '${m[2]}') is missing the required property '${m[1]}' that '${m[3]}' expects. ` +
49
+ `Add '${m[1]}' to the object, or make it optional in the type definition if it shouldn't be required.`,
50
+ },
51
+ {
52
+ id: "wrong-argument-count",
53
+ code: "TS2554",
54
+ match: /Expected (\d+) arguments?, but got (\d+)\.?$/,
55
+ explain: (m) => `This function expects ${m[1]} argument(s), but it's being called with ${m[2]}. ` +
56
+ `Check the function's signature and make sure you're passing the right number of arguments.`,
57
+ },
58
+ {
59
+ id: "implicit-any-parameter",
60
+ code: "TS7006",
61
+ match: /Parameter '(.+)' implicitly has an '(.+)' type\.?$/,
62
+ explain: (m) => `The parameter '${m[1]}' doesn't have an explicit type, so TypeScript is falling back to '${m[2]}'. ` +
63
+ `Add a type annotation (e.g. '${m[1]}: string') to get proper type checking here.`,
64
+ },
65
+ {
66
+ id: "no-overload-matches",
67
+ code: "TS2769",
68
+ match: /No overload matches this call\.?$/,
69
+ explain: () => `None of this function's overloads accept the arguments you're passing. ` +
70
+ `Check the function's available signatures (hover over it in your editor) and compare them against what you're passing in — usually one argument's type is slightly off.`,
71
+ },
72
+ {
73
+ id: "did-you-mean",
74
+ code: "TS2551",
75
+ match: /Property '(.+)' does not exist on type '(.+)'\. Did you mean '(.+)'\?\.?$/,
76
+ explain: (m) => `'.${m[1]}' doesn't exist on type '${m[2]}'. TypeScript thinks you meant '.${m[3]}' — check for a typo.`,
77
+ },
78
+ {
79
+ id: "not-callable",
80
+ code: "TS2349",
81
+ match: /This expression is not callable\./,
82
+ explain: () => `You're trying to call something as a function, but its type says it isn't callable. ` +
83
+ `Check whether the variable actually holds a function, or if you're missing parentheses somewhere.`,
84
+ },
85
+ {
86
+ id: "possibly-undefined-access",
87
+ code: "TS2532",
88
+ match: /Object is possibly 'undefined'\.?$/,
89
+ explain: () => `This value might be 'undefined' at this point. ` +
90
+ `Add a check (like 'if (value !== undefined)') or use optional chaining '?.' before accessing it.`,
91
+ },
92
+ {
93
+ id: "readonly-cannot-assign",
94
+ code: "TS2540",
95
+ match: /Cannot assign to '(.+)' because it is a read-only property\.?$/,
96
+ explain: (m) => `'${m[1]}' is marked as 'readonly' and cannot be reassigned after initialization. ` +
97
+ `If you need to change this value, remove the 'readonly' modifier from the type definition.`,
98
+ },
99
+ {
100
+ id: "cannot-find-module",
101
+ code: "TS2307",
102
+ match: /Cannot find module '(.+)' or its corresponding type declarations\.?$/,
103
+ explain: (m) => `TypeScript can't find the module '${m[1]}'. ` +
104
+ `Check that the package is installed ('npm install ${m[1]}'), and if it's a third-party library, you may also need '@types/${m[1]}' for type declarations.`,
105
+ },
106
+ {
107
+ id: "type-has-no-index-signature",
108
+ code: "TS7053",
109
+ match: /Element implicitly has an 'any' type because expression of type '(.+)' can't be used to index type '(.+)'\./,
110
+ explain: (m) => `You're indexing into '${m[2]}' with a key of type '${m[1]}', but that type has no index signature. ` +
111
+ `Either add an index signature to the type, or use 'keyof' to constrain the key type.`,
112
+ },
113
+ {
114
+ id: "union-not-narrowed",
115
+ code: "TS2367",
116
+ match: /This condition will always return '(.+)' since the types '(.+)' and '(.+)' have no overlap\.?$/,
117
+ explain: (m) => `Comparing '${m[2]}' and '${m[3]}' will always be '${m[1]}' because these two types can never be equal. ` +
118
+ `This is usually a sign that a variable was narrowed to an unexpected type, or there's a logic error in your condition.`,
119
+ },
120
+ {
121
+ id: "missing-return-type",
122
+ code: "TS2366",
123
+ match: /Function lacks ending return statement and return type does not include 'undefined'\.?$/,
124
+ explain: () => `Not all code paths in this function return a value, but the return type doesn't include 'undefined'. ` +
125
+ `Either add a return statement at the end, or update the return type to include '| undefined'.`,
126
+ },
127
+ {
128
+ id: "abstract-class-instantiation",
129
+ code: "TS2511",
130
+ match: /Cannot create an instance of an abstract class\.?$/,
131
+ explain: () => `You're trying to use 'new' on an abstract class, which isn't allowed. ` +
132
+ `Abstract classes are meant to be extended, not instantiated directly — create a concrete subclass and instantiate that instead.`,
133
+ },
134
+ {
135
+ id: "conversion-may-be-mistake",
136
+ code: "TS2352",
137
+ match: /Conversion of type '(.+)' to type '(.+)' may be a mistake because neither type sufficiently overlaps with the other\./,
138
+ explain: (m) => `Casting '${m[1]}' to '${m[2]}' looks suspicious because these types don't share enough in common. ` +
139
+ `If you're sure about the cast, use 'unknown' as an intermediate step: '(value as unknown) as ${m[2]}'. Otherwise double-check your types.`,
140
+ },
141
+ ];
@@ -0,0 +1,21 @@
1
+ export interface ErrorPattern {
2
+ /** Unique id for this pattern, used internally and in tests */
3
+ id: string;
4
+ /** TypeScript error code, e.g. TS2322 */
5
+ code: string;
6
+ /** Regex used to detect and capture details from the raw tsc message */
7
+ match: RegExp;
8
+ /** Builds a human-readable explanation from the regex match */
9
+ explain: (m: RegExpMatchArray) => string;
10
+ }
11
+ export interface ParsedDiagnostic {
12
+ file: string;
13
+ line: number;
14
+ column: number;
15
+ code: string;
16
+ rawMessage: string;
17
+ }
18
+ export interface ClearedDiagnostic extends ParsedDiagnostic {
19
+ explanation: string;
20
+ matchedPatternId: string | null;
21
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "ts-explainer",
3
+ "version": "0.1.0",
4
+ "description": "Turns confusing TypeScript compiler errors into clear, human-readable explanations.",
5
+ "type": "module",
6
+ "bin": {
7
+ "ts-explainer": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "tsc -p tsconfig.json",
13
+ "dev": "tsx src/cli.ts",
14
+ "test": "vitest run",
15
+ "lint": "eslint src --ext .ts",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": ["typescript", "cli", "error", "compiler", "devtools", "tsc", "errors"],
19
+ "license": "MIT",
20
+ "files": ["dist"],
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.6.0",
26
+ "tsx": "^4.19.0",
27
+ "vitest": "^2.1.0",
28
+ "@types/node": "^22.0.0",
29
+ "eslint": "^9.0.0"
30
+ }
31
+ }