• 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

72.86
/src/modules/moduleLoaderAsync.ts
1
import type { Node } from 'estree'
2
import { memoize } from 'lodash'
65✔
3

4
import type { Context } from '..'
5
import { PromiseTimeoutError, timeoutPromise } from '../utils/misc'
65✔
6
import { wrapSourceModule } from '../utils/operators'
65✔
7
import { ModuleConnectionError, ModuleInternalError, ModuleNotFoundError } from './errors'
65✔
8
import { MODULES_STATIC_URL } from './moduleLoader'
65✔
9
import type { ModuleBundle, ModuleDocumentation, ModuleManifest } from './moduleTypes'
10
import { getRequireProvider } from './requireProvider'
65✔
11

12
export function httpGetAsync(path: string, type: 'json'): Promise<object>
13
export function httpGetAsync(path: string, type: 'text'): Promise<string>
14
export async function httpGetAsync(path: string, type: 'json' | 'text') {
65✔
15
  try {
12✔
16
    const resp = await timeoutPromise(
12✔
17
      fetch(path, {
18
        method: 'GET'
19
      }),
20
      10000
21
    )
22

23
    if (resp.status !== 200 && resp.status !== 304) {
11✔
24
      throw new ModuleConnectionError()
1✔
25
    }
26

27
    const promise = type === 'text' ? resp.text() : resp.json()
10✔
28
    return timeoutPromise(promise, 10000)
10✔
29
  } catch (error) {
30
    if (error instanceof TypeError || error instanceof PromiseTimeoutError) {
2✔
31
      throw new ModuleConnectionError()
1✔
32
    }
33
    if (!(error instanceof ModuleConnectionError)) throw new ModuleInternalError(path, error)
1!
34
    throw error
1✔
35
  }
36
}
37

38
/**
39
 * Send a HTTP GET request to the modules endpoint to retrieve the manifest
40
 * @return Modules
41
 */
42
export const memoizedGetModuleManifestAsync = memoize(getModuleManifestAsync)
65✔
43
function getModuleManifestAsync(): Promise<ModuleManifest> {
44
  return httpGetAsync(`${MODULES_STATIC_URL}/modules.json`, 'json') as Promise<ModuleManifest>
2✔
45
}
46

47
async function checkModuleExists(moduleName: string, node?: Node) {
48
  const modules = await memoizedGetModuleManifestAsync()
1✔
49
  // Check if the module exists
50
  if (!(moduleName in modules)) throw new ModuleNotFoundError(moduleName, node)
1!
51

52
  return modules[moduleName]
1✔
53
}
54

55
export const memoizedGetModuleBundleAsync = memoize(getModuleBundleAsync)
65✔
56
async function getModuleBundleAsync(moduleName: string): Promise<string> {
57
  return httpGetAsync(`${MODULES_STATIC_URL}/bundles/${moduleName}.js`, 'text')
3✔
58
}
59

60
export const memoizedGetModuleTabAsync = memoize(getModuleTabAsync)
65✔
61
function getModuleTabAsync(tabName: string): Promise<string> {
62
  return httpGetAsync(`${MODULES_STATIC_URL}/tabs/${tabName}.js`, 'text')
3✔
63
}
64

65
export const memoizedGetModuleDocsAsync = memoize(getModuleDocsAsync)
65✔
66
async function getModuleDocsAsync(moduleName: string): Promise<ModuleDocumentation | null> {
67
  try {
×
68
    const result = await httpGetAsync(`${MODULES_STATIC_URL}/jsons/${moduleName}.json`, 'json')
×
69
    return result as ModuleDocumentation
×
70
  } catch (error) {
71
    console.warn(`Failed to load documentation for ${moduleName}:`, error)
×
72
    return null
×
73
  }
74
}
75

76
export async function loadModuleTabsAsync(moduleName: string, node?: Node) {
65✔
77
  const moduleInfo = await checkModuleExists(moduleName, node)
1✔
78

79
  // Load the tabs for the current module
80
  return Promise.all(
1✔
81
    moduleInfo.tabs.map(async path => {
2✔
82
      const rawTabFile = await memoizedGetModuleTabAsync(path)
2✔
83
      try {
2✔
84
        return eval(rawTabFile)
2✔
85
      } catch (error) {
86
        // console.error('tab error:', error);
87
        throw new ModuleInternalError(path, error, node)
2✔
88
      }
89
    })
90
  )
91
}
92

93
export async function loadModuleBundleAsync(
65✔
94
  moduleName: string,
95
  context: Context,
96
  wrapModule: boolean,
97
  node?: Node
98
) {
99
  // await checkModuleExists(moduleName, node)
100
  const moduleText = await memoizedGetModuleBundleAsync(moduleName)
2✔
101
  try {
2✔
102
    const moduleBundle: ModuleBundle = eval(moduleText)
2✔
103

104
    if (wrapModule) return wrapSourceModule(moduleName, moduleBundle, getRequireProvider(context))
1!
105
    return moduleBundle(getRequireProvider(context))
1✔
106
  } catch (error) {
107
    // console.error("bundle error: ", error)
108
    throw new ModuleInternalError(moduleName, error, node)
1✔
109
  }
110
}
111

112
/**
113
 * Initialize module contexts and add UI tabs needed for modules to program context
114
 *
115
 * @param program AST of program to be ran
116
 * @param context The context of the program
117
 */
118
export async function initModuleContextAsync(
65✔
119
  moduleName: string,
120
  context: Context,
121
  loadTabs: boolean
122
) {
123
  // Load the module's tabs
124
  if (!(moduleName in context.moduleContexts)) {
×
125
    context.moduleContexts[moduleName] = {
×
126
      state: null,
127
      tabs: loadTabs ? await loadModuleTabsAsync(moduleName) : null
×
128
    }
129
  } else if (context.moduleContexts[moduleName].tabs === null && loadTabs) {
×
130
    context.moduleContexts[moduleName].tabs = await loadModuleTabsAsync(moduleName)
×
131
  }
132
}
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