intellerror 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/formatter/index.d.ts +1 -0
- package/dist/index.cjs +174 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/parser/index.d.ts +9 -0
- package/dist/register.cjs +148 -0
- package/dist/register.cjs.map +1 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +124 -0
- package/dist/register.js.map +1 -0
- package/dist/suggestions/index.d.ts +2 -0
- package/dist/suggestions/rules.d.ts +7 -0
- package/examples/async-error.ts +16 -0
- package/examples/basic-usage.ts +18 -0
- package/examples/custom-error.ts +21 -0
- package/examples/express-middleware.ts +19 -0
- package/package.json +64 -0
- package/src/config.ts +31 -0
- package/src/formatter/browser.ts +166 -0
- package/src/formatter/index.ts +120 -0
- package/src/formatter/snapshot.ts +34 -0
- package/src/index.ts +6 -0
- package/src/integrations/webhook.ts +23 -0
- package/src/middleware/index.ts +23 -0
- package/src/parser/index.ts +38 -0
- package/src/register.ts +68 -0
- package/src/suggestions/index.ts +12 -0
- package/src/suggestions/links.ts +12 -0
- package/src/suggestions/rules.ts +236 -0
- package/test.ts +16 -0
- package/tests/config.test.ts +25 -0
- package/tests/rules.test.ts +33 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +9 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { parseStack } from '../parser/index.js';
|
|
3
|
+
import { getSuggestions } from '../suggestions/index.js';
|
|
4
|
+
import { getConfig } from '../config.js';
|
|
5
|
+
import { getCodeSnapshot } from './snapshot.js';
|
|
6
|
+
import { getSearchLinks } from '../suggestions/links.js';
|
|
7
|
+
|
|
8
|
+
export function formatError(error: Error | unknown): string {
|
|
9
|
+
if (!(error instanceof Error)) {
|
|
10
|
+
return String(error);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
const stackFrames = parseStack(error);
|
|
15
|
+
|
|
16
|
+
// Find first user frame that is not node internal or node_modules
|
|
17
|
+
const userFrame = stackFrames.find(frame => !frame.isNodeInternal && !frame.isNodeModule);
|
|
18
|
+
|
|
19
|
+
const errorType = error.constructor.name || 'Error';
|
|
20
|
+
const typeLabel = chalk.bgRed.white(` ${errorType} `);
|
|
21
|
+
let output = `\n${typeLabel} ${chalk.red(error.message)}\n\n`;
|
|
22
|
+
|
|
23
|
+
if (userFrame && userFrame.file) {
|
|
24
|
+
output += `${chalk.bold('📍 Location:')}\n`;
|
|
25
|
+
output += `${chalk.cyan(userFrame.file)}:${chalk.yellow(userFrame.lineNumber)}:${chalk.yellow(userFrame.column)} ${chalk.gray('← YOUR CODE')}\n\n`;
|
|
26
|
+
|
|
27
|
+
// Add code snapshot (Node only)
|
|
28
|
+
if (userFrame.lineNumber && userFrame.file) {
|
|
29
|
+
output += getCodeSnapshot(userFrame.file, userFrame.lineNumber);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const suggestions = config.suggestionsEnabled ? getSuggestions(error) : [];
|
|
34
|
+
if (suggestions.length > 0) {
|
|
35
|
+
output += `${chalk.bold('💡 Suggestions:')}\n`;
|
|
36
|
+
for (const suggestion of suggestions) {
|
|
37
|
+
output += `• ${chalk.green(suggestion.message)}\n`;
|
|
38
|
+
if (suggestion.description) {
|
|
39
|
+
output += ` ${chalk.dim(suggestion.description)}\n`;
|
|
40
|
+
}
|
|
41
|
+
if (suggestion.fix) {
|
|
42
|
+
output += ` ${chalk.blue('Fix: ' + suggestion.fix)}\n`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
output += `\n`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (config.showSearchLinks) {
|
|
49
|
+
const searchLinks = getSearchLinks(error);
|
|
50
|
+
output += `${chalk.bold('🔍 Troubleshoot:')}\n`;
|
|
51
|
+
output += `• ${chalk.dim('Google:')} ${chalk.blue(searchLinks[0])}\n`;
|
|
52
|
+
output += `• ${chalk.dim('StackOverflow:')} ${chalk.blue(searchLinks[1])}\n`;
|
|
53
|
+
output += `• ${chalk.dim('GitHub:')} ${chalk.blue(searchLinks[2])}\n\n`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
output += `${chalk.bold('📦 Stack:')}\n`;
|
|
57
|
+
|
|
58
|
+
let hiddenInternalsCount = 0;
|
|
59
|
+
let hiddenModulesCount = 0;
|
|
60
|
+
|
|
61
|
+
for (const frame of stackFrames) {
|
|
62
|
+
if (frame.isNodeInternal && !config.showNodeInternals) {
|
|
63
|
+
hiddenInternalsCount++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (frame.isNodeModule && !config.showNodeModules) {
|
|
68
|
+
hiddenModulesCount++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const fileStr = frame.file
|
|
73
|
+
? `${chalk.cyan(frame.file)}:${chalk.yellow(frame.lineNumber)}:${chalk.yellow(frame.column)}`
|
|
74
|
+
: chalk.gray('<unknown>');
|
|
75
|
+
|
|
76
|
+
output += `→ ${fileStr}\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (hiddenModulesCount > 0 || hiddenInternalsCount > 0) {
|
|
80
|
+
const hiddenMessages = [];
|
|
81
|
+
if (hiddenModulesCount > 0) hiddenMessages.push(`${hiddenModulesCount} node_modules`);
|
|
82
|
+
if (hiddenInternalsCount > 0) hiddenMessages.push(`${hiddenInternalsCount} node internals`);
|
|
83
|
+
output += chalk.dim(`(${hiddenMessages.join(' and ')} hidden)\n`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return output;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function formatWarning(message: string, error?: Error): string {
|
|
90
|
+
const config = getConfig();
|
|
91
|
+
const actualError = error || new Error(message);
|
|
92
|
+
const stackFrames = parseStack(actualError);
|
|
93
|
+
|
|
94
|
+
const userFrame = stackFrames.find(frame => !frame.isNodeInternal && !frame.isNodeModule);
|
|
95
|
+
|
|
96
|
+
const typeLabel = chalk.bgYellow.black(` WARNING `);
|
|
97
|
+
let output = `\n${typeLabel} ${chalk.yellow(message)}\n\n`;
|
|
98
|
+
|
|
99
|
+
if (userFrame && userFrame.file) {
|
|
100
|
+
output += `${chalk.bold('📍 Location:')}\n`;
|
|
101
|
+
output += `${chalk.cyan(userFrame.file)}:${chalk.yellow(userFrame.lineNumber)}:${chalk.yellow(userFrame.column)} ${chalk.gray('← YOUR CODE')}\n\n`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const suggestions = config.suggestionsEnabled ? getSuggestions(actualError) : [];
|
|
105
|
+
if (suggestions.length > 0) {
|
|
106
|
+
output += `${chalk.bold('💡 Suggestions:')}\n`;
|
|
107
|
+
for (const suggestion of suggestions) {
|
|
108
|
+
output += `• ${chalk.green(suggestion.message)}\n`;
|
|
109
|
+
if (suggestion.description) {
|
|
110
|
+
output += ` ${chalk.dim(suggestion.description)}\n`;
|
|
111
|
+
}
|
|
112
|
+
if (suggestion.fix) {
|
|
113
|
+
output += ` ${chalk.blue('Fix: ' + suggestion.fix)}\n`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
output += `\n`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return output;
|
|
120
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a 5-line code snippet from a source file around the given line number.
|
|
6
|
+
* Node.js ONLY.
|
|
7
|
+
*/
|
|
8
|
+
export function getCodeSnapshot(filePath: string, lineNumber: number): string {
|
|
9
|
+
try {
|
|
10
|
+
if (!fs.existsSync(filePath)) return '';
|
|
11
|
+
|
|
12
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
13
|
+
const lines = content.split('\n');
|
|
14
|
+
|
|
15
|
+
const start = Math.max(0, lineNumber - 2);
|
|
16
|
+
const end = Math.min(lines.length - 1, lineNumber + 1);
|
|
17
|
+
|
|
18
|
+
let output = '\n';
|
|
19
|
+
for (let i = start; i <= end; i++) {
|
|
20
|
+
const line = lines[i];
|
|
21
|
+
const displayLineNumber = i + 1;
|
|
22
|
+
const isTarget = displayLineNumber === lineNumber;
|
|
23
|
+
|
|
24
|
+
const prefix = isTarget ? chalk.red(' > ') : ' ';
|
|
25
|
+
const lineNumStr = chalk.gray(displayLineNumber.toString().padStart(3, ' ') + ' | ');
|
|
26
|
+
|
|
27
|
+
output += `${prefix}${lineNumStr}${isTarget ? chalk.white(line) : chalk.dim(line)}\n`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return output;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { formatError, formatWarning } from './formatter/index.js';
|
|
2
|
+
export { formatErrorBrowser, formatWarningBrowser } from './formatter/browser.js';
|
|
3
|
+
export { setupConfig } from './config.js';
|
|
4
|
+
export { registerRule } from './suggestions/index.js';
|
|
5
|
+
export { parseStack } from './parser/index.js';
|
|
6
|
+
export { errorFormatter } from './middleware/index.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getConfig } from '../config.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sends the error details to a Slack or Discord webhook.
|
|
5
|
+
*/
|
|
6
|
+
export async function sendWebhookNotification(error: Error) {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
if (!config.webhookUrl) return;
|
|
9
|
+
|
|
10
|
+
const payload = {
|
|
11
|
+
text: `🔥 *IntellError Alert*\n*Error:* ${error.constructor.name}\n*Message:* ${error.message}\n\n*Stack Trace Overview:*\n\`\`\`\n${error.stack?.split('\n').slice(0, 5).join('\n')}\n\`\`\``
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await fetch(config.webhookUrl, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
body: JSON.stringify(payload),
|
|
18
|
+
headers: { 'Content-Type': 'application/json' }
|
|
19
|
+
});
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// Silently fail to avoid crashing the error handler itself
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { formatError } from '../formatter/index.js';
|
|
2
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
3
|
+
|
|
4
|
+
export function errorFormatter() {
|
|
5
|
+
return (err: any, _req: Request, res: Response, _next: NextFunction) => {
|
|
6
|
+
const formatted = formatError(err);
|
|
7
|
+
|
|
8
|
+
// Log to console with nice formatting
|
|
9
|
+
console.error(formatted);
|
|
10
|
+
|
|
11
|
+
// If headers not sent, send a standard error response
|
|
12
|
+
if (!res.headersSent) {
|
|
13
|
+
res.status(err.status || 500).json({
|
|
14
|
+
error: {
|
|
15
|
+
name: err.name,
|
|
16
|
+
message: err.message,
|
|
17
|
+
// We don't send the full formatted error to the client for security,
|
|
18
|
+
// but we provide the basic info.
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as stackTraceParser from 'stacktrace-parser';
|
|
2
|
+
|
|
3
|
+
export interface ParsedStackFrame {
|
|
4
|
+
file: string | null;
|
|
5
|
+
methodName: string;
|
|
6
|
+
lineNumber: number | null;
|
|
7
|
+
column: number | null;
|
|
8
|
+
isNodeInternal: boolean;
|
|
9
|
+
isNodeModule: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseStack(error: Error): ParsedStackFrame[] {
|
|
13
|
+
if (!error.stack) return [];
|
|
14
|
+
|
|
15
|
+
const parsed = stackTraceParser.parse(error.stack);
|
|
16
|
+
|
|
17
|
+
return parsed.map(frame => {
|
|
18
|
+
const file = frame.file || '';
|
|
19
|
+
|
|
20
|
+
// Check if it's a built-in node module or internal
|
|
21
|
+
// e.g. node:internal/... or just internal/... or events.js
|
|
22
|
+
const isNodeInternal =
|
|
23
|
+
file.startsWith('node:') ||
|
|
24
|
+
file.startsWith('internal/') ||
|
|
25
|
+
!file.includes('/') && !file.includes('\\'); // typically a core module if no path separators
|
|
26
|
+
|
|
27
|
+
const isNodeModule = file.includes('node_modules');
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
file: frame.file,
|
|
31
|
+
methodName: frame.methodName || '<unknown>',
|
|
32
|
+
lineNumber: frame.lineNumber,
|
|
33
|
+
column: frame.column,
|
|
34
|
+
isNodeInternal: Boolean(isNodeInternal),
|
|
35
|
+
isNodeModule: Boolean(isNodeModule)
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { formatError, formatWarning } from './formatter/index.js';
|
|
2
|
+
import { formatErrorBrowser, formatWarningBrowser } from './formatter/browser.js';
|
|
3
|
+
import { getConfig } from './config.js';
|
|
4
|
+
import { sendWebhookNotification } from './integrations/webhook.js';
|
|
5
|
+
|
|
6
|
+
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
7
|
+
|
|
8
|
+
if (isBrowser) {
|
|
9
|
+
// --- BROWSER REGISTRATION ---
|
|
10
|
+
window.addEventListener('error', (event) => {
|
|
11
|
+
// We only want to handle the error if it contains an Error object
|
|
12
|
+
if (event.error) {
|
|
13
|
+
console.error('%c 🛑 UNHANDLED RUNTIME ERROR:', 'background: #ff4d4f; color: white; font-weight: bold; padding: 2px 5px;');
|
|
14
|
+
console.log(...formatErrorBrowser(event.error));
|
|
15
|
+
sendWebhookNotification(event.error);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
20
|
+
console.error('%c 🛑 UNHANDLED PROMISE REJECTION:', 'background: #ff4d4f; color: white; font-weight: bold; padding: 2px 5px;');
|
|
21
|
+
const reason = event.reason;
|
|
22
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
23
|
+
console.log(...formatErrorBrowser(error));
|
|
24
|
+
sendWebhookNotification(error);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log('%c ✅ IntellError Browser listener active.', 'color: #52c41a; font-weight: bold;');
|
|
28
|
+
} else if (typeof process !== 'undefined' && process.on) {
|
|
29
|
+
// --- NODE.JS REGISTRATION ---
|
|
30
|
+
process.on('uncaughtException', (err) => {
|
|
31
|
+
console.error('\n 🔥 UNCAUGHT EXCEPTION DETECTED:');
|
|
32
|
+
console.error(formatError(err));
|
|
33
|
+
sendWebhookNotification(err).finally(() => {
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
process.on('unhandledRejection', (reason) => {
|
|
39
|
+
console.error('\n 🔥 UNHANDLED REJECTION DETECTED:');
|
|
40
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
41
|
+
console.error(formatError(error));
|
|
42
|
+
sendWebhookNotification(error);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
console.log('✅ IntellError Node.js listener active.');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- OPTIONAL WARNING INTERCEPTION ---
|
|
49
|
+
const originalWarn = console.warn;
|
|
50
|
+
console.warn = (...args: any[]) => {
|
|
51
|
+
const config = getConfig();
|
|
52
|
+
|
|
53
|
+
if (config.interceptWarnings && args.length > 0) {
|
|
54
|
+
const message = args[0];
|
|
55
|
+
if (typeof message === 'string') {
|
|
56
|
+
const isBrowser = typeof window !== 'undefined';
|
|
57
|
+
if (isBrowser) {
|
|
58
|
+
originalWarn(...formatWarningBrowser(message));
|
|
59
|
+
} else {
|
|
60
|
+
originalWarn(formatWarning(message));
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
originalWarn(...args);
|
|
67
|
+
};
|
|
68
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { rules, SuggesionRule } from './rules.js';
|
|
2
|
+
|
|
3
|
+
export function getSuggestions(error: Error): SuggesionRule[] {
|
|
4
|
+
return rules.filter(rule => rule.match(error));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registers a new custom suggestion rule.
|
|
9
|
+
*/
|
|
10
|
+
export function registerRule(rule: SuggesionRule) {
|
|
11
|
+
rules.push(rule);
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates search URLs for a given error based on its message and type.
|
|
3
|
+
*/
|
|
4
|
+
export function getSearchLinks(error: Error): string[] {
|
|
5
|
+
const query = encodeURIComponent(`${error.constructor.name}: ${error.message}`);
|
|
6
|
+
|
|
7
|
+
return [
|
|
8
|
+
`https://www.google.com/search?q=${query}`,
|
|
9
|
+
`https://stackoverflow.com/search?q=${query}`,
|
|
10
|
+
`https://github.com/issues?q=${query}`
|
|
11
|
+
];
|
|
12
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
export interface SuggesionRule {
|
|
2
|
+
match: (error: Error) => boolean;
|
|
3
|
+
message: string;
|
|
4
|
+
fix?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const rules: SuggesionRule[] = [
|
|
9
|
+
// --- TYPE ERRORS & NULL/UNDEFINED ---
|
|
10
|
+
{
|
|
11
|
+
match: (err) => err.message.includes("Cannot read properties of undefined (reading '"),
|
|
12
|
+
message: "Accessing property on undefined object.",
|
|
13
|
+
fix: "Use optional chaining like 'obj?.prop' or ensure the object is initialized.",
|
|
14
|
+
description: "You're trying to access a property on a variable that is currently undefined."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
match: (err) => err.message.includes("Cannot read properties of null (reading '"),
|
|
18
|
+
message: "Accessing property on a null object.",
|
|
19
|
+
fix: "Check why the object is null before accessing its properties, or use 'obj?.prop'.",
|
|
20
|
+
description: "You're trying to read a property from a variable that is null."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
match: (err) => err.message.includes("is not defined"),
|
|
24
|
+
message: "Reference to an undeclared variable.",
|
|
25
|
+
fix: "Ensure the variable is declared with 'let', 'const', or 'var' and is within its correct scope.",
|
|
26
|
+
description: "You're using a variable that hasn't been defined yet."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
match: (err) => err.message.includes("Cannot destructure property"),
|
|
30
|
+
message: "Failed to destructure object.",
|
|
31
|
+
fix: "Provide a default object like 'const { x } = data || {}' to avoid destructuring null/undefined.",
|
|
32
|
+
description: "You're trying to extract properties from a variable that turned out to be empty."
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// --- FUNCTIONS & ARRAYS ---
|
|
36
|
+
{
|
|
37
|
+
match: (err) => err.message.includes(".map is not a function"),
|
|
38
|
+
message: "Array method '.map()' called on non-array value.",
|
|
39
|
+
fix: "Verify that the variable is an array. If it's from an API, ensure the result is '[]' if empty.",
|
|
40
|
+
description: "Commonly happens when an API returns an object or null instead of the expected list."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
match: (err) => err.message.includes(".forEach is not a function"),
|
|
44
|
+
message: "Array method '.forEach()' called on non-array value.",
|
|
45
|
+
fix: "Check if you're actually getting an array. Use 'Array.isArray(obj)' to verify.",
|
|
46
|
+
description: "You attempted to iterate over something that isn't a list."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
match: (err) => err.message.includes("is not a function"),
|
|
50
|
+
message: "Called a non-function value.",
|
|
51
|
+
fix: "Double-check if the property you're calling is actually a function and not undefined or a string.",
|
|
52
|
+
description: "You tried to execute something as a function that isn't one."
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// --- SYNTAX & DATA ---
|
|
56
|
+
{
|
|
57
|
+
match: (err) => err instanceof SyntaxError && err.message.includes("JSON"),
|
|
58
|
+
message: "Invalid JSON format.",
|
|
59
|
+
fix: "Verify the source string. Use a JSON validator to find trailing commas or incorrect quotes.",
|
|
60
|
+
description: "The JSON you are parsing contains syntax errors."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
match: (err) => err instanceof RangeError && err.message.includes("Maximum call stack size exceeded"),
|
|
64
|
+
message: "Infinite recursion detected.",
|
|
65
|
+
fix: "Look for functions that call themselves without a base case (e.g., missing exit condition).",
|
|
66
|
+
description: "Your code likely has an infinite loop or too many nested function calls."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
match: (err) => err.message.includes("Invalid URL"),
|
|
70
|
+
message: "Failed to construct URL.",
|
|
71
|
+
fix: "Check that the input string is a valid absolute URL (e.g., starts with http:// or https://).",
|
|
72
|
+
description: "The URL constructor was given a malformed or relative string."
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// --- NETWORK & API (Browser) ---
|
|
76
|
+
{
|
|
77
|
+
match: (err) => err.message.includes("Failed to fetch"),
|
|
78
|
+
message: "Network request failed.",
|
|
79
|
+
fix: "Check your internet connection or verify if the API server is down or the URL is wrong.",
|
|
80
|
+
description: "The browser could not reach the server. This is often a DNS or connectivity issue."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
match: (err) => err.message.includes("cors") && err.message.includes("access-control-allow-origin"),
|
|
84
|
+
message: "CORS policy violation.",
|
|
85
|
+
fix: "The API server needs to allow your origin. If you control the API, use the 'cors' middleware.",
|
|
86
|
+
description: "The browser's security policy blocked the cross-origin request."
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// --- NODE.JS CORE (Server) ---
|
|
90
|
+
{
|
|
91
|
+
match: (err: any) => err.code === 'ENOENT',
|
|
92
|
+
message: "File or directory not found.",
|
|
93
|
+
fix: "Verify the file path. Use 'path.join(__dirname, ...)' to ensure you have an absolute path.",
|
|
94
|
+
description: "Node.js could not find the file or folder you're looking for at that location."
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
match: (err: any) => err.code === 'EADDRINUSE',
|
|
98
|
+
message: "Port already in use.",
|
|
99
|
+
fix: "Close the process currently using that port (e.g., kill another terminal) or change the PORT.",
|
|
100
|
+
description: "Another application is already running on the port you're trying to use."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
match: (err: any) => err.code === 'ECONNREFUSED',
|
|
104
|
+
message: "Network connection refused.",
|
|
105
|
+
fix: "The target server is not listening. Ensure the API or Database is actually running.",
|
|
106
|
+
description: "The connection attempt was rejected by the target machine."
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
match: (err: any) => err.code === 'EACCES',
|
|
110
|
+
message: "Permission denied.",
|
|
111
|
+
fix: "Run the command with sudo/administrator privileges or check filesystem permissions.",
|
|
112
|
+
description: "Your application doesn't have the internal rights to read/write to this location."
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// --- REACT-SPECIFIC ---
|
|
116
|
+
{
|
|
117
|
+
match: (err) => err.message.includes("Hooks can only be called inside of the body of a function component"),
|
|
118
|
+
message: "Invalid Hook Call.",
|
|
119
|
+
fix: "Ensure you aren't calling hooks inside loops, conditions, or nested functions.",
|
|
120
|
+
description: "React hooks must be called at the top level of a Functional Component."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
match: (err) => err.message.includes("Too many re-renders"),
|
|
124
|
+
message: "Infinite re-render loop.",
|
|
125
|
+
fix: "You're likely updating state inside the render body. Move the update into a 'useEffect'.",
|
|
126
|
+
description: "React prevents infinite loops when an update is triggered every single time a component renders."
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// --- INTERACTIVE STYLING & DOM ---
|
|
130
|
+
{
|
|
131
|
+
match: (err) => err.message.includes("Cannot read properties of null (reading 'style')"),
|
|
132
|
+
message: "Attempted to style a missing element.",
|
|
133
|
+
fix: "The element you're trying to style wasn't found in the DOM. Verify your CSS selector (class or ID) match the HTML exactly.",
|
|
134
|
+
description: "Your query (querySelector or getElementById) returned null."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
match: (err) => err.message.includes("is not a valid selector"),
|
|
138
|
+
message: "Invalid CSS Selector.",
|
|
139
|
+
fix: "Ensure your selector follows CSS rules (e.g., '.class-name', '#id-name', 'div > p'). Check for missing dots/hashes.",
|
|
140
|
+
description: "The string provided to a DOM selection method is not syntactically correct."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
match: (err) => err.message.includes("CSSStyleDeclaration") && err.message.includes("read-only"),
|
|
144
|
+
message: "Attempted to modify a read-only style property.",
|
|
145
|
+
fix: "Some browser styles (like getComputedStyle results) are read-only. Use 'element.style' instead for manual changes.",
|
|
146
|
+
description: "You're trying to write to a value that the browser keeps locked."
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
match: (err) => err.message.includes("Failed to execute 'animate' on 'Element'"),
|
|
150
|
+
message: "Invalid Web Animation.",
|
|
151
|
+
fix: "Check your keyframes and timing options. Ensure properties like 'transform' use standard syntax.",
|
|
152
|
+
description: "The Web Animations API rejected your parameters."
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
match: (err) => err.message.includes("Failed to execute 'add' on 'DOMTokenList'") || err.message.includes("token contains HTML space characters"),
|
|
156
|
+
message: "Invalid CSS Class Name.",
|
|
157
|
+
fix: "CSS classes cannot contain spaces. Use 'classList.add('class1', 'class2')' with separate arguments instead.",
|
|
158
|
+
description: "You tried to add an invalid token (like a sentence) into an element's className."
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// --- WARNINGS & BEST PRACTICES ---
|
|
162
|
+
{
|
|
163
|
+
match: (err) => err.message.includes("Each child in a list should have a unique \"key\" prop"),
|
|
164
|
+
message: "Missing React Key Prop.",
|
|
165
|
+
fix: "Add a 'key' prop to the top-level element inside your .map() (e.g., <li key={item.id}>).",
|
|
166
|
+
description: "React needs keys to efficiently track and update items in a list."
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
match: (err) => err.message.includes("Changing an uncontrolled input to be controlled"),
|
|
170
|
+
message: "Uncontrolled to Controlled Input switch.",
|
|
171
|
+
fix: "Ensure the 'value' prop is never undefined. Initialize your state with an empty string: useState('') instead of useState().",
|
|
172
|
+
description: "React expects an input to stay either controlled or uncontrolled for its entire lifecycle."
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
match: (err) => err.message.includes("ExperimentalWarning"),
|
|
176
|
+
message: "Using Experimental Node.js Feature.",
|
|
177
|
+
fix: "This feature is functional but the API might change in future Node versions. High-risk for production.",
|
|
178
|
+
description: "You're using a feature that is still under active development by the Node.js team."
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// --- ADVANCED JAVASCRIPT & ENGINES ---
|
|
182
|
+
{
|
|
183
|
+
match: (err) => err.message.includes("Cannot mix BigInt and other types"),
|
|
184
|
+
message: "BigInt Type Mismatch.",
|
|
185
|
+
fix: "Explicitly convert other types to BigInt using 'BigInt(value)' before performing arithmetic operations.",
|
|
186
|
+
description: "JavaScript does not allow implicit coercion between BigInt and standard Number types."
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
match: (err) => err.message.includes("is not iterable"),
|
|
190
|
+
message: "Iteration Failure.",
|
|
191
|
+
fix: "Check if the variable you're trying to spread or loop over (...) is null or an object instead of an Array/Map/Set.",
|
|
192
|
+
description: "You attempted to use a spread operator or for...of loop on a non-iterable value."
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
match: (err) => err instanceof SyntaxError && err.message.includes("Invalid regular expression"),
|
|
196
|
+
message: "Malformed Regex.",
|
|
197
|
+
fix: "Check for unclosed parentheses, invalid escape characters, or incorrect flags in your RegExp.",
|
|
198
|
+
description: "The JavaScript engine could not compile your regular expression pattern."
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
// --- BROWSER DOM & STORAGE ---
|
|
202
|
+
{
|
|
203
|
+
match: (err) => err.message.includes("QuotaExceededError") || err.message.includes("exceeded the quota"),
|
|
204
|
+
message: "Storage Quota Exceeded.",
|
|
205
|
+
fix: "Clear some space in localStorage/IndexedDB or handle the error by clearing old cached data.",
|
|
206
|
+
description: "You've reached the maximum storage limit allowed by the browser for this origin."
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
match: (err) => err.name === 'NotAllowedError' && err.message.includes("play() failed"),
|
|
210
|
+
message: "Autoplay Blocked.",
|
|
211
|
+
fix: "Wait for a user interaction (click/tap) before calling .play() on audio or video elements.",
|
|
212
|
+
description: "Modern browsers block media from playing automatically without a user gesture."
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
// --- NODE/EXPRESS SPECIFICS ---
|
|
216
|
+
{
|
|
217
|
+
match: (err) => err.message.includes("Cannot set headers after they are sent to the client"),
|
|
218
|
+
message: "Express Header Conflict.",
|
|
219
|
+
fix: "Check for multiple 'res.send()', 'res.json()', or 'res.end()' calls in a single route handler. Use 'return res.json(...)' to stop execution early.",
|
|
220
|
+
description: "You're trying to send a response to the client after a response has already been finished."
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
match: (err: any) => err.code === 'EADDRNOTAVAIL',
|
|
224
|
+
message: "Address Not Available.",
|
|
225
|
+
fix: "Check your host/IP configuration. You might be trying to bind to an IP address that doesn't exist on this machine.",
|
|
226
|
+
description: "The requested network address could not be assigned."
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// --- REACT REFS & STATE ---
|
|
230
|
+
{
|
|
231
|
+
match: (err) => err.message.includes("Cannot assign to read only property 'current'"),
|
|
232
|
+
message: "Immutable Ref Modification.",
|
|
233
|
+
fix: "Ensure you're not trying to override a ref's .current property directly if it was provided by a parent or a third-party library.",
|
|
234
|
+
description: "React protects certain ref objects from being overwritten to maintain internal consistency."
|
|
235
|
+
}
|
|
236
|
+
];
|
package/test.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { formatError } from './src/index.js';
|
|
2
|
+
|
|
3
|
+
function triggerError() {
|
|
4
|
+
const obj: any = {};
|
|
5
|
+
console.log(obj.name.first); // TypeError: Cannot read properties of undefined
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function intermediateFunction() {
|
|
9
|
+
triggerError();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
intermediateFunction();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.log(formatError(error));
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { setupConfig, getConfig } from '../src/config.js';
|
|
3
|
+
|
|
4
|
+
describe('Config System', () => {
|
|
5
|
+
it('should have default values', () => {
|
|
6
|
+
const config = getConfig();
|
|
7
|
+
expect(config.showNodeModules).toBe(false);
|
|
8
|
+
expect(config.showNodeInternals).toBe(false);
|
|
9
|
+
expect(config.suggestionsEnabled).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should update specific values', () => {
|
|
13
|
+
setupConfig({ showNodeModules: true });
|
|
14
|
+
const config = getConfig();
|
|
15
|
+
expect(config.showNodeModules).toBe(true);
|
|
16
|
+
expect(config.showNodeInternals).toBe(false); // remains same
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should preserve existing values', () => {
|
|
20
|
+
setupConfig({ suggestionsEnabled: false });
|
|
21
|
+
const config = getConfig();
|
|
22
|
+
expect(config.showNodeModules).toBe(true); // From previous test
|
|
23
|
+
expect(config.suggestionsEnabled).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getSuggestions } from '../src/suggestions/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Intelligent Suggestions Rules', () => {
|
|
5
|
+
it('should match TypeError for undefined properties', () => {
|
|
6
|
+
const error = new TypeError("Cannot read properties of undefined (reading 'id')");
|
|
7
|
+
const suggestions = getSuggestions(error);
|
|
8
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
9
|
+
expect(suggestions[0].message).toContain('Accessing property on undefined object');
|
|
10
|
+
expect(suggestions[0].fix).toContain('optional chaining');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should match EADDRINUSE error', () => {
|
|
14
|
+
const error: any = new Error("bind EADDRINUSE 0.0.0.0:3000");
|
|
15
|
+
error.code = 'EADDRINUSE';
|
|
16
|
+
const suggestions = getSuggestions(error);
|
|
17
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
18
|
+
expect(suggestions[0].message).toContain('Port already in use');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should match React Hooks error', () => {
|
|
22
|
+
const error = new Error("Hooks can only be called inside of the body of a function component");
|
|
23
|
+
const suggestions = getSuggestions(error);
|
|
24
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
25
|
+
expect(suggestions[0].message).toContain('Invalid Hook Call');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return no suggestions for unknown errors', () => {
|
|
29
|
+
const error = new Error("Random unknown error message");
|
|
30
|
+
const suggestions = getSuggestions(error);
|
|
31
|
+
expect(suggestions.length).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
});
|