• 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

86.36
/src/stepper/generator.ts
1
/*
2
TODO: Write docs  
3
convert estree into corresponding stepper type
4
Every class should have the following properties
5
- basic StepperBaseNodeInterface
6
- constructor: create new AST from class type StepperBaseNode
7
- static create: factory method to parse estree to StepperAST
8
*/
9

10
import { generate } from 'astring';
11
import type es from 'estree';
12
import assert from '../utils/assert';
13
import { isBuiltinFunction } from './builtins';
14
import type { StepperBaseNode } from './interface';
15
import { StepperArrayExpression } from './nodes/Expression/ArrayExpression';
16
import { StepperArrowFunctionExpression } from './nodes/Expression/ArrowFunctionExpression';
17
import { StepperBinaryExpression } from './nodes/Expression/BinaryExpression';
18
import { StepperConditionalExpression } from './nodes/Expression/ConditionalExpression';
19
import { StepperFunctionApplication } from './nodes/Expression/FunctionApplication';
20
import { StepperIdentifier } from './nodes/Expression/Identifier';
21
import { StepperLiteral } from './nodes/Expression/Literal';
22
import { StepperLogicalExpression } from './nodes/Expression/LogicalExpression';
23
import { StepperUnaryExpression } from './nodes/Expression/UnaryExpression';
24
import { StepperProgram } from './nodes/Program';
25
import { StepperBlockStatement } from './nodes/Statement/BlockStatement';
26
import { StepperExpressionStatement } from './nodes/Statement/ExpressionStatement';
27
import { StepperFunctionDeclaration } from './nodes/Statement/FunctionDeclaration';
28
import { StepperIfStatement } from './nodes/Statement/IfStatement';
29
import { StepperReturnStatement } from './nodes/Statement/ReturnStatement';
30
import {
31
  StepperVariableDeclaration,
32
  StepperVariableDeclarator,
33
} from './nodes/Statement/VariableDeclaration';
34
import {
35
  type StepperExpression,
36
  type StepperPattern,
37
  type StepperStatement,
38
  undefinedNode,
39
  type StepperNode,
40
} from './nodes';
41
import { StepperDebuggerStatement } from './nodes/Statement/DebuggerStatement';
42

43
const nodeConverters = {
56✔
44
  Literal: (node: es.SimpleLiteral) => StepperLiteral.create(node),
1,286✔
45
  UnaryExpression: (node: es.UnaryExpression) => StepperUnaryExpression.create(node),
103✔
46
  BinaryExpression: (node: es.BinaryExpression) => StepperBinaryExpression.create(node),
583✔
47
  LogicalExpression: (node: es.LogicalExpression) => StepperLogicalExpression.create(node),
405✔
48
  FunctionDeclaration: (node: es.FunctionDeclaration) => StepperFunctionDeclaration.create(node),
210✔
49
  ExpressionStatement: (node: es.ExpressionStatement) => StepperExpressionStatement.create(node),
521✔
50
  ConditionalExpression: (node: es.ConditionalExpression) =>
51
    StepperConditionalExpression.create(node),
382✔
52
  ArrowFunctionExpression: (node: es.ArrowFunctionExpression) =>
53
    StepperArrowFunctionExpression.create(node),
211✔
54
  ArrayExpression: (node: es.ArrayExpression) => StepperArrayExpression.create(node),
1✔
55
  CallExpression: (node: es.CallExpression) =>
56
    StepperFunctionApplication.create(node as es.SimpleCallExpression),
1,716✔
57
  ReturnStatement: (node: es.ReturnStatement) => StepperReturnStatement.create(node),
195✔
58
  Program: (node: es.Program) => StepperProgram.create(node),
384✔
59
  VariableDeclaration: (node: es.VariableDeclaration) => StepperVariableDeclaration.create(node),
350✔
UNCOV
60
  VariableDeclarator: (node: es.VariableDeclarator) => StepperVariableDeclarator.create(node),
×
61
  Identifier: (node: es.Identifier) => {
62
    if (node.name === 'NaN') {
4,821✔
63
      return new StepperLiteral(NaN, 'NaN');
1✔
64
    } else if (node.name === 'Infinity') {
4,820!
65
      return new StepperLiteral(Infinity, 'Infinity');
×
66
    } else {
67
      return StepperIdentifier.create(node);
4,820✔
68
    }
69
  },
70
  BlockStatement: (node: es.BlockStatement) => StepperBlockStatement.create(node),
439✔
71
  IfStatement: (node: es.IfStatement) => StepperIfStatement.create(node),
72✔
72
  DebuggerStatement: (node: es.DebuggerStatement) => StepperDebuggerStatement.create(node),
2✔
73
} satisfies { [key: string]: (node: any) => StepperBaseNode };
74

75
const explainers: {
76
  [K in StepperNode['type']]?: (node: Extract<StepperNode, { type: K }>) => string;
77
} = {
56✔
78
  ArrowFunctionExpression: () => {
NEW
79
    throw new Error('Not implemented.');
×
80
  },
81
  BinaryExpression(node) {
82
    return `Binary expression ${generate(node)} evaluated`;
540✔
83
  },
84
  BlockStatement(node) {
85
    if (node.body.length === 0) {
61✔
86
      return 'Empty block expression evaluated';
15✔
87
    }
88
    return `${generate(node.body[0])} finished evaluating`;
46✔
89
  },
90
  CallExpression(node) {
91
    if (node.callee.type !== 'ArrowFunctionExpression' && node.callee.type !== 'Identifier') {
1,384!
NEW
92
      throw new Error('`callee` should be function expression.');
×
93
    }
94

95
    // Determine whether the called function is built-in or not and create explanation accordingly
96
    const func: StepperArrowFunctionExpression = node.callee as StepperArrowFunctionExpression;
1,384✔
97
    if (func.name && isBuiltinFunction(func.name)) {
1,384✔
98
      return `${func.name} runs`;
486✔
99
      // @ts-expect-error func.body.type can be StepperBlockExpression
100
    } else if (func.body.type === 'BlockStatement') {
898✔
101
      if (func.params.length === 0) {
685✔
102
        return '() => {...}' + ' runs';
520✔
103
      }
104
      const paramDisplay = func.params.map(x => x.name).join(', ');
188✔
105
      const argDisplay: string = node.arguments
165✔
106
        .map(x =>
188✔
107
          (x.type === 'ArrowFunctionExpression' || x.type === 'Identifier') && x.name !== undefined
108
            ? x.name
109
            : generate(x),
110
        )
111
        .join(', ');
112
      return 'Function ' + func.name + ' takes in ' + argDisplay + ' as input ' + paramDisplay;
165✔
113
    } else {
114
      if (func.params.length === 0) {
213✔
115
        return generate(func) + ' runs';
12✔
116
      }
117
      return (
201✔
118
        node.arguments.map(x => generate(x)).join(', ') +
347✔
119
        ' substituted into ' +
120
        func.params.map(x => x.name).join(', ') +
347✔
121
        ' of ' +
122
        generate(func)
123
      );
124
    }
125
  },
126
  ConditionalExpression(node) {
127
    const test = node.test; // test should have typeof literal
279✔
128
    assert(test.type === 'Literal', 'Invalid conditional contraction. `test` should be literal.');
279✔
129

130
    const testStatus = test.value;
279✔
131
    if (typeof testStatus !== 'boolean') {
279!
NEW
132
      throw new Error(
×
133
        'Invalid conditional contraction. `test` should be boolean, got ' +
134
          typeof testStatus +
135
          ' instead.',
136
      );
137
    }
138
    if (testStatus === true) {
279✔
139
      return 'Conditional expression evaluated, condition is true, consequent evaluated';
69✔
140
    } else {
141
      return 'Conditional expression evaluated, condition is false, alternate evaluated';
210✔
142
    }
143
  },
144
  DebuggerStatement: () => {
145
    return 'Debugger statement reached';
2✔
146
  },
147
  ExpressionStatement(node) {
148
    return `${generate(node.expression)} finished evaluating`;
49✔
149
  },
150
  FunctionDeclaration(node) {
151
    return `Function ${node.id.name} declared, parameter(s) ${node.params.map(x =>
75✔
152
      generate(x),
153
    )} required`;
154
  },
155
  IfStatement(node) {
156
    if (node.test instanceof StepperLiteral) {
30!
157
      if (node.test.value) {
30✔
158
        return 'If statement evaluated, condition true, proceed to if block';
12✔
159
      } else {
160
        return 'If statement evaluated, condition false, proceed to else block';
18✔
161
      }
162
    } else {
UNCOV
163
      throw new Error('Not implemented');
×
164
    }
165
  },
166
  LogicalExpression(node) {
167
    if (node.operator == '&&') {
59✔
168
      return (node.left as StepperLiteral).value === true
55✔
169
        ? 'AND operation evaluated, left of operator is true, continue evaluating right of operator'
170
        : 'AND operation evaluated, left of operator is false, stop evaluation';
171
    } else if (node.operator === '||') {
4!
172
      return (node.left as StepperLiteral).value === true
4✔
173
        ? 'OR operation evaluated, left of operator is true, stop evaluation'
174
        : 'OR operation evaluated, left of operator is false, continue evaluating right of operator';
175
    }
176

NEW
177
    throw new Error(`Invalid operator for LogicalExpression: ${node.operator}`);
×
178
  },
179
  ReturnStatement(node) {
180
    assert(!!node.argument, 'return argument should not be empty');
38✔
181
    return `${generate(node.argument)} returned`;
38✔
182
  },
183
  UnaryExpression(node) {
184
    if (node.operator === '-') {
4✔
185
      return (
1✔
186
        'Unary expression evaluated, value ' +
187
        JSON.stringify((node.argument as StepperLiteral).value) +
188
        ' negated.'
189
      );
190
    } else if (node.operator === '!') {
3!
191
      return (
3✔
192
        'Unary expression evaluated, boolean ' +
193
        JSON.stringify((node.argument as StepperLiteral).value) +
194
        ' negated.'
195
      );
196
    } else {
NEW
197
      throw new Error('Unsupported unary operator ' + node.operator);
×
198
    }
199
  },
200
  VariableDeclaration(node) {
201
    if (node.kind === 'const') {
116!
202
      return (
116✔
203
        'Constant ' +
204
        node.declarations.map(ast => ast.id.name).join(', ') +
116✔
205
        ' declared and substituted into the rest of block'
206
      );
207
    } else {
UNCOV
208
      return '...';
×
209
    }
210
  },
211
};
212

213
export function convert(node: es.Expression): StepperExpression;
214
export function convert(node: es.Pattern): StepperPattern;
215
export function convert(node: es.Statement): StepperStatement;
216
export function convert(node: es.BaseNode): StepperBaseNode;
217
export function convert(node: es.BaseNode): StepperBaseNode {
218
  const converter = nodeConverters[node.type as keyof typeof nodeConverters];
11,681✔
219
  return converter ? converter(node as any) : undefinedNode;
11,681!
220
}
221

222
// Explanation generator
223
export function explain(redex: StepperBaseNode): string {
224
  return redex.type in explainers
2,637!
225
    ? explainers[redex.type as keyof typeof explainers]!(redex as any)
226
    : '...';
227
}
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