Skip to content

Commit 88a93af

Browse files
committedMar 16, 2025
Updating tests
1 parent c16eddf commit 88a93af

18 files changed

+252
-160
lines changed
 

‎src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -407862,7 +407862,7 @@ function stream_ref(s, n) {
407862407862
}
407863407863
`;
407864407864

407865-
exports[`Syntaxes are allowed in the chapter they are introduced 38 1`] = `"Line 4: Export default declarations are not allowed"`;
407865+
exports[`Syntaxes are allowed in the chapter they are introduced 38 1`] = `"Line 4: Export default declarations are not allowed."`;
407866407866

407867407867
exports[`Syntaxes are allowed in the chapter they are introduced 38 2`] = `
407868407868
Object {
@@ -419449,6 +419449,6 @@ function stream_ref(s, n) {
419449419449
}
419450419450
`;
419451419451

419452-
exports[`Syntaxes are allowed in the chapter they are introduced 39 1`] = `"Line 1: Import default specifiers are not allowed"`;
419452+
exports[`Syntaxes are allowed in the chapter they are introduced 39 1`] = `"Line 1: Import default specifiers are not allowed."`;
419453419453

419454419454
exports[`Syntaxes are allowed in the chapter they are introduced 40 1`] = `"Line 1: Namespace imports are not allowed"`;

‎src/parser/__tests__/allowed-syntax.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { Chapter } from '../../types'
1+
import { Chapter, Variant } from '../../types'
22
import { stripIndent } from '../../utils/formatters'
3-
import { snapshotFailure, snapshotSuccess } from '../../utils/testing'
3+
import { snapshotFailure, snapshotSuccess, testSuccess } from '../../utils/testing'
4+
import { expectFinishedResultValue } from '../../utils/testing/misc'
45

56
jest.mock('../../modules/loader/loaders')
67

@@ -354,3 +355,8 @@ test.each([
354355
return Promise.all(tests)
355356
}
356357
)
358+
359+
test('typeof operator is allowed in typed variant', async () => {
360+
const { result } = await testSuccess(`typeof "0";`, { variant: Variant.TYPED })
361+
expectFinishedResultValue(result, 'string')
362+
})

‎src/parser/__tests__/disallowed-syntax.ts

+167-104
Large diffs are not rendered by default.

‎src/parser/parser.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { Program } from 'estree'
1+
import type { Program } from 'estree'
22

3-
import { Context } from '..'
3+
import type { Context } from '..'
44
import { Chapter, Variant } from '../types'
55
import { FullJSParser } from './fullJS'
66
import { FullTSParser } from './fullTS'
77
import { PythonParser } from './python'
88
import { SchemeParser } from './scheme'
99
import { SourceParser } from './source'
1010
import { SourceTypedParser } from './source/typed'
11-
import { AcornOptions, Parser } from './types'
11+
import type { AcornOptions, Parser } from './types'
1212

1313
export function parse<TOptions extends AcornOptions>(
1414
programStr: string,

‎src/parser/source/index.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { parse as acornParse, Token, tokenizer } from 'acorn'
2-
import * as es from 'estree'
1+
import { parse as acornParse, type Token, tokenizer } from 'acorn'
2+
import type es from 'estree'
33

44
import { DEFAULT_ECMA_VERSION } from '../../constants'
5-
import { Chapter, Context, Node, SourceError, Variant } from '../../types'
6-
import { Rule } from '../types'
5+
import { Chapter, type Context, type Node, type SourceError, Variant } from '../../types'
76
import { ancestor, AncestorWalkerFn } from '../../utils/walkers'
87
import { DisallowedConstructError, FatalSyntaxError } from '../errors'
9-
import { AcornOptions, Parser } from '../types'
8+
import type { AcornOptions, Rule, Parser } from '../types'
109
import { createAcornParserOptions, positionToSourceLocation } from '../utils'
1110
import defaultRules from './rules'
1211
import syntaxBlacklist from './syntax'

‎src/parser/source/rules/bracesAroundIfElse.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { RuleError } from '../../errors'
77
export class BracesAroundIfElseError extends RuleError<IfStatement> {
88
constructor(
99
node: IfStatement,
10-
private branch: 'consequent' | 'alternate'
10+
private readonly branch: 'consequent' | 'alternate'
1111
) {
1212
super(node)
1313
}

‎src/parser/source/rules/noDeclareMutable.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import { generate } from 'astring'
22
import type { VariableDeclaration } from 'estree'
33
import type { Rule } from '../../types'
4-
import { getVariableDeclarationName } from '../../../utils/ast/astCreator'
54
import { RuleError } from '../../errors'
65
import { Chapter } from '../../../types'
6+
import { getSourceVariableDeclaration } from '../../../utils/ast/helpers'
77

8-
const mutableDeclarators = ['let', 'var']
8+
const mutableDeclarators: VariableDeclaration['kind'][] = ['let', 'var']
99

1010
export class NoDeclareMutableError extends RuleError<VariableDeclaration> {
1111
public explain() {
12-
return (
13-
'Mutable variable declaration using keyword ' + `'${this.node.kind}'` + ' is not allowed.'
14-
)
12+
return `Mutable variable declaration using keyword '${this.node.kind}' is not allowed.`
1513
}
1614

1715
public elaborate() {
18-
const name = getVariableDeclarationName(this.node)
19-
const value = generate(this.node.declarations[0].init)
16+
const {
17+
id: { name },
18+
init
19+
} = getSourceVariableDeclaration(this.node)
20+
const value = generate(init)
2021

2122
return `Use keyword "const" instead, to declare a constant:\n\n\tconst ${name} = ${value};`
2223
}

‎src/parser/source/rules/noExportNamedDeclarationWithDefault.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { defaultExportLookupName } from '../../../stdlib/localImport.prelude'
33
import type { Rule } from '../../types'
44
import syntaxBlacklist from '../syntax'
55
import { RuleError } from '../../errors'
6+
import { mapAndFilter } from '../../../utils/misc'
67

78
export class NoExportNamedDeclarationWithDefaultError extends RuleError<ExportNamedDeclaration> {
89
public explain() {
9-
return 'Export default declarations are not allowed'
10+
return 'Export default declarations are not allowed.'
1011
}
1112

1213
public elaborate() {
@@ -20,13 +21,11 @@ const noExportNamedDeclarationWithDefault: Rule<ExportNamedDeclaration> = {
2021

2122
checkers: {
2223
ExportNamedDeclaration(node) {
23-
const errors: NoExportNamedDeclarationWithDefaultError[] = []
24-
node.specifiers.forEach(specifier => {
25-
if (specifier.exported.name === defaultExportLookupName) {
26-
errors.push(new NoExportNamedDeclarationWithDefaultError(node))
27-
}
28-
})
29-
return errors
24+
return mapAndFilter(node.specifiers, specifier =>
25+
specifier.exported.name === defaultExportLookupName
26+
? new NoExportNamedDeclarationWithDefaultError(node)
27+
: undefined
28+
)
3029
}
3130
}
3231
}

‎src/parser/source/rules/noExportNamedDeclarationWithSource.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
import type { ExportNamedDeclaration } from 'estree'
22
import type { Rule } from '../../types'
33
import { RuleError } from '../../errors'
4+
import { speciferToString } from '../../../utils/ast/helpers'
45

56
export class NoExportNamedDeclarationWithSourceError extends RuleError<ExportNamedDeclaration> {
67
public explain() {
7-
return 'exports of the form export { a } from "./file.js"; are not allowed.'
8+
return 'exports of the form `export { a } from "./file.js";` are not allowed.'
89
}
910

1011
public elaborate() {
11-
return 'Import what you are trying to export, then export it again.'
12+
const [imports, exps] = this.node.specifiers.reduce(
13+
([ins, outs], spec) => [
14+
[...ins, spec.local.name],
15+
[...outs, speciferToString(spec)]
16+
],
17+
[[], []] as [string[], string[]]
18+
)
19+
const importStr = `import { ${imports.join(', ')} } from "${this.node.source!.value}";`
20+
const exportStr = `export { ${exps.join(', ')} };`
21+
22+
return `Import what you are trying to export, then export it again, like this:\n${importStr}\n${exportStr}`
1223
}
1324
}
1425

‎src/parser/source/rules/noUnspecifiedOperator.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { RuleError } from '../../errors'
55
type ExpressionNodeType = AssignmentExpression | BinaryExpression | UnaryExpression
66

77
export class NoUnspecifiedOperatorError<T extends ExpressionNodeType> extends RuleError<T> {
8-
public unspecifiedOperator: T['operator']
8+
public readonly unspecifiedOperator: T['operator']
99

1010
constructor(node: T) {
1111
super(node)
@@ -22,16 +22,16 @@ export class NoUnspecifiedOperatorError<T extends ExpressionNodeType> extends Ru
2222
}
2323

2424
export class StrictEqualityError extends NoUnspecifiedOperatorError<BinaryExpression> {
25-
public explain() {
25+
public override explain() {
2626
if (this.node.operator === '==') {
27-
return 'Use === instead of =='
27+
return 'Use === instead of ==.'
2828
} else {
29-
return 'Use !== instead of !='
29+
return 'Use !== instead of !=.'
3030
}
3131
}
3232

33-
public elaborate() {
34-
return '== and != is not a valid operator'
33+
public override elaborate() {
34+
return '== and != are not valid operators.'
3535
}
3636
}
3737

‎src/parser/source/rules/noUpdateAssignment.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import type { Rule } from '../../types'
44
import { NoUnspecifiedOperatorError } from './noUnspecifiedOperator'
55

66
export class NoUpdateAssignment extends NoUnspecifiedOperatorError<AssignmentExpression> {
7-
public explain() {
8-
return 'The assignment operator ' + this.node.operator + ' is not allowed. Use = instead.'
7+
public override explain() {
8+
return `The assignment operator ${this.node.operator} is not allowed. Use = instead.`
99
}
1010

11-
public elaborate() {
11+
public override elaborate() {
1212
const leftStr = generate(this.node.left)
1313
const rightStr = generate(this.node.right)
1414
const newOpStr = this.node.operator.slice(0, -1)

‎src/parser/source/rules/noVar.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ import type { VariableDeclaration } from 'estree'
22
import { generate } from 'astring'
33
import type { Rule } from '../../types'
44
import { RuleError } from '../../errors'
5-
import { getVariableDeclarationName } from '../../../utils/ast/astCreator'
5+
import { getSourceVariableDeclaration } from '../../../utils/ast/helpers'
66

77
export class NoVarError extends RuleError<VariableDeclaration> {
88
public explain() {
99
return 'Variable declaration using "var" is not allowed.'
1010
}
1111

1212
public elaborate() {
13-
const name = getVariableDeclarationName(this.node)
14-
const value = generate(this.node.declarations[0].init)
13+
const {
14+
id: { name },
15+
init
16+
} = getSourceVariableDeclaration(this.node)
17+
const value = generate(init)
1518

1619
return `Use keyword "let" instead, to declare a variable:\n\n\tlet ${name} = ${value};`
1720
}

‎src/parser/source/rules/singleVariableDeclaration.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class MultipleDeclarationsError extends RuleError<VariableDeclaration> {
2121
}
2222

2323
public elaborate() {
24-
const fixs = this.fixs.map(n => '\t' + generate(n)).join('\n')
24+
const fixs = this.fixs.map(n => ' ' + generate(n)).join('\n')
2525
return 'Split the variable declaration into multiple lines as follows\n\n' + fixs + '\n'
2626
}
2727
}

‎src/parser/utils.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import {
2-
Comment,
2+
type Comment,
33
ecmaVersion,
4-
Node,
4+
type Node,
55
parse as acornParse,
66
parseExpressionAt as acornParseAt,
7-
Position
7+
type Position
88
} from 'acorn'
99
import { parse as acornLooseParse } from 'acorn-loose'
10-
import { Program, SourceLocation } from 'estree'
10+
import type { Program, SourceLocation } from 'estree'
1111

12-
import { Context } from '..'
12+
import type { Context } from '..'
1313
import { DEFAULT_ECMA_VERSION } from '../constants'
14-
import { SourceError } from '../types'
14+
import type { SourceError } from '../types'
1515
import { validateAndAnnotate } from '../validator/validator'
1616
import { MissingSemicolonError, TrailingCommaError } from './errors'
17-
import { AcornOptions, BabelOptions } from './types'
17+
import type { AcornOptions, BabelOptions } from './types'
1818

1919
/**
2020
* Generates options object for acorn parser
@@ -62,7 +62,7 @@ export function parseAt(
6262
): Node | null {
6363
try {
6464
return acornParseAt(programStr, offset, { ecmaVersion })
65-
} catch (_error) {
65+
} catch {
6666
return null
6767
}
6868
}

‎src/repl/__tests__/__snapshots__/transpiler.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ exports[`Nothing should be written to disk if no output file was specified 1`] =
9393
const builtins = native.operators.get(\\"builtins\\");
9494
native.evaller = program => eval(program);
9595
undefined;
96-
binaryOp(\\"+\\", 4, 1, 1, 1, 0, \\"/Users/henz/Repos/SourceAcademy/js-slang/test.js\\");
96+
binaryOp(\\"+\\", 4, 1, 1, 1, 0, \\"/test.js\\");
9797
}
9898
}
9999
"
@@ -192,7 +192,7 @@ exports[`Writing to file 1`] = `
192192
const builtins = native.operators.get(\\"builtins\\");
193193
native.evaller = program => eval(program);
194194
undefined;
195-
binaryOp(\\"+\\", 4, 1, 1, 1, 0, \\"/Users/henz/Repos/SourceAcademy/js-slang/test.js\\");
195+
binaryOp(\\"+\\", 4, 1, 1, 1, 0, \\"/test.js\\");
196196
}
197197
}
198198
"

‎src/repl/__tests__/transpiler.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
import * as fs from 'fs/promises'
2+
import type pathlib from 'path'
13
import { asMockedFunc } from '../../utils/testing/misc'
24
import { getTranspilerCommand } from '../transpiler'
3-
import * as fs from 'fs/promises'
45
import { expectWritten, getCommandRunner } from './utils'
56

67
jest.mock('fs/promises', () => ({
78
readFile: jest.fn(),
89
writeFile: jest.fn()
910
}))
1011

12+
jest.mock('path', () => {
13+
const actualPath: typeof pathlib = jest.requireActual('path/posix')
14+
return {
15+
...actualPath,
16+
resolve: (...args: string[]) => actualPath.resolve('/', ...args)
17+
}
18+
})
19+
1120
beforeEach(() => {
1221
jest.clearAllMocks()
1322
})

‎src/repl/utils.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import { Chapter, type Language, Variant, type Result } from '../types'
55
import { stringify } from '../utils/stringify'
66
import Closure from '../cse-machine/closure'
77
import { parseError, type Context } from '..'
8+
import { objectKeys } from '../utils/misc'
89

910
export function chapterParser(str: string): Chapter {
1011
let foundChapter: string | undefined
1112

1213
if (/^-?[0-9]+$/.test(str)) {
1314
// Chapter is fully numeric
1415
const value = parseInt(str)
15-
foundChapter = Object.keys(Chapter).find(chapterName => Chapter[chapterName] === value)
16+
foundChapter = objectKeys(Chapter).find(chapterName => Chapter[chapterName] === value)
1617

1718
if (foundChapter === undefined) {
1819
throw new Error(`Invalid chapter value: ${str}`)

‎src/utils/testing/__tests__/testing.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('Testing createTestContext', () => {
2424
expect(context.variant).toEqual(Variant.DEFAULT)
2525
})
2626

27-
test('Providing a chaper runs default variant and that chapter', () => {
27+
test('Providing a chapter runs default variant and that chapter', () => {
2828
const context = createTestContext(Chapter.SOURCE_3)
2929
expect(context.chapter).toEqual(Chapter.SOURCE_3)
3030
expect(context.variant).toEqual(Variant.DEFAULT)

0 commit comments

Comments
 (0)
Please sign in to comment.