diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 104a19c..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - plugins: [ - '@typescript-eslint', - ], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - ], - rules: { - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/explicit-module-boundary-types': 0 - } -}; diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 0769f48..8035dbb 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -16,13 +16,13 @@ jobs: strategy: matrix: - node-version: [14, 16, 18] + node-version: [20, 22] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm i diff --git a/.gitignore b/.gitignore index fe30b6a..4913e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules package-lock.json pnpm-lock.yaml yarn.lock +bun.lockb /dist /build /.nyc_output diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..96e6b31 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,30 @@ +import eslint from "@eslint/js"; +import tseslint from 'typescript-eslint'; +import tsParser from "@typescript-eslint/parser"; +import globals from "globals"; + +export default [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + files: ["**/*.ts"], + + rules: { + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/explicit-module-boundary-types': 0 + }, + + languageOptions: { + globals: { + ...globals.builtin, + ...globals.nodeBuiltin, + ...globals.browser, + ...globals.node, + }, + + parser: tsParser, + ecmaVersion: 2019, + sourceType: "module", + }, + } +]; diff --git a/package.json b/package.json index c2136dc..5b09e30 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "node": ">=10.0.0" }, "scripts": { - "test": "NODE_OPTIONS='--loader ts-node/esm' c8 pta 'test/**/*.spec.ts'", + "test": "bun test --coverage", "lint": "eslint src test", "preversion": "npm test", "version": "standard-changelog && git add CHANGELOG.md", @@ -36,22 +36,20 @@ }, "homepage": "https://github.com/3cp/scoped-eval#readme", "devDependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estraverse": "^5.1.1", - "@typescript-eslint/eslint-plugin": "^5.29.0", - "@typescript-eslint/parser": "^5.29.0", - "@vercel/ncc": "^0.34.0", - "c8": "^7.11.3", - "eslint": "^8.18.0", - "eslint-scope": "^7.1.1", + "@types/eslint-scope": "^3.7.7", + "@types/estraverse": "^5.1.7", + "@vercel/ncc": "^0.38.3", + "bun": "^1.1.38", + "eslint": "^9.17.0", + "eslint-scope": "^8.2.0", "estraverse": "^5.3.0", - "meriyah": "^4.2.1", - "pta": "^1.1.0", - "rimraf": "^3.0.2", - "standard-changelog": "^2.0.27", - "ts-node": "^10.8.1", - "typescript": "^4.7.4", - "zora": "^5.0.3" + "globals": "^15.13.0", + "meriyah": "^6.0.3", + "rimraf": "^6.0.1", + "standard-changelog": "^6.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.1" }, "ava": { "extensions": [ diff --git a/src/get-globals.ts b/src/get-globals.ts index 6aea436..6aa6a30 100644 --- a/src/get-globals.ts +++ b/src/get-globals.ts @@ -19,7 +19,6 @@ export default function (ast: ESTree.Node, allowedGlobals: {[key: string]: boole // TODO: warn user about usage of not by default allowed global? // show user how to allow extra globals. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const range = ref.identifier.range!; if (globals[name]) { diff --git a/src/index.ts b/src/index.ts index 37db7db..15acd16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -83,7 +83,6 @@ export default class ScopedEval { traverse(ast as ESTree.Node, { enter: function (node: ESTree.Node) { if (node.type === 'ImportExpression') { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion throw new Error(`[${node.loc!.start.line}:${node.loc!.start.column}]: Dynamic import is not allowed`); } } diff --git a/src/string-interpolation.ts b/src/string-interpolation.ts index cfdb5fc..856a6ad 100644 --- a/src/string-interpolation.ts +++ b/src/string-interpolation.ts @@ -51,7 +51,7 @@ function split(code: string): Part[] { // Reject non-expression throw new Error("not a valid expression: " + interpolation); } - } catch (e) { + } catch { // Try next "}" continue; } diff --git a/test/get-globals.spec.ts b/test/get-globals.spec.ts index 40cdfe2..fcf68e0 100644 --- a/test/get-globals.spec.ts +++ b/test/get-globals.spec.ts @@ -1,4 +1,4 @@ -import {test} from 'zora'; +import { expect, test } from "bun:test"; import parse from '../src/parse'; import * as ESTree from 'estree'; import _getGlobals from '../src/get-globals'; @@ -7,13 +7,13 @@ function getGlobals(ast: ESTree.Node, allowedGlobals: {[key: string]: boolean}): return Object.assign({}, _getGlobals(ast, allowedGlobals)); } -test('getGlobals return global variables range', t => { - t.deepEqual(getGlobals(parse('a'), {}), { +test('getGlobals return global variables range', () => { + expect(getGlobals(parse('a'), {})).toEqual({ a: [ [0, 1] ] }); - t.deepEqual(getGlobals(parse('a=a*b'), {}), { + expect(getGlobals(parse('a=a*b'), {})).toEqual({ a: [ [0, 1], [2, 3], @@ -24,8 +24,8 @@ test('getGlobals return global variables range', t => { }); }); -test('getGlobals excludes allowed globals', t => { - t.deepEqual(getGlobals(parse('a=undefined'), {}), { +test('getGlobals excludes allowed globals', () => { + expect(getGlobals(parse('a=undefined'), {})).toEqual({ a: [ [0, 1] ], @@ -33,16 +33,16 @@ test('getGlobals excludes allowed globals', t => { [2, 11] ] }); - t.deepEqual(getGlobals(parse('a=undefined'), {'undefined': true}), { + expect(getGlobals(parse('a=undefined'), {'undefined': true})).toEqual({ a: [ [0, 1] ] }); }); -test('getGlobals only extracts global variables', t => { +test('getGlobals only extracts global variables', () => { const code = `if (typeof foo === 'number') { return Math.floor(foo / 7); }`; - t.deepEqual(getGlobals(parse(code), {'Math': true}), { + expect(getGlobals(parse(code), {'Math': true})).toEqual({ foo: [ [11, 14], [49, 52] @@ -50,54 +50,54 @@ test('getGlobals only extracts global variables', t => { }); }); -test('getGlobals skips local variables', t => { +test('getGlobals skips local variables', () => { const code = `let b=a+1;b;`; - t.deepEqual(getGlobals(parse(code), {}), { + expect(getGlobals(parse(code), {})).toEqual({ a: [ [6, 7] ] }); }); -test('getGlobals skips local variables defined with const', t => { +test('getGlobals skips local variables defined with const', () => { const code = `const b=a+1;b;`; - t.deepEqual(getGlobals(parse(code), {}), { + expect(getGlobals(parse(code), {})).toEqual({ a: [ [8, 9] ] }); }); -test('getGlobals skips local variables defined with var', t => { +test('getGlobals skips local variables defined with var', () => { const code = `var b=a+1;b;`; - t.deepEqual(getGlobals(parse(code), {}), { + expect(getGlobals(parse(code), {})).toEqual({ a: [ [6, 7] ] }); }); -test('getGlobals skips inner function scope', t => { +test('getGlobals skips inner function scope', () => { const code = `a.map(i => '#'+i).join(',')`; - t.deepEqual(getGlobals(parse(code), {}), { + expect(getGlobals(parse(code), {})).toEqual({ a: [ [0, 1] ] }); }); -test('getGlobals skips function definition', t => { +test('getGlobals skips function definition', () => { const code = 'function a() { return b } a()'; - t.deepEqual(getGlobals(parse(code), {}), { + expect(getGlobals(parse(code), {})).toEqual({ b: [ [22, 23] ] }); }); -test('getGlobals reads deconstruct', t => { +test('getGlobals reads deconstruct', () => { const code = `let {a = b, c} = d; a + c`; - t.deepEqual(getGlobals(parse(code), {}), { + expect(getGlobals(parse(code), {})).toEqual({ b: [ [9, 10] ], diff --git a/test/index.spec.ts b/test/index.spec.ts index 524a708..205a90a 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -1,186 +1,171 @@ -import {test} from 'zora'; +import { expect, test } from "bun:test"; import ScopedEval from '../src'; -test('ScopedEval has allowedGlobals', t => { +test('ScopedEval has allowedGlobals', () => { const se = new ScopedEval(); - t.is(se.allowedGlobals['JSON'], true); - t.is(se.allowedGlobals['TextDecoder'], undefined); + expect(se.allowedGlobals['JSON']).toBe(true); + expect(se.allowedGlobals['TextDecoder']).toBe(undefined); se.allowGlobals(['TextDecoder', 'Uint8Array']); - t.is(se.allowedGlobals['TextDecoder'], true); - t.is(se.allowedGlobals['Uint8Array'], true); - t.is(se.allowedGlobals['TextEncoder'], undefined); + expect(se.allowedGlobals['TextDecoder']).toBe(true); + expect(se.allowedGlobals['Uint8Array']).toBe(true); + expect(se.allowedGlobals['TextEncoder']).toBe(undefined); se.allowGlobals('TextEncoder'); - t.is(se.allowedGlobals['TextEncoder'], true); + expect(se.allowedGlobals['TextEncoder']).toBe(true); }); -test('ScopedEval preprocesses expression', t => { +test('ScopedEval preprocesses expression', () => { const se = new ScopedEval(); const result = se.preprocess('foo'); - t.deepEqual(result, ["a", "return a.foo"]); + expect(result).toEqual(["a", "return a.foo"]); const func = se.build('foo'); - t.is(func({}), undefined); - t.is(func({foo: 'Foo'}), 'Foo'); + expect(func({})).toBe(undefined); + expect(func({foo: 'Foo'})).toBe('Foo'); }); -test('ScopedEval preprocesses expression with explicit return', t => { +test('ScopedEval preprocesses expression with explicit return', () => { const se = new ScopedEval(); const result = se.preprocess('return foo'); - t.deepEqual(result, ["a", "return a.foo"]); + expect(result).toEqual(["a", "return a.foo"]); const func = se.build('foo'); - t.is(func({}), undefined); - t.is(func({ foo: 'Foo' }), 'Foo'); + expect(func({})).toBe(undefined); + expect(func({ foo: 'Foo' })).toBe('Foo'); }); -test('ScopedEval preprocesses expression with explicit return, case 2', t => { +test('ScopedEval preprocesses expression with explicit return, case 2', () => { const se = new ScopedEval(); const code = 'if (a) { return foo; } bar;'; const result = se.preprocess(code); - t.deepEqual(result, ["b", "if (b.a) { return b.foo; } return b.bar;"]); + expect(result).toEqual(["b", "if (b.a) { return b.foo; } return b.bar;"]); const func = se.build(code); - t.is(func({ foo: 'Foo', bar: 'Bar' }), 'Bar'); - t.is(func({ foo: 'Foo', bar: 'Bar', a: true }), 'Foo'); + expect(func({ foo: 'Foo', bar: 'Bar' })).toBe('Bar'); + expect(func({ foo: 'Foo', bar: 'Bar', a: true })).toBe('Foo'); }); -test('ScopedEval preprocesses expression with allowed globals', t => { +test('ScopedEval preprocesses expression with allowed globals', () => { const se = new ScopedEval(); const code = 'JSON.stringify(Object.keys(foo))'; const result = se.preprocess(code); - t.deepEqual(result, ["a", "return JSON.stringify(Object.keys(a.foo))"]); + expect(result).toEqual(["a", "return JSON.stringify(Object.keys(a.foo))"]); const func = se.build(code); - t.is(func({foo: {a:1, b:2}}), '["a","b"]'); + expect(func({foo: {a:1, b:2}})).toBe('["a","b"]'); }); -test('ScopedEval preprocesses expression with assignment', t => { +test('ScopedEval preprocesses expression with assignment', () => { const se = new ScopedEval(); const code = 'a = true'; const result = se.preprocess(code); - t.deepEqual(result, ["b", "return b.a = true"]); + expect(result).toEqual(["b", "return b.a = true"]); const func = se.build(code); const obj = {a: false}; - t.is(func(obj), true); - t.is(obj.a, true); + expect(func(obj)).toBe(true); + expect(obj.a).toBe(true); }); -test('ScopedEval preprocesses expression with local variable', t => { +test('ScopedEval preprocesses expression with local variable', () => { const se = new ScopedEval(); const code = 'let a = b + 1; return c + a;'; const result = se.preprocess(code); - t.deepEqual(result, ["d", "let a = d.b + 1; return d.c + a;"]); + expect(result).toEqual(["d", "let a = d.b + 1; return d.c + a;"]); const func = se.build(code); const obj = {a: 5, b: 1, c: 2}; - t.is(func(obj), 4); - t.deepEqual(obj, {a: 5, b: 1, c: 2}); + expect(func(obj)).toBe(4); + expect(obj).toEqual({a: 5, b: 1, c: 2}); }); -test('ScopedEval preprocesses expression with complex assignment', t => { +test('ScopedEval preprocesses expression with complex assignment', () => { const se = new ScopedEval(); const code = 'a <<= a | b'; const result = se.preprocess(code); - t.deepEqual(result, ["c", "return c.a <<= c.a | c.b"]); + expect(result).toEqual(["c", "return c.a <<= c.a | c.b"]); const obj = { a: 2, b: 1 }; - t.is(se.eval(code, obj), 16); - t.deepEqual(obj, { a: 16, b: 1 }); + expect(se.eval(code, obj)).toBe(16); + expect(obj).toEqual({ a: 16, b: 1 }); }); -test('ScopedEval builds empty function for empty input', t => { +test('ScopedEval builds empty function for empty input', () => { const se = new ScopedEval(); const result = se.preprocess(""); - t.deepEqual(result, ["a", ""]); - t.is(se.eval("", {}), undefined); + expect(result).toEqual(["a", ""]); + expect(se.eval("", {})).toBe(undefined); }); -test('ScopedEval rejects esm import/exports', t => { +test('ScopedEval rejects esm import/exports', () => { const se = new ScopedEval(); const code = "import a from './a'; return a;"; - t.throws(() => se.preprocess(code)); + expect(() => se.preprocess(code)).toThrow(); const code2 = "const a = 1; exports default a;"; - t.throws(() => se.build(code2)); - t.throws(() => se.eval(code2, {})); + expect(() => se.build(code2)).toThrow(); + expect(() => se.eval(code2, {})).toThrow(); const code3 = "import('./a')"; - t.throws(() => se.preprocess(code3)); + expect(() => se.preprocess(code3)).toThrow(); }); -test('ScopedEval correctly ignores local variable in inner function', t => { +test('ScopedEval correctly ignores local variable in inner function', () => { const se = new ScopedEval(); const code = "list.map(n => n.name).join()"; const result = se.preprocess(code); - t.deepEqual(result, ["a", "return a.list.map(n => n.name).join()"]); - t.is(se.eval(code, {list: [{name: "A"}, {name: "B"}]}), "A,B"); + expect(result).toEqual(["a", "return a.list.map(n => n.name).join()"]); + expect(se.eval(code, {list: [{name: "A"}, {name: "B"}]})).toBe("A,B"); }); -test('ScopeEval supports string interpolation mode', t => { +test('ScopeEval supports string interpolation mode', () => { const se = new ScopedEval(); const code = "b + c"; const result = se.preprocess(code, true); - t.deepEqual(result,["a", 'return "b + c"']); - t.is(se.eval(code, {b: 1, c: 2}, true), "b + c"); + expect(result).toEqual(["a", 'return "b + c"']); + expect(se.eval(code, {b: 1, c: 2}, true)).toBe("b + c"); }); -test('ScopeEval supports string interpolation mode with interpolation', t => { +test('ScopeEval supports string interpolation mode with interpolation', () => { const se = new ScopedEval(); const code = "${\"a\"}${a + '}' + `${b + c}`}"; const result = se.preprocess(code, true); - t.deepEqual(result, ["d", 'return "" + ("a") + (d.a + \'}\' + `${d.b + d.c}`)']); - t.is(se.eval(code, {a: 1, b: 2, c: 3 }, true), 'a1}5'); + expect(result).toEqual(["d", 'return "" + ("a") + (d.a + \'}\' + `${d.b + d.c}`)']); + expect(se.eval(code, {a: 1, b: 2, c: 3 }, true)).toBe('a1}5'); }); -test('ScopeEval supports string interpolation mode with interpolation, case 2', t => { +test('ScopeEval supports string interpolation mode with interpolation, case 2', () => { const se = new ScopedEval(); const code = "`a`${`b${c}`}`d`"; const result = se.preprocess(code, true); - t.deepEqual(result, ["a", 'return "`a`" + (`b${a.c}`) + "`d`"']); - t.is(se.eval(code, { a: 1, b: 2, c: 3 }, true), '`a`b3`d`'); + expect(result).toEqual(["a", 'return "`a`" + (`b${a.c}`) + "`d`"']); + expect(se.eval(code, { a: 1, b: 2, c: 3 }, true)).toBe('`a`b3`d`'); }); -test('ScopeEval supports expression using RegExp', t => { +test('ScopeEval supports expression using RegExp', () => { const se = new ScopedEval(); const code = "/\\d/.test(value)"; const result = se.preprocess(code); - t.deepEqual(result, ["a", 'return /\\d/.test(a.value)']); - t.is(se.eval(code, { value: 'abc' }), false); - t.is(se.eval(code, { value: 'ab9c' }), true); -}); - -test('ScopeEval rejects non-string code', t => { - const se = new ScopedEval(); - try { - se.preprocess(null); - t.fail('should not pass'); - } catch (e) { - t.is(e.message, 'Code to be evaluated must be a string, but received object: null'); - } - try { - se.preprocess(1 as any); - t.fail('should not pass'); - } catch (e) { - t.is(e.message, 'Code to be evaluated must be a string, but received number: 1'); - } - try { - se.preprocess(undefined); - t.fail('should not pass'); - } catch (e) { - t.is(e.message, 'Code to be evaluated must be a string, but received undefined: undefined'); - } -}); - -test('ScopeEval properly handles var definition', t => { + expect(result).toEqual(["a", 'return /\\d/.test(a.value)']); + expect(se.eval(code, { value: 'abc' })).toBe(false); + expect(se.eval(code, { value: 'ab9c' })).toBe(true); +}); + +test('ScopeEval rejects non-string code', () => { + const se = new ScopedEval(); + expect(() => se.preprocess(null as unknown as string)).toThrow('Code to be evaluated must be a string, but received object: null'); + expect(() => se.preprocess(1 as any)).toThrow('Code to be evaluated must be a string, but received number: 1'); + expect(() => se.preprocess(undefined as unknown as string)).toThrow('Code to be evaluated must be a string, but received undefined: undefined'); +}); + +test('ScopeEval properly handles var definition', () => { const se = new ScopedEval(); const code = 'var a = 1; a + b'; const result = se.preprocess(code); - t.deepEqual(result, ["c", 'var a = 1; return a + c.b']); - t.is(se.eval(code, { b: 2 }), 3); + expect(result).toEqual(["c", 'var a = 1; return a + c.b']); + expect(se.eval(code, { b: 2 })).toBe(3); }); -test('ScopeEval properly handles function scope', t => { +test('ScopeEval properly handles function scope', () => { const se = new ScopedEval(); const code = 'function a() {return b;} a()'; const result = se.preprocess(code); - t.deepEqual(result, ["c", 'function a() {return c.b;} return a()']); - t.is(se.eval(code, { b: 2 }), 2); + expect(result).toEqual(["c", 'function a() {return c.b;} return a()']); + expect(se.eval(code, { b: 2 })).toBe(2); }); diff --git a/test/insert-code.spec.ts b/test/insert-code.spec.ts index 011dea5..1cd4912 100644 --- a/test/insert-code.spec.ts +++ b/test/insert-code.spec.ts @@ -1,22 +1,22 @@ -import {test} from 'zora'; +import { expect, test } from "bun:test"; import InsertCode from '../src/insert-code'; -test('InsertCode inserts', t => { +test('InsertCode inserts', () => { const m = new InsertCode("foo bar foo"); m.insert(0, "this."); m.insert(8, "return "); m.insert(8, "this."); m.insert(4, "this."); - t.is(m.transform(), "this.foo this.bar return this.foo"); + expect(m.transform()).toBe("this.foo this.bar return this.foo"); }); -test('InsertCode inserts case 2', t => { +test('InsertCode inserts case 2', () => { const m = new InsertCode("a <<= a | b"); m.insert(0, "return "); m.insert(0, "this."); m.insert(6, "this."); m.insert(10, "this."); - t.is(m.transform(), "return this.a <<= this.a | this.b"); + expect(m.transform()).toBe("return this.a <<= this.a | this.b"); }); diff --git a/test/parse.spec.ts b/test/parse.spec.ts index a0f1fc8..19415ff 100644 --- a/test/parse.spec.ts +++ b/test/parse.spec.ts @@ -1,7 +1,7 @@ -import {test} from 'zora'; +import { expect, test } from "bun:test"; import parse from '../src/parse'; -test('parse returns AST tree from meriyah', t => { +test('parse returns AST tree from meriyah', () => { const code = 'a += 1'; const expected = { "type": "Program", @@ -104,10 +104,10 @@ test('parse returns AST tree from meriyah', t => { } } }; - t.deepEqual(parse(code) as any, expected); + expect(parse(code) as any).toEqual(expected); }); -test('parse returns AST tree without parentheses', t => { +test('parse returns AST tree without parentheses', () => { const code = '((b.c))=a'; const expected = { "type": "Program", @@ -250,10 +250,10 @@ test('parse returns AST tree without parentheses', t => { } } }; - t.deepEqual(parse(code) as any, expected); + expect(parse(code) as any).toEqual(expected); }); -test('parse supports global return', t => { +test('parse supports global return', () => { const code = 'let b = a + 1; return b;'; const expected = { "type": "Program", @@ -437,5 +437,5 @@ test('parse supports global return', t => { } } }; - t.deepEqual(parse(code) as any, expected); + expect(parse(code) as any).toEqual(expected); }); diff --git a/test/string-interpolation.spec.ts b/test/string-interpolation.spec.ts index 2016b7c..f33c92e 100644 --- a/test/string-interpolation.spec.ts +++ b/test/string-interpolation.spec.ts @@ -1,31 +1,31 @@ -import {test} from 'zora'; +import { expect, test } from "bun:test"; import stringInterpolation from '../src/string-interpolation'; -test('stringInterpolation translates single string part', t => { - t.is(stringInterpolation("a"), '"a"'); - t.is(stringInterpolation("b + c"), '"b + c"'); - t.is(stringInterpolation("\"a\""), '"\\"a\\""'); - t.is(stringInterpolation("`'"), '"`\'"'); - t.is(stringInterpolation("a\\$b"), '"a\\\\$b"'); +test('stringInterpolation translates single string part', () => { + expect(stringInterpolation("a")).toBe('"a"'); + expect(stringInterpolation("b + c")).toBe('"b + c"'); + expect(stringInterpolation("\"a\"")).toBe('"\\"a\\""'); + expect(stringInterpolation("`'")).toBe('"`\'"'); + expect(stringInterpolation("a\\$b")).toBe('"a\\\\$b"'); // \${ means ${ - t.is(stringInterpolation("\\${a"), '"${a"'); - t.is(stringInterpolation("b\\${a"), '"b${a"'); - t.is(stringInterpolation("b\\${"), '"b${"'); + expect(stringInterpolation("\\${a")).toBe('"${a"'); + expect(stringInterpolation("b\\${a")).toBe('"b${a"'); + expect(stringInterpolation("b\\${")).toBe('"b${"'); // Edge case: \\${ becomes \${, // In real string interpolation, \\${ means single \ // followed by opening interpolation - t.is(stringInterpolation("b\\\\${a"), '"b\\\\${a"'); + expect(stringInterpolation("b\\\\${a")).toBe('"b\\\\${a"'); }); -test('stringInterpolation translates interpolation', t => { - t.is(stringInterpolation("${a}"), '"" + (a)'); - t.is(stringInterpolation("a ${b + c}"), '"a " + (b + c)'); - t.is(stringInterpolation("${\"a\"}${a + '}' + `${b + c}`}"), '"" + ("a") + (a + \'}\' + `${b + c}`)'); - t.is(stringInterpolation("`a`${`b${c}`}`d`"), '"`a`" + (`b${c}`) + "`d`"'); +test('stringInterpolation translates interpolation', () => { + expect(stringInterpolation("${a}")).toBe('"" + (a)'); + expect(stringInterpolation("a ${b + c}")).toBe('"a " + (b + c)'); + expect(stringInterpolation("${\"a\"}${a + '}' + `${b + c}`}")).toBe('"" + ("a") + (a + \'}\' + `${b + c}`)'); + expect(stringInterpolation("`a`${`b${c}`}`d`")).toBe('"`a`" + (`b${c}`) + "`d`"'); }); -test('stringInterpolation rejects malformed interpolation', t => { - t.throws(() => stringInterpolation("${")); - t.throws(() => stringInterpolation("${}")); - t.throws(() => stringInterpolation("a ${b + '}'")); +test('stringInterpolation rejects malformed interpolation', () => { + expect(() => stringInterpolation("${")).toThrow(); + expect(() => stringInterpolation("${}")).toThrow(); + expect(() => stringInterpolation("a ${b + '}'")).toThrow(); }); diff --git a/test/unused-name.spec.ts b/test/unused-name.spec.ts index fa7a85a..2f72dd8 100644 --- a/test/unused-name.spec.ts +++ b/test/unused-name.spec.ts @@ -1,31 +1,26 @@ -import {test} from 'zora'; +import { expect, test } from "bun:test"; import parse from '../src/parse'; import unusedName from '../src/unused-name'; -test('unusedName returns first unused name', t => { - t.is(unusedName(parse('foo')), 'a'); - t.is(unusedName(parse('let a = b + 1; d + a')), 'c'); +test('unusedName returns first unused name', () => { + expect(unusedName(parse('foo'))).toBe('a'); + expect(unusedName(parse('let a = b + 1; d + a'))).toBe('c'); let code = ''; for (let n = 97; n <= 122; n++) { // a to z code += String.fromCharCode(n) + ';'; } - t.is(unusedName(parse(code)), '$'); - t.is(unusedName(parse(code + '$;')), '_'); + expect(unusedName(parse(code))).toBe('$'); + expect(unusedName(parse(code + '$;'))).toBe('_'); }); -test('unusedName gave up if user exhausted a to z, $ and _', t => { +test('unusedName gave up if user exhausted a to z, $ and _', () => { let code = ''; for (let n = 97; n <= 122; n++) { // a to z code += String.fromCharCode(n) + ';'; } code += '$;_;'; - try { - unusedName(parse(code)); - t.fail('should not pass'); - } catch (e) { - t.ok(e.message.match(/^I gave up/)); - } + expect(() => unusedName(parse(code))).toThrow(/^I gave up/); });