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

source-academy / js-slang / 5936042370

22 Aug 2023 07:58AM UTC coverage: 83.014% (+0.1%) from 82.893%
5936042370

Pull #1471

github

web-flow
Merge 6d88e2972 into 1d991166b
Pull Request #1471: Use Asynchronous Code for Loading Modules

3618 of 4779 branches covered (75.71%)

Branch coverage included in aggregate %.

269 of 269 new or added lines in 23 files covered. (100.0%)

10838 of 12635 relevant lines covered (85.78%)

106101.79 hits per line

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

91.63
/src/transpiler/transpiler.ts
1
/* eslint-disable @typescript-eslint/no-unused-vars */
2
import { generate } from 'astring'
67✔
3
import * as es from 'estree'
4
import { partition } from 'lodash'
67✔
5
import { RawSourceMap, SourceMapGenerator } from 'source-map'
67✔
6

7
import { NATIVE_STORAGE_ID, REQUIRE_PROVIDER_ID, UNKNOWN_LOCATION } from '../constants'
67✔
8
import { UndefinedVariable } from '../errors/errors'
67✔
9
import { ModuleNotFoundError } from '../errors/moduleErrors'
67✔
10
import { ModuleInternalError, UndefinedImportError } from '../modules/errors'
67✔
11
import {
67✔
12
  initModuleContextAsync,
13
  memoizedGetModuleBundleAsync,
14
  memoizedGetModuleDocsAsync,
15
  memoizedGetModuleManifestAsync
16
} from '../modules/moduleLoaderAsync'
17
import type { ImportTransformOptions } from '../modules/moduleTypes'
18
import {
67✔
19
  AllowedDeclarations,
20
  Chapter,
21
  Context,
22
  NativeStorage,
23
  RecursivePartial,
24
  Variant
25
} from '../types'
26
import assert from '../utils/assert'
67✔
27
import { isImportDeclaration } from '../utils/ast/typeGuards'
67✔
28
import * as create from '../utils/astCreator'
67✔
29
import {
67✔
30
  getIdentifiersInNativeStorage,
31
  getIdentifiersInProgram,
32
  getUniqueId
33
} from '../utils/uniqueIds'
34
import { ancestor, simple } from '../utils/walkers'
67✔
35

36
/**
37
 * This whole transpiler includes many many many many hacks to get stuff working.
38
 * Order in which certain functions are called matter as well.
39
 * There should be an explanation on it coming up soon.
40
 */
41

42
const globalIdNames = [
67✔
43
  'native',
44
  'callIfFuncAndRightArgs',
45
  'boolOrErr',
46
  'wrap',
47
  'wrapSourceModule',
48
  'unaryOp',
49
  'binaryOp',
50
  'throwIfTimeout',
51
  'setProp',
52
  'getProp',
53
  'builtins'
54
] as const
55

56
export type NativeIds = Record<typeof globalIdNames[number], es.Identifier>
57

58
export async function transformImportDeclarations(
67✔
59
  program: es.Program,
60
  usedIdentifiers: Set<string>,
61
  { wrapSourceModules, checkImports, loadTabs }: ImportTransformOptions,
62
  context?: Context,
63
  nativeId?: es.Identifier,
64
  useThis: boolean = false
1,350✔
65
): Promise<[string, es.VariableDeclaration[], es.Program['body']]> {
66
  const [importNodes, otherNodes] = partition(program.body, isImportDeclaration)
1,350✔
67

68
  if (importNodes.length === 0) return ['', [], otherNodes]
1,350✔
69
  const importNodeMap = importNodes.reduce((res, node) => {
5✔
70
    const moduleName = node.source.value
7✔
71
    assert(
7✔
72
      typeof moduleName === 'string',
73
      `Expected ImportDeclaration to have a source of type string, got ${moduleName}`
74
    )
75

76
    if (!(moduleName in res)) {
7✔
77
      res[moduleName] = []
7✔
78
    }
79

80
    res[moduleName].push(node)
7✔
81

82
    node.specifiers.forEach(({ local: { name } }) => usedIdentifiers.add(name))
7✔
83
    return res
7✔
84
  }, {} as Record<string, es.ImportDeclaration[]>)
85

86
  const manifest = await memoizedGetModuleManifestAsync()
5✔
87

88
  const loadedModules = await Promise.all(
5✔
89
    Object.entries(importNodeMap).map(async ([moduleName, nodes]) => {
7✔
90
      if (!(moduleName in manifest)) {
7!
91
        throw new ModuleNotFoundError(moduleName, nodes[0])
×
92
      }
93

94
      const [text, docs] = await Promise.all([
7✔
95
        memoizedGetModuleBundleAsync(moduleName),
96
        memoizedGetModuleDocsAsync(moduleName),
97
        context ? initModuleContextAsync(moduleName, context, loadTabs) : Promise.resolve()
7✔
98
      ])
99

100
      const namespaced = getUniqueId(usedIdentifiers, '__MODULE__')
7✔
101

102
      if (checkImports && !docs) {
7!
103
        throw new ModuleInternalError(
×
104
          moduleName,
105
          new Error('checkImports was true, but failed to load docs'),
106
          nodes[0]
107
        )
108
      }
109

110
      const declNodes = nodes.flatMap(({ specifiers }) =>
7✔
111
        specifiers.map(spec => {
7✔
112
          assert(spec.type === 'ImportSpecifier', `Expected ImportSpecifier, got ${spec.type}`)
7✔
113

114
          if (checkImports && !(spec.imported.name in docs!)) {
7✔
115
            throw new UndefinedImportError(spec.imported.name, moduleName, spec)
1✔
116
          }
117

118
          // Convert each import specifier to its corresponding local variable declaration
119
          return create.constantDeclaration(
6✔
120
            spec.local.name,
121
            create.memberExpression(
122
              create.identifier(`${useThis ? 'this.' : ''}${namespaced}`),
6!
123
              spec.imported.name
124
            )
125
          )
126
        })
127
      )
128

129
      return [moduleName, { text, nodes: declNodes, namespaced }] as [
6✔
130
        string,
131
        {
132
          text: string
133
          nodes: es.VariableDeclaration[]
134
          namespaced: string
135
        }
136
      ]
137
    })
138
  )
139

140
  const [prefixes, declNodes] = loadedModules.reduce(
4✔
141
    ([prefix, decls], [moduleName, { text, nodes, namespaced }]) => {
142
      const modifiedText = wrapSourceModules
6✔
143
        ? `${NATIVE_STORAGE_ID}.operators.get("wrapSourceModule")("${moduleName}", ${text}, ${REQUIRE_PROVIDER_ID})`
6✔
144
        : `(${text})(${REQUIRE_PROVIDER_ID})`
145

146
      return [
6✔
147
        [...prefix, `const ${namespaced} = ${modifiedText}\n`],
148
        [...decls, ...nodes]
149
      ]
150
    },
151
    [[], []] as [string[], es.VariableDeclaration[]]
152
  )
153

154
  return [prefixes.join('\n'), declNodes, otherNodes]
4✔
155
}
156

157
export function getGloballyDeclaredIdentifiers(program: es.Program): string[] {
67✔
158
  return program.body
1,274✔
159
    .filter(statement => statement.type === 'VariableDeclaration')
12,742✔
160
    .map(
161
      ({
162
        declarations: {
163
          0: { id }
164
        },
165
        kind
166
      }: es.VariableDeclaration) => (id as es.Identifier).name
11,700✔
167
    )
168
}
169

170
export function getBuiltins(nativeStorage: NativeStorage): es.Statement[] {
67✔
171
  const builtinsStatements: es.Statement[] = []
954✔
172
  nativeStorage.builtins.forEach((_unused, name: string) => {
954✔
173
    builtinsStatements.push(
69,749✔
174
      create.declaration(
175
        name,
176
        'const',
177
        create.callExpression(
178
          create.memberExpression(
179
            create.memberExpression(create.identifier(NATIVE_STORAGE_ID), 'builtins'),
180
            'get'
181
          ),
182
          [create.literal(name)]
183
        )
184
      )
185
    )
186
  })
187

188
  return builtinsStatements
954✔
189
}
190

191
export function evallerReplacer(
67✔
192
  nativeStorageId: NativeIds['native'],
193
  usedIdentifiers: Set<string>
194
): es.ExpressionStatement {
195
  const arg = create.identifier(getUniqueId(usedIdentifiers, 'program'))
1,291✔
196
  return create.expressionStatement(
1,291✔
197
    create.assignmentExpression(
198
      create.memberExpression(nativeStorageId, 'evaller'),
199
      create.arrowFunctionExpression([arg], create.callExpression(create.identifier('eval'), [arg]))
200
    )
201
  )
202
}
203

204
function generateFunctionsToStringMap(program: es.Program) {
205
  const map: Map<es.Node, string> = new Map()
1,377✔
206
  simple(program, {
1,377✔
207
    ArrowFunctionExpression(node: es.ArrowFunctionExpression) {
208
      map.set(node, generate(node))
11,807✔
209
    },
210
    FunctionDeclaration(node: es.FunctionDeclaration) {
211
      map.set(node, generate(node))
11,219✔
212
    }
213
  })
214
  return map
1,377✔
215
}
216

217
function transformFunctionDeclarationsToArrowFunctions(
218
  program: es.Program,
219
  functionsToStringMap: Map<es.Node, string>
220
) {
221
  simple(program, {
1,258✔
222
    FunctionDeclaration(node) {
223
      const { id, params, body } = node as es.FunctionDeclaration
11,210✔
224
      node.type = 'VariableDeclaration'
11,210✔
225
      node = node as es.VariableDeclaration
11,210✔
226
      const asArrowFunction = create.blockArrowFunction(params as es.Identifier[], body)
11,210✔
227
      functionsToStringMap.set(asArrowFunction, functionsToStringMap.get(node)!)
11,210✔
228
      node.declarations = [
11,210✔
229
        {
230
          type: 'VariableDeclarator',
231
          id: id as es.Identifier,
232
          init: asArrowFunction
233
        }
234
      ]
235
      node.kind = 'const'
11,210✔
236
    }
237
  })
238
}
239

240
/**
241
 * Transforms all arrow functions
242
 * (arg1, arg2, ...) => { statement1; statement2; return statement3; }
243
 *
244
 * to
245
 *
246
 * <NATIVE STORAGE>.operators.wrap((arg1, arg2, ...) => {
247
 *   statement1;statement2;return statement3;
248
 * })
249
 *
250
 * to allow for iterative processes to take place
251
 */
252

253
function wrapArrowFunctionsToAllowNormalCallsAndNiceToString(
254
  program: es.Program,
255
  functionsToStringMap: Map<es.Node, string>,
256
  globalIds: NativeIds
257
) {
258
  simple(program, {
1,258✔
259
    ArrowFunctionExpression(node: es.ArrowFunctionExpression) {
260
      // If it's undefined then we're dealing with a thunk
261
      if (functionsToStringMap.get(node)! !== undefined) {
22,711✔
262
        create.mutateToCallExpression(node, globalIds.wrap, [
22,711✔
263
          { ...node },
264
          create.literal(functionsToStringMap.get(node)!),
265
          create.literal(node.params[node.params.length - 1]?.type === 'RestElement'),
68,133✔
266

267
          globalIds.native
268
        ])
269
      }
270
    }
271
  })
272
}
273

274
/**
275
 * Transforms all return statements (including expression arrow functions) to return an intermediate value
276
 * return nonFnCall + 1;
277
 *  =>
278
 * return {isTail: false, value: nonFnCall + 1};
279
 *
280
 * return fnCall(arg1, arg2);
281
 * => return {isTail: true, function: fnCall, arguments: [arg1, arg2]}
282
 *
283
 * conditional and logical expressions will be recursively looped through as well
284
 */
285
function transformReturnStatementsToAllowProperTailCalls(program: es.Program) {
286
  function transformLogicalExpression(expression: es.Expression): es.Expression {
287
    switch (expression.type) {
49,209✔
288
      case 'LogicalExpression':
49,209✔
289
        return create.logicalExpression(
2,368✔
290
          expression.operator,
291
          expression.left,
292
          transformLogicalExpression(expression.right),
293
          expression.loc
294
        )
295
      case 'ConditionalExpression':
296
        return create.conditionalExpression(
11,175✔
297
          expression.test,
298
          transformLogicalExpression(expression.consequent),
299
          transformLogicalExpression(expression.alternate),
300
          expression.loc
301
        )
302
      case 'CallExpression':
303
        expression = expression as es.CallExpression
22,138✔
304
        const { line, column } = (expression.loc ?? UNKNOWN_LOCATION).start
22,138!
305
        const source = expression.loc?.source ?? null
22,138!
306
        const functionName =
307
          expression.callee.type === 'Identifier' ? expression.callee.name : '<anonymous>'
22,138✔
308

309
        const args = expression.arguments
22,138✔
310

311
        return create.objectExpression([
22,138✔
312
          create.property('isTail', create.literal(true)),
313
          create.property('function', expression.callee as es.Expression),
314
          create.property('functionName', create.literal(functionName)),
315
          create.property('arguments', create.arrayExpression(args as es.Expression[])),
316
          create.property('line', create.literal(line)),
317
          create.property('column', create.literal(column)),
318
          create.property('source', create.literal(source))
319
        ])
320
      default:
321
        return create.objectExpression([
13,528✔
322
          create.property('isTail', create.literal(false)),
323
          create.property('value', expression)
324
        ])
325
    }
326
  }
327

328
  simple(program, {
1,377✔
329
    ReturnStatement(node: es.ReturnStatement) {
330
      node.argument = transformLogicalExpression(node.argument!)
13,661✔
331
    },
332
    ArrowFunctionExpression(node: es.ArrowFunctionExpression) {
333
      if (node.expression) {
11,807✔
334
        node.body = transformLogicalExpression(node.body as es.Expression)
10,830✔
335
      }
336
    }
337
  })
338
}
339

340
function transformCallExpressionsToCheckIfFunction(program: es.Program, globalIds: NativeIds) {
341
  simple(program, {
1,377✔
342
    CallExpression(node: es.CallExpression) {
343
      const { line, column } = (node.loc ?? UNKNOWN_LOCATION).start
50,981!
344
      const source = node.loc?.source ?? null
50,981!
345
      const args = node.arguments
50,981✔
346

347
      node.arguments = [
50,981✔
348
        node.callee as es.Expression,
349
        create.literal(line),
350
        create.literal(column),
351
        create.literal(source),
352
        ...args
353
      ]
354

355
      node.callee = globalIds.callIfFuncAndRightArgs
50,981✔
356
    }
357
  })
358
}
359

360
export function checkForUndefinedVariables(
67✔
361
  program: es.Program,
362
  nativeStorage: NativeStorage,
363
  globalIds: NativeIds,
364
  skipUndefined: boolean
365
) {
366
  const builtins = nativeStorage.builtins
1,394✔
367
  const identifiersIntroducedByNode = new Map<es.Node, Set<string>>()
1,394✔
368
  function processBlock(node: es.Program | es.BlockStatement) {
369
    const identifiers = new Set<string>()
16,583✔
370
    for (const statement of node.body) {
16,583✔
371
      if (statement.type === 'VariableDeclaration') {
31,769✔
372
        identifiers.add((statement.declarations[0].id as es.Identifier).name)
3,590✔
373
      } else if (statement.type === 'FunctionDeclaration') {
28,179✔
374
        if (statement.id === null) {
11,219!
375
          throw new Error(
×
376
            'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.'
377
          )
378
        }
379
        identifiers.add(statement.id.name)
11,219✔
380
      } else if (statement.type === 'ImportDeclaration') {
16,960✔
381
        for (const specifier of statement.specifiers) {
2✔
382
          identifiers.add(specifier.local.name)
2✔
383
        }
384
      }
385
    }
386
    identifiersIntroducedByNode.set(node, identifiers)
16,583✔
387
  }
388
  function processFunction(
389
    node: es.FunctionDeclaration | es.ArrowFunctionExpression,
390
    _ancestors: es.Node[]
391
  ) {
392
    identifiersIntroducedByNode.set(
23,039✔
393
      node,
394
      new Set(
395
        node.params.map(id =>
396
          id.type === 'Identifier'
26,742✔
397
            ? id.name
26,742✔
398
            : ((id as es.RestElement).argument as es.Identifier).name
399
        )
400
      )
401
    )
402
  }
403
  const identifiersToAncestors = new Map<es.Identifier, es.Node[]>()
1,394✔
404
  ancestor(program, {
1,394✔
405
    Program: processBlock,
406
    BlockStatement: processBlock,
407
    FunctionDeclaration: processFunction,
408
    ArrowFunctionExpression: processFunction,
409
    ForStatement(forStatement: es.ForStatement, ancestors: es.Node[]) {
410
      const init = forStatement.init!
26✔
411
      if (init.type === 'VariableDeclaration') {
26✔
412
        identifiersIntroducedByNode.set(
24✔
413
          forStatement,
414
          new Set([(init.declarations[0].id as es.Identifier).name])
415
        )
416
      }
417
    },
418
    Identifier(identifier: es.Identifier, ancestors: es.Node[]) {
419
      identifiersToAncestors.set(identifier, [...ancestors])
215,509✔
420
    },
421
    Pattern(node: es.Pattern, ancestors: es.Node[]) {
422
      if (node.type === 'Identifier') {
41,659✔
423
        identifiersToAncestors.set(node, [...ancestors])
41,649✔
424
      } else if (node.type === 'MemberExpression') {
10✔
425
        if (node.object.type === 'Identifier') {
2!
426
          identifiersToAncestors.set(node.object, [...ancestors])
×
427
        }
428
      }
429
    }
430
  })
431
  const nativeInternalNames = new Set(Object.values(globalIds).map(({ name }) => name))
15,334✔
432

433
  for (const [identifier, ancestors] of identifiersToAncestors) {
1,394✔
434
    const name = identifier.name
181,947✔
435
    const isCurrentlyDeclared = ancestors.some(a => identifiersIntroducedByNode.get(a)?.has(name))
948,683✔
436
    if (isCurrentlyDeclared) {
181,947✔
437
      continue
126,808✔
438
    }
439
    const isPreviouslyDeclared = nativeStorage.previousProgramsIdentifiers.has(name)
55,139✔
440
    if (isPreviouslyDeclared) {
55,139✔
441
      continue
222✔
442
    }
443
    const isBuiltin = builtins.has(name)
54,917✔
444
    if (isBuiltin) {
54,917✔
445
      continue
52,271✔
446
    }
447
    const isNativeId = nativeInternalNames.has(name)
2,646✔
448
    if (!isNativeId && !skipUndefined) {
2,646✔
449
      throw new UndefinedVariable(name, identifier)
119✔
450
    }
451
  }
452
}
453

454
function transformSomeExpressionsToCheckIfBoolean(program: es.Program, globalIds: NativeIds) {
455
  function transform(
456
    node:
457
      | es.IfStatement
458
      | es.ConditionalExpression
459
      | es.LogicalExpression
460
      | es.ForStatement
461
      | es.WhileStatement
462
  ) {
463
    const { line, column } = (node.loc ?? UNKNOWN_LOCATION).start
16,070!
464
    const source = node.loc?.source ?? null
16,070!
465
    const test = node.type === 'LogicalExpression' ? 'left' : 'test'
16,070✔
466
    node[test] = create.callExpression(globalIds.boolOrErr, [
16,070✔
467
      node[test],
468
      create.literal(line),
469
      create.literal(column),
470
      create.literal(source)
471
    ])
472
  }
473

474
  simple(program, {
1,377✔
475
    IfStatement: transform,
476
    ConditionalExpression: transform,
477
    LogicalExpression: transform,
478
    ForStatement: transform,
479
    WhileStatement: transform
480
  })
481
}
482

483
function getNativeIds(program: es.Program, usedIdentifiers: Set<string>): NativeIds {
484
  const globalIds = {}
1,399✔
485
  for (const identifier of globalIdNames) {
1,399✔
486
    globalIds[identifier] = create.identifier(getUniqueId(usedIdentifiers, identifier))
15,389✔
487
  }
488
  return globalIds as NativeIds
1,399✔
489
}
490

491
function transformUnaryAndBinaryOperationsToFunctionCalls(
492
  program: es.Program,
493
  globalIds: NativeIds,
494
  chapter: Chapter
495
) {
496
  simple(program, {
1,377✔
497
    BinaryExpression(node: es.BinaryExpression) {
498
      const { line, column } = (node.loc ?? UNKNOWN_LOCATION).start
9,406!
499
      const source = node.loc?.source ?? null
9,406!
500
      const { operator, left, right } = node
9,406✔
501
      create.mutateToCallExpression(node, globalIds.binaryOp, [
9,406✔
502
        create.literal(operator),
503
        create.literal(chapter),
504
        left,
505
        right,
506
        create.literal(line),
507
        create.literal(column),
508
        create.literal(source)
509
      ])
510
    },
511
    UnaryExpression(node: es.UnaryExpression) {
512
      const { line, column } = (node.loc ?? UNKNOWN_LOCATION).start
562!
513
      const source = node.loc?.source ?? null
562!
514
      const { operator, argument } = node as es.UnaryExpression
562✔
515
      create.mutateToCallExpression(node, globalIds.unaryOp, [
562✔
516
        create.literal(operator),
517
        argument,
518
        create.literal(line),
519
        create.literal(column),
520
        create.literal(source)
521
      ])
522
    }
523
  })
524
}
525

526
function getComputedProperty(computed: boolean, property: es.Expression): es.Expression {
527
  return computed ? property : create.literal((property as es.Identifier).name)
73✔
528
}
529

530
function transformPropertyAssignment(program: es.Program, globalIds: NativeIds) {
531
  simple(program, {
1,377✔
532
    AssignmentExpression(node: es.AssignmentExpression) {
533
      if (node.left.type === 'MemberExpression') {
97✔
534
        const { object, property, computed, loc } = node.left
29✔
535
        const { line, column } = (loc ?? UNKNOWN_LOCATION).start
29!
536
        const source = loc?.source ?? null
29!
537
        create.mutateToCallExpression(node, globalIds.setProp, [
29✔
538
          object as es.Expression,
539
          getComputedProperty(computed, property as es.Expression),
540
          node.right,
541
          create.literal(line),
542
          create.literal(column),
543
          create.literal(source)
544
        ])
545
      }
546
    }
547
  })
548
}
549

550
function transformPropertyAccess(program: es.Program, globalIds: NativeIds) {
551
  simple(program, {
1,377✔
552
    MemberExpression(node: es.MemberExpression) {
553
      const { object, property, computed, loc } = node
44✔
554
      const { line, column } = (loc ?? UNKNOWN_LOCATION).start
44!
555
      const source = loc?.source ?? null
44!
556
      create.mutateToCallExpression(node, globalIds.getProp, [
44✔
557
        object as es.Expression,
558
        getComputedProperty(computed, property as es.Expression),
559
        create.literal(line),
560
        create.literal(column),
561
        create.literal(source)
562
      ])
563
    }
564
  })
565
}
566

567
function addInfiniteLoopProtection(
568
  program: es.Program,
569
  globalIds: NativeIds,
570
  usedIdentifiers: Set<string>
571
) {
572
  const getTimeAst = () => create.callExpression(create.identifier('get_time'), [])
1,258✔
573

574
  function instrumentLoops(node: es.Program | es.BlockStatement) {
575
    const newStatements = []
16,423✔
576
    for (const statement of node.body) {
16,423✔
577
      if (statement.type === 'ForStatement' || statement.type === 'WhileStatement') {
31,516✔
578
        const startTimeConst = getUniqueId(usedIdentifiers, 'startTime')
35✔
579
        newStatements.push(create.constantDeclaration(startTimeConst, getTimeAst()))
35✔
580
        if (statement.body.type === 'BlockStatement') {
35✔
581
          const { line, column } = (statement.loc ?? UNKNOWN_LOCATION).start
35!
582
          const source = statement.loc?.source ?? null
35!
583
          statement.body.body.unshift(
35✔
584
            create.expressionStatement(
585
              create.callExpression(globalIds.throwIfTimeout, [
586
                globalIds.native,
587
                create.identifier(startTimeConst),
588
                getTimeAst(),
589
                create.literal(line),
590
                create.literal(column),
591
                create.literal(source)
592
              ])
593
            )
594
          )
595
        }
596
      }
597
      newStatements.push(statement)
31,516✔
598
    }
599
    node.body = newStatements
16,423✔
600
  }
601

602
  simple(program, {
1,258✔
603
    Program: instrumentLoops,
604
    BlockStatement: instrumentLoops
605
  })
606
}
607

608
function wrapWithBuiltins(statements: es.Statement[], nativeStorage: NativeStorage) {
609
  return create.blockStatement([...getBuiltins(nativeStorage), create.blockStatement(statements)])
940✔
610
}
611

612
function getDeclarationsToAccessTranspilerInternals(
613
  globalIds: NativeIds
614
): es.VariableDeclaration[] {
615
  return Object.entries(globalIds).map(([key, { name }]) => {
1,257✔
616
    let value: es.Expression
617
    const kind: AllowedDeclarations = 'const'
13,827✔
618
    if (key === 'native') {
13,827✔
619
      value = create.identifier(NATIVE_STORAGE_ID)
1,257✔
620
    } else if (key === 'globals') {
12,570!
621
      value = create.memberExpression(globalIds.native, 'globals')
×
622
    } else {
623
      value = create.callExpression(
12,570✔
624
        create.memberExpression(create.memberExpression(globalIds.native, 'operators'), 'get'),
625
        [create.literal(key)]
626
      )
627
    }
628
    return create.declaration(name, kind, value)
13,827✔
629
  })
630
}
631

632
export type TranspiledResult = { transpiled: string; sourceMapJson?: RawSourceMap }
633

634
async function transpileToSource(
635
  program: es.Program,
636
  context: Context,
637
  skipUndefined: boolean,
638
  importOptions: ImportTransformOptions
639
): Promise<TranspiledResult> {
640
  const usedIdentifiers = new Set<string>([
1,396✔
641
    ...getIdentifiersInProgram(program),
642
    ...getIdentifiersInNativeStorage(context.nativeStorage)
643
  ])
644
  const globalIds = getNativeIds(program, usedIdentifiers)
1,382✔
645
  if (program.body.length === 0) {
1,382✔
646
    return { transpiled: '' }
5✔
647
  }
648

649
  const functionsToStringMap = generateFunctionsToStringMap(program)
1,377✔
650

651
  transformReturnStatementsToAllowProperTailCalls(program)
1,377✔
652
  transformCallExpressionsToCheckIfFunction(program, globalIds)
1,377✔
653
  transformUnaryAndBinaryOperationsToFunctionCalls(program, globalIds, context.chapter)
1,377✔
654
  transformSomeExpressionsToCheckIfBoolean(program, globalIds)
1,377✔
655
  transformPropertyAssignment(program, globalIds)
1,377✔
656
  transformPropertyAccess(program, globalIds)
1,377✔
657
  checkForUndefinedVariables(program, context.nativeStorage, globalIds, skipUndefined)
1,377✔
658
  transformFunctionDeclarationsToArrowFunctions(program, functionsToStringMap)
1,258✔
659
  wrapArrowFunctionsToAllowNormalCallsAndNiceToString(program, functionsToStringMap, globalIds)
1,258✔
660
  addInfiniteLoopProtection(program, globalIds, usedIdentifiers)
1,258✔
661

662
  const [modulePrefix, importNodes, otherNodes] = await transformImportDeclarations(
1,258✔
663
    program,
664
    usedIdentifiers,
665
    importOptions,
666
    context,
667
    globalIds.native
668
  )
669
  program.body = (importNodes as es.Program['body']).concat(otherNodes)
1,257✔
670

671
  getGloballyDeclaredIdentifiers(program).forEach(id =>
1,257✔
672
    context.nativeStorage.previousProgramsIdentifiers.add(id)
11,694✔
673
  )
674
  const statements = program.body as es.Statement[]
1,257✔
675
  const newStatements = [
1,257✔
676
    ...getDeclarationsToAccessTranspilerInternals(globalIds),
677
    evallerReplacer(globalIds.native, usedIdentifiers),
678
    create.expressionStatement(create.identifier('undefined')),
679
    ...statements
680
  ]
681

682
  program.body =
1,257✔
683
    context.nativeStorage.evaller === null
684
      ? [wrapWithBuiltins(newStatements, context.nativeStorage)]
1,257✔
685
      : [create.blockStatement(newStatements)]
686

687
  const map = new SourceMapGenerator({ file: 'source' })
1,257✔
688
  const transpiled = modulePrefix + generate(program, { sourceMap: map })
1,257✔
689
  const sourceMapJson = map.toJSON()
1,257✔
690
  return { transpiled, sourceMapJson }
1,257✔
691
}
692

693
async function transpileToFullJS(
694
  program: es.Program,
695
  context: Context,
696
  importOptions: ImportTransformOptions,
697
  skipUndefined: boolean
698
): Promise<TranspiledResult> {
699
  const usedIdentifiers = new Set<string>([
17✔
700
    ...getIdentifiersInProgram(program),
701
    ...getIdentifiersInNativeStorage(context.nativeStorage)
702
  ])
703

704
  const globalIds = getNativeIds(program, usedIdentifiers)
17✔
705
  checkForUndefinedVariables(program, context.nativeStorage, globalIds, skipUndefined)
17✔
706

707
  const [modulePrefix, importNodes, otherNodes] = await transformImportDeclarations(
17✔
708
    program,
709
    usedIdentifiers,
710
    importOptions,
711
    context,
712
    globalIds.native
713
  )
714

715
  getGloballyDeclaredIdentifiers(program).forEach(id =>
17✔
716
    context.nativeStorage.previousProgramsIdentifiers.add(id)
6✔
717
  )
718
  const transpiledProgram: es.Program = create.program([
17✔
719
    evallerReplacer(create.identifier(NATIVE_STORAGE_ID), new Set()),
720
    create.expressionStatement(create.identifier('undefined')),
721
    ...(importNodes as es.Statement[]),
722
    ...(otherNodes as es.Statement[])
723
  ])
724

725
  const sourceMap = new SourceMapGenerator({ file: 'source' })
17✔
726
  const transpiled = modulePrefix + generate(transpiledProgram, { sourceMap })
17✔
727
  const sourceMapJson = sourceMap.toJSON()
17✔
728

729
  return { transpiled, sourceMapJson }
17✔
730
}
731

732
export function transpile(
67✔
733
  program: es.Program,
734
  context: Context,
735
  importOptions: RecursivePartial<ImportTransformOptions> = {},
540✔
736
  skipUndefined = false
1,412✔
737
): Promise<TranspiledResult> {
738
  if (context.chapter === Chapter.FULL_JS || context.chapter === Chapter.PYTHON_1) {
1,413✔
739
    const fullImportOptions = {
12✔
740
      checkImports: false,
741
      loadTabs: true,
742
      wrapSourceModules: false,
743
      ...importOptions
744
    }
745

746
    return transpileToFullJS(program, context, fullImportOptions, true)
12✔
747
  } else if (context.variant == Variant.NATIVE) {
1,401✔
748
    const fullImportOptions = {
5✔
749
      checkImports: true,
750
      loadTabs: true,
751
      wrapSourceModules: true,
752
      ...importOptions
753
    }
754
    return transpileToFullJS(program, context, fullImportOptions, false)
5✔
755
  } else {
756
    const fullImportOptions = {
1,396✔
757
      checkImports: true,
758
      loadTabs: true,
759
      wrapSourceModules: true,
760
      ...importOptions
761
    }
762
    return transpileToSource(program, context, skipUndefined, fullImportOptions)
1,396✔
763
  }
764
}
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