• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / js-slang / 24834367427

23 Apr 2026 12:09PM UTC coverage: 78.541% (+0.2%) from 78.391%
24834367427

Pull #1893

github

web-flow
Merge ab101147d into 715603479
Pull Request #1893: Error Handling and Stringify Changes

3126 of 4197 branches covered (74.48%)

Branch coverage included in aggregate %.

801 of 975 new or added lines in 76 files covered. (82.15%)

20 existing lines in 11 files now uncovered.

7056 of 8767 relevant lines covered (80.48%)

173930.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.21
/src/runner/errors.ts
1
import { type NullableMappedPosition, type RawSourceMap, SourceMapConsumer } from 'source-map';
2

3
import { UNKNOWN_LOCATION } from '../constants';
4
import type { SourceError } from '../errors/base';
5
import { ConstAssignmentError, ExceptionError, UndefinedVariableError } from '../errors/errors';
6
import { locationDummyNode } from '../utils/ast/astCreator';
7

8
enum BrowserType {
55✔
9
  Chrome = 'Chrome',
55✔
10
  FireFox = 'FireFox',
55✔
11
  Unsupported = 'Unsupported',
55✔
12
}
13

14
interface EvalErrorLocator {
15
  regex: RegExp;
16
  browser: BrowserType;
17
}
18

19
const ChromeEvalErrorLocator = {
55✔
20
  regex: /eval at.+<anonymous>:(\d+):(\d+)/gm,
21
  browser: BrowserType.Chrome,
22
};
23

24
const FireFoxEvalErrorLocator = {
55✔
25
  regex: /eval:(\d+):(\d+)/gm,
26
  browser: BrowserType.FireFox,
27
};
28

29
const EVAL_LOCATORS: EvalErrorLocator[] = [ChromeEvalErrorLocator, FireFoxEvalErrorLocator];
55✔
30

31
const UNDEFINED_VARIABLE_MESSAGES: string[] = ['is not defined'];
55✔
32

33
// brute-forced from MDN website for phrasing of errors from different browsers
34
// FWIW node and chrome uses V8 so they'll have the same error messages
35
// unable to test on other engines
36
const ASSIGNMENT_TO_CONST_ERROR_MESSAGES: string[] = [
55✔
37
  'invalid assignment to const',
38
  'Assignment to constant variable',
39
  'Assignment to const',
40
  'Redeclaration of const',
41
];
42

43
function getBrowserType(): BrowserType {
44
  const userAgent: string = navigator.userAgent.toLowerCase();
14✔
45
  return userAgent.indexOf('chrome') > -1
14!
46
    ? BrowserType.Chrome
47
    : userAgent.indexOf('firefox') > -1
14!
48
      ? BrowserType.FireFox
49
      : BrowserType.Unsupported;
50
}
51

52
function extractErrorLocation(
53
  errorStack: string,
54
  lineOffset: number,
55
  errorLocator: EvalErrorLocator,
56
): { line: number; column: number } | undefined {
57
  const evalErrors = Array.from(errorStack.matchAll(errorLocator.regex));
28✔
58
  if (evalErrors.length) {
28✔
59
    const baseEvalError = evalErrors[0];
14✔
60
    const [lineNumStr, colNumStr] = baseEvalError.slice(1, 3);
14✔
61
    return { line: parseInt(lineNumStr) - lineOffset, column: parseInt(colNumStr) };
14✔
62
  }
63

64
  return undefined;
14✔
65
}
66

67
function getErrorLocation(
68
  error: Error,
69
  lineOffset: number = 0,
14✔
70
): { line: number; column: number } | undefined {
71
  const browser: BrowserType = getBrowserType();
14✔
72
  const errorLocator: EvalErrorLocator | undefined = EVAL_LOCATORS.find(
14✔
73
    locator => locator.browser === browser,
28✔
74
  );
75
  const errorStack: string | undefined = error.stack;
14✔
76

77
  if (errorStack && errorLocator) {
14!
78
    return extractErrorLocation(errorStack, lineOffset, errorLocator);
×
79
  } else if (errorStack) {
14!
80
    // if browser is unsupported try all supported locators until the first success
81
    return EVAL_LOCATORS.map(locator => extractErrorLocation(errorStack, lineOffset, locator)).find(
28✔
82
      x => x !== undefined,
14✔
83
    );
84
  }
85

86
  return undefined;
×
87
}
88

89
export async function getPositionWithSourceMap(
90
  line: number,
91
  column: number,
92
  sourceMap: RawSourceMap,
93
) {
94
  const originalPosition: NullableMappedPosition = await SourceMapConsumer.with(
14✔
95
    sourceMap,
96
    null,
97
    consumer => consumer.originalPositionFor({ line, column }),
14✔
98
  );
99

100
  return {
14✔
101
    line: originalPosition.line ?? -1, // use -1 in place of null
20✔
102
    column: originalPosition.column ?? -1,
20✔
103
    identifier: originalPosition.name ?? 'UNKNOWN',
21✔
104
    source: originalPosition.source ?? null,
20✔
105
  };
106
}
107

108
/**
109
 * Converts native errors to SourceError
110
 *
111
 * @param error
112
 * @param sourceMap
113
 * @returns
114
 */
115
export async function toSourceError(error: Error, sourceMap?: RawSourceMap): Promise<SourceError> {
116
  const errorLocation: { line: number; column: number } | undefined = getErrorLocation(error);
14✔
117
  if (!errorLocation) {
14!
118
    return new ExceptionError(error, UNKNOWN_LOCATION);
×
119
  }
120

121
  let { line, column } = errorLocation;
14✔
122
  let identifier: string = 'UNKNOWN';
14✔
123
  let source: string | null = null;
14✔
124

125
  if (sourceMap && !(line === -1 || column === -1)) {
14!
126
    ({ line, column, source, identifier } = await getPositionWithSourceMap(
14✔
127
      line,
128
      column,
129
      sourceMap,
130
    ));
131
  }
132

133
  const errorMessage: string = error.message;
14✔
134
  const errorMessageContains = (possibleMessages: string[]) =>
14✔
135
    possibleMessages.some(possibleMessage => errorMessage.includes(possibleMessage));
70✔
136

137
  if (errorMessageContains(ASSIGNMENT_TO_CONST_ERROR_MESSAGES)) {
14!
NEW
138
    return new ConstAssignmentError(locationDummyNode(line, column, source), identifier);
×
139
  } else if (errorMessageContains(UNDEFINED_VARIABLE_MESSAGES)) {
14✔
140
    return new UndefinedVariableError(identifier, locationDummyNode(line, column, source));
2✔
141
  } else {
142
    const location =
143
      line === -1 || column === -1
12✔
144
        ? UNKNOWN_LOCATION
145
        : {
146
            start: { line, column },
147
            end: { line: -1, column: -1 },
148
          };
149
    return new ExceptionError(error, location);
12✔
150
  }
151
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc