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

source-academy / js-slang / 14529259416

18 Apr 2025 03:46AM CUT coverage: 80.538%. Remained the same
14529259416

Pull #1757

github

web-flow
Merge 63eac9783 into fea2b4cad
Pull Request #1757: Language options

3458 of 4687 branches covered (73.78%)

Branch coverage included in aggregate %.

15 of 88 new or added lines in 6 files covered. (17.05%)

84 existing lines in 6 files now uncovered.

10811 of 13030 relevant lines covered (82.97%)

142544.19 hits per line

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

86.4
/src/typeChecker/typeErrorChecker.ts
1
import { parse as babelParse } from '@babel/parser'
74✔
2
import * as es from 'estree'
3
import { cloneDeep, isEqual } from 'lodash'
74✔
4

5
import {
74✔
6
  ConstNotAssignableTypeError,
7
  DuplicateTypeAliasError,
8
  FunctionShouldHaveReturnValueError,
9
  InvalidArrayAccessTypeError,
10
  InvalidIndexTypeError,
11
  InvalidNumberOfArgumentsTypeError,
12
  InvalidNumberOfTypeArgumentsForGenericTypeError,
13
  TypeAliasNameNotAllowedError,
14
  TypecastError,
15
  TypeMismatchError,
16
  TypeNotAllowedError,
17
  TypeNotCallableError,
18
  TypeNotFoundError,
19
  TypeNotGenericError,
20
  TypeParameterNameNotAllowedError,
21
  UndefinedVariableTypeError
22
} from '../errors/typeErrors'
23
import {
74✔
24
  BindableType,
25
  Chapter,
26
  type Context,
27
  disallowedTypes,
28
  type Pair,
29
  PrimitiveType,
30
  SArray,
31
  TSAllowedTypes,
32
  TSBasicType,
33
  TSDisallowedTypes,
34
  type Type,
35
  TypeEnvironment,
36
  Variable
37
} from '../types'
38
import { TypecheckError } from './internalTypeErrors'
74✔
39
import { parseTreeTypesPrelude } from './parseTreeTypes.prelude'
74✔
40
import * as tsEs from './tsESTree'
41
import {
74✔
42
  formatTypeString,
43
  getTypeOverrides,
44
  lookupDeclKind,
45
  lookupType,
46
  lookupTypeAlias,
47
  pushEnv,
48
  RETURN_TYPE_IDENTIFIER,
49
  setDeclKind,
50
  setType,
51
  setTypeAlias,
52
  tAny,
53
  tArray,
54
  tBool,
55
  tForAll,
56
  tFunc,
57
  tList,
58
  tLiteral,
59
  tNull,
60
  tNumber,
61
  tPair,
62
  tPrimitive,
63
  tStream,
64
  tString,
65
  tUndef,
66
  tUnion,
67
  tVar,
68
  tVoid,
69
  typeAnnotationKeywordToBasicTypeMap
70
} from './utils'
71
// import { ModuleNotFoundError } from '../modules/errors'
72

73
// Context and type environment are saved as global variables so that they are not passed between functions excessively
74
let context: Context = {} as Context
74✔
75
let env: TypeEnvironment = []
74✔
76

77
/**
78
 * Entry function for type error checker.
79
 * Checks program for type errors, and returns the program with all TS-related nodes removed.
80
 */
81
export function checkForTypeErrors(program: tsEs.Program, inputContext: Context): es.Program {
74✔
82
  // Set context as global variable
83
  context = inputContext
111✔
84
  // Deep copy type environment to avoid modifying type environment in the context,
85
  // which might affect the type inference checker
86
  env = cloneDeep(context.typeEnvironment)
111✔
87
  // Override predeclared function types
88
  for (const [name, type] of getTypeOverrides(context.chapter)) {
111✔
89
    setType(name, type, env)
2,703✔
90
  }
91
  if (context.chapter >= 4) {
111✔
92
    // Add parse tree types to type environment
93
    const source4Types = babelParse(parseTreeTypesPrelude, {
28✔
94
      sourceType: 'module',
95
      plugins: ['typescript', 'estree']
96
    }).program as unknown as tsEs.Program
97
    typeCheckAndReturnType(source4Types)
28✔
98
  }
99
  try {
111✔
100
    typeCheckAndReturnType(program)
111✔
101
  } catch (error) {
102
    // Catch-all for thrown errors
103
    // (either errors that cause early termination or errors that should not be reached logically)
104
    // console.error(error)
105
    context.errors.push(
1✔
106
      error instanceof TypecheckError
1!
107
        ? error
108
        : new TypecheckError(
109
            program,
110
            'Uncaught error during typechecking, report this to the administrators!\n' +
111
              error.message
112
          )
113
    )
114
  }
115
  // Reset global variables
116
  context = {} as Context
111✔
117
  env = []
111✔
118
  return removeTSNodes(program)
111✔
119
}
120

121
/**
122
 * Recurses through the given node to check for any type errors,
123
 * then returns the node's inferred/declared type.
124
 * Any errors found are added to the context.
125
 */
126
function typeCheckAndReturnType(node: tsEs.Node): Type {
127
  switch (node.type) {
3,243!
128
    case 'Literal': {
129
      // Infers type
130
      if (node.value === undefined) {
522!
131
        return tUndef
×
132
      }
133
      if (node.value === null) {
522✔
134
        // For Source 1, skip typecheck as null literals will be handled by the noNull rule,
135
        // which is run after typechecking
136
        return context.chapter === Chapter.SOURCE_1 ? tAny : tNull
14✔
137
      }
138
      if (
508!
139
        typeof node.value !== 'string' &&
952✔
140
        typeof node.value !== 'number' &&
141
        typeof node.value !== 'boolean'
142
      ) {
143
        // Skip typecheck as unspecified literals will be handled by the noUnspecifiedLiteral rule,
144
        // which is run after typechecking
145
        return tAny
×
146
      }
147
      // Casting is safe here as above check already narrows type to string, number or boolean
148
      return tPrimitive(typeof node.value as PrimitiveType, node.value)
508✔
149
    }
150
    case 'TemplateLiteral': {
151
      // Quasis array should only have one element as
152
      // string interpolation is not allowed in Source
153
      return tPrimitive('string', node.quasis[0].value.raw)
×
154
    }
155
    case 'Identifier': {
156
      const varName = node.name
478✔
157
      const varType = lookupTypeAndRemoveForAllAndPredicateTypes(varName)
478✔
158
      if (varType) {
478✔
159
        return varType
477✔
160
      } else {
161
        context.errors.push(new UndefinedVariableTypeError(node, varName))
1✔
162
        return tAny
1✔
163
      }
164
    }
165
    case 'RestElement':
166
    case 'SpreadElement':
167
      // TODO: Add support for rest and spread element
168
      return tAny
×
169
    case 'Program':
170
    case 'BlockStatement': {
171
      let returnType: Type = tVoid
245✔
172
      pushEnv(env)
245✔
173

174
      if (node.type === 'Program') {
245✔
175
        // Import statements should only exist in program body
176
        handleImportDeclarations(node)
162✔
177
      }
178

179
      // Add all declarations in the current scope to the environment first
180
      addTypeDeclarationsToEnvironment(node)
245✔
181

182
      // Check all statements in program/block body
183
      for (const stmt of node.body) {
244✔
184
        if (stmt.type === 'IfStatement' || stmt.type === 'ReturnStatement') {
1,556✔
185
          returnType = typeCheckAndReturnType(stmt)
65✔
186
          if (stmt.type === 'ReturnStatement') {
65✔
187
            // If multiple return statements are present, only take the first type
188
            break
55✔
189
          }
190
        } else {
191
          typeCheckAndReturnType(stmt)
1,491✔
192
        }
193
      }
194
      if (node.type === 'BlockStatement') {
243✔
195
        // Types are saved for programs, but not for blocks
196
        env.pop()
82✔
197
      }
198

199
      return returnType
243✔
200
    }
201
    case 'ExpressionStatement': {
202
      // Check expression
203
      return typeCheckAndReturnType(node.expression)
90✔
204
    }
205
    case 'ConditionalExpression':
206
    case 'IfStatement': {
207
      // Typecheck predicate against boolean
208
      const predicateType = typeCheckAndReturnType(node.test)
23✔
209
      checkForTypeMismatch(node, predicateType, tBool)
23✔
210

211
      // Return type is union of consequent and alternate type
212
      const consType = typeCheckAndReturnType(node.consequent)
23✔
213
      const altType = node.alternate ? typeCheckAndReturnType(node.alternate) : tUndef
23!
214
      return mergeTypes(node, consType, altType)
23✔
215
    }
216
    case 'UnaryExpression': {
217
      const argType = typeCheckAndReturnType(node.argument)
15✔
218
      const operator = node.operator
15✔
219
      switch (operator) {
15!
220
        case '-':
221
          // Typecheck against number
222
          checkForTypeMismatch(node, argType, tNumber)
5✔
223
          return tNumber
5✔
224
        case '!':
225
          // Typecheck against boolean
226
          checkForTypeMismatch(node, argType, tBool)
5✔
227
          return tBool
5✔
228
        case 'typeof':
229
          // No checking needed, typeof operation can be used on any type
230
          return tString
5✔
231
        default:
232
          throw new TypecheckError(node, 'Unknown operator')
×
233
      }
234
    }
235
    case 'BinaryExpression': {
236
      return typeCheckAndReturnBinaryExpressionType(node)
101✔
237
    }
238
    case 'LogicalExpression': {
239
      // Typecheck left type against boolean
240
      const leftType = typeCheckAndReturnType(node.left)
14✔
241
      checkForTypeMismatch(node, leftType, tBool)
14✔
242

243
      // Return type is union of boolean and right type
244
      const rightType = typeCheckAndReturnType(node.right)
14✔
245
      return mergeTypes(node, tBool, rightType)
14✔
246
    }
247
    case 'ArrowFunctionExpression': {
248
      return typeCheckAndReturnArrowFunctionType(node)
48✔
249
    }
250
    case 'FunctionDeclaration':
251
      if (node.id === null) {
45!
252
        // Block should not be reached since node.id is only null when function declaration
253
        // is part of `export default function`, which is not used in Source
254
        throw new TypecheckError(node, 'Function declaration should always have an identifier')
×
255
      }
256

257
      // Only identifiers/rest elements are used as function params in Source
258
      const params = node.params.filter(
45✔
259
        (param): param is tsEs.Identifier | tsEs.RestElement =>
260
          param.type === 'Identifier' || param.type === 'RestElement'
44!
261
      )
262
      if (params.length !== node.params.length) {
45!
263
        throw new TypecheckError(node, 'Unknown function parameter type')
×
264
      }
265
      const fnName = node.id.name
45✔
266
      const expectedReturnType = getTypeAnnotationType(node.returnType)
45✔
267

268
      // If the function has variable number of arguments, set function type as any
269
      // TODO: Add support for variable number of function arguments
270
      const hasVarArgs = params.reduce((prev, curr) => prev || curr.type === 'RestElement', false)
45✔
271
      if (hasVarArgs) {
45!
272
        setType(fnName, tAny, env)
×
273
        return tUndef
×
274
      }
275

276
      const types = getParamTypes(params)
45✔
277
      // Return type will always be last item in types array
278
      types.push(expectedReturnType)
45✔
279
      const fnType = tFunc(...types)
45✔
280

281
      // Typecheck function body, creating new environment to store arg types, return type and function type
282
      pushEnv(env)
45✔
283
      params.forEach((param: tsEs.Identifier) => {
45✔
284
        setType(param.name, getTypeAnnotationType(param.typeAnnotation), env)
44✔
285
      })
286
      // Set unique identifier so that typechecking can be carried out for return statements
287
      setType(RETURN_TYPE_IDENTIFIER, expectedReturnType, env)
45✔
288
      setType(fnName, fnType, env)
45✔
289
      const actualReturnType = typeCheckAndReturnType(node.body)
45✔
290
      env.pop()
45✔
291

292
      if (
45✔
293
        isEqual(actualReturnType, tVoid) &&
51✔
294
        !isEqual(expectedReturnType, tAny) &&
295
        !isEqual(expectedReturnType, tVoid)
296
      ) {
297
        // Type error where function does not return anything when it should
298
        context.errors.push(new FunctionShouldHaveReturnValueError(node))
1✔
299
      } else {
300
        checkForTypeMismatch(node, actualReturnType, expectedReturnType)
44✔
301
      }
302

303
      // Save function type in type env
304
      setType(fnName, fnType, env)
45✔
305
      return tUndef
45✔
306
    case 'VariableDeclaration': {
307
      if (node.kind === 'var') {
420!
308
        throw new TypecheckError(node, 'Variable declaration using "var" is not allowed')
×
309
      }
310
      if (node.declarations.length !== 1) {
420!
311
        throw new TypecheckError(
×
312
          node,
313
          'Variable declaration should have one and only one declaration'
314
        )
315
      }
316
      if (node.declarations[0].id.type !== 'Identifier') {
420!
317
        throw new TypecheckError(node, 'Variable declaration ID should be an identifier')
×
318
      }
319
      const id = node.declarations[0].id
420✔
320
      if (!node.declarations[0].init) {
420!
321
        throw new TypecheckError(node, 'Variable declaration must have value')
×
322
      }
323
      const init = node.declarations[0].init
420✔
324
      // Look up declared type if current environment contains name
325
      const expectedType = env[env.length - 1].typeMap.has(id.name)
420✔
326
        ? (lookupTypeAndRemoveForAllAndPredicateTypes(id.name) ??
414!
327
          getTypeAnnotationType(id.typeAnnotation))
328
        : getTypeAnnotationType(id.typeAnnotation)
329
      const initType = typeCheckAndReturnType(init)
420✔
330
      checkForTypeMismatch(node, initType, expectedType)
420✔
331

332
      // Save variable type and decl kind in type env
333
      setType(id.name, expectedType, env)
420✔
334
      setDeclKind(id.name, node.kind, env)
420✔
335
      return tUndef
420✔
336
    }
337
    case 'ClassDeclaration': {
338
      return tAny
69✔
339
    }
340
    case 'CallExpression': {
341
      const callee = node.callee
169✔
342
      const args = node.arguments
169✔
343
      if (context.chapter >= 2 && callee.type === 'Identifier') {
169✔
344
        // Special functions for Source 2+: list, head, tail, stream
345
        // The typical way of getting the return type of call expressions is insufficient to type lists,
346
        // as we need to save the pair representation of the list as well (lists are pairs).
347
        // head and tail should preserve the pair representation of lists whenever possible.
348
        // Hence, these 3 functions are handled separately.
349
        // Streams are treated similarly to lists, except only for Source 3+ and we do not need to store the pair representation.
350
        const fnName = callee.name
109✔
351
        if (fnName === 'list') {
109✔
352
          if (args.length === 0) {
24✔
353
            return tNull
2✔
354
          }
355
          // Element type is union of all types of arguments in list
356
          let elementType = typeCheckAndReturnType(args[0])
22✔
357
          for (let i = 1; i < args.length; i++) {
22✔
358
            elementType = mergeTypes(node, elementType, typeCheckAndReturnType(args[i]))
19✔
359
          }
360
          // Type the list as a pair, for use when checking for type mismatches against pairs
361
          let pairType = tPair(typeCheckAndReturnType(args[args.length - 1]), tNull)
22✔
362
          for (let i = args.length - 2; i >= 0; i--) {
22✔
363
            pairType = tPair(typeCheckAndReturnType(args[i]), pairType)
19✔
364
          }
365
          return tList(elementType, pairType)
22✔
366
        }
367
        if (fnName === 'head' || fnName === 'tail') {
85✔
368
          if (args.length !== 1) {
32✔
369
            context.errors.push(new InvalidNumberOfArgumentsTypeError(node, 1, args.length))
2✔
370
            return tAny
2✔
371
          }
372
          const actualType = typeCheckAndReturnType(args[0])
30✔
373
          // Argument should be either a pair or a list
374
          const expectedType = tUnion(tPair(tAny, tAny), tList(tAny))
30✔
375
          const numErrors = context.errors.length
30✔
376
          checkForTypeMismatch(node, actualType, expectedType)
30✔
377
          if (context.errors.length > numErrors) {
30!
378
            // If errors were found, return "any" type
379
            return tAny
×
380
          }
381
          return fnName === 'head' ? getHeadType(node, actualType) : getTailType(node, actualType)
30✔
382
        }
383
        if (fnName === 'stream' && context.chapter >= 3) {
53✔
384
          if (args.length === 0) {
2!
385
            return tNull
×
386
          }
387
          // Element type is union of all types of arguments in stream
388
          let elementType = typeCheckAndReturnType(args[0])
2✔
389
          for (let i = 1; i < args.length; i++) {
2✔
390
            elementType = mergeTypes(node, elementType, typeCheckAndReturnType(args[i]))
4✔
391
          }
392
          return tStream(elementType)
2✔
393
        }
394

395
        // Due to the use of generics, pair, list and stream functions are handled separately
396
        const pairFunctions = ['pair', 'is_pair', 'head', 'tail', 'is_null', 'set_head', 'set_tail']
51✔
397
        const listFunctions = [
51✔
398
          'list',
399
          'equal',
400
          'length',
401
          'map',
402
          'build_list',
403
          'for_each',
404
          'list_to_string',
405
          'append',
406
          'member',
407
          'remove',
408
          'remove_all',
409
          'filter',
410
          'enum_list',
411
          'list_ref',
412
          'accumulate',
413
          'reverse'
414
        ]
415
        const streamFunctions = [
51✔
416
          'stream_tail',
417
          'is_stream',
418
          'list_to_stream',
419
          'stream_to_list',
420
          'stream_length',
421
          'stream_map',
422
          'build_stream',
423
          'stream_for_each',
424
          'stream_reverse',
425
          'stream_append',
426
          'stream_member',
427
          'stream_remove',
428
          'stream_remove_all',
429
          'stream_filter',
430
          'enum_stream',
431
          'integers_from',
432
          'eval_stream',
433
          'stream_ref'
434
        ]
435
        if (
51✔
436
          pairFunctions.includes(fnName) ||
104✔
437
          listFunctions.includes(fnName) ||
438
          streamFunctions.includes(fnName)
439
        ) {
440
          const calleeType = cloneDeep(typeCheckAndReturnType(callee))
28✔
441
          if (calleeType.kind !== 'function') {
28!
UNCOV
442
            if (calleeType.kind !== 'primitive' || calleeType.name !== 'any') {
×
UNCOV
443
              context.errors.push(new TypeNotCallableError(node, formatTypeString(calleeType)))
×
444
            }
UNCOV
445
            return tAny
×
446
          }
447

448
          const expectedTypes = calleeType.parameterTypes
28✔
449
          let returnType = calleeType.returnType
28✔
450

451
          // Check argument types before returning declared return type
452
          if (args.length !== expectedTypes.length) {
28✔
453
            context.errors.push(
2✔
454
              new InvalidNumberOfArgumentsTypeError(node, expectedTypes.length, args.length)
455
            )
456
            return returnType
2✔
457
          }
458

459
          for (let i = 0; i < expectedTypes.length; i++) {
26✔
460
            const node = args[i]
51✔
461
            const actualType = typeCheckAndReturnType(node)
51✔
462
            // Get all valid type variable mappings for current argument
463
            const mappings = getTypeVariableMappings(node, actualType, expectedTypes[i])
51✔
464
            // Apply type variable mappings to subsequent argument types and return type
465
            for (const mapping of mappings) {
51✔
466
              const typeVar = tVar(mapping[0])
53✔
467
              const typeToSub = mapping[1]
53✔
468
              for (let j = i; j < expectedTypes.length; j++) {
53✔
469
                expectedTypes[j] = substituteVariableTypes(expectedTypes[j], typeVar, typeToSub)
84✔
470
              }
471
              returnType = substituteVariableTypes(returnType, typeVar, typeToSub)
53✔
472
            }
473
            // Typecheck current argument
474
            checkForTypeMismatch(node, actualType, expectedTypes[i])
51✔
475
          }
476
          return returnType
26✔
477
        }
478
      }
479
      const calleeType = cloneDeep(typeCheckAndReturnType(callee))
83✔
480
      if (calleeType.kind !== 'function') {
83✔
481
        if (calleeType.kind !== 'primitive' || calleeType.name !== 'any') {
11✔
482
          context.errors.push(new TypeNotCallableError(node, formatTypeString(calleeType)))
1✔
483
        }
484
        return tAny
11✔
485
      }
486

487
      const expectedTypes = calleeType.parameterTypes
72✔
488
      let returnType = calleeType.returnType
72✔
489

490
      // If any of the arguments is a spread element, skip type checking of arguments
491
      // TODO: Add support for type checking of call expressions with spread elements
492
      const hasVarArgs = args.reduce((prev, curr) => prev || curr.type === 'SpreadElement', false)
112✔
493
      if (hasVarArgs) {
72!
UNCOV
494
        return returnType
×
495
      }
496

497
      // Check argument types before returning declared return type
498
      if (args.length !== expectedTypes.length) {
72✔
499
        context.errors.push(
8✔
500
          new InvalidNumberOfArgumentsTypeError(node, expectedTypes.length, args.length)
501
        )
502
        return returnType
8✔
503
      }
504

505
      for (let i = 0; i < expectedTypes.length; i++) {
64✔
506
        const node = args[i]
94✔
507
        const actualType = typeCheckAndReturnType(node)
94✔
508
        // Typecheck current argument
509
        checkForTypeMismatch(node, actualType, expectedTypes[i])
94✔
510
      }
511
      return returnType
64✔
512
    }
513
    case 'AssignmentExpression':
514
      const expectedType = typeCheckAndReturnType(node.left)
25✔
515
      const actualType = typeCheckAndReturnType(node.right)
25✔
516

517
      if (node.left.type === 'Identifier' && lookupDeclKind(node.left.name, env) === 'const') {
25✔
518
        context.errors.push(new ConstNotAssignableTypeError(node, node.left.name))
1✔
519
      }
520
      checkForTypeMismatch(node, actualType, expectedType)
25✔
521
      return actualType
25✔
522
    case 'ArrayExpression':
523
      // Casting is safe here as Source disallows use of spread elements and holes in arrays
524
      const elements = node.elements.filter(
30✔
525
        (elem): elem is Exclude<tsEs.ArrayExpression['elements'][0], tsEs.SpreadElement | null> =>
526
          elem !== null && elem.type !== 'SpreadElement'
54✔
527
      )
528
      if (elements.length !== node.elements.length) {
30!
UNCOV
529
        throw new TypecheckError(node, 'Disallowed array element type')
×
530
      }
531
      if (elements.length === 0) {
30✔
532
        return tArray(tAny)
2✔
533
      }
534
      const elementTypes = elements.map(elem => typeCheckAndReturnType(elem))
54✔
535
      return tArray(mergeTypes(node, ...elementTypes))
28✔
536
    case 'MemberExpression':
537
      const indexType = typeCheckAndReturnType(node.property)
14✔
538
      const objectType = typeCheckAndReturnType(node.object)
14✔
539
      // Typecheck index against number
540
      if (hasTypeMismatchErrors(node, indexType, tNumber)) {
14✔
541
        context.errors.push(new InvalidIndexTypeError(node, formatTypeString(indexType, true)))
5✔
542
      }
543
      // Expression being accessed must be array
544
      if (objectType.kind !== 'array') {
14✔
545
        context.errors.push(new InvalidArrayAccessTypeError(node, formatTypeString(objectType)))
5✔
546
        return tAny
5✔
547
      }
548
      return objectType.elementType
9✔
549
    case 'ReturnStatement': {
550
      if (!node.argument) {
55!
551
        // Skip typecheck as unspecified literals will be handled by the noImplicitReturnUndefined rule,
552
        // which is run after typechecking
UNCOV
553
        return tUndef
×
554
      } else {
555
        // Check type only if return type is specified
556
        const expectedType = lookupTypeAndRemoveForAllAndPredicateTypes(RETURN_TYPE_IDENTIFIER)
55✔
557
        if (expectedType) {
55!
558
          const argumentType = typeCheckAndReturnType(node.argument)
55✔
559
          checkForTypeMismatch(node, argumentType, expectedType)
55✔
560
          return expectedType
55✔
561
        } else {
UNCOV
562
          return typeCheckAndReturnType(node.argument)
×
563
        }
564
      }
565
    }
566
    case 'WhileStatement': {
567
      // Typecheck predicate against boolean
568
      const testType = typeCheckAndReturnType(node.test)
5✔
569
      checkForTypeMismatch(node, testType, tBool)
5✔
570
      return typeCheckAndReturnType(node.body)
5✔
571
    }
572
    case 'ForStatement': {
573
      // Add new environment so that new variable declared in init node can be isolated to within for statement only
574
      pushEnv(env)
7✔
575
      if (node.init) {
7✔
576
        typeCheckAndReturnType(node.init)
7✔
577
      }
578
      if (node.test) {
7✔
579
        // Typecheck predicate against boolean
580
        const testType = typeCheckAndReturnType(node.test)
7✔
581
        checkForTypeMismatch(node, testType, tBool)
7✔
582
      }
583
      if (node.update) {
7✔
584
        typeCheckAndReturnType(node.update)
7✔
585
      }
586
      const bodyType = typeCheckAndReturnType(node.body)
7✔
587
      env.pop()
7✔
588
      return bodyType
7✔
589
    }
590
    case 'ImportDeclaration':
591
      // No typechecking needed, import declarations have already been handled separately
592
      return tUndef
25✔
593
    case 'TSTypeAliasDeclaration':
594
      // No typechecking needed, type has already been added to environment
595
      return tUndef
834✔
596
    case 'TSAsExpression':
597
      const originalType = typeCheckAndReturnType(node.expression)
9✔
598
      const typeToCastTo = getTypeAnnotationType(node)
9✔
599
      const formatAsLiteral =
600
        typeContainsLiteralType(originalType) || typeContainsLiteralType(typeToCastTo)
9✔
601
      // Type to cast to must have some overlap with original type
602
      if (hasTypeMismatchErrors(node, typeToCastTo, originalType)) {
9✔
603
        context.errors.push(
2✔
604
          new TypecastError(
605
            node,
606
            formatTypeString(originalType, formatAsLiteral),
607
            formatTypeString(typeToCastTo, formatAsLiteral)
608
          )
609
        )
610
      }
611
      return typeToCastTo
9✔
612
    case 'TSInterfaceDeclaration':
UNCOV
613
      throw new TypecheckError(node, 'Interface declarations are not allowed')
×
614
    case 'ExportNamedDeclaration':
UNCOV
615
      return typeCheckAndReturnType(node.declaration!)
×
616
    default:
UNCOV
617
      throw new TypecheckError(node, 'Unknown node type')
×
618
  }
619
}
620

621
/**
622
 * Adds types for imported functions to the type environment.
623
 * All imports have their types set to the "any" primitive type.
624
 */
625
function handleImportDeclarations(node: tsEs.Program) {
626
  const importStmts: tsEs.ImportDeclaration[] = node.body.filter(
162✔
627
    (stmt): stmt is tsEs.ImportDeclaration => stmt.type === 'ImportDeclaration'
1,470✔
628
  )
629
  if (importStmts.length === 0) {
162✔
630
    return
137✔
631
  }
632

633
  const importedModuleTypesTextMap: Record<string, string> = {}
25✔
634

635
  importStmts.forEach(stmt => {
25✔
636
    // Source only uses strings for import source value
637
    const moduleName = stmt.source.value as string
25✔
638

639
    // Precondition: loadedModulesTypes are fetched from the modules repo
640
    const moduleTypesTextMap = context.nativeStorage.loadedModuleTypes
25✔
641

642
    // Module has no types
643
    if (!moduleTypesTextMap[moduleName]) {
25✔
644
      // Set all imported names to be of type any
645
      // TODO: Consider switching to 'Module not supported' error after more modules have been typed
646
      stmt.specifiers.map(spec => {
2✔
647
        if (spec.type !== 'ImportSpecifier') {
4!
UNCOV
648
          throw new TypecheckError(stmt, 'Unknown specifier type')
×
649
        }
650
        setType(spec.local.name, tAny, env)
4✔
651
      })
652
      return
2✔
653
    }
654

655
    // Add prelude for module, which contains types that are shared by
656
    // multiple variables and functions in the module
657
    if (!importedModuleTypesTextMap[moduleName]) {
23!
658
      importedModuleTypesTextMap[moduleName] = moduleTypesTextMap[moduleName]['prelude']
23✔
659
    } else {
UNCOV
660
      importedModuleTypesTextMap[moduleName] =
×
661
        importedModuleTypesTextMap[moduleName] + '\n' + moduleTypesTextMap[moduleName]['prelude']
662
    }
663

664
    stmt.specifiers.forEach(spec => {
23✔
665
      if (spec.type !== 'ImportSpecifier') {
49!
UNCOV
666
        throw new TypecheckError(stmt, 'Unknown specifier type')
×
667
      }
668

669
      const importedName = spec.local.name
49✔
670
      const importedType = moduleTypesTextMap[moduleName][importedName]
49✔
671
      if (!importedType) {
49!
672
        // Set imported name to be of type any to prevent further typecheck errors
UNCOV
673
        setType(importedName, tAny, env)
×
UNCOV
674
        return
×
675
      }
676

677
      importedModuleTypesTextMap[moduleName] =
49✔
678
        importedModuleTypesTextMap[moduleName] + '\n' + importedType
679
    })
680
  })
681

682
  Object.values(importedModuleTypesTextMap).forEach(typesText => {
25✔
683
    const parsedModuleTypes = babelParse(typesText, {
23✔
684
      sourceType: 'module',
685
      plugins: ['typescript', 'estree']
686
    }).program as unknown as tsEs.Program
687
    typeCheckAndReturnType(parsedModuleTypes)
23✔
688
  })
689
}
690

691
/**
692
 * Adds all types for variable/function/type declarations to the current environment.
693
 * This is so that the types can be referenced before the declarations are initialized.
694
 * Type checking is not carried out as this function is only responsible for hoisting declarations.
695
 */
696
function addTypeDeclarationsToEnvironment(node: tsEs.Program | tsEs.BlockStatement) {
697
  node.body.forEach(bodyNode => {
245✔
698
    switch (bodyNode.type) {
1,557✔
699
      case 'FunctionDeclaration':
700
        if (bodyNode.id === null) {
45!
UNCOV
701
          throw new Error(
×
702
            'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'
703
          )
704
        }
705
        // Only identifiers/rest elements are used as function params in Source
706
        const params = bodyNode.params.filter(
45✔
707
          (param): param is tsEs.Identifier | tsEs.RestElement =>
708
            param.type === 'Identifier' || param.type === 'RestElement'
44!
709
        )
710
        if (params.length !== bodyNode.params.length) {
45!
UNCOV
711
          throw new TypecheckError(bodyNode, 'Unknown function parameter type')
×
712
        }
713
        const fnName = bodyNode.id.name
45✔
714
        const returnType = getTypeAnnotationType(bodyNode.returnType)
45✔
715

716
        // If the function has variable number of arguments, set function type as any
717
        // TODO: Add support for variable number of function arguments
718
        const hasVarArgs = params.reduce((prev, curr) => prev || curr.type === 'RestElement', false)
45✔
719
        if (hasVarArgs) {
45!
UNCOV
720
          setType(fnName, tAny, env)
×
UNCOV
721
          break
×
722
        }
723

724
        const types = getParamTypes(params)
45✔
725
        // Return type will always be last item in types array
726
        types.push(returnType)
45✔
727
        const fnType = tFunc(...types)
45✔
728

729
        // Save function type in type env
730
        setType(fnName, fnType, env)
45✔
731
        break
45✔
732
      case 'VariableDeclaration':
733
        if (bodyNode.kind === 'var') {
414!
UNCOV
734
          throw new TypecheckError(bodyNode, 'Variable declaration using "var" is not allowed')
×
735
        }
736
        if (bodyNode.declarations.length !== 1) {
414!
737
          throw new TypecheckError(
×
738
            bodyNode,
739
            'Variable declaration should have one and only one declaration'
740
          )
741
        }
742
        if (bodyNode.declarations[0].id.type !== 'Identifier') {
414!
UNCOV
743
          throw new TypecheckError(bodyNode, 'Variable declaration ID should be an identifier')
×
744
        }
745
        const id = bodyNode.declarations[0].id as tsEs.Identifier
414✔
746
        const expectedType = getTypeAnnotationType(id.typeAnnotation)
414✔
747

748
        // Save variable type and decl kind in type env
749
        setType(id.name, expectedType, env)
414✔
750
        setDeclKind(id.name, bodyNode.kind, env)
414✔
751
        break
414✔
752
      case 'ClassDeclaration':
753
        const className: string = bodyNode.id.name
69✔
754
        const classType: Variable = {
69✔
755
          kind: 'variable',
756
          name: className,
757
          constraint: 'none'
758
        }
759
        setType(className, classType, env)
69✔
760
        setTypeAlias(className, classType, env)
69✔
761
        break
69✔
762
      case 'TSTypeAliasDeclaration':
763
        if (node.type === 'BlockStatement') {
835✔
764
          throw new TypecheckError(
1✔
765
            bodyNode,
766
            'Type alias declarations may only appear at the top level'
767
          )
768
        }
769
        const alias = bodyNode.id.name
834✔
770
        if (Object.values(typeAnnotationKeywordToBasicTypeMap).includes(alias as TSBasicType)) {
834✔
771
          context.errors.push(new TypeAliasNameNotAllowedError(bodyNode, alias))
4✔
772
          break
4✔
773
        }
774
        if (lookupTypeAlias(alias, env) !== undefined) {
830✔
775
          // Only happens when attempting to declare type aliases that share names with predeclared types (e.g. Pair, List)
776
          // Declaration of two type aliases with the same name will be caught as syntax error by parser
777
          context.errors.push(new DuplicateTypeAliasError(bodyNode, alias))
3✔
778
          break
3✔
779
        }
780

781
        let type: BindableType = tAny
827✔
782
        if (bodyNode.typeParameters && bodyNode.typeParameters.params.length > 0) {
827✔
783
          const typeParams: Variable[] = []
4✔
784
          // Check validity of type parameters
785
          pushEnv(env)
4✔
786
          bodyNode.typeParameters.params.forEach(param => {
4✔
787
            if (param.type !== 'TSTypeParameter') {
10!
UNCOV
788
              throw new TypecheckError(bodyNode, 'Invalid type parameter type')
×
789
            }
790
            const name = param.name
10✔
791
            if (Object.values(typeAnnotationKeywordToBasicTypeMap).includes(name as TSBasicType)) {
10✔
792
              context.errors.push(new TypeParameterNameNotAllowedError(param, name))
4✔
793
              return
4✔
794
            }
795
            typeParams.push(tVar(name))
6✔
796
          })
797
          type = tForAll(getTypeAnnotationType(bodyNode), typeParams)
4✔
798
          env.pop()
4✔
799
        } else {
800
          type = getTypeAnnotationType(bodyNode)
823✔
801
        }
802
        setTypeAlias(alias, type, env)
827✔
803
        break
827✔
804
      default:
805
        break
194✔
806
    }
807
  })
808
}
809

810
/**
811
 * Typechecks the body of a binary expression, adding any type errors to context if necessary.
812
 * Then, returns the type of the binary expression, inferred based on the operator.
813
 */
814
function typeCheckAndReturnBinaryExpressionType(node: tsEs.BinaryExpression): Type {
815
  const leftType = typeCheckAndReturnType(node.left)
101✔
816
  const rightType = typeCheckAndReturnType(node.right)
101✔
817
  const leftTypeString = formatTypeString(leftType)
101✔
818
  const rightTypeString = formatTypeString(rightType)
101✔
819
  const operator = node.operator
101✔
820
  switch (operator) {
101!
821
    case '-':
822
    case '*':
823
    case '/':
824
    case '%':
825
      // Typecheck both sides against number
826
      checkForTypeMismatch(node, leftType, tNumber)
11✔
827
      checkForTypeMismatch(node, rightType, tNumber)
11✔
828
      // Return type number
829
      return tNumber
11✔
830
    case '+':
831
      // Typecheck both sides against number or string
832
      // However, the case where one side is string and other side is number is not allowed
833
      if (leftTypeString === 'number' || leftTypeString === 'string') {
57✔
834
        checkForTypeMismatch(node, rightType, leftType)
43✔
835
        // If left type is number or string, return left type
836
        return leftType
43✔
837
      }
838
      if (rightTypeString === 'number' || rightTypeString === 'string') {
14✔
839
        checkForTypeMismatch(node, leftType, rightType)
6✔
840
        // If left type is not number or string but right type is number or string, return right type
841
        return rightType
6✔
842
      }
843

844
      checkForTypeMismatch(node, leftType, tUnion(tNumber, tString))
8✔
845
      checkForTypeMismatch(node, rightType, tUnion(tNumber, tString))
8✔
846
      // Return type is number | string if both left and right are neither number nor string
847
      return tUnion(tNumber, tString)
8✔
848
    case '<':
849
    case '<=':
850
    case '>':
851
    case '>=':
852
    case '!==':
853
    case '===':
854
      // In Source 3 and above, skip type checking as equality can be applied between two items of any type
855
      if (context.chapter > 2 && (operator === '===' || operator === '!==')) {
33✔
856
        return tBool
13✔
857
      }
858
      // Typecheck both sides against number or string
859
      // However, case where one side is string and other side is number is not allowed
860
      if (leftTypeString === 'number' || leftTypeString === 'string') {
20✔
861
        checkForTypeMismatch(node, rightType, leftType)
14✔
862
        return tBool
14✔
863
      }
864
      if (rightTypeString === 'number' || rightTypeString === 'string') {
6✔
865
        checkForTypeMismatch(node, leftType, rightType)
3✔
866
        return tBool
3✔
867
      }
868

869
      checkForTypeMismatch(node, leftType, tUnion(tNumber, tString))
3✔
870
      checkForTypeMismatch(node, rightType, tUnion(tNumber, tString))
3✔
871
      // Return type boolean
872
      return tBool
3✔
873
    default:
UNCOV
874
      throw new TypecheckError(node, 'Unknown operator')
×
875
  }
876
}
877

878
/**
879
 * Typechecks the body of an arrow function, adding any type errors to context if necessary.
880
 * Then, returns the inferred/declared type of the function.
881
 */
882
function typeCheckAndReturnArrowFunctionType(node: tsEs.ArrowFunctionExpression): Type {
883
  // Only identifiers/rest elements are used as function params in Source
884
  const params = node.params.filter(
48✔
885
    (param): param is tsEs.Identifier | tsEs.RestElement =>
886
      param.type === 'Identifier' || param.type === 'RestElement'
70!
887
  )
888
  if (params.length !== node.params.length) {
48!
UNCOV
889
    throw new TypecheckError(node, 'Unknown function parameter type')
×
890
  }
891
  const expectedReturnType = getTypeAnnotationType(node.returnType)
48✔
892

893
  // If the function has variable number of arguments, set function type as any
894
  // TODO: Add support for variable number of function arguments
895
  const hasVarArgs = params.reduce((prev, curr) => prev || curr.type === 'RestElement', false)
70✔
896
  if (hasVarArgs) {
48!
UNCOV
897
    return tAny
×
898
  }
899

900
  // Typecheck function body, creating new environment to store arg types and return type
901
  pushEnv(env)
48✔
902
  params.forEach((param: tsEs.Identifier) => {
48✔
903
    setType(param.name, getTypeAnnotationType(param.typeAnnotation), env)
70✔
904
  })
905
  // Set unique identifier so that typechecking can be carried out for return statements
906
  setType(RETURN_TYPE_IDENTIFIER, expectedReturnType, env)
48✔
907
  const actualReturnType = typeCheckAndReturnType(node.body)
48✔
908
  env.pop()
48✔
909

910
  if (
48✔
911
    isEqual(actualReturnType, tVoid) &&
52✔
912
    !isEqual(expectedReturnType, tAny) &&
913
    !isEqual(expectedReturnType, tVoid)
914
  ) {
915
    // Type error where function does not return anything when it should
916
    context.errors.push(new FunctionShouldHaveReturnValueError(node))
1✔
917
  } else {
918
    checkForTypeMismatch(node, actualReturnType, expectedReturnType)
47✔
919
  }
920

921
  const types = getParamTypes(params)
48✔
922
  // Return type will always be last item in types array
923
  types.push(node.returnType ? expectedReturnType : actualReturnType)
48✔
924
  return tFunc(...types)
48✔
925
}
926

927
/**
928
 * Recurses through the two given types and returns an array of tuples
929
 * that map type variable names to the type to substitute.
930
 */
931
function getTypeVariableMappings(
932
  node: tsEs.Node,
933
  actualType: Type,
934
  expectedType: Type
935
): [string, Type][] {
936
  // If type variable mapping is found, terminate early
937
  if (expectedType.kind === 'variable') {
67✔
938
    return [[expectedType.name, actualType]]
53✔
939
  }
940
  // If actual type is a type reference, expand type first
941
  if (actualType.kind === 'variable') {
14✔
942
    actualType = lookupTypeAliasAndRemoveForAllTypes(node, actualType)
5✔
943
  }
944
  const mappings: [string, Type][] = []
14✔
945
  switch (expectedType.kind) {
14✔
946
    case 'pair':
947
      if (actualType.kind === 'list') {
2!
UNCOV
948
        if (actualType.typeAsPair !== undefined) {
×
UNCOV
949
          mappings.push(
×
950
            ...getTypeVariableMappings(node, actualType.typeAsPair.headType, expectedType.headType)
951
          )
UNCOV
952
          mappings.push(
×
953
            ...getTypeVariableMappings(node, actualType.typeAsPair.tailType, expectedType.tailType)
954
          )
955
        } else {
UNCOV
956
          mappings.push(
×
957
            ...getTypeVariableMappings(node, actualType.elementType, expectedType.headType)
958
          )
UNCOV
959
          mappings.push(
×
960
            ...getTypeVariableMappings(node, actualType.elementType, expectedType.tailType)
961
          )
962
        }
963
      }
964
      if (actualType.kind === 'pair') {
2✔
965
        mappings.push(...getTypeVariableMappings(node, actualType.headType, expectedType.headType))
2✔
966
        mappings.push(...getTypeVariableMappings(node, actualType.tailType, expectedType.tailType))
2✔
967
      }
968
      break
2✔
969
    case 'list':
970
      if (actualType.kind === 'list') {
3✔
971
        mappings.push(
3✔
972
          ...getTypeVariableMappings(node, actualType.elementType, expectedType.elementType)
973
        )
974
      }
975
      break
3✔
976
    case 'function':
977
      if (
5✔
978
        actualType.kind === 'function' &&
10✔
979
        actualType.parameterTypes.length === expectedType.parameterTypes.length
980
      ) {
981
        for (let i = 0; i < actualType.parameterTypes.length; i++) {
5✔
982
          mappings.push(
4✔
983
            ...getTypeVariableMappings(
984
              node,
985
              actualType.parameterTypes[i],
986
              expectedType.parameterTypes[i]
987
            )
988
          )
989
        }
990
        mappings.push(
5✔
991
          ...getTypeVariableMappings(node, actualType.returnType, expectedType.returnType)
992
        )
993
      }
994
      break
5✔
995
    default:
996
      break
4✔
997
  }
998
  return mappings
14✔
999
}
1000

1001
/**
1002
 * Checks if the actual type matches the expected type.
1003
 * If not, adds type mismatch error to context.
1004
 */
1005
function checkForTypeMismatch(node: tsEs.Node, actualType: Type, expectedType: Type): void {
1006
  const formatAsLiteral =
1007
    typeContainsLiteralType(expectedType) || typeContainsLiteralType(actualType)
935✔
1008
  if (hasTypeMismatchErrors(node, actualType, expectedType)) {
935✔
1009
    context.errors.push(
156✔
1010
      new TypeMismatchError(
1011
        node,
1012
        formatTypeString(actualType, formatAsLiteral),
1013
        formatTypeString(expectedType, formatAsLiteral)
1014
      )
1015
    )
1016
  }
1017
}
1018

1019
/**
1020
 * Returns true if given type contains literal type, false otherwise.
1021
 * This is necessary to determine whether types should be formatted as
1022
 * literal type or primitive type in error messages.
1023
 */
1024
function typeContainsLiteralType(type: Type): boolean {
1025
  switch (type.kind) {
2,463✔
1026
    case 'primitive':
1027
    case 'variable':
1028
      return false
2,040✔
1029
    case 'literal':
1030
      return true
19✔
1031
    case 'function':
1032
      return (
66✔
1033
        typeContainsLiteralType(type.returnType) ||
132✔
1034
        type.parameterTypes.reduce((prev, curr) => prev || typeContainsLiteralType(curr), false)
86✔
1035
      )
1036
    case 'union':
1037
      return type.types.reduce((prev, curr) => prev || typeContainsLiteralType(curr), false)
446✔
1038
    default:
1039
      return false
152✔
1040
  }
1041
}
1042

1043
/**
1044
 * Returns true if the actual type and the expected type do not match, false otherwise.
1045
 * The two types will not match if the intersection of the two types is empty.
1046
 *
1047
 * @param node Current node being checked
1048
 * @param actualType Type being checked
1049
 * @param expectedType Type the actual type is being checked against
1050
 * @param visitedTypeAliasesForActualType Array that keeps track of previously encountered type aliases
1051
 * for actual type to prevent infinite recursion
1052
 * @param visitedTypeAliasesForExpectedType Array that keeps track of previously encountered type aliases
1053
 * for expected type to prevent infinite recursion
1054
 * @param skipTypeAliasExpansion If true, type aliases are not expanded (e.g. in type alias declarations)
1055
 * @returns true if the actual type and the expected type do not match, false otherwise
1056
 */
1057
function hasTypeMismatchErrors(
1058
  node: tsEs.Node,
1059
  actualType: Type,
1060
  expectedType: Type,
1061
  visitedTypeAliasesForActualType: Variable[] = [],
958✔
1062
  visitedTypeAliasesForExpectedType: Variable[] = [],
958✔
1063
  skipTypeAliasExpansion: boolean = false
958✔
1064
): boolean {
1065
  if (isEqual(actualType, tAny) || isEqual(expectedType, tAny)) {
7,669✔
1066
    // Exit early as "any" is guaranteed not to cause type mismatch errors
1067
    return false
207✔
1068
  }
1069
  if (expectedType.kind !== 'variable' && actualType.kind === 'variable') {
7,462✔
1070
    // If the expected type is not a variable type but the actual type is a variable type,
1071
    // Swap the order of the types around
1072
    // This removes the need to check if the actual type is a variable type in all of the switch cases
1073
    return hasTypeMismatchErrors(
106✔
1074
      node,
1075
      expectedType,
1076
      actualType,
1077
      visitedTypeAliasesForExpectedType,
1078
      visitedTypeAliasesForActualType,
1079
      skipTypeAliasExpansion
1080
    )
1081
  }
1082
  if (expectedType.kind !== 'union' && actualType.kind === 'union') {
7,356✔
1083
    // If the expected type is not a union type but the actual type is a union type,
1084
    // Check if the expected type matches any of the actual types
1085
    // This removes the need to check if the actual type is a union type in all of the switch cases
1086
    return !containsType(
71✔
1087
      node,
1088
      actualType.types,
1089
      expectedType,
1090
      visitedTypeAliasesForActualType,
1091
      visitedTypeAliasesForExpectedType
1092
    )
1093
  }
1094
  switch (expectedType.kind) {
7,285!
1095
    case 'variable':
1096
      if (actualType.kind === 'variable') {
3,967✔
1097
        // If both are variable types, types match if both name and type arguments match
1098
        if (expectedType.name === actualType.name) {
3,525✔
1099
          if (expectedType.typeArgs === undefined || expectedType.typeArgs.length === 0) {
42✔
1100
            return actualType.typeArgs === undefined ? false : actualType.typeArgs.length !== 0
12!
1101
          }
1102
          if (actualType.typeArgs?.length !== expectedType.typeArgs.length) {
30!
UNCOV
1103
            return true
×
1104
          }
1105
          for (let i = 0; i < expectedType.typeArgs.length; i++) {
30✔
1106
            if (
31✔
1107
              hasTypeMismatchErrors(
1108
                node,
1109
                actualType.typeArgs[i],
1110
                expectedType.typeArgs[i],
1111
                visitedTypeAliasesForActualType,
1112
                visitedTypeAliasesForExpectedType,
1113
                skipTypeAliasExpansion
1114
              )
1115
            ) {
1116
              return true
22✔
1117
            }
1118
          }
1119
          return false
8✔
1120
        }
1121
      }
1122
      for (const visitedType of visitedTypeAliasesForExpectedType) {
3,925✔
1123
        if (isEqual(visitedType, expectedType)) {
3,309✔
1124
          // Circular dependency, treat as type mismatch
1125
          return true
63✔
1126
        }
1127
      }
1128
      // Skips expansion, treat as type mismatch
1129
      if (skipTypeAliasExpansion) {
3,862✔
1130
        return true
3,447✔
1131
      }
1132
      visitedTypeAliasesForExpectedType.push(expectedType)
415✔
1133
      // Expand type and continue typechecking
1134
      const aliasType = lookupTypeAliasAndRemoveForAllTypes(node, expectedType)
415✔
1135
      return hasTypeMismatchErrors(
415✔
1136
        node,
1137
        actualType,
1138
        aliasType,
1139
        visitedTypeAliasesForActualType,
1140
        visitedTypeAliasesForExpectedType,
1141
        skipTypeAliasExpansion
1142
      )
1143
    case 'primitive':
1144
      if (actualType.kind === 'literal') {
1,222✔
1145
        return expectedType.value === undefined
11!
1146
          ? typeof actualType.value !== expectedType.name
1147
          : actualType.value !== expectedType.value
1148
      }
1149
      if (actualType.kind !== 'primitive') {
1,211✔
1150
        return true
1✔
1151
      }
1152
      return actualType.name !== expectedType.name
1,210✔
1153
    case 'function':
1154
      if (actualType.kind !== 'function') {
37!
UNCOV
1155
        return true
×
1156
      }
1157
      // Check parameter types
1158
      const actualParamTypes = actualType.parameterTypes
37✔
1159
      const expectedParamTypes = expectedType.parameterTypes
37✔
1160
      if (actualParamTypes.length !== expectedParamTypes.length) {
37✔
1161
        return true
3✔
1162
      }
1163
      for (let i = 0; i < actualType.parameterTypes.length; i++) {
34✔
1164
        // Note that actual and expected types are swapped here
1165
        // to simulate contravariance for function parameter types
1166
        // This will be useful if type checking in Source Typed were to be made stricter in the future
1167
        if (
36✔
1168
          hasTypeMismatchErrors(
1169
            node,
1170
            expectedParamTypes[i],
1171
            actualParamTypes[i],
1172
            visitedTypeAliasesForExpectedType,
1173
            visitedTypeAliasesForActualType,
1174
            skipTypeAliasExpansion
1175
          )
1176
        ) {
1177
          return true
3✔
1178
        }
1179
      }
1180
      // Check return type
1181
      return hasTypeMismatchErrors(
31✔
1182
        node,
1183
        actualType.returnType,
1184
        expectedType.returnType,
1185
        visitedTypeAliasesForActualType,
1186
        visitedTypeAliasesForExpectedType,
1187
        skipTypeAliasExpansion
1188
      )
1189
    case 'union':
1190
      // If actual type is not union type, check if actual type matches one of the expected types
1191
      if (actualType.kind !== 'union') {
111✔
1192
        return !containsType(node, expectedType.types, actualType)
75✔
1193
      }
1194
      // If both are union types, there are no type errors as long as one of the types match
1195
      for (const type of actualType.types) {
36✔
1196
        if (containsType(node, expectedType.types, type)) {
36✔
1197
          return false
36✔
1198
        }
1199
      }
UNCOV
1200
      return true
×
1201
    case 'literal':
1202
      if (actualType.kind !== 'literal' && actualType.kind !== 'primitive') {
1,680!
UNCOV
1203
        return true
×
1204
      }
1205
      if (actualType.kind === 'primitive' && actualType.value === undefined) {
1,680✔
1206
        return actualType.name !== typeof expectedType.value
17✔
1207
      }
1208
      return actualType.value !== expectedType.value
1,663✔
1209
    case 'pair':
1210
      if (actualType.kind === 'list') {
196✔
1211
        // Special case, as lists are pairs
1212
        if (actualType.typeAsPair !== undefined) {
30✔
1213
          // If pair representation of list is present, check against pair type
1214
          return hasTypeMismatchErrors(
7✔
1215
            node,
1216
            actualType.typeAsPair,
1217
            expectedType,
1218
            visitedTypeAliasesForActualType,
1219
            visitedTypeAliasesForExpectedType,
1220
            skipTypeAliasExpansion
1221
          )
1222
        }
1223
        // Head of pair should match list element type; tail of pair should match list type
1224
        return (
23✔
1225
          hasTypeMismatchErrors(
23!
1226
            node,
1227
            actualType.elementType,
1228
            expectedType.headType,
1229
            visitedTypeAliasesForActualType,
1230
            visitedTypeAliasesForExpectedType,
1231
            skipTypeAliasExpansion
1232
          ) ||
1233
          hasTypeMismatchErrors(
1234
            node,
1235
            actualType,
1236
            expectedType.tailType,
1237
            visitedTypeAliasesForActualType,
1238
            visitedTypeAliasesForExpectedType,
1239
            skipTypeAliasExpansion
1240
          )
1241
        )
1242
      }
1243
      if (actualType.kind !== 'pair') {
166✔
1244
        return true
100✔
1245
      }
1246
      return (
66✔
1247
        hasTypeMismatchErrors(
106✔
1248
          node,
1249
          actualType.headType,
1250
          expectedType.headType,
1251
          visitedTypeAliasesForActualType,
1252
          visitedTypeAliasesForExpectedType,
1253
          skipTypeAliasExpansion
1254
        ) ||
1255
        hasTypeMismatchErrors(
1256
          node,
1257
          actualType.tailType,
1258
          expectedType.tailType,
1259
          visitedTypeAliasesForActualType,
1260
          visitedTypeAliasesForExpectedType,
1261
          skipTypeAliasExpansion
1262
        )
1263
      )
1264
    case 'list':
1265
      if (isEqual(actualType, tNull)) {
40✔
1266
        // Null matches against any list type as null is empty list
1267
        return false
5✔
1268
      }
1269
      if (actualType.kind === 'pair') {
35✔
1270
        // Special case, as pairs can be lists
1271
        if (expectedType.typeAsPair !== undefined) {
10!
1272
          // If pair representation of list is present, check against pair type
UNCOV
1273
          return hasTypeMismatchErrors(
×
1274
            node,
1275
            actualType,
1276
            expectedType.typeAsPair,
1277
            visitedTypeAliasesForActualType,
1278
            visitedTypeAliasesForExpectedType,
1279
            skipTypeAliasExpansion
1280
          )
1281
        }
1282
        // Head of pair should match list element type; tail of pair should match list type
1283
        return (
10✔
1284
          hasTypeMismatchErrors(
20✔
1285
            node,
1286
            actualType.headType,
1287
            expectedType.elementType,
1288
            visitedTypeAliasesForActualType,
1289
            visitedTypeAliasesForExpectedType,
1290
            skipTypeAliasExpansion
1291
          ) ||
1292
          hasTypeMismatchErrors(
1293
            node,
1294
            actualType.tailType,
1295
            expectedType,
1296
            visitedTypeAliasesForActualType,
1297
            visitedTypeAliasesForExpectedType,
1298
            skipTypeAliasExpansion
1299
          )
1300
        )
1301
      }
1302
      if (actualType.kind !== 'list') {
25✔
1303
        return true
1✔
1304
      }
1305
      return hasTypeMismatchErrors(
24✔
1306
        node,
1307
        actualType.elementType,
1308
        expectedType.elementType,
1309
        visitedTypeAliasesForActualType,
1310
        visitedTypeAliasesForExpectedType,
1311
        skipTypeAliasExpansion
1312
      )
1313
    case 'array':
1314
      if (actualType.kind === 'union') {
32!
1315
        // Special case: number[] | string[] matches with (number | string)[]
UNCOV
1316
        const types = actualType.types.filter((type): type is SArray => type.kind === 'array')
×
UNCOV
1317
        if (types.length !== actualType.types.length) {
×
UNCOV
1318
          return true
×
1319
        }
UNCOV
1320
        const combinedType = types.map(type => type.elementType)
×
UNCOV
1321
        return hasTypeMismatchErrors(
×
1322
          node,
1323
          tUnion(...combinedType),
1324
          expectedType.elementType,
1325
          visitedTypeAliasesForActualType,
1326
          visitedTypeAliasesForExpectedType,
1327
          skipTypeAliasExpansion
1328
        )
1329
      }
1330
      if (actualType.kind !== 'array') {
32✔
1331
        return true
1✔
1332
      }
1333
      return hasTypeMismatchErrors(
31✔
1334
        node,
1335
        actualType.elementType,
1336
        expectedType.elementType,
1337
        visitedTypeAliasesForActualType,
1338
        visitedTypeAliasesForExpectedType,
1339
        skipTypeAliasExpansion
1340
      )
1341
    default:
UNCOV
1342
      return true
×
1343
  }
1344
}
1345

1346
/**
1347
 * Converts type annotation/type alias declaration node to its corresponding type representation in Source.
1348
 * If no type annotation exists, returns the "any" primitive type.
1349
 */
1350
function getTypeAnnotationType(
1351
  annotationNode:
1352
    | tsEs.TSTypeAnnotation
1353
    | tsEs.TSTypeAliasDeclaration
1354
    | tsEs.TSAsExpression
1355
    | undefined
1356
): Type {
1357
  if (!annotationNode) {
1,729✔
1358
    return tAny
103✔
1359
  }
1360
  return getAnnotatedType(annotationNode.typeAnnotation)
1,626✔
1361
}
1362

1363
/**
1364
 * Converts type node to its corresponding type representation in Source.
1365
 */
1366
function getAnnotatedType(typeNode: tsEs.TSType): Type {
1367
  switch (typeNode.type) {
6,998!
1368
    case 'TSFunctionType':
1369
      const params = typeNode.parameters
25✔
1370
      // If the function has variable number of arguments, set function type as any
1371
      // TODO: Add support for variable number of function arguments
1372
      const hasVarArgs = params.reduce((prev, curr) => prev || curr.type === 'RestElement', false)
38✔
1373
      if (hasVarArgs) {
25!
UNCOV
1374
        return tAny
×
1375
      }
1376
      const fnTypes = getParamTypes(params)
25✔
1377
      // Return type will always be last item in types array
1378
      fnTypes.push(getTypeAnnotationType(typeNode.typeAnnotation))
25✔
1379
      return tFunc(...fnTypes)
25✔
1380
    case 'TSLiteralType':
1381
      const value = typeNode.literal.value
1,094✔
1382
      if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
1,094!
UNCOV
1383
        throw new TypecheckError(typeNode, 'Unknown literal type')
×
1384
      }
1385
      return tLiteral(value)
1,094✔
1386
    case 'TSArrayType':
1387
      return tArray(getAnnotatedType(typeNode.elementType))
22✔
1388
    case 'TSUnionType':
1389
      const unionTypes = typeNode.types.map(node => getAnnotatedType(node))
1,459✔
1390
      return mergeTypes(typeNode, ...unionTypes)
302✔
1391
    case 'TSIntersectionType':
UNCOV
1392
      throw new TypecheckError(typeNode, 'Intersection types are not allowed')
×
1393
    case 'TSTypeReference':
1394
      const name = typeNode.typeName.name
3,918✔
1395
      // Save name and type arguments in variable type
1396
      if (typeNode.typeParameters) {
3,918✔
1397
        const typesToSub: Type[] = []
2,018✔
1398
        for (const paramNode of typeNode.typeParameters.params) {
2,018✔
1399
          if (paramNode.type === 'TSTypeParameter') {
3,889!
UNCOV
1400
            throw new TypecheckError(typeNode, 'Type argument should not be type parameter')
×
1401
          }
1402
          typesToSub.push(getAnnotatedType(paramNode))
3,889✔
1403
        }
1404
        return tVar(name, typesToSub)
2,018✔
1405
      }
1406
      return tVar(name)
1,900✔
1407
    case 'TSParenthesizedType':
1408
      return getAnnotatedType(typeNode.typeAnnotation)
2✔
1409
    default:
1410
      return getBasicType(typeNode)
1,635✔
1411
  }
1412
}
1413

1414
/**
1415
 * Converts an array of function parameters into an array of types.
1416
 */
1417
function getParamTypes(params: (tsEs.Identifier | tsEs.RestElement)[]): Type[] {
1418
  return params.map(param => getTypeAnnotationType(param.typeAnnotation))
196✔
1419
}
1420

1421
/**
1422
 * Returns the head type of the input type.
1423
 */
1424
function getHeadType(node: tsEs.Node, type: Type): Type {
1425
  switch (type.kind) {
136✔
1426
    case 'pair':
1427
      return type.headType
49✔
1428
    case 'list':
1429
      return type.elementType
5✔
1430
    case 'union':
1431
      return tUnion(...type.types.map(type => getHeadType(node, type)))
46✔
1432
    case 'variable':
1433
      return getHeadType(node, lookupTypeAliasAndRemoveForAllTypes(node, type))
75✔
1434
    default:
1435
      return type
3✔
1436
  }
1437
}
1438

1439
/**
1440
 * Returns the tail type of the input type.
1441
 */
1442
function getTailType(node: tsEs.Node, type: Type): Type {
1443
  switch (type.kind) {
163✔
1444
    case 'pair':
1445
      return type.tailType
51✔
1446
    case 'list':
1447
      return tList(
5✔
1448
        type.elementType,
1449
        type.typeAsPair && type.typeAsPair.tailType.kind === 'pair'
12✔
1450
          ? type.typeAsPair.tailType
1451
          : undefined
1452
      )
1453
    case 'union':
1454
      return tUnion(...type.types.map(type => getTailType(node, type)))
46✔
1455
    case 'variable':
1456
      return getTailType(node, lookupTypeAliasAndRemoveForAllTypes(node, type))
102✔
1457
    default:
1458
      return type
1✔
1459
  }
1460
}
1461

1462
/**
1463
 * Converts node type to basic type, adding errors to context if disallowed/unknown types are used.
1464
 * If errors are found, returns the "any" type to prevent throwing of further errors.
1465
 */
1466
function getBasicType(node: tsEs.TSKeywordType) {
1467
  const basicType = typeAnnotationKeywordToBasicTypeMap[node.type] ?? 'unknown'
1,635!
1468
  if (
1,635✔
1469
    disallowedTypes.includes(basicType as TSDisallowedTypes) ||
3,786✔
1470
    (context.chapter === 1 && basicType === 'null')
1471
  ) {
1472
    context.errors.push(new TypeNotAllowedError(node, basicType))
6✔
1473
    return tAny
6✔
1474
  }
1475
  return tPrimitive(basicType as PrimitiveType | TSAllowedTypes)
1,629✔
1476
}
1477

1478
/**
1479
 * Wrapper function for lookupTypeAlias that removes forall and predicate types.
1480
 * Predicate types are substituted with the function type that takes in 1 argument and returns a boolean.
1481
 * For forall types, the poly type is returned.
1482
 */
1483
function lookupTypeAndRemoveForAllAndPredicateTypes(name: string): Type | undefined {
1484
  const type = lookupType(name, env)
947✔
1485
  if (!type) {
947✔
1486
    return undefined
1✔
1487
  }
1488
  if (type.kind === 'forall') {
946✔
1489
    if (type.polyType.kind !== 'function') {
35✔
1490
      // Skip typecheck as function has variable number of arguments;
1491
      // this only occurs for certain prelude functions
1492
      // TODO: Add support for functions with variable number of arguments
1493
      return tAny
7✔
1494
    }
1495
    // Clone type so that original type is not modified
1496
    return cloneDeep(type.polyType)
28✔
1497
  }
1498
  if (type.kind === 'predicate') {
911!
1499
    // All predicate functions (e.g. is_number)
1500
    // take in 1 parameter and return a boolean
UNCOV
1501
    return tFunc(tAny, tBool)
×
1502
  }
1503
  return type
911✔
1504
}
1505

1506
/**
1507
 * Wrapper function for lookupTypeAlias that removes forall and predicate types.
1508
 * An error is thrown for predicate types as type aliases should not ever be predicate types.
1509
 * For forall types, the given type arguments are substituted into the poly type,
1510
 * and the resulting type is returned.
1511
 */
1512
function lookupTypeAliasAndRemoveForAllTypes(node: tsEs.Node, varType: Variable): Type {
1513
  // Check if type alias exists
1514
  const aliasType = lookupTypeAlias(varType.name, env)
597✔
1515
  if (!aliasType) {
597✔
1516
    context.errors.push(new TypeNotFoundError(node, varType.name))
1✔
1517
    return tAny
1✔
1518
  }
1519
  // If type saved in type environment is not generic,
1520
  // given variable type should not have type arguments
1521
  if (aliasType.kind !== 'forall') {
596✔
1522
    if (varType.typeArgs !== undefined && varType.typeArgs.length > 0) {
265✔
1523
      context.errors.push(new TypeNotGenericError(node, varType.name))
1✔
1524
      return tAny
1✔
1525
    }
1526
    return aliasType
264✔
1527
  }
1528
  // Check type parameters
1529
  if (aliasType.typeParams === undefined) {
331!
UNCOV
1530
    if (varType.typeArgs !== undefined && varType.typeArgs.length > 0) {
×
UNCOV
1531
      context.errors.push(new TypeNotGenericError(node, varType.name))
×
1532
    }
UNCOV
1533
    return tAny
×
1534
  }
1535
  if (varType.typeArgs?.length !== aliasType.typeParams.length) {
331✔
1536
    context.errors.push(
4✔
1537
      new InvalidNumberOfTypeArgumentsForGenericTypeError(
1538
        node,
1539
        varType.name,
1540
        aliasType.typeParams.length
1541
      )
1542
    )
1543
    return tAny
4✔
1544
  }
1545
  // Clone type to prevent modifying generic type saved in type env
1546
  let polyType = cloneDeep(aliasType.polyType)
327✔
1547
  // Substitute all type parameters with type arguments
1548
  for (let i = 0; i < varType.typeArgs.length; i++) {
327✔
1549
    polyType = substituteVariableTypes(polyType, aliasType.typeParams[i], varType.typeArgs[i])
597✔
1550
  }
1551
  return polyType
327✔
1552
}
1553

1554
/**
1555
 * Recurses through the given type and returns a new type
1556
 * with all variable types that match the given type variable substituted with the type to substitute.
1557
 */
1558
function substituteVariableTypes(type: Type, typeVar: Variable, typeToSub: Type): Type {
1559
  switch (type.kind) {
2,045!
1560
    case 'primitive':
1561
    case 'literal':
1562
      return type
291✔
1563
    case 'variable':
1564
      if (isEqual(type, typeVar)) {
1,063✔
1565
        return typeToSub
699✔
1566
      }
1567
      if (type.typeArgs !== undefined) {
364✔
1568
        for (let i = 0; i < type.typeArgs.length; i++) {
25✔
1569
          if (isEqual(type.typeArgs[i], typeVar)) {
25✔
1570
            type.typeArgs[i] = typeToSub
15✔
1571
          }
1572
        }
1573
      }
1574
      return type
364✔
1575
    case 'function':
1576
      const types = type.parameterTypes.map(param =>
28✔
1577
        substituteVariableTypes(param, typeVar, typeToSub)
10✔
1578
      )
1579
      types.push(substituteVariableTypes(type.returnType, typeVar, typeToSub))
28✔
1580
      return tFunc(...types)
28✔
1581
    case 'union':
1582
      return tUnion(...type.types.map(type => substituteVariableTypes(type, typeVar, typeToSub)))
28✔
1583
    case 'pair':
1584
      return tPair(
595✔
1585
        substituteVariableTypes(type.headType, typeVar, typeToSub),
1586
        substituteVariableTypes(type.tailType, typeVar, typeToSub)
1587
      )
1588
    case 'list':
1589
      return tList(
55✔
1590
        substituteVariableTypes(type.elementType, typeVar, typeToSub),
1591
        type.typeAsPair && (substituteVariableTypes(type.typeAsPair, typeVar, typeToSub) as Pair)
55!
1592
      )
1593
    case 'array':
UNCOV
1594
      return tArray(substituteVariableTypes(type.elementType, typeVar, typeToSub))
×
1595
    default:
UNCOV
1596
      return type
×
1597
  }
1598
}
1599

1600
/**
1601
 * Combines all types provided in the parameters into one, removing duplicate types in the process.
1602
 * Type aliases encountered are not expanded as it is sufficient to compare the variable types at name level without expanding them;
1603
 * in fact, expanding type aliases here would lead to type aliases with circular dependencies being incorrectly flagged as not declared.
1604
 */
1605
function mergeTypes(node: tsEs.Node, ...types: Type[]): Type {
1606
  const mergedTypes: Type[] = []
390✔
1607
  for (const currType of types) {
390✔
1608
    if (isEqual(currType, tAny)) {
1,633✔
1609
      return tAny
2✔
1610
    }
1611
    if (currType.kind === 'union') {
1,631✔
1612
      for (const type of currType.types) {
6✔
1613
        if (!containsType(node, mergedTypes, type, [], [], true)) {
14✔
1614
          mergedTypes.push(type)
9✔
1615
        }
1616
      }
1617
    } else {
1618
      if (!containsType(node, mergedTypes, currType, [], [], true)) {
1,625✔
1619
        mergedTypes.push(currType)
1,559✔
1620
      }
1621
    }
1622
  }
1623
  if (mergedTypes.length === 1) {
388✔
1624
    return mergedTypes[0]
65✔
1625
  }
1626
  return tUnion(...mergedTypes)
323✔
1627
}
1628

1629
/**
1630
 * Checks if a type exists in an array of types.
1631
 */
1632
function containsType(
1633
  node: tsEs.Node,
1634
  arr: Type[],
1635
  typeToCheck: Type,
1636
  visitedTypeAliasesForTypes: Variable[] = [],
111✔
1637
  visitedTypeAliasesForTypeToCheck: Variable[] = [],
111✔
1638
  skipTypeAliasExpansion: boolean = false
182✔
1639
) {
1640
  for (const type of arr) {
1,821✔
1641
    if (
5,881✔
1642
      !hasTypeMismatchErrors(
1643
        node,
1644
        typeToCheck,
1645
        type,
1646
        visitedTypeAliasesForTypeToCheck,
1647
        visitedTypeAliasesForTypes,
1648
        skipTypeAliasExpansion
1649
      )
1650
    ) {
1651
      return true
201✔
1652
    }
1653
  }
1654
  return false
1,620✔
1655
}
1656

1657
/**
1658
 * Traverses through the program and removes all TS-related nodes, returning the result.
1659
 */
1660
export function removeTSNodes(node: tsEs.Node | undefined | null): any {
74✔
1661
  if (node === undefined || node === null) {
2,852!
UNCOV
1662
    return node
×
1663
  }
1664
  const type = node.type
2,852✔
1665
  switch (type) {
2,852✔
1666
    case 'Literal':
1667
    case 'Identifier': {
1668
      return node
1,134✔
1669
    }
1670
    case 'Program':
1671
    case 'BlockStatement': {
1672
      const newBody: tsEs.Statement[] = []
250✔
1673
      node.body.forEach(stmt => {
250✔
1674
        const type = stmt.type
748✔
1675
        if (type.startsWith('TS')) {
748✔
1676
          switch (type) {
24!
1677
            case 'TSAsExpression':
UNCOV
1678
              newBody.push(removeTSNodes(stmt))
×
UNCOV
1679
              break
×
1680
            default:
1681
              // Remove node from body
1682
              break
24✔
1683
          }
1684
        } else {
1685
          newBody.push(removeTSNodes(stmt))
724✔
1686
        }
1687
      })
1688
      node.body = newBody
250✔
1689
      return node
250✔
1690
    }
1691
    case 'ExpressionStatement': {
1692
      node.expression = removeTSNodes(node.expression)
98✔
1693
      return node
98✔
1694
    }
1695
    case 'ConditionalExpression':
1696
    case 'IfStatement': {
1697
      node.test = removeTSNodes(node.test)
74✔
1698
      node.consequent = removeTSNodes(node.consequent)
74✔
1699
      node.alternate = removeTSNodes(node.alternate)
74✔
1700
      return node
74✔
1701
    }
1702
    case 'UnaryExpression':
1703
    case 'RestElement':
1704
    case 'SpreadElement':
1705
    case 'ReturnStatement': {
1706
      node.argument = removeTSNodes(node.argument)
113✔
1707
      return node
113✔
1708
    }
1709
    case 'BinaryExpression':
1710
    case 'LogicalExpression':
1711
    case 'AssignmentExpression': {
1712
      node.left = removeTSNodes(node.left)
177✔
1713
      node.right = removeTSNodes(node.right)
177✔
1714
      return node
177✔
1715
    }
1716
    case 'ArrowFunctionExpression':
1717
    case 'FunctionDeclaration':
1718
      node.body = removeTSNodes(node.body)
130✔
1719
      return node
130✔
1720
    case 'VariableDeclaration': {
1721
      node.declarations[0].init = removeTSNodes(node.declarations[0].init)
394✔
1722
      return node
394✔
1723
    }
1724
    case 'CallExpression': {
1725
      node.arguments = node.arguments.map(removeTSNodes)
389✔
1726
      return node
389✔
1727
    }
1728
    case 'ArrayExpression':
1729
      // Casting is safe here as Source disallows use of spread elements and holes in arrays
1730
      node.elements = node.elements.map(removeTSNodes)
30✔
1731
      return node
30✔
1732
    case 'MemberExpression':
1733
      node.property = removeTSNodes(node.property)
14✔
1734
      node.object = removeTSNodes(node.object)
14✔
1735
      return node
14✔
1736
    case 'WhileStatement': {
1737
      node.test = removeTSNodes(node.test)
5✔
1738
      node.body = removeTSNodes(node.body)
5✔
1739
      return node
5✔
1740
    }
1741
    case 'ForStatement': {
1742
      node.init = removeTSNodes(node.init)
7✔
1743
      node.test = removeTSNodes(node.test)
7✔
1744
      node.update = removeTSNodes(node.update)
7✔
1745
      node.body = removeTSNodes(node.body)
7✔
1746
      return node
7✔
1747
    }
1748
    case 'TSAsExpression':
1749
      // Remove wrapper node
1750
      return removeTSNodes(node.expression)
9✔
1751
    default:
1752
      // Remove all other TS nodes
1753
      return type.startsWith('TS') ? undefined : node
28!
1754
  }
1755
}
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

© 2025 Coveralls, Inc