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

source-academy / js-slang / 5406352152

pending completion
5406352152

Pull #1428

github

web-flow
Merge 0380f5ed7 into 8618e26e4
Pull Request #1428: Further Enhancements to the Module System

3611 of 4728 branches covered (76.37%)

Branch coverage included in aggregate %.

831 of 831 new or added lines in 50 files covered. (100.0%)

10852 of 12603 relevant lines covered (86.11%)

93898.15 hits per line

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

75.36
/src/modules/moduleLoader.ts
1
import es from 'estree'
2
import { memoize } from 'lodash'
1✔
3
import { XMLHttpRequest as NodeXMLHttpRequest } from 'xmlhttprequest-ts'
1✔
4

5
import { Context } from '../types'
6
import { wrapSourceModule } from '../utils/operators'
1✔
7
import { ModuleConnectionError, ModuleInternalError, ModuleNotFoundError } from './errors'
1✔
8
import type {
9
  ModuleBundle,
10
  ModuleDocumentation,
11
  ModuleFunctions,
12
  ModuleManifest
13
} from './moduleTypes'
14
import { getRequireProvider } from './requireProvider'
1✔
15

16
// Supports both JSDom (Web Browser) environment and Node environment
17
export const newHttpRequest = () =>
1✔
18
  typeof window === 'undefined' ? new NodeXMLHttpRequest() : new XMLHttpRequest()
16!
19

20
// Default modules static url. Exported for testing.
21
export let MODULES_STATIC_URL = 'https://source-academy.github.io/modules'
1✔
22

23
export function setModulesStaticURL(url: string) {
1✔
24
  MODULES_STATIC_URL = url
1✔
25
}
26

27
/**
28
 * Send a HTTP Get request to the specified endpoint.
29
 * @return NodeXMLHttpRequest | XMLHttpRequest
30
 */
31
export function httpGet(url: string): string {
1✔
32
  const request = newHttpRequest()
15✔
33
  try {
15✔
34
    // If running function in node environment, set request timeout
35
    if (typeof window === 'undefined') request.timeout = 10000
15!
36
    request.open('GET', url, false)
15✔
37
    request.send(null)
15✔
38
  } catch (error) {
39
    if (!(error instanceof DOMException)) throw error
×
40
  }
41
  if (request.status !== 200 && request.status !== 304) throw new ModuleConnectionError()
15✔
42
  return request.responseText
14✔
43
}
44

45
/**
46
 * Send a HTTP GET request to the modules endpoint to retrieve the manifest
47
 * @return Modules
48
 */
49
export const memoizedGetModuleManifest = memoize(getModuleManifest)
1✔
50
function getModuleManifest(): ModuleManifest {
51
  try {
5✔
52
    const rawManifest = httpGet(`${MODULES_STATIC_URL}/modules.json`)
5✔
53
    return JSON.parse(rawManifest)
5✔
54
  } catch (error) {
55
    throw new ModuleConnectionError(error)
×
56
  }
57
}
58

59
export const memoizedGetBundle = memoize(getModuleBundle)
1✔
60
function getModuleBundle(path: string) {
61
  return httpGet(`${MODULES_STATIC_URL}/bundles/${path}.js`)
3✔
62
}
63

64
export const memoizedGetTab = memoize(getModuleTab)
1✔
65
function getModuleTab(path: string) {
66
  return httpGet(`${MODULES_STATIC_URL}/tabs/${path}.js`)
5✔
67
}
68

69
// /**
70
//  * Send a HTTP GET request to the modules endpoint to retrieve the specified file
71
//  * @return String of module file contents
72
//  */
73

74
// const memoizedGetModuleFileInternal = memoize(getModuleFile)
75
// export const memoizedGetModuleFile = (name: string, type: 'tab' | 'bundle' | 'json') =>
76
//   memoizedGetModuleFileInternal({ name, type })
77
// function getModuleFile({ name, type }: { name: string; type: 'tab' | 'bundle' | 'json' }): string {
78
//   return httpGet(`${MODULES_STATIC_URL}/${type}s/${name}.js${type === 'json' ? 'on' : ''}`)
79
// }
80

81
/**
82
 * Loads the respective module package (functions from the module)
83
 * @param path imported module name
84
 * @param context
85
 * @param node import declaration node
86
 * @returns the module's functions object
87
 */
88
export function loadModuleBundle(
1✔
89
  path: string,
90
  context: Context,
91
  wrapModules: boolean,
92
  node?: es.Node
93
): ModuleFunctions {
94
  const modules = memoizedGetModuleManifest()
2✔
95

96
  // Check if the module exists
97
  const moduleList = Object.keys(modules)
2✔
98
  if (moduleList.includes(path) === false) throw new ModuleNotFoundError(path, node)
2!
99

100
  // Get module file
101
  const moduleText = memoizedGetBundle(path)
2✔
102
  try {
2✔
103
    const moduleBundle: ModuleBundle = eval(moduleText)
2✔
104
    if (wrapModules) return moduleBundle(getRequireProvider(context))
1!
105
    return wrapSourceModule(path, moduleBundle, getRequireProvider(context))
1✔
106
  } catch (error) {
107
    // console.error("bundle error: ", error)
108
    throw new ModuleInternalError(path, error, node)
1✔
109
  }
110
}
111

112
/**
113
 * Loads the module contents of a package
114
 *
115
 * @param path imported module name
116
 * @param node import declaration node
117
 * @returns an array of functions
118
 */
119
export function loadModuleTabs(path: string, node?: es.Node) {
1✔
120
  const modules = memoizedGetModuleManifest()
2✔
121
  // Check if the module exists
122
  const moduleList = Object.keys(modules)
2✔
123
  if (moduleList.includes(path) === false) throw new ModuleNotFoundError(path, node)
2!
124

125
  // Retrieves the tabs the module has from modules.json
126
  const sideContentTabPaths: string[] = modules[path].tabs
2✔
127
  // Load the tabs for the current module
128
  return sideContentTabPaths.map(path => {
2✔
129
    const rawTabFile = memoizedGetTab(path)
4✔
130
    try {
4✔
131
      return eval(rawTabFile)
4✔
132
    } catch (error) {
133
      // console.error('tab error:', error);
134
      throw new ModuleInternalError(path, error, node)
1✔
135
    }
136
  })
137
}
138

139
export const memoizedGetModuleDocs = memoize(loadModuleDocs)
1✔
140
export function loadModuleDocs(path: string, node?: es.Node) {
1✔
141
  try {
×
142
    const modules = memoizedGetModuleManifest()
×
143
    // Check if the module exists
144
    const moduleList = Object.keys(modules)
×
145
    if (!moduleList.includes(path)) throw new ModuleNotFoundError(path, node)
×
146
    const result = httpGet(`${MODULES_STATIC_URL}/jsons/${path}.json`)
×
147
    return JSON.parse(result) as ModuleDocumentation
×
148
  } catch (error) {
149
    console.warn(`Failed to load documentation for ${path}:`, error)
×
150
    return null
×
151
  }
152
}
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