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

source-academy / js-slang / 23995741899

05 Apr 2026 06:14AM UTC coverage: 77.093% (+0.002%) from 77.091%
23995741899

push

github

web-flow
Upgrade to TypeScript 6 and Prettier improvements (#1936)

* Upgrade TypeScript to v6

* Fix import source

* Fix tsconfig

* Fix preexisting type errors

* Remove scm-slang

* Bump node types

* Fix tsconfig

* Fix node types specifier

* Enable trailing commas

* Enable semicolons

* Check and commit files with changed line numbers

* Update Yarn to 4.13.0

* Remove unneeded sicp package deps

3112 of 4282 branches covered (72.68%)

Branch coverage included in aggregate %.

3761 of 5218 new or added lines in 152 files covered. (72.08%)

26 existing lines in 9 files now uncovered.

7136 of 9011 relevant lines covered (79.19%)

175254.05 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 { getSourceVariableDeclaration } from '../utils/ast/helpers';
8
import { isDeclaration } from '../utils/ast/typeGuards';
9
import { oneLine } from '../utils/formatters';
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
  console.error(oneLine`
×
21
    UNREACHABLE CODE REACHED!
22
    Please file an issue at
23
    https://github.com/source-academy/js-slang/issues
24
    if you see this.
25
  `);
26
}
27

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

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

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

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

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

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

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

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

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

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

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

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