• 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

69.49
/src/modules/errors.ts
1
import type es from 'estree';
2

3
import { UNKNOWN_LOCATION } from '../constants';
4
import {
5
  ErrorSeverity,
6
  ErrorType,
7
  InternalRuntimeError,
8
  SourceErrorWithNode,
9
} from '../errors/base';
10
import type { Node } from '../types';
11
import type { Chapter } from '../langs';
12
import { getChapterName } from '../utils/misc';
13
import { nonAlphanumericCharEncoding } from './preprocessor/filePaths';
14
import type { ModuleDeclarationWithSource } from './moduleTypes';
15

16
/**
17
 * Error thrown at runtime when loading amodule throws an unhandled error.
18
 */
19
export class ModuleInternalError extends InternalRuntimeError<ModuleDeclarationWithSource> {
20
  constructor(
NEW
21
    public readonly moduleName: string,
×
22
    node: ModuleDeclarationWithSource,
NEW
23
    public readonly error?: any,
×
24
  ) {
NEW
25
    super(`Error(s) occured when executing the module '${moduleName}'.`, node);
×
26
  }
27

28
  public override elaborate() {
29
    return 'You may need to contact with the author for this module to fix this error.';
×
30
  }
31
}
32

33
abstract class ImportError<T extends Node | undefined> extends SourceErrorWithNode<T> {
34
  type = ErrorType.IMPORT;
64✔
35
  severity = ErrorSeverity.ERROR;
64✔
36
}
37

38
/**
39
 * Error thrown at Import time when the configured modules server can't be reached.
40
 */
41
export class ModuleConnectionError extends ImportError<ModuleDeclarationWithSource | undefined> {
42
  private static message: string = `Unable to get modules.`;
66✔
43
  private static elaboration: string = `You should check your Internet connection, and ensure you have used the correct module path.`;
66✔
44

45
  public override explain() {
46
    return ModuleConnectionError.message;
×
47
  }
48

49
  public override elaborate() {
50
    return ModuleConnectionError.elaboration;
×
51
  }
52
}
53

54
/**
55
 * Error thrown at import time when the requested module can't be found on the configured modules
56
 * server. If this error is thrown, it means that the modules server is reachable.
57
 */
58
export class ModuleNotFoundError extends ImportError<ModuleDeclarationWithSource | undefined> {
59
  constructor(
60
    public readonly moduleName: string,
14✔
61
    node?: ModuleDeclarationWithSource,
62
  ) {
63
    super(node);
14✔
64
  }
65

66
  public explain() {
67
    return `Module '${this.moduleName}' not found.`;
9✔
68
  }
69

70
  public elaborate() {
71
    return 'You should check your import declarations, and ensure that all are valid modules.';
3✔
72
  }
73
}
74

75
/**
76
 * Error thrown when the given module has a chapter restriction and the current
77
 * evaluation context is not of a high enough chapter
78
 */
79
export class WrongChapterForModuleError extends ImportError<
80
  ModuleDeclarationWithSource | undefined
81
> {
82
  constructor(
83
    public readonly moduleName: string,
2✔
84
    public readonly required: Chapter,
2✔
85
    public readonly actual: Chapter,
2✔
86
    node?: ModuleDeclarationWithSource,
87
  ) {
88
    super(node);
2✔
89
  }
90

91
  public override explain(): string {
92
    const reqName = getChapterName(this.required);
1✔
93
    const actName = getChapterName(this.actual);
1✔
94
    return `${this.moduleName} needs at least ${reqName}, but you are using ${actName}`;
1✔
95
  }
96

97
  public override elaborate() {
NEW
98
    return this.explain();
×
99
  }
100
}
101

102
/**
103
 * Nodes that directly import and declare an export from another module
104
 */
105
type ImportingNodes =
106
  | Exclude<es.ModuleDeclaration, es.ExportDefaultDeclaration>
107
  | es.ImportDeclaration['specifiers'][number]
108
  | es.ExportSpecifier;
109

110
/**
111
 * Error thrown when the module being imported doesn't actually export any symbols
112
 */
113
export class UndefinedNamespaceImportError<T extends ImportingNodes> extends ImportError<
114
  T | undefined
115
> {
116
  constructor(
117
    public readonly moduleName: string,
20✔
118
    node?: T,
119
  ) {
120
    super(node);
20✔
121
  }
122

123
  public override explain() {
UNCOV
124
    return `'${this.moduleName}' does not export any symbols!`;
×
125
  }
126

127
  public override elaborate() {
128
    return "Check your imports and make sure what you're trying to import exists!";
×
129
  }
130
}
131

132
/**
133
 * Error thrown when the module being imported doesn't export a symbol with the given name
134
 */
135
export class UndefinedImportError extends UndefinedNamespaceImportError<
136
  es.ImportDeclaration['specifiers'][number] | es.ExportSpecifier
137
> {
138
  constructor(
139
    /**
140
     * Symbol that is being imported
141
     */
142
    public readonly symbol: string,
17✔
143
    moduleName: string,
144
    node?: es.ImportDeclaration['specifiers'][number] | es.ExportSpecifier,
145
  ) {
146
    super(moduleName, node);
17✔
147
  }
148

149
  public override explain() {
150
    return `'${this.moduleName}' does not contain a definition for '${this.symbol}'`;
×
151
  }
152
}
153

154
export class UndefinedDefaultImportError extends UndefinedImportError {
155
  constructor(
156
    moduleName: string,
157
    node?: es.ImportDeclaration['specifiers'][number] | es.ExportSpecifier,
158
  ) {
159
    super('default', moduleName, node);
10✔
160
  }
161

162
  public override explain(): string {
163
    return `'${this.moduleName}' does not have a default export!`;
×
164
  }
165
}
166

167
/**
168
 * Error thrown when together, the imports by a group of files
169
 * causes an import cycle.
170
 */
171
export class CircularImportError extends ImportError<undefined> {
172
  constructor(public readonly filePathsInCycle: string[]) {
6✔
173
    super(undefined);
6✔
174
  }
175

176
  public override explain() {
177
    // We need to reverse the file paths in the cycle so that the
178
    // semantics of "'/a.js' -> '/b.js'" is "'/a.js' imports '/b.js'".
179
    const formattedCycle = this.filePathsInCycle
4✔
180
      .map(filePath => `'${filePath}'`)
12✔
181
      .reverse()
182
      .join(' -> ');
183
    return `Circular import detected: ${formattedCycle}.`;
4✔
184
  }
185

186
  public override elaborate() {
187
    return 'Break the circular import cycle by removing imports from any of the offending files.';
2✔
188
  }
189
}
190

191
export abstract class InvalidFilePathError extends ImportError<undefined> {
192
  constructor(public readonly filePath: string) {
8✔
193
    super(undefined);
8✔
194
  }
195
}
196

197
export class IllegalCharInFilePathError extends InvalidFilePathError {
198
  public override explain() {
199
    const validNonAlphanumericChars = Object.keys(nonAlphanumericCharEncoding)
4✔
200
      .map(char => `'${char}'`)
16✔
201
      .join(', ');
202
    return `File path '${this.filePath}' must only contain alphanumeric chars and/or ${validNonAlphanumericChars}.`;
4✔
203
  }
204

205
  public override elaborate() {
206
    return 'Rename the offending file path to only use valid chars.';
2✔
207
  }
208
}
209

210
export class ConsecutiveSlashesInFilePathError extends InvalidFilePathError {
211
  public override explain() {
212
    return `File path '${this.filePath}' cannot contain consecutive slashes '//'.`;
4✔
213
  }
214

215
  public override elaborate() {
216
    return 'Remove consecutive slashes from the offending file path.';
2✔
217
  }
218
}
219

220
export class DuplicateImportNameError extends ImportError<undefined> {
221
  public readonly locString: string;
222

223
  public override get location() {
224
    return this.nodes[0].loc ?? UNKNOWN_LOCATION;
×
225
  }
226

227
  constructor(public readonly nodes: Node[]) {
10✔
228
    super(undefined);
10✔
229

230
    this.locString = nodes
10✔
231
      .map(({ loc }) => {
232
        const { source, start } = loc ?? UNKNOWN_LOCATION;
23!
233
        return `(${source ?? 'Unknown File'}:${start.line}:${start.column})`;
23!
234
      })
235
      .join(', ');
236
  }
237

238
  public override explain() {
239
    return `Source does not support different imports from Source modules being given the same name. The following are the offending imports: ${this.locString}`;
×
240
  }
241

242
  public override elaborate() {
243
    return `You cannot have different symbols across different files being given the same declared name, for example: \`import { foo as a } from 'one_module';\` and \`import { bar as a } from 'another_module';
×
244
    You also cannot have different symbols from the same module with the same declared name, for example: \`import { foo as a } from 'one_module';\` and \`import { bar as a } from 'one_module'; `;
245
  }
246
}
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