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

source-academy / js-slang / 19080105112

04 Nov 2025 07:13PM UTC coverage: 76.655% (-1.5%) from 78.192%
19080105112

push

github

web-flow
Migrate to Vitest (#1815)

* Upgrade TS to 5.8

* Remove deprecated tsconfig option

* Remove duplicate properties

* Upgrade TS to v5.9

* Add types for mathjs

* Fix some type errors

* Update tsconfig

* Fix more type errors

* Fix remaining errors

* Update GitHub workflows

* Fix type error

* Update scm-slang to latest

* Add newline to EOF

* Fix cse-machine types and utils to use fewer type assertions

* Migrate to vitest tests

* Migrate tests to vitest

* Relocate base error files and types

* Get modules tests working

* run format

* Sort tsconfig compiler options

* Update eslint packages to match typescript version

* Small linting change

* Use function names instead of strings for describe blocks

* Include scripts in linting

* Move tests and replace describe titles with functions

* Add type modifiers and reformat tests

* Simplify isEnvDependent code

* Instruct tsc to ignore py-slang's tests during build

* Move walkers to be under utils/ast

* Update tests failing due to timeout

* Update cse-machine typings

* Incorporate import assertions into docs importer

* Add context property to error result

* Update test timeout and add no-restricted-import rule for commander imports

* Update snapshots

* Run format

* Update snapshots again....

* Run format

* Change to use the test.each

* Disable the svmc snapshot test cause it doesn't work

* Add a new test for properties when loading modules

* Run format

* Convert stdlib parser to use nodetypetonode helper type

* A working version of the statementSeqTransform

* More compact version of seq transform

* Remove unnecessary type assertions

* Clean up some documentation bits and pieces

* Use type imports for tracer

* Swap the list library to use generics

* Fix some error messages and tests

* Fix list tests

* Run format

* Update stream library and tests

* Running format

* Add some documentation for the scripts

* Remove unnecessary packages

* Remove even more unnecessary ty... (continued)

3501 of 4761 branches covered (73.53%)

Branch coverage included in aggregate %.

429 of 636 new or added lines in 54 files covered. (67.45%)

34 existing lines in 12 files now uncovered.

7085 of 9049 relevant lines covered (78.3%)

193248.37 hits per line

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

75.0
/src/stdlib/parser.ts
1
import type es from 'estree'
2

3
import { parse as sourceParse } from '../parser/parser'
4
import { SourceParser } from '../parser/source'
5
import { libraryParserLanguage } from '../parser/source/syntax'
6
import type { Context, ContiguousArrayElements, Node, NodeTypeToNode, Value } from '../types'
7
import { oneLine } from '../utils/formatters'
8
import { getSourceVariableDeclaration } from '../utils/ast/helpers'
9
import { isDeclaration } from '../utils/ast/typeGuards'
10
import { vector_to_list, type List } from './list'
11

12
class ParseError extends Error {
13
  constructor(message: string) {
14
    super(message)
3✔
15
    this.name = 'ParseError'
3✔
16
  }
17
}
18

19
function unreachable() {
20
  // tslint:disable-next-line:no-console
21
  console.error(oneLine`
×
22
    UNREACHABLE CODE REACHED!
23
    Please file an issue at
24
    https://github.com/source-academy/js-slang/issues
25
    if you see this.
26
  `)
27
}
28

29
// sequences of expressions of length 1
30
// can be represented by the element itself,
31
// instead of constructing a sequence
32

33
function makeSequenceIfNeeded(exs: Node[]): List {
34
  return exs.length === 1
99✔
35
    ? transform(exs[0])
36
    : vector_to_list(['sequence', vector_to_list(exs.map(transform))])
37
}
38

39
function makeBlockIfNeeded(exs: Node[]): List {
40
  return hasDeclarationAtToplevel(exs)
30✔
41
    ? vector_to_list(['block', makeSequenceIfNeeded(exs)])
42
    : makeSequenceIfNeeded(exs)
43
}
44

45
// checks if sequence has declaration at toplevel
46
// (outside of any block)
47
function hasDeclarationAtToplevel(exs: Node[]) {
48
  return exs.some(isDeclaration)
30✔
49
}
50

51
type ParseTransformer<T extends Node> = (node: T) => List
52
type ASTTransformers = {
53
  [K in Node['type']]?: ParseTransformer<NodeTypeToNode<K>>
54
}
55

56
const transformers: ASTTransformers = {
70✔
57
  ArrayExpression: ({ elements }) =>
6✔
58
    vector_to_list([
59
      'array_expression',
60
      vector_to_list((elements as ContiguousArrayElements).map(transform))
61
    ]),
62
  ArrowFunctionExpression: node =>
13✔
63
    vector_to_list([
64
      'lambda_expression',
65
      vector_to_list(node.params.map(transform)),
66
      node.body.type === 'BlockStatement'
13✔
67
        ? // body.body: strip away one layer of block:
68
          // The body of a function is the statement
69
          // inside the curly braces.
70
          makeBlockIfNeeded(node.body.body)
71
        : vector_to_list(['return_statement', transform(node.body)])
72
    ]),
73
  AssignmentExpression: node => {
74
    if (node.left.type === 'Identifier') {
24✔
75
      return vector_to_list(['assignment', transform(node.left), transform(node.right)])
16✔
76
    } else if (node.left.type === 'MemberExpression') {
8!
77
      return vector_to_list(['object_assignment', transform(node.left), transform(node.right)])
8✔
78
    } else {
NEW
79
      unreachable()
×
NEW
80
      throw new ParseError('Invalid assignment')
×
81
    }
82
  },
83
  BinaryExpression: node =>
44✔
84
    vector_to_list([
85
      'binary_operator_combination',
86
      node.operator,
87
      transform(node.left),
88
      transform(node.right)
89
    ]),
90
  BlockStatement: ({ body }) => makeBlockIfNeeded(body),
20✔
91
  BreakStatement: () => vector_to_list(['break_statement']),
4✔
92
  CallExpression: ({ callee, arguments: args }) =>
18✔
93
    vector_to_list(['application', transform(callee), vector_to_list(args.map(transform))]),
94
  ClassDeclaration: node => {
NEW
95
    return vector_to_list([
×
96
      'class_declaration',
97
      vector_to_list([
98
        'name',
99
        node.id?.name,
100
        !node.superClass ? null : transform(node.superClass),
×
101
        node.body.body.map(transform)
102
      ])
103
    ])
104
  },
105
  ConditionalExpression: node =>
3✔
106
    vector_to_list([
107
      'conditional_expression',
108
      transform(node.test),
109
      transform(node.consequent),
110
      transform(node.alternate)
111
    ]),
112
  ContinueStatement: () => vector_to_list(['continue_statement']),
4✔
113
  ExportDefaultDeclaration: node =>
4✔
114
    vector_to_list(['export_default_declaration', transform(node.declaration)]),
115
  ExportNamedDeclaration: ({ declaration, specifiers }) =>
8✔
116
    vector_to_list([
117
      'export_named_declaration',
118
      declaration ? transform(declaration) : specifiers.map(transform)
8✔
119
    ]),
120
  ExportSpecifier: node => vector_to_list(['name', node.exported.name]),
2✔
121
  ExpressionStatement: ({ expression }) => transform(expression),
75✔
122
  ForStatement: node =>
4✔
123
    vector_to_list([
124
      'for_loop',
125
      transform(node.init!),
126
      transform(node.test!),
127
      transform(node.update!),
128
      transform(node.body)
129
    ]),
130
  FunctionDeclaration: node =>
8✔
131
    vector_to_list([
132
      'function_declaration',
133
      transform(node.id!),
134
      vector_to_list(node.params.map(transform)),
135
      makeBlockIfNeeded(node.body.body)
136
    ]),
NEW
137
  FunctionExpression: ({ body: { body }, params }) =>
×
138
    vector_to_list([
139
      'lambda_expression',
140
      vector_to_list(params.map(transform)),
141
      makeBlockIfNeeded(body)
142
    ]),
143
  Identifier: ({ name }) => vector_to_list(['name', name]),
179✔
144
  IfStatement: node =>
8✔
145
    vector_to_list([
146
      'conditional_statement',
147
      transform(node.test),
148
      transform(node.consequent),
149
      node.alternate == null ? makeSequenceIfNeeded([]) : transform(node.alternate)
8✔
150
    ]),
151
  ImportDeclaration: node =>
3✔
152
    vector_to_list([
153
      'import_declaration',
154
      vector_to_list(node.specifiers.map(transform)),
155
      node.source.value
156
    ]),
157
  ImportDefaultSpecifier: () => vector_to_list(['default']),
2✔
158
  ImportSpecifier: node => vector_to_list(['name', node.imported.name]),
1✔
159
  Literal: ({ value }) => vector_to_list(['literal', value]),
159✔
160
  LogicalExpression: node =>
7✔
161
    vector_to_list([
162
      'logical_composition',
163
      node.operator,
164
      transform(node.left),
165
      transform(node.right)
166
    ]),
167
  MemberExpression: node => {
168
    // "computed" property of MemberExpression distinguishes
169
    // between dot access (not computed) and
170
    // a[...] (computed)
171
    // the key in dot access is meant as string, and
172
    // represented by a "property" node in parse result
173
    return vector_to_list([
19✔
174
      'object_access',
175
      transform(node.object),
176
      !node.computed && node.property.type === 'Identifier'
43✔
177
        ? vector_to_list(['property', node.property.name])
178
        : transform(node.property)
179
    ])
180
  },
NEW
181
  MethodDefinition: node =>
×
182
    vector_to_list([
183
      'method_definition',
184
      node.kind,
185
      node.static,
186
      transform(node.key),
187
      transform(node.value)
188
    ]),
NEW
189
  NewExpression: ({ callee, arguments: args }) =>
×
190
    vector_to_list(['new_expression', transform(callee), vector_to_list(args.map(transform))]),
191
  ObjectExpression: ({ properties }) =>
11✔
192
    vector_to_list(['object_expression', vector_to_list(properties.map(transform))]),
193
  Program: ({ body }) => makeSequenceIfNeeded(body),
68✔
194
  Property: node => {
195
    // identifiers before the ":" in literal objects are meant
196
    // as string, and represented by a "property" node in parse result
197
    return vector_to_list([
21✔
198
      'key_value_pair',
199
      node.key.type === 'Identifier'
21✔
200
        ? vector_to_list(['property', node.key.name])
201
        : transform(node.key),
202
      transform(node.value)
203
    ])
204
  },
205
  RestElement: ({ argument }) => vector_to_list(['rest_element', transform(argument)]),
1✔
206
  ReturnStatement: node => vector_to_list(['return_statement', transform(node.argument!)]),
13✔
207
  SpreadElement: ({ argument }) => vector_to_list(['spread_element', transform(argument)]),
1✔
NEW
208
  StatementSequence: ({ body }) => makeSequenceIfNeeded(body),
×
NEW
209
  Super: () => vector_to_list(['super_expression']),
×
NEW
210
  ThisExpression: () => vector_to_list(['this_expression']),
×
NEW
211
  ThrowStatement: ({ argument }) => vector_to_list(['throw_statement', transform(argument)]),
×
212
  TryStatement: node => {
NEW
213
    return vector_to_list([
×
214
      'try_statement',
215
      transform(node.block),
216
      !node.handler ? null : vector_to_list(['name', (node.handler.param as es.Identifier).name]),
×
217
      !node.handler ? null : transform(node.handler.body)
×
218
    ])
219
  },
220
  UnaryExpression: ({ operator, argument }) =>
5✔
221
    vector_to_list([
222
      'unary_operator_combination',
223
      operator === '-' ? '-unary' : operator,
5✔
224
      transform(argument)
225
    ]),
226
  VariableDeclaration: node => {
227
    const { id, init } = getSourceVariableDeclaration(node)
28✔
228

229
    if (node.kind === 'let') {
28✔
230
      return vector_to_list(['variable_declaration', transform(id), transform(init)])
15✔
231
    } else if (node.kind === 'const') {
13!
232
      return vector_to_list(['constant_declaration', transform(id), transform(init)])
13✔
233
    } else {
NEW
234
      unreachable()
×
NEW
235
      throw new ParseError('Invalid declaration kind')
×
236
    }
237
  },
238
  WhileStatement: ({ test, body }) =>
2✔
239
    vector_to_list(['while_loop', transform(test), transform(body)])
240
}
241

242
/**
243
 * Converts the given Node to a Source Value (which will be a list
244
 * consisting of a string description followed by that node's components)
245
 */
246
function transform(node: Node) {
247
  if (!(node.type in transformers)) {
765!
UNCOV
248
    unreachable()
×
249
    throw new ParseError('Cannot transform unknown type: ' + node.type)
×
250
  }
251

252
  const transformer = transformers[node.type] as ParseTransformer<Node>
765✔
253
  return transformer(node)
765✔
254
}
255

256
export function parse(x: string, context: Context): Value {
257
  context.chapter = libraryParserLanguage
71✔
258
  const program = sourceParse(x, context)
71✔
259
  if (context.errors.length > 0) {
71✔
260
    throw new ParseError(context.errors[0].explain())
3✔
261
  }
262

263
  if (program) {
68!
264
    return transform(program)
68✔
265
  } else {
266
    unreachable()
×
267
    throw new ParseError('Invalid parse')
×
268
  }
269
}
270

271
export function tokenize(x: string, context: Context): Value {
272
  const tokensArr = SourceParser.tokenize(x, context).map(tok => x.substring(tok.start, tok.end))
58✔
273
  return vector_to_list(tokensArr)
3✔
274
}
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