• 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

92.66
/src/stepper/nodes/Expression/FunctionApplication.ts
1
import type { Comment, Expression, SimpleCallExpression, SourceLocation } from 'estree';
2
import type { StepperExpression, StepperPattern } from '..';
3
import { getBuiltinFunction, isBuiltinFunction } from '../../builtins';
4
import { convert } from '../../generator';
5
import { StepperBaseNode } from '../../interface';
6
import { StepperBlockStatement } from '../Statement/BlockStatement';
7
import {
8
  CallingNonFunctionValueError,
9
  InvalidNumberOfArgumentsError,
10
} from '../../../errors/errors';
11
import { GeneralRuntimeError, InternalRuntimeError } from '../../../errors/base';
12
import type { RedexInfo } from '../..';
13
import { StepperBlockExpression } from './BlockExpression';
14

15
export class StepperFunctionApplication
16
  extends StepperBaseNode<SimpleCallExpression>
17
  implements SimpleCallExpression
18
{
19
  public readonly arguments: StepperExpression[];
20

21
  constructor(
22
    public readonly callee: StepperExpression,
8,688✔
23
    args: StepperExpression[],
24
    public readonly optional: boolean = false,
8,688✔
25
    leadingComments?: Comment[],
26
    trailingComments?: Comment[],
27
    loc?: SourceLocation | null,
28
    range?: [number, number],
29
  ) {
30
    super('CallExpression', leadingComments, trailingComments, loc, range);
8,688✔
31
    this.arguments = args;
8,688✔
32
  }
33

34
  static create(node: SimpleCallExpression) {
35
    return new StepperFunctionApplication(
1,716✔
36
      convert(node.callee as Expression),
37
      node.arguments.map(arg => convert(arg as Expression)),
2,010✔
38
      node.optional,
39
      node.leadingComments,
40
      node.trailingComments,
41
      node.loc,
42
      node.range,
43
    );
44
  }
45

46
  public override isContractible(redex: RedexInfo): boolean {
47
    const isValidCallee =
48
      this.callee.type === 'ArrowFunctionExpression' ||
16,815✔
49
      (this.callee.type === 'Identifier' && isBuiltinFunction(this.callee.name));
50

51
    if (!isValidCallee) {
16,815✔
52
      // Since the callee can not proceed further, calling non callables should result to an error.
53

54
      if (
172✔
55
        !this.callee.isOneStepPossible(redex) &&
180✔
56
        this.arguments.every(arg => !arg.isOneStepPossible(redex))
8✔
57
      ) {
58
        throw new CallingNonFunctionValueError(this.callee, this);
2✔
59
      }
60
      return false;
170✔
61
    }
62

63
    if (this.callee.type === 'ArrowFunctionExpression') {
16,643✔
64
      const arrowFunction = this.callee;
6,528✔
65
      if (arrowFunction.params.length !== this.arguments.length) {
6,528✔
66
        throw new InvalidNumberOfArgumentsError(
2✔
67
          this,
68
          arrowFunction.params.length,
69
          this.arguments.length,
70
        );
71
      }
72
    }
73

74
    return this.arguments.every(arg => !arg.isOneStepPossible(redex));
19,777✔
75
  }
76

77
  public override isOneStepPossible(redex: RedexInfo): boolean {
78
    if (this.isContractible(redex)) return true;
13,032✔
79
    if (this.callee.isOneStepPossible(redex)) return true;
4,752✔
80
    return this.arguments.some(arg => arg.isOneStepPossible(redex));
6,072✔
81
  }
82

83
  public override contract(redex: RedexInfo): StepperExpression | StepperBlockExpression {
84
    redex.preRedex = [this];
1,387✔
85

86
    if (!this.isContractible(redex))
1,387!
NEW
87
      throw new InternalRuntimeError('Trying to contract ineliglble CallExpression', this);
×
88

89
    if (this.callee.type === 'Identifier') {
1,387✔
90
      const functionName = this.callee.name;
489✔
91
      if (isBuiltinFunction(functionName)) {
489!
92
        const result = getBuiltinFunction(functionName, this);
489✔
93
        redex.postRedex = [result];
489✔
94
        return result;
489✔
95
      }
NEW
96
      throw new GeneralRuntimeError(`Unknown builtin function: ${functionName}`, this);
×
97
    }
98

99
    if (this.callee.type !== 'ArrowFunctionExpression') {
898!
NEW
100
      throw new CallingNonFunctionValueError(this.callee, this);
×
101
    }
102

103
    const lambda = this.callee;
898✔
104
    const args = this.arguments;
898✔
105

106
    let result: StepperBlockExpression | StepperExpression = lambda.body;
898✔
107

108
    if (result instanceof StepperBlockStatement) {
898✔
109
      const blockStatement = lambda.body as unknown as StepperBlockStatement;
685✔
110
      if (blockStatement.body.length === 0) {
685✔
111
        result = new StepperBlockExpression([]);
3✔
112
      } else if (blockStatement.body[0].type === 'ReturnStatement') {
682✔
113
        // (x => {return 2 + 3;})(3) -> 2 + 3;
114
        result = blockStatement.body[0].argument!;
635✔
115
      } else {
116
        result = new StepperBlockExpression(blockStatement.body);
47✔
117
      }
118
    } else {
119
      result = lambda.body;
213✔
120
    }
121
    if (lambda.name && !this.callee.scanAllDeclarationNames().includes(lambda.name)) {
898✔
122
      result = result.substitute(
747✔
123
        { type: 'Identifier', name: lambda.name } as StepperPattern,
124
        lambda,
125
        redex,
126
      );
127
    }
128

129
    lambda.params.forEach((param, i) => {
898✔
130
      result = result.substitute(param, args[i], redex);
535✔
131
    });
132

133
    redex.postRedex = [result];
898✔
134
    return result;
898✔
135
  }
136

137
  public override oneStep(redex: RedexInfo): StepperExpression {
138
    if (this.isContractible(redex)) {
2,394✔
139
      // @ts-expect-error: contract can return StepperBlockExpression but it's handled at runtime
140
      return this.contract(redex);
1,387✔
141
    }
142

143
    if (this.callee.isOneStepPossible(redex)) {
1,007✔
144
      return new StepperFunctionApplication(
47✔
145
        this.callee.oneStep(redex),
146
        this.arguments,
147
        this.optional,
148
        this.leadingComments,
149
        this.trailingComments,
150
        this.loc,
151
        this.range,
152
      );
153
    }
154

155
    for (let i = 0; i < this.arguments.length; i++) {
960✔
156
      if (this.arguments[i].isOneStepPossible(redex)) {
1,222✔
157
        const newArgs = [...this.arguments];
960✔
158
        newArgs[i] = this.arguments[i].oneStep(redex);
960✔
159
        return new StepperFunctionApplication(
960✔
160
          this.callee,
161
          newArgs,
162
          this.optional,
163
          this.leadingComments,
164
          this.trailingComments,
165
          this.loc,
166
          this.range,
167
        );
168
      }
169
    }
170

NEW
171
    throw new InternalRuntimeError('No one step possible for CallExpression', this);
×
172
  }
173

174
  public override substitute(
175
    id: StepperPattern,
176
    value: StepperExpression,
177
    redex: RedexInfo,
178
  ): StepperExpression {
179
    return new StepperFunctionApplication(
5,818✔
180
      this.callee.substitute(id, value, redex),
181
      this.arguments.map(arg => arg.substitute(id, value, redex)),
6,315✔
182
      this.optional,
183
      this.leadingComments,
184
      this.trailingComments,
185
      this.loc,
186
      this.range,
187
    );
188
  }
189

190
  public override freeNames(): string[] {
191
    return Array.from(
2,080✔
192
      new Set([...this.callee.freeNames(), ...this.arguments.flatMap(arg => arg.freeNames())]),
2,386✔
193
    );
194
  }
195

196
  public override allNames(): string[] {
197
    return Array.from(
3,861✔
198
      new Set([...this.callee.allNames(), ...this.arguments.flatMap(arg => arg.allNames())]),
4,712✔
199
    );
200
  }
201

202
  public override rename(before: string, after: string): StepperExpression {
203
    return new StepperFunctionApplication(
40✔
204
      this.callee.rename(before, after),
205
      this.arguments.map(arg => arg.rename(before, after)),
×
206
      this.optional,
207
      this.leadingComments,
208
      this.trailingComments,
209
      this.loc,
210
      this.range,
211
    );
212
  }
213
}
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