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

source-academy / js-slang / 4524428240

pending completion
4524428240

push

github

GitHub
Use UNKNOWN_LOCATION if node location does not exist (#1378)

3466 of 4530 branches covered (76.51%)

Branch coverage included in aggregate %.

111 of 111 new or added lines in 34 files covered. (100.0%)

10225 of 11863 relevant lines covered (86.19%)

110365.17 hits per line

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

93.08
/src/utils/operators.ts
1
import { BinaryOperator, UnaryOperator } from 'estree'
2

3
import { LazyBuiltIn } from '../createContext'
61✔
4
import {
61✔
5
  CallingNonFunctionValue,
6
  ExceptionError,
7
  GetInheritedPropertyError,
8
  InvalidNumberOfArguments
9
} from '../errors/errors'
10
import { RuntimeSourceError } from '../errors/runtimeSourceError'
61✔
11
import {
61✔
12
  PotentialInfiniteLoopError,
13
  PotentialInfiniteRecursionError
14
} from '../errors/timeoutErrors'
15
import { Chapter, NativeStorage, Thunk } from '../types'
16
import { callExpression, locationDummyNode } from './astCreator'
61✔
17
import * as create from './astCreator'
61✔
18
import { makeWrapper } from './makeWrapper'
61✔
19
import * as rttc from './rttc'
61✔
20

21
export function throwIfTimeout(
61✔
22
  nativeStorage: NativeStorage,
23
  start: number,
24
  current: number,
25
  line: number,
26
  column: number,
27
  source: string | null
28
) {
29
  if (current - start > nativeStorage.maxExecTime) {
16,559,333✔
30
    throw new PotentialInfiniteLoopError(
2✔
31
      create.locationDummyNode(line, column, source),
32
      nativeStorage.maxExecTime
33
    )
34
  }
35
}
36

37
export function forceIt(val: Thunk | any): any {
61✔
38
  if (val !== undefined && val !== null && val.isMemoized !== undefined) {
69,235,842✔
39
    if (val.isMemoized) {
16,507✔
40
      return val.memoizedValue
5,840✔
41
    }
42

43
    const evaluatedValue = forceIt(val.f())
10,667✔
44

45
    val.isMemoized = true
10,666✔
46
    val.memoizedValue = evaluatedValue
10,666✔
47

48
    return evaluatedValue
10,666✔
49
  } else {
50
    return val
69,219,335✔
51
  }
52
}
53

54
export function delayIt(f: () => any): Thunk {
61✔
55
  return {
11,725✔
56
    isMemoized: false,
57
    value: undefined,
58
    f
59
  }
60
}
61

62
export function wrapLazyCallee(candidate: any) {
61✔
63
  candidate = forceIt(candidate)
8,128✔
64
  if (typeof candidate === 'function') {
8,128✔
65
    const wrapped: any = (...args: any[]) => candidate(...args.map(forceIt))
134✔
66
    makeWrapper(candidate, wrapped)
134✔
67
    wrapped[Symbol.toStringTag] = () => candidate.toString()
134✔
68
    wrapped.toString = () => candidate.toString()
134✔
69
    return wrapped
134✔
70
  } else if (candidate instanceof LazyBuiltIn) {
7,994✔
71
    if (candidate.evaluateArgs) {
7,994✔
72
      const wrapped: any = (...args: any[]) => candidate.func(...args.map(forceIt))
4,460✔
73
      makeWrapper(candidate.func, wrapped)
4,460✔
74
      wrapped[Symbol.toStringTag] = () => candidate.toString()
4,460✔
75
      wrapped.toString = () => candidate.toString()
4,460✔
76
      return wrapped
4,460✔
77
    } else {
78
      return candidate
3,534✔
79
    }
80
  }
81
  // doesn't look like a function, not our business to error now
82
  return candidate
×
83
}
84

85
export function makeLazyFunction(candidate: any) {
61✔
86
  return new LazyBuiltIn(candidate, false)
845✔
87
}
88

89
export function callIfFuncAndRightArgs(
61✔
90
  candidate: any,
91
  line: number,
92
  column: number,
93
  source: string | null,
94
  ...args: any[]
95
) {
96
  const dummy = create.callExpression(create.locationDummyNode(line, column, source), args, {
47,611✔
97
    start: { line, column },
98
    end: { line, column }
99
  })
100

101
  if (typeof candidate === 'function') {
47,611✔
102
    const originalCandidate = candidate
18,556✔
103
    if (candidate.transformedFunction !== undefined) {
18,556✔
104
      candidate = candidate.transformedFunction
9,406✔
105
    }
106
    const expectedLength = candidate.length
18,556✔
107
    const receivedLength = args.length
18,556✔
108
    const hasVarArgs = candidate.minArgsNeeded !== undefined
18,556✔
109
    if (hasVarArgs ? candidate.minArgsNeeded > receivedLength : expectedLength !== receivedLength) {
18,556✔
110
      throw new InvalidNumberOfArguments(
11✔
111
        dummy,
112
        hasVarArgs ? candidate.minArgsNeeded : expectedLength,
11✔
113
        receivedLength,
114
        hasVarArgs
115
      )
116
    }
117
    try {
18,545✔
118
      const forcedArgs = args.map(forceIt)
18,545✔
119
      return originalCandidate(...forcedArgs)
18,545✔
120
    } catch (error) {
121
      // if we already handled the error, simply pass it on
122
      if (!(error instanceof RuntimeSourceError || error instanceof ExceptionError)) {
3,685✔
123
        throw new ExceptionError(error, dummy.loc)
85✔
124
      } else {
125
        throw error
3,600✔
126
      }
127
    }
128
  } else if (candidate instanceof LazyBuiltIn) {
29,055✔
129
    try {
29,050✔
130
      if (candidate.evaluateArgs) {
29,050✔
131
        args = args.map(forceIt)
28,940✔
132
      }
133
      return candidate.func(...args)
29,049✔
134
    } catch (error) {
135
      // if we already handled the error, simply pass it on
136
      if (!(error instanceof RuntimeSourceError || error instanceof ExceptionError)) {
9!
137
        throw new ExceptionError(error, dummy.loc)
×
138
      } else {
139
        throw error
9✔
140
      }
141
    }
142
  } else {
143
    throw new CallingNonFunctionValue(candidate, dummy)
5✔
144
  }
145
}
146

147
export function boolOrErr(candidate: any, line: number, column: number, source: string | null) {
61✔
148
  candidate = forceIt(candidate)
16,630,847✔
149
  const error = rttc.checkIfStatement(create.locationDummyNode(line, column, source), candidate)
16,630,847✔
150
  if (error === undefined) {
16,630,847✔
151
    return candidate
16,630,846✔
152
  } else {
153
    throw error
1✔
154
  }
155
}
156

157
export function unaryOp(
61✔
158
  operator: UnaryOperator,
159
  argument: any,
160
  line: number,
161
  column: number,
162
  source: string | null
163
) {
164
  argument = forceIt(argument)
48✔
165
  const error = rttc.checkUnaryExpression(
48✔
166
    create.locationDummyNode(line, column, source),
167
    operator,
168
    argument
169
  )
170
  if (error === undefined) {
48!
171
    return evaluateUnaryExpression(operator, argument)
48✔
172
  } else {
173
    throw error
×
174
  }
175
}
176

177
export function evaluateUnaryExpression(operator: UnaryOperator, value: any) {
61✔
178
  if (operator === '!') {
586✔
179
    return !value
458✔
180
  } else if (operator === '-') {
128!
181
    return -value
128✔
182
  } else if (operator === 'typeof') {
×
183
    return typeof value
×
184
  } else {
185
    return +value
×
186
  }
187
}
188

189
export function binaryOp(
61✔
190
  operator: BinaryOperator,
191
  chapter: Chapter,
192
  left: any,
193
  right: any,
194
  line: number,
195
  column: number,
196
  source: string | null
197
) {
198
  left = forceIt(left)
20,397,709✔
199
  right = forceIt(right)
20,397,709✔
200
  const error = rttc.checkBinaryExpression(
20,397,709✔
201
    create.locationDummyNode(line, column, source),
202
    operator,
203
    chapter,
204
    left,
205
    right
206
  )
207
  if (error === undefined) {
20,397,708✔
208
    return evaluateBinaryExpression(operator, left, right)
20,397,695✔
209
  } else {
210
    throw error
13✔
211
  }
212
}
213

214
export function evaluateBinaryExpression(operator: BinaryOperator, left: any, right: any) {
61✔
215
  switch (operator) {
20,855,811✔
216
    case '+':
20,855,811!
217
      return left + right
20,399,882✔
218
    case '-':
219
      return left - right
196,319✔
220
    case '*':
221
      return left * right
269✔
222
    case '/':
223
      return left / right
4,685✔
224
    case '%':
225
      return left % right
84✔
226
    case '===':
227
      return left === right
65,148✔
228
    case '!==':
229
      return left !== right
79✔
230
    case '<=':
231
      return left <= right
167,761✔
232
    case '<':
233
      return left < right
5,546✔
234
    case '>':
235
      return left > right
15,942✔
236
    case '>=':
237
      return left >= right
96✔
238
    default:
239
      return undefined
×
240
  }
241
}
242

243
/**
244
 * Limitations for current properTailCalls implementation:
245
 * Obviously, if objects ({}) are reintroduced,
246
 * we have to change this for a more stringent check,
247
 * as isTail and transformedFunctions are properties
248
 * and may be added by Source code.
249
 */
250
export const callIteratively = (f: any, nativeStorage: NativeStorage, ...args: any[]) => {
61✔
251
  let line = -1
24,101✔
252
  let column = -1
24,101✔
253
  let source: string | null = null
24,101✔
254
  const startTime = Date.now()
24,101✔
255
  const pastCalls: [string, any[]][] = []
24,101✔
256
  while (true) {
24,101✔
257
    const dummy = locationDummyNode(line, column, source)
11,714,332✔
258
    f = forceIt(f)
11,714,332✔
259
    if (typeof f === 'function') {
11,714,332✔
260
      if (f.transformedFunction !== undefined) {
11,710,908✔
261
        f = f.transformedFunction
11,683,981✔
262
      }
263
      const expectedLength = f.length
11,710,908✔
264
      const receivedLength = args.length
11,710,908✔
265
      const hasVarArgs = f.minArgsNeeded !== undefined
11,710,908✔
266
      if (hasVarArgs ? f.minArgsNeeded > receivedLength : expectedLength !== receivedLength) {
11,710,908✔
267
        throw new InvalidNumberOfArguments(
1✔
268
          callExpression(dummy, args, {
269
            start: { line, column },
270
            end: { line, column },
271
            source
272
          }),
273
          hasVarArgs ? f.minArgsNeeded : expectedLength,
1!
274
          receivedLength,
275
          hasVarArgs
276
        )
277
      }
278
    } else if (f instanceof LazyBuiltIn) {
3,424!
279
      if (f.evaluateArgs) {
3,424!
280
        args = args.map(forceIt)
×
281
      }
282
      f = f.func
3,424✔
283
    } else {
284
      throw new CallingNonFunctionValue(f, dummy)
×
285
    }
286
    let res
287
    try {
11,714,331✔
288
      res = f(...args)
11,714,331✔
289
      if (Date.now() - startTime > nativeStorage.maxExecTime) {
11,710,655✔
290
        throw new PotentialInfiniteRecursionError(dummy, pastCalls, nativeStorage.maxExecTime)
5✔
291
      }
292
    } catch (error) {
293
      // if we already handled the error, simply pass it on
294
      if (!(error instanceof RuntimeSourceError || error instanceof ExceptionError)) {
3,681✔
295
        throw new ExceptionError(error, dummy.loc)
67✔
296
      } else {
297
        throw error
3,614✔
298
      }
299
    }
300
    if (res === null || res === undefined) {
11,710,650✔
301
      return res
50✔
302
    } else if (res.isTail === true) {
11,710,600✔
303
      f = res.function
11,690,231✔
304
      args = res.arguments
11,690,231✔
305
      line = res.line
11,690,231✔
306
      column = res.column
11,690,231✔
307
      source = res.source
11,690,231✔
308
      pastCalls.push([res.functionName, args])
11,690,231✔
309
    } else if (res.isTail === false) {
20,369✔
310
      return res.value
14,161✔
311
    } else {
312
      return res
6,208✔
313
    }
314
  }
315
}
316

317
export const wrap = (
61✔
318
  f: (...args: any[]) => any,
319
  stringified: string,
320
  hasVarArgs: boolean,
321
  nativeStorage: NativeStorage
322
) => {
323
  if (hasVarArgs) {
24,556✔
324
    // @ts-ignore
325
    f.minArgsNeeded = f.length
4✔
326
  }
327
  const wrapped = (...args: any[]) => callIteratively(f, nativeStorage, ...args)
24,556✔
328
  makeWrapper(f, wrapped)
24,556✔
329
  wrapped.transformedFunction = f
24,556✔
330
  wrapped[Symbol.toStringTag] = () => stringified
24,556✔
331
  wrapped.toString = () => stringified
24,556✔
332
  return wrapped
24,556✔
333
}
334

335
export const setProp = (
61✔
336
  obj: any,
337
  prop: any,
338
  value: any,
339
  line: number,
340
  column: number,
341
  source: string | null
342
) => {
343
  const dummy = locationDummyNode(line, column, source)
129✔
344
  const error = rttc.checkMemberAccess(dummy, obj, prop)
129✔
345
  if (error === undefined) {
129✔
346
    return (obj[prop] = value)
127✔
347
  } else {
348
    throw error
2✔
349
  }
350
}
351

352
export const getProp = (
61✔
353
  obj: any,
354
  prop: any,
355
  line: number,
356
  column: number,
357
  source: string | null
358
) => {
359
  const dummy = locationDummyNode(line, column, source)
26✔
360
  const error = rttc.checkMemberAccess(dummy, obj, prop)
26✔
361
  if (error === undefined) {
26✔
362
    if (obj[prop] !== undefined && !obj.hasOwnProperty(prop)) {
23✔
363
      throw new GetInheritedPropertyError(dummy, obj, prop)
1✔
364
    } else {
365
      return obj[prop]
22✔
366
    }
367
  } else {
368
    throw error
3✔
369
  }
370
}
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