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

source-academy / js-slang / 4591476433

pending completion
4591476433

push

github

GitHub
Make stdlib available to modules (#1388)

3486 of 4560 branches covered (76.45%)

Branch coverage included in aggregate %.

173 of 173 new or added lines in 17 files covered. (100.0%)

10321 of 11973 relevant lines covered (86.2%)

136574.04 hits per line

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

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

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

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

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

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

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

44
/**
45
 * Send a HTTP GET request to the modules endpoint to retrieve the manifest
46
 * @return Modules
47
 */
48
export const memoizedGetModuleManifest = memoize(getModuleManifest)
62✔
49
function getModuleManifest(): Modules {
50
  const rawManifest = httpGet(`${MODULES_STATIC_URL}/modules.json`)
8✔
51
  return JSON.parse(rawManifest)
8✔
52
}
53

54
/**
55
 * Send a HTTP GET request to the modules endpoint to retrieve the specified file
56
 * @return String of module file contents
57
 */
58

59
const memoizedGetModuleFileInternal = memoize(getModuleFile)
62✔
60
export const memoizedGetModuleFile = (name: string, type: 'tab' | 'bundle' | 'json') =>
62✔
61
  memoizedGetModuleFileInternal({ name, type })
10✔
62
function getModuleFile({ name, type }: { name: string; type: 'tab' | 'bundle' | 'json' }): string {
63
  return httpGet(`${MODULES_STATIC_URL}/${type}s/${name}.js${type === 'json' ? 'on' : ''}`)
10!
64
}
65

66
/**
67
 * Loads the respective module package (functions from the module)
68
 * @param path imported module name
69
 * @param context
70
 * @param node import declaration node
71
 * @returns the module's functions object
72
 */
73
export function loadModuleBundle(path: string, context: Context, node?: es.Node): ModuleFunctions {
62✔
74
  const modules = memoizedGetModuleManifest()
4✔
75

76
  // Check if the module exists
77
  const moduleList = Object.keys(modules)
4✔
78
  if (moduleList.includes(path) === false) throw new ModuleNotFoundError(path, node)
4!
79

80
  // Get module file
81
  const moduleText = memoizedGetModuleFile(path, 'bundle')
4✔
82
  try {
4✔
83
    const moduleBundle: ModuleBundle = eval(moduleText)
4✔
84
    return wrapSourceModule(path, moduleBundle, getRequireProvider(context))
3✔
85
  } catch (error) {
86
    // console.error("bundle error: ", error)
87
    throw new ModuleInternalError(path, error, node)
1✔
88
  }
89
}
90

91
/**
92
 * Loads the module contents of a package
93
 *
94
 * @param path imported module name
95
 * @param node import declaration node
96
 * @returns an array of functions
97
 */
98
export function loadModuleTabs(path: string, node?: es.Node) {
62✔
99
  const modules = memoizedGetModuleManifest()
4✔
100
  // Check if the module exists
101
  const moduleList = Object.keys(modules)
4✔
102
  if (moduleList.includes(path) === false) throw new ModuleNotFoundError(path, node)
4!
103

104
  // Retrieves the tabs the module has from modules.json
105
  const sideContentTabPaths: string[] = modules[path].tabs
4✔
106
  // Load the tabs for the current module
107
  return sideContentTabPaths.map(path => {
4✔
108
    const rawTabFile = memoizedGetModuleFile(path, 'tab')
4✔
109
    try {
4✔
110
      return eval(rawTabFile)
4✔
111
    } catch (error) {
112
      // console.error('tab error:', error);
113
      throw new ModuleInternalError(path, error, node)
1✔
114
    }
115
  })
116
}
117

118
export const memoizedloadModuleDocs = memoize(loadModuleDocs)
62✔
119
export function loadModuleDocs(path: string, node?: es.Node) {
62✔
120
  try {
×
121
    const modules = memoizedGetModuleManifest()
×
122
    // Check if the module exists
123
    const moduleList = Object.keys(modules)
×
124
    if (!moduleList.includes(path)) throw new ModuleNotFoundError(path, node)
×
125
    const result = getModuleFile({ name: path, type: 'json' })
×
126
    return JSON.parse(result) as ModuleDocumentation
×
127
  } catch (error) {
128
    console.warn('Failed to load module documentation')
×
129
    return null
×
130
  }
131
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc