• 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

59.05
/src/errors/errors.ts
1
import { baseGenerator, generate } from 'astring';
2
import type es from 'estree';
3

4
import { UNKNOWN_LOCATION } from '../constants';
5
import type { Node, Value } from '../types';
6
import { stringify } from '../utils/stringify';
7
import { getSourceVariableDeclaration } from '../utils/ast/helpers';
8
import { RuntimeSourceError } from './base';
9

10
//Wrap build-in function error in SourceError
11
export class BuiltInFunctionError extends RuntimeSourceError<undefined> {
NEW
12
  constructor(private readonly explanation: string) {
×
13
    super(undefined);
×
14
  }
15

16
  public override explain() {
NEW
17
    return this.explanation;
×
18
  }
19

20
  public override elaborate() {
21
    return this.explain();
×
22
  }
23
}
24

25
export class InterruptedError extends RuntimeSourceError<Node> {
26
  public override explain() {
UNCOV
27
    return 'Execution aborted by user.';
×
28
  }
29

30
  public override elaborate() {
31
    return 'TODO';
×
32
  }
33
}
34

35
/**
36
 * General Error type to represent RuntimeErrors that aren't thrown by
37
 * Source
38
 */
39
export class ExceptionError extends RuntimeSourceError<undefined> {
40
  private readonly _location: es.SourceLocation;
41

42
  constructor(
43
    public readonly error: Error,
32✔
44
    location?: es.SourceLocation | null,
45
  ) {
46
    super(undefined);
32✔
47
    this._location = location ?? UNKNOWN_LOCATION;
32✔
48
  }
49

50
  public override get location() {
51
    return this._location;
45✔
52
  }
53

54
  public override explain() {
55
    return this.error.toString();
12✔
56
  }
57

58
  public override elaborate() {
59
    return 'TODO';
×
60
  }
61
}
62

63
export class MaximumStackLimitExceededError extends RuntimeSourceError<Node> {
64
  public static MAX_CALLS_TO_SHOW = 3;
72✔
65

66
  private customGenerator = {
4✔
67
    ...baseGenerator,
68
    CallExpression(node: any, state: any) {
69
      state.write(generate(node.callee));
12✔
70
      state.write('(');
12✔
71
      const argsRepr = node.arguments.map((arg: any) => stringify(arg.value));
15✔
72
      state.write(argsRepr.join(', '));
12✔
73
      state.write(')');
12✔
74
    },
75
  };
76

77
  constructor(
78
    node: Node,
79
    private readonly calls: es.CallExpression[],
4✔
80
  ) {
81
    super(node);
4✔
82
  }
83

84
  public override explain() {
85
    const repr = (call: es.CallExpression) => generate(call, { generator: this.customGenerator });
12✔
86
    return (
4✔
87
      'Maximum call stack size exceeded\n  ' + this.calls.map(call => repr(call) + '..').join('  ')
12✔
88
    );
89
  }
90

91
  public override elaborate() {
92
    return 'TODO';
×
93
  }
94
}
95

96
/**
97
 * Error thrown when a value that isn't a function is called. Usually thrown by `callIfRightFuncAndArgs`.
98
 */
99
export class CallingNonFunctionValueError extends RuntimeSourceError<es.CallExpression> {
100
  constructor(
101
    /**
102
     * Value being called
103
     */
104
    private readonly callee: Value,
24✔
105

106
    /**
107
     * The {@link es.CallExpression| Call Expression} that is responsible for calling the
108
     * non-function value
109
     */
110
    node: es.CallExpression,
111
  ) {
112
    super(node);
24✔
113
  }
114

115
  public override explain() {
116
    return `Calling non-function value ${stringify(this.callee)}.`;
24✔
117
  }
118

119
  public override elaborate() {
120
    const calleeVal = this.callee;
16✔
121
    const calleeStr = stringify(calleeVal);
16✔
122
    const callArgs = this.node.arguments;
16✔
123

124
    const argStr = callArgs.map(generate).join(', ');
16✔
125

126
    const elabStr = `Because ${calleeStr} is not a function, you cannot run ${calleeStr}(${argStr}).`;
16✔
127

128
    if (Number.isFinite(calleeVal)) {
16✔
129
      const multStr = `If you were planning to perform multiplication by ${calleeStr}, you need to use the * operator.`;
3✔
130
      return `${elabStr} ${multStr}`;
3✔
131
    } else {
132
      return elabStr;
13✔
133
    }
134
  }
135
}
136

137
/**
138
 * Error thrown when an attempt to access an undefined variable is made
139
 */
140
export class UndefinedVariableError extends RuntimeSourceError<Node> {
141
  constructor(
142
    public readonly varname: string,
17✔
143
    node: Node,
144
  ) {
145
    super(node);
17✔
146
  }
147

148
  public override explain() {
149
    return `Name ${this.varname} not declared.`;
15✔
150
  }
151

152
  public override elaborate() {
153
    return `Before you can read the value of ${this.varname}, you need to declare it as a variable or a constant. You can do this using the let or const keywords.`;
3✔
154
  }
155
}
156

157
/**
158
 * Error thrown when a variable is accessed in the temporal dead zone
159
 */
160
export class UnassignedVariableError extends RuntimeSourceError<Node> {
161
  constructor(
162
    public readonly varname: string,
4✔
163
    node: Node,
164
  ) {
165
    super(node);
4✔
166
  }
167

168
  public override explain() {
169
    return `Name ${this.varname} declared later in current scope but not yet assigned`;
4✔
170
  }
171

172
  public override elaborate() {
NEW
173
    return `If you're trying to access the value of ${this.varname} from an outer scope, please rename the inner ${this.varname}. An easy way to avoid this issue in future would be to avoid declaring any variables or constants with the name ${this.varname} in the same scope.`;
×
174
  }
175
}
176

177
/**
178
 * Error thrown when a function is called with the incorrect number of arguments. Usually thrown by
179
 * `callIfRightFuncAndArgs`
180
 */
181
export class InvalidNumberOfArgumentsError extends RuntimeSourceError<es.CallExpression> {
182
  private readonly calleeStr: string;
183

184
  constructor(
185
    node: es.CallExpression,
186
    private readonly expected: number,
27✔
187
    private readonly got: number,
27✔
188
    private readonly funcName?: string,
27✔
189
    private readonly hasVarArgs = false,
27✔
190
  ) {
191
    super(node);
27✔
192
    this.calleeStr = generate(node.callee);
27✔
193
  }
194

195
  public override explain() {
196
    const funcStr = this.funcName !== undefined ? `${this.funcName}: ` : '';
30✔
197
    return `${funcStr}Expected ${this.expected} ${this.hasVarArgs ? 'or more ' : ''}arguments, but got ${
30✔
198
      this.got
199
    }.`;
200
  }
201

202
  public override elaborate() {
203
    const calleeStr = this.calleeStr;
6✔
204
    const pluralS = this.expected === 1 ? '' : 's';
6✔
205

206
    return `Try calling function ${calleeStr} again, but with ${this.expected} argument${pluralS} instead. Remember that arguments are separated by a ',' (comma).`;
6✔
207
  }
208
}
209

210
export class VariableRedeclarationError extends RuntimeSourceError<
211
  es.Declaration | es.ImportSpecifier | es.ImportDefaultSpecifier | es.ImportNamespaceSpecifier
212
> {
213
  constructor(
214
    node:
215
      | es.Declaration
216
      | es.ImportSpecifier
217
      | es.ImportDefaultSpecifier
218
      | es.ImportNamespaceSpecifier,
NEW
219
    private readonly varname: string,
×
NEW
220
    private readonly writable: boolean,
×
221
  ) {
222
    super(node);
×
223
  }
224

225
  public override explain() {
NEW
226
    return `Redeclaring name ${this.varname}.`;
×
227
  }
228

229
  public override elaborate() {
NEW
230
    if (this.writable) {
×
NEW
231
      const elabStr = `Since ${this.varname} has already been declared, you can assign a value to it without re-declaring.`;
×
232

233
      let initStr = '';
×
NEW
234
      switch (this.node.type) {
×
235
        case 'FunctionDeclaration': {
NEW
236
          initStr = '(' + this.node.params.map(generate).join(',') + ') => {...';
×
NEW
237
          break;
×
238
        }
239
        case 'VariableDeclaration': {
NEW
240
          const { init } = getSourceVariableDeclaration(this.node);
×
NEW
241
          initStr = generate(init);
×
NEW
242
          break;
×
243
        }
244
      }
245

NEW
246
      return `${elabStr} As such, you can just do\n\n\t${this.varname} = ${initStr};\n`;
×
247
    } else {
NEW
248
      return `You will need to declare another variable, as ${this.varname} is read-only.`;
×
249
    }
250
  }
251
}
252

253
export class ConstAssignmentError extends RuntimeSourceError<Node> {
254
  constructor(
255
    node: Node,
256
    private readonly varname: string,
5✔
257
  ) {
258
    super(node);
5✔
259
  }
260

261
  public override explain() {
262
    return `Cannot assign new value to constant ${this.varname}.`;
5✔
263
  }
264

265
  public override elaborate() {
266
    return `As ${this.varname} was declared as a constant, its value cannot be changed. You will have to declare a new variable.`;
2✔
267
  }
268
}
269

270
export class GetPropertyError extends RuntimeSourceError<Node> {
271
  constructor(
272
    node: Node,
NEW
273
    private readonly obj: Value,
×
NEW
274
    private readonly prop: string,
×
275
  ) {
276
    super(node);
×
277
  }
278

279
  public override explain() {
280
    return `Cannot read property ${this.prop} of ${stringify(this.obj)}.`;
×
281
  }
282

283
  public override elaborate() {
284
    return 'TODO';
×
285
  }
286
}
287

288
export class GetInheritedPropertyError extends RuntimeSourceError<Node> {
289
  constructor(
290
    node: Node,
NEW
291
    private readonly obj: Value,
×
NEW
292
    private readonly prop: string,
×
293
  ) {
294
    super(node);
×
295
  }
296

297
  public override explain() {
298
    return `Cannot read inherited property ${this.prop} of ${stringify(this.obj)}.`;
×
299
  }
300

301
  public override elaborate() {
302
    return 'TODO';
×
303
  }
304
}
305

306
export class SetPropertyError extends RuntimeSourceError<Node> {
307
  constructor(
308
    node: Node,
NEW
309
    private readonly obj: Value,
×
NEW
310
    private readonly prop: string,
×
311
  ) {
312
    super(node);
×
313
  }
314

315
  public override explain() {
316
    return `Cannot assign property ${this.prop} of ${stringify(this.obj)}.`;
×
317
  }
318

319
  public override elaborate() {
320
    return 'TODO';
×
321
  }
322
}
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