From c9a2261e6fcbc173b48d485e1a79aa900aafda72 Mon Sep 17 00:00:00 2001 From: Danilo Alonso Date: Thu, 28 May 2026 09:36:27 -0400 Subject: [PATCH 01/17] chore: convert to esm + vitest + oxlint/oxfmt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch module system to ESM (named exports, drop `internals` namespace), test framework to vitest + @vitest/coverage-v8, lint/format to oxlint + oxfmt (configured to match the previous hapi style — 4-space indent, single quotes, semicolons, no trailing commas). Add tsconfig.json with allowJs/checkJs so source stays JS while the existing lib/index.d.ts type contracts are verified. Rewrite test/index.ts to vitest's expectTypeOf. Add typescript devDep + npm typecheck script. Adopt Node 22+ idioms: - Object.hasOwn(node, '__proto__') over Object.prototype.hasOwnProperty.call - catch {} for the intentionally-ignored error in safeParse Lab does not support ESM, so the test/lint tooling migration has to happen alongside the module-system swap — this is the per-package pattern for the ESM phase of the ecosystem-wide migration. Refs the ESM roadmap in the workspace docs (not in this repo). --- .github/workflows/ci-module.yml | 2 +- .gitignore | 1 + .oxfmtrc.json | 10 +++ .oxlintrc.json | 44 +++++++++++ lib/index.d.ts | 26 +++---- lib/index.js | 45 ++++------- package.json | 68 +++++++++------- test/index.js | 133 +++++++++++++++----------------- test/index.ts | 51 ++++++------ tsconfig.json | 17 ++++ vitest.config.js | 22 ++++++ 11 files changed, 254 insertions(+), 165 deletions(-) create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json create mode 100644 tsconfig.json create mode 100644 vitest.config.js diff --git a/.github/workflows/ci-module.yml b/.github/workflows/ci-module.yml index 54426ca..f1d20b5 100644 --- a/.github/workflows/ci-module.yml +++ b/.github/workflows/ci-module.yml @@ -11,4 +11,4 @@ jobs: test: uses: hapijs/.github/.github/workflows/ci-module.yml@master with: - min-node-version: 14 + min-node-version: 22 diff --git a/.gitignore b/.gitignore index 8f679c9..f35fedf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ **/package-lock.json coverage.* +coverage/ **/.DS_Store **/._* diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..ed8df42 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "tabWidth": 4, + "singleQuote": true, + "semi": true, + "trailingComma": "none", + "arrowParens": "always", + "bracketSpacing": true, + "printWidth": 120 +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..803d5a4 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,44 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "env": { + "node": true + }, + "categories": { + "correctness": "error", + "perf": "error", + "suspicious": "error", + "style": "warn" + }, + "rules": { + "curly": ["error", "all"], + "eqeqeq": "error", + "no-array-constructor": "error", + "no-eq-null": "warn", + "no-else-return": "error", + "no-new-wrappers": "error", + "no-unused-vars": ["warn", { "vars": "all", "caughtErrors": "all", "args": "none" }], + "no-shadow": ["warn", { "allow": ["err", "done"] }], + "prefer-const": ["error", { "destructuring": "all" }], + + "arrow-body-style": "off", + "capitalized-comments": "off", + "func-style": "off", + "guard-for-in": "off", + "id-length": "off", + "max-depth": "off", + "max-lines-per-function": "off", + "max-params": "off", + "max-statements": "off", + "no-magic-numbers": "off", + "no-ternary": "off", + "no-void": "off", + "sort-imports": "off", + + "unicorn/consistent-function-scoping": "off", + "unicorn/no-null": "off", + "unicorn/prefer-string-raw": "off", + + "typescript/prefer-function-type": "off", + "typescript/unified-signatures": "off" + } +} diff --git a/lib/index.d.ts b/lib/index.d.ts index 4ad3038..23e19b0 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,14 +1,14 @@ interface Reviver { - (this: any, key: string, value: any): any; + (this: any, key: string, value: any): any; } interface ParseOptions { - /** - * - `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value. - * - `'remove'` - deletes any `__proto__` keys from the result object. - * - `'ignore'` - skips all validation (same as calling `JSON.parse()` directly). - */ - protoAction?: 'error' | 'remove' | 'ignore'; + /** + * - `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value. + * - `'remove'` - deletes any `__proto__` keys from the result object. + * - `'ignore'` - skips all validation (same as calling `JSON.parse()` directly). + */ + protoAction?: 'error' | 'remove' | 'ignore'; } /** @@ -40,11 +40,11 @@ export function parse(text: string, options: ParseOptions): any; export function parse(text: string, reviver: Reviver, options: ParseOptions): any; interface ScanOptions { - /** - * - `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value. - * - `'remove'` - deletes any `__proto__` keys from the input `obj`. - */ - protoAction?: 'error' | 'remove'; + /** + * - `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value. + * - `'remove'` - deletes any `__proto__` keys from the input `obj`. + */ + protoAction?: 'error' | 'remove'; } /** @@ -59,4 +59,4 @@ export function scan(obj: any, options?: ScanOptions): void; * @param text the JSON text string. * @param reviver the `JSON.parse()` optional `reviver` argument. */ -export function safeParse(text: string, reviver?: Reviver) : any | null; +export function safeParse(text: string, reviver?: Reviver): any | null; diff --git a/lib/index.js b/lib/index.js index b5da747..fa10b3a 100755 --- a/lib/index.js +++ b/lib/index.js @@ -1,13 +1,7 @@ -'use strict'; - - -const internals = { - suspectRx: /"(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])"\s*\:/ -}; - - -exports.parse = function (text, ...args) { +const suspectRx = + /"(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])"\s*:/; +export function parse(text, ...args) { // Normalize arguments const firstOptions = typeof args[0] === 'object' && args[0]; @@ -26,28 +20,24 @@ exports.parse = function (text, ...args) { // Ignore null and non-objects - if (!obj || - typeof obj !== 'object') { - + if (!obj || typeof obj !== 'object') { return obj; } // Check original string for potential exploit - if (!text.match(internals.suspectRx)) { + if (!text.match(suspectRx)) { return obj; } // Scan result for proto keys - exports.scan(obj, options); + scan(obj, options); return obj; -}; - - -exports.scan = function (obj, options = {}) { +} +export function scan(obj, options = {}) { let next = [obj]; while (next.length) { @@ -55,7 +45,7 @@ exports.scan = function (obj, options = {}) { next = []; for (const node of nodes) { - if (Object.prototype.hasOwnProperty.call(node, '__proto__')) { // Avoid calling node.hasOwnProperty directly + if (Object.hasOwn(node, '__proto__')) { if (options.protoAction !== 'remove') { throw new SyntaxError('Object contains forbidden prototype property'); } @@ -65,23 +55,18 @@ exports.scan = function (obj, options = {}) { for (const key in node) { const value = node[key]; - if (value && - typeof value === 'object') { - + if (value && typeof value === 'object') { next.push(node[key]); } } } } -}; - - -exports.safeParse = function (text, reviver) { +} +export function safeParse(text, reviver) { try { - return exports.parse(text, reviver); - } - catch (ignoreError) { + return parse(text, reviver); + } catch { return null; } -}; +} diff --git a/package.json b/package.json index f1b4f34..97f49c4 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,42 @@ { - "name": "@hapi/bourne", - "description": "JSON parse with prototype poisoning protection", - "version": "3.0.0", - "repository": "git://github.com/hapijs/bourne", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "files": [ - "lib" - ], - "keywords": [ - "JSON", - "parse", - "safe", - "prototype" - ], - "devDependencies": { - "@hapi/code": "^9.0.0", - "@hapi/eslint-plugin": "^6.0.0", - "@hapi/lab": "25.0.0-beta.1", - "@types/node": "^17.0.31", - "benchmark": "2.x.x", - "typescript": "^4.6.3" - }, - "scripts": { - "test": "lab -a @hapi/code -t 100 -L -Y", - "test-cov-html": "lab -a @hapi/code -r html -o coverage.html" - }, - "license": "BSD-3-Clause" + "name": "@hapi/bourne", + "description": "JSON parse with prototype poisoning protection", + "version": "3.0.0", + "type": "module", + "repository": "git://github.com/hapijs/bourne", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + }, + "types": "lib/index.d.ts", + "files": [ + "lib" + ], + "engines": { + "node": ">=22" + }, + "keywords": [ + "JSON", + "parse", + "safe", + "prototype" + ], + "devDependencies": { + "@vitest/coverage-v8": "^3.2.1", + "oxfmt": "^0.43.0", + "oxlint": "^1.59.0", + "typescript": "^6.0.3", + "vitest": "^3.2.1" + }, + "scripts": { + "test": "vitest run --coverage", + "typecheck": "tsc --noEmit", + "lint": "oxlint", + "fmt": "oxfmt --check", + "fmt:fix": "oxfmt", + "check": "npm run lint && npm run fmt && npm run typecheck && npm test" + }, + "license": "BSD-3-Clause" } diff --git a/test/index.js b/test/index.js index 5916205..8b93cb0 100755 --- a/test/index.js +++ b/test/index.js @@ -1,161 +1,152 @@ -'use strict'; - -const Code = require('@hapi/code'); -const Bourne = require('..'); -const Lab = require('@hapi/lab'); - - -const internals = {}; - - -const { describe, it } = exports.lab = Lab.script(); -const expect = Code.expect; +import { describe, it, expect } from 'vitest'; +import * as Bourne from '../lib/index.js'; describe('Bourne', () => { - describe('parse()', () => { - it('parses object string', () => { - - expect(Bourne.parse('{"a": 5, "b": 6}')).to.equal({ a: 5, b: 6 }); + expect(Bourne.parse('{"a": 5, "b": 6}')).toEqual({ a: 5, b: 6 }); }); it('parses null string', () => { - - expect(Bourne.parse('null')).to.equal(null); + expect(Bourne.parse('null')).toBeNull(); }); it('parses zero string', () => { - - expect(Bourne.parse('0')).to.equal(0); + expect(Bourne.parse('0')).toBe(0); }); it('parses string string', () => { - - expect(Bourne.parse('"x"')).to.equal('x'); + expect(Bourne.parse('"x"')).toBe('x'); }); it('parses object string (reviver)', () => { - const reviver = (key, value) => { - return typeof value === 'number' ? value + 1 : value; }; - expect(Bourne.parse('{"a": 5, "b": 6}', reviver)).to.equal({ a: 6, b: 7 }); + expect(Bourne.parse('{"a": 5, "b": 6}', reviver)).toEqual({ a: 6, b: 7 }); }); it('sanitizes object string (reviver, options)', () => { - const reviver = (key, value) => { - return typeof value === 'number' ? value + 1 : value; }; - expect(Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', reviver, { protoAction: 'remove' })).to.equal({ a: 6, b: 7 }); + expect( + Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', reviver, { + protoAction: 'remove' + }) + ).toEqual({ a: 6, b: 7 }); }); it('sanitizes object string (options)', () => { - - expect(Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', { protoAction: 'remove' })).to.equal({ a: 5, b: 6 }); + expect(Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', { protoAction: 'remove' })).toEqual({ + a: 5, + b: 6 + }); }); it('sanitizes object string (null, options)', () => { - - expect(Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', null, { protoAction: 'remove' })).to.equal({ a: 5, b: 6 }); + expect( + Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', null, { + protoAction: 'remove' + }) + ).toEqual({ a: 5, b: 6 }); }); it('sanitizes nested object string', () => { - - const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'; - expect(Bourne.parse(text, { protoAction: 'remove' })).to.equal({ a: 5, b: 6, c: { d: 0, e: 'text', f: { g: 2 } } }); + const text = + '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'; + expect(Bourne.parse(text, { protoAction: 'remove' })).toEqual({ + a: 5, + b: 6, + c: { d: 0, e: 'text', f: { g: 2 } } + }); }); it('ignores proto property', () => { - const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'; - expect(Bourne.parse(text, { protoAction: 'ignore' })).to.equal(JSON.parse(text)); + expect(Bourne.parse(text, { protoAction: 'ignore' })).toEqual(JSON.parse(text)); }); it('ignores proto value', () => { - - expect(Bourne.parse('{"a": 5, "b": "__proto__"}')).to.equal({ a: 5, b: '__proto__' }); + expect(Bourne.parse('{"a": 5, "b": "__proto__"}')).toEqual({ a: 5, b: '__proto__' }); }); it('errors on proto property', () => { - - expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')).to.throw(SyntaxError); - expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__" : { "x": 7 } }')).to.throw(SyntaxError); - expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__" \n\r\t : { "x": 7 } }')).to.throw(SyntaxError); - expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__" \n \r \t : { "x": 7 } }')).to.throw(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')).toThrow(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__" : { "x": 7 } }')).toThrow(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__" \n\r\t : { "x": 7 } }')).toThrow(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__" \n \r \t : { "x": 7 } }')).toThrow(SyntaxError); }); it('errors on proto property (null, null)', () => { - - expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', null, null)).to.throw(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', null, null)).toThrow(SyntaxError); }); it('errors on proto property (explicit options)', () => { - - expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', { protoAction: 'error' })).to.throw(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', { protoAction: 'error' })).toThrow( + SyntaxError + ); }); it('errors on proto property (unicode)', () => { - - expect(() => Bourne.parse('{ "a": 5, "b": 6, "\\u005f_proto__": { "x": 7 } }')).to.throw(SyntaxError); - expect(() => Bourne.parse('{ "a": 5, "b": 6, "_\\u005fp\\u0072oto__": { "x": 7 } }')).to.throw(SyntaxError); - expect(() => Bourne.parse('{ "a": 5, "b": 6, "\\u005f\\u005f\\u0070\\u0072\\u006f\\u0074\\u006f\\u005f\\u005f": { "x": 7 } }')).to.throw(SyntaxError); - expect(() => Bourne.parse('{ "a": 5, "b": 6, "\\u005F_proto__": { "x": 7 } }')).to.throw(SyntaxError); - expect(() => Bourne.parse('{ "a": 5, "b": 6, "_\\u005Fp\\u0072oto__": { "x": 7 } }')).to.throw(SyntaxError); - expect(() => Bourne.parse('{ "a": 5, "b": 6, "\\u005F\\u005F\\u0070\\u0072\\u006F\\u0074\\u006F\\u005F\\u005F": { "x": 7 } }')).to.throw(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "\\u005f_proto__": { "x": 7 } }')).toThrow(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "_\\u005fp\\u0072oto__": { "x": 7 } }')).toThrow(SyntaxError); + expect(() => + Bourne.parse( + '{ "a": 5, "b": 6, "\\u005f\\u005f\\u0070\\u0072\\u006f\\u0074\\u006f\\u005f\\u005f": { "x": 7 } }' + ) + ).toThrow(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "\\u005F_proto__": { "x": 7 } }')).toThrow(SyntaxError); + expect(() => Bourne.parse('{ "a": 5, "b": 6, "_\\u005Fp\\u0072oto__": { "x": 7 } }')).toThrow(SyntaxError); + expect(() => + Bourne.parse( + '{ "a": 5, "b": 6, "\\u005F\\u005F\\u0070\\u0072\\u006F\\u0074\\u006F\\u005F\\u005F": { "x": 7 } }' + ) + ).toThrow(SyntaxError); }); }); describe('scan()', () => { - it('sanitizes nested object string', () => { - - const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'; + const text = + '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'; const obj = JSON.parse(text); Bourne.scan(obj, { protoAction: 'remove' }); - expect(obj).to.equal({ a: 5, b: 6, c: { d: 0, e: 'text', f: { g: 2 } } }); + expect(obj).toEqual({ a: 5, b: 6, c: { d: 0, e: 'text', f: { g: 2 } } }); }); it('errors on proto property', () => { - - const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'; + const text = + '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'; const obj = JSON.parse(text); - expect(() => Bourne.scan(obj)).to.throw(SyntaxError); + expect(() => Bourne.scan(obj)).toThrow(SyntaxError); }); it('does not break when hasOwnProperty is overwritten', () => { - const text = '{ "a": 5, "b": 6, "hasOwnProperty": "text", "__proto__": { "x": 7 } }'; const obj = JSON.parse(text); Bourne.scan(obj, { protoAction: 'remove' }); - expect(obj).to.equal({ a: 5, b: 6, hasOwnProperty: 'text' }); + expect(obj).toEqual({ a: 5, b: 6, hasOwnProperty: 'text' }); }); }); describe('safeParse()', () => { - it('parses object string', () => { - - expect(Bourne.safeParse('{"a": 5, "b": 6}')).to.equal({ a: 5, b: 6 }); + expect(Bourne.safeParse('{"a": 5, "b": 6}')).toEqual({ a: 5, b: 6 }); }); it('returns null on proto object string', () => { - - expect(Bourne.safeParse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')).to.be.null(); + expect(Bourne.safeParse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')).toBeNull(); }); it('returns null on invalid object string', () => { - - expect(Bourne.safeParse('{"a": 5, "b": 6')).to.be.null(); + expect(Bourne.safeParse('{"a": 5, "b": 6')).toBeNull(); }); }); }); diff --git a/test/index.ts b/test/index.ts index 3dffb52..4caec61 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1,31 +1,38 @@ -import * as Bourne from '..'; -import * as Lab from '@hapi/lab'; +import { expectTypeOf } from 'vitest'; -const { expect } = Lab.types; +import * as Bourne from '../lib/index.js'; -// parse +// parse — call signatures compile -expect.type(Bourne.parse('{}')); -expect.type(Bourne.parse('{}', () => {})); -expect.type(Bourne.parse('{}', (key, value) => ({ key, value }))); -expect.type(Bourne.parse('{}', {})); -expect.type(Bourne.parse('{}', { protoAction: 'error' })); -expect.type(Bourne.parse('{}', () => {}, { protoAction: 'error' })); +expectTypeOf(Bourne.parse).toBeFunction(); +expectTypeOf(Bourne.parse('{}')).toBeAny(); +expectTypeOf(Bourne.parse('{}', () => {})).toBeAny(); +expectTypeOf(Bourne.parse('{}', (key, value) => ({ key, value }))).toBeAny(); +expectTypeOf(Bourne.parse('{}', {})).toBeAny(); +expectTypeOf(Bourne.parse('{}', { protoAction: 'error' })).toBeAny(); +expectTypeOf(Bourne.parse('{}', () => {}, { protoAction: 'error' })).toBeAny(); -expect.error(Bourne.parse({})); -expect.error(Bourne.parse('{}', '')); -expect.error(Bourne.parse('{}', { protAct: 'error' })); +// parse — invalid calls rejected -// scan +// @ts-expect-error first arg must be string +Bourne.parse({}); +// @ts-expect-error reviver must be function or options object +Bourne.parse('{}', ''); +// @ts-expect-error invalid option key +Bourne.parse('{}', { protAct: 'error' }); -expect.type(Bourne.scan({})); -expect.type(Bourne.scan({}, {})); -expect.type(Bourne.scan({}, { protoAction: 'remove' })); +// scan — returns void, accepts options -// safeParse +expectTypeOf(Bourne.scan).toBeFunction(); +expectTypeOf(Bourne.scan({})).toBeVoid(); +expectTypeOf(Bourne.scan({}, {})).toBeVoid(); +expectTypeOf(Bourne.scan({}, { protoAction: 'remove' })).toBeVoid(); -expect.type(Bourne.safeParse('{}')); -expect.type(Bourne.safeParse('{}', () => {})); +// safeParse — call signatures compile, invalid calls rejected -expect.error(Bourne.safeParse({})); -expect.error(Bourne.safeParse('{}', '')); +expectTypeOf(Bourne.safeParse).toBeFunction(); + +// @ts-expect-error first arg must be string +Bourne.safeParse({}); +// @ts-expect-error reviver must be function +Bourne.safeParse('{}', ''); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f9e2047 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "allowJs": true, + "checkJs": false, + "noEmit": true, + "skipLibCheck": true, + "esModuleInterop": true, + "isolatedModules": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["lib/**/*", "test/**/*"] +} diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..1775fa9 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['test/*.js'], + typecheck: { + enabled: true, + include: ['test/**/*.ts'] + }, + coverage: { + provider: 'v8', + include: ['lib/**'], + thresholds: { + functions: 100, + lines: 100, + branches: 100, + statements: 100 + } + } + } +}); From f729e505deb841982bafc4ea9722e5b0b0ab5126 Mon Sep 17 00:00:00 2001 From: Danilo Alonso Date: Thu, 28 May 2026 10:14:15 -0400 Subject: [PATCH 02/17] chore: point ci at the modernized shared workflow branch Switch from `ci-module.yml@master` to `@min-node-22-hapi-21` so bourne's CI runs against the new fixed matrix (node 22 + 24 + lts/* + latest) instead of the broken inputs-conditional matrix on master (which falls through to testing Node 12/14/16). The `min-node-version` input is removed in the new workflow, so the caller's `with:` block goes away. Once that workflow merges to master, flip back to `@master`. --- .github/workflows/ci-module.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci-module.yml b/.github/workflows/ci-module.yml index f1d20b5..0581710 100644 --- a/.github/workflows/ci-module.yml +++ b/.github/workflows/ci-module.yml @@ -9,6 +9,4 @@ on: jobs: test: - uses: hapijs/.github/.github/workflows/ci-module.yml@master - with: - min-node-version: 22 + uses: hapijs/.github/.github/workflows/ci-module.yml@min-node-22-hapi-21 From 1a5248932502cf28736141933ec7b0c347bda090 Mon Sep 17 00:00:00 2001 From: Danilo Alonso Date: Fri, 5 Jun 2026 01:06:58 -0400 Subject: [PATCH 03/17] chore: adopt @hapi/oxc-plugin shared lint/fmt config Replace local .oxlintrc.json/.oxfmtrc.json with oxlint.config.ts/oxfmt.config.ts extending the shared @hapi/oxc-plugin configs. Format the tree under the shared oxfmt config and bump oxlint/oxfmt to the versions the plugin requires. --- .oxfmtrc.json | 10 --------- .oxlintrc.json | 44 ------------------------------------ API.md | 2 ++ benchmarks/ignore.js | 13 +---------- benchmarks/no__proto__.js | 13 ++--------- benchmarks/remove.js | 13 +---------- benchmarks/throw.js | 24 +++++--------------- lib/index.d.ts | 30 +++++++++++++++---------- oxfmt.config.ts | 6 +++++ oxlint.config.ts | 9 ++++++++ package.json | 47 ++++++++++++++++++++------------------- test/index.js | 22 +++++++++--------- vitest.config.js | 10 ++++----- 13 files changed, 84 insertions(+), 159 deletions(-) delete mode 100644 .oxfmtrc.json delete mode 100644 .oxlintrc.json create mode 100644 oxfmt.config.ts create mode 100644 oxlint.config.ts diff --git a/.oxfmtrc.json b/.oxfmtrc.json deleted file mode 100644 index ed8df42..0000000 --- a/.oxfmtrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "./node_modules/oxfmt/configuration_schema.json", - "tabWidth": 4, - "singleQuote": true, - "semi": true, - "trailingComma": "none", - "arrowParens": "always", - "bracketSpacing": true, - "printWidth": 120 -} diff --git a/.oxlintrc.json b/.oxlintrc.json deleted file mode 100644 index 803d5a4..0000000 --- a/.oxlintrc.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "./node_modules/oxlint/configuration_schema.json", - "env": { - "node": true - }, - "categories": { - "correctness": "error", - "perf": "error", - "suspicious": "error", - "style": "warn" - }, - "rules": { - "curly": ["error", "all"], - "eqeqeq": "error", - "no-array-constructor": "error", - "no-eq-null": "warn", - "no-else-return": "error", - "no-new-wrappers": "error", - "no-unused-vars": ["warn", { "vars": "all", "caughtErrors": "all", "args": "none" }], - "no-shadow": ["warn", { "allow": ["err", "done"] }], - "prefer-const": ["error", { "destructuring": "all" }], - - "arrow-body-style": "off", - "capitalized-comments": "off", - "func-style": "off", - "guard-for-in": "off", - "id-length": "off", - "max-depth": "off", - "max-lines-per-function": "off", - "max-params": "off", - "max-statements": "off", - "no-magic-numbers": "off", - "no-ternary": "off", - "no-void": "off", - "sort-imports": "off", - - "unicorn/consistent-function-scoping": "off", - "unicorn/no-null": "off", - "unicorn/prefer-string-raw": "off", - - "typescript/prefer-function-type": "off", - "typescript/unified-signatures": "off" - } -} diff --git a/API.md b/API.md index 33eb20b..a1e8ce6 100644 --- a/API.md +++ b/API.md @@ -26,6 +26,7 @@ iterated on and values copied, the `__proto__` property leaks and becomes the ob ### `Bourne.parse(text, [reviver], [options])` Parses a given JSON-formatted text into an object where: + - `text` - the JSON text string. - `reviver` - the `JSON.parse()` optional `reviver` argument. - `options` - optional configuration object where: @@ -37,6 +38,7 @@ Parses a given JSON-formatted text into an object where: ### `Bourne.scan(obj, [options])` Scans a given object for prototype properties where: + - `obj` - the object being scanned. - `options` - optional configuration object where: - `protoAction` - optional string with one of: diff --git a/benchmarks/ignore.js b/benchmarks/ignore.js index 51780a3..d203392 100755 --- a/benchmarks/ignore.js +++ b/benchmarks/ignore.js @@ -3,41 +3,30 @@ const Benchmark = require('benchmark'); const Bourne = require('..'); - const internals = { - text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }' + text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', }; - const suite = new Benchmark.Suite(); - suite .add('JSON.parse', () => { - JSON.parse(internals.text); }) .add('Bourne.parse', () => { - Bourne.parse(internals.text, { protoAction: 'ignore' }); }) .add('reviver', () => { - JSON.parse(internals.text, internals.reviver); }) .on('cycle', (event) => { - console.log(String(event.target)); }) .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); }) .run({ async: true }); - internals.reviver = function (key, value) { - return value; }; - diff --git a/benchmarks/no__proto__.js b/benchmarks/no__proto__.js index 35d3675..a4b9273 100755 --- a/benchmarks/no__proto__.js +++ b/benchmarks/no__proto__.js @@ -3,42 +3,33 @@ const Benchmark = require('benchmark'); const Bourne = require('..'); - const internals = { text: '{ "a": 5, "b": 6, "proto": { "x": 7 }, "c": { "d": 0, "e": "text", "\\u005f\\u005fproto": { "y": 8 }, "f": { "g": 2 } } }', - suspectRx: /"(?:_|\\u005f)(?:_|\\u005f)(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006f)(?:t|\\u0074)(?:o|\\u006f)(?:_|\\u005f)(?:_|\\u005f)"/ + suspectRx: + /"(?:_|\\u005f)(?:_|\\u005f)(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006f)(?:t|\\u0074)(?:o|\\u006f)(?:_|\\u005f)(?:_|\\u005f)"/, }; - const suite = new Benchmark.Suite(); - suite .add('JSON.parse', () => { - JSON.parse(internals.text); }) .add('Bourne.parse', () => { - Bourne.parse(internals.text); }) .add('reviver', () => { - JSON.parse(internals.text, internals.reviver); }) .on('cycle', (event) => { - console.log(String(event.target)); }) .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); }) .run({ async: true }); - internals.reviver = function (key, value) { - if (key.match(internals.suspectRx)) { return undefined; } diff --git a/benchmarks/remove.js b/benchmarks/remove.js index 3a33f12..c7217de 100755 --- a/benchmarks/remove.js +++ b/benchmarks/remove.js @@ -3,45 +3,34 @@ const Benchmark = require('benchmark'); const Bourne = require('..'); - const internals = { - text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }' + text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', }; - const suite = new Benchmark.Suite(); - suite .add('JSON.parse', () => { - JSON.parse(internals.text); }) .add('Bourne.parse', () => { - Bourne.parse(internals.text, { protoAction: 'remove' }); }) .add('reviver', () => { - JSON.parse(internals.text, internals.reviver); }) .on('cycle', (event) => { - console.log(String(event.target)); }) .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); }) .run({ async: true }); - internals.reviver = function (key, value) { - if (key === '__proto__') { return undefined; } return value; }; - diff --git a/benchmarks/throw.js b/benchmarks/throw.js index 80d42f5..4c5e5e2 100755 --- a/benchmarks/throw.js +++ b/benchmarks/throw.js @@ -3,59 +3,45 @@ const Benchmark = require('benchmark'); const Bourne = require('..'); - const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', - invalid: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } } }' + invalid: + '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } } }', }; - const suite = new Benchmark.Suite(); - suite .add('JSON.parse', () => { - JSON.parse(internals.text); }) .add('JSON.parse error', () => { - try { JSON.parse(internals.invalid); - } - catch (ignoreErr) { } + } catch (ignoreErr) {} }) .add('Bourne.parse', () => { - try { Bourne.parse(internals.text); - } - catch (ignoreErr) { } + } catch (ignoreErr) {} }) .add('reviver', () => { - try { JSON.parse(internals.text, internals.reviver); - } - catch (ignoreErr) { } + } catch (ignoreErr) {} }) .on('cycle', (event) => { - console.log(String(event.target)); }) .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); }) .run({ async: true }); - internals.reviver = function (key, value) { - if (key === '__proto__') { throw new Error('kaboom'); } return value; }; - diff --git a/lib/index.d.ts b/lib/index.d.ts index 23e19b0..20fe3c6 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -13,29 +13,33 @@ interface ParseOptions { /** * Parses a given JSON-formatted text into an object. - * @param text the JSON text string. + * + * @param text The JSON text string. */ export function parse(text: string): any; /** * Parses a given JSON-formatted text into an object. - * @param text the JSON text string. - * @param reviver the `JSON.parse()` optional `reviver` argument. + * + * @param text The JSON text string. + * @param reviver The `JSON.parse()` optional `reviver` argument. */ export function parse(text: string, reviver: Reviver): any; /** * Parses a given JSON-formatted text into an object. - * @param text the JSON text string. - * @param options optional configuration object. + * + * @param text The JSON text string. + * @param options Optional configuration object. */ export function parse(text: string, options: ParseOptions): any; /** * Parses a given JSON-formatted text into an object. - * @param text the JSON text string. - * @param reviver the `JSON.parse()` optional `reviver` argument. - * @param options optional configuration object. + * + * @param text The JSON text string. + * @param reviver The `JSON.parse()` optional `reviver` argument. + * @param options Optional configuration object. */ export function parse(text: string, reviver: Reviver, options: ParseOptions): any; @@ -49,14 +53,16 @@ interface ScanOptions { /** * Scans a given object for prototype properties. - * @param obj the object being scanned. - * @param options optional configuration object. + * + * @param obj The object being scanned. + * @param options Optional configuration object. */ export function scan(obj: any, options?: ScanOptions): void; /** * Parses a given JSON-formatted text into an object or `null` if an error is found. - * @param text the JSON text string. - * @param reviver the `JSON.parse()` optional `reviver` argument. + * + * @param text The JSON text string. + * @param reviver The `JSON.parse()` optional `reviver` argument. */ export function safeParse(text: string, reviver?: Reviver): any | null; diff --git a/oxfmt.config.ts b/oxfmt.config.ts new file mode 100644 index 0000000..5f74e6e --- /dev/null +++ b/oxfmt.config.ts @@ -0,0 +1,6 @@ +import DefaultOxfmtConfig from '@hapi/oxc-plugin/oxfmt'; +import { defineConfig } from 'oxfmt'; + +export default defineConfig({ + ...DefaultOxfmtConfig, +}); diff --git a/oxlint.config.ts b/oxlint.config.ts new file mode 100644 index 0000000..1e87556 --- /dev/null +++ b/oxlint.config.ts @@ -0,0 +1,9 @@ +import HapiRecommended from '@hapi/oxc-plugin/oxlint'; +import { defineConfig } from 'oxlint'; + +export default defineConfig({ + extends: [HapiRecommended], + env: { + ...HapiRecommended.env, + }, +}); diff --git a/package.json b/package.json index 97f49c4..6ffcdb7 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,26 @@ { "name": "@hapi/bourne", - "description": "JSON parse with prototype poisoning protection", "version": "3.0.0", - "type": "module", + "description": "JSON parse with prototype poisoning protection", + "keywords": [ + "JSON", + "parse", + "prototype", + "safe" + ], + "license": "BSD-3-Clause", "repository": "git://github.com/hapijs/bourne", + "files": [ + "lib" + ], + "type": "module", + "types": "lib/index.d.ts", "exports": { ".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" } }, - "types": "lib/index.d.ts", - "files": [ - "lib" - ], - "engines": { - "node": ">=22" - }, - "keywords": [ - "JSON", - "parse", - "safe", - "prototype" - ], - "devDependencies": { - "@vitest/coverage-v8": "^3.2.1", - "oxfmt": "^0.43.0", - "oxlint": "^1.59.0", - "typescript": "^6.0.3", - "vitest": "^3.2.1" - }, "scripts": { "test": "vitest run --coverage", "typecheck": "tsc --noEmit", @@ -38,5 +29,15 @@ "fmt:fix": "oxfmt", "check": "npm run lint && npm run fmt && npm run typecheck && npm test" }, - "license": "BSD-3-Clause" + "devDependencies": { + "@hapi/oxc-plugin": "^1.0.0", + "@vitest/coverage-v8": "^3.2.1", + "oxfmt": "^0.53.0", + "oxlint": "^1.68.0", + "typescript": "^6.0.3", + "vitest": "^3.2.1" + }, + "engines": { + "node": ">=22" + } } diff --git a/test/index.js b/test/index.js index 8b93cb0..842e3eb 100755 --- a/test/index.js +++ b/test/index.js @@ -35,23 +35,23 @@ describe('Bourne', () => { expect( Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', reviver, { - protoAction: 'remove' - }) + protoAction: 'remove', + }), ).toEqual({ a: 6, b: 7 }); }); it('sanitizes object string (options)', () => { expect(Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', { protoAction: 'remove' })).toEqual({ a: 5, - b: 6 + b: 6, }); }); it('sanitizes object string (null, options)', () => { expect( Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', null, { - protoAction: 'remove' - }) + protoAction: 'remove', + }), ).toEqual({ a: 5, b: 6 }); }); @@ -61,7 +61,7 @@ describe('Bourne', () => { expect(Bourne.parse(text, { protoAction: 'remove' })).toEqual({ a: 5, b: 6, - c: { d: 0, e: 'text', f: { g: 2 } } + c: { d: 0, e: 'text', f: { g: 2 } }, }); }); @@ -87,7 +87,7 @@ describe('Bourne', () => { it('errors on proto property (explicit options)', () => { expect(() => Bourne.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', { protoAction: 'error' })).toThrow( - SyntaxError + SyntaxError, ); }); @@ -96,15 +96,15 @@ describe('Bourne', () => { expect(() => Bourne.parse('{ "a": 5, "b": 6, "_\\u005fp\\u0072oto__": { "x": 7 } }')).toThrow(SyntaxError); expect(() => Bourne.parse( - '{ "a": 5, "b": 6, "\\u005f\\u005f\\u0070\\u0072\\u006f\\u0074\\u006f\\u005f\\u005f": { "x": 7 } }' - ) + '{ "a": 5, "b": 6, "\\u005f\\u005f\\u0070\\u0072\\u006f\\u0074\\u006f\\u005f\\u005f": { "x": 7 } }', + ), ).toThrow(SyntaxError); expect(() => Bourne.parse('{ "a": 5, "b": 6, "\\u005F_proto__": { "x": 7 } }')).toThrow(SyntaxError); expect(() => Bourne.parse('{ "a": 5, "b": 6, "_\\u005Fp\\u0072oto__": { "x": 7 } }')).toThrow(SyntaxError); expect(() => Bourne.parse( - '{ "a": 5, "b": 6, "\\u005F\\u005F\\u0070\\u0072\\u006F\\u0074\\u006F\\u005F\\u005F": { "x": 7 } }' - ) + '{ "a": 5, "b": 6, "\\u005F\\u005F\\u0070\\u0072\\u006F\\u0074\\u006F\\u005F\\u005F": { "x": 7 } }', + ), ).toThrow(SyntaxError); }); }); diff --git a/vitest.config.js b/vitest.config.js index 1775fa9..6036e1e 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -6,7 +6,7 @@ export default defineConfig({ include: ['test/*.js'], typecheck: { enabled: true, - include: ['test/**/*.ts'] + include: ['test/**/*.ts'], }, coverage: { provider: 'v8', @@ -15,8 +15,8 @@ export default defineConfig({ functions: 100, lines: 100, branches: 100, - statements: 100 - } - } - } + statements: 100, + }, + }, + }, }); From 693842b8eaef1a8672714ef67396b6d68fcf387e Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 12:56:27 +0200 Subject: [PATCH 04/17] chore: add types to ox files --- .github/workflows/ci-module.yml | 14 +++++++------- oxfmt.config.ts | 4 +++- oxlint.config.ts | 4 +++- package.json | 6 +++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci-module.yml b/.github/workflows/ci-module.yml index 0581710..4b7ee12 100644 --- a/.github/workflows/ci-module.yml +++ b/.github/workflows/ci-module.yml @@ -1,12 +1,12 @@ name: ci on: - push: - branches: - - master - pull_request: - workflow_dispatch: + push: + branches: + - master + pull_request: + workflow_dispatch: jobs: - test: - uses: hapijs/.github/.github/workflows/ci-module.yml@min-node-22-hapi-21 + test: + uses: hapijs/.github/.github/workflows/ci-module.yml@min-node-22-hapi-21 diff --git a/oxfmt.config.ts b/oxfmt.config.ts index 5f74e6e..12e357b 100644 --- a/oxfmt.config.ts +++ b/oxfmt.config.ts @@ -1,6 +1,8 @@ import DefaultOxfmtConfig from '@hapi/oxc-plugin/oxfmt'; import { defineConfig } from 'oxfmt'; +import type { OxfmtConfig } from 'oxfmt'; + export default defineConfig({ ...DefaultOxfmtConfig, -}); +}) as OxfmtConfig; diff --git a/oxlint.config.ts b/oxlint.config.ts index 1e87556..fd4548e 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1,9 +1,11 @@ import HapiRecommended from '@hapi/oxc-plugin/oxlint'; import { defineConfig } from 'oxlint'; +import type { OxlintConfig } from 'oxlint'; + export default defineConfig({ extends: [HapiRecommended], env: { ...HapiRecommended.env, }, -}); +}) as OxlintConfig; diff --git a/package.json b/package.json index 6ffcdb7..22f19e3 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,10 @@ "check": "npm run lint && npm run fmt && npm run typecheck && npm test" }, "devDependencies": { - "@hapi/oxc-plugin": "^1.0.0", + "@hapi/oxc-plugin": "^1.0.1", "@vitest/coverage-v8": "^3.2.1", - "oxfmt": "^0.53.0", - "oxlint": "^1.68.0", + "oxfmt": "^0.55.0", + "oxlint": "^1.70.0", "typescript": "^6.0.3", "vitest": "^3.2.1" }, From eee71943ef034cb1ef2dd857838fd7a650df639d Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 12:53:01 +0200 Subject: [PATCH 05/17] chore: update benchmarks --- benchmarks/ignore.js | 28 ++++++++++++---------------- benchmarks/no__proto__.js | 34 +++++++++++++++------------------- benchmarks/remove.js | 34 +++++++++++++++------------------- benchmarks/throw.js | 34 +++++++++++++++------------------- package.json | 1 + 5 files changed, 58 insertions(+), 73 deletions(-) diff --git a/benchmarks/ignore.js b/benchmarks/ignore.js index d203392..627a79a 100755 --- a/benchmarks/ignore.js +++ b/benchmarks/ignore.js @@ -1,15 +1,18 @@ -'use strict'; +import { Bench } from 'tinybench'; -const Benchmark = require('benchmark'); -const Bourne = require('..'); +import * as Bourne from '../lib/index.js'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', }; -const suite = new Benchmark.Suite(); +internals.reviver = function (key, value) { + return value; +}; -suite +const bench = new Bench(); + +bench .add('JSON.parse', () => { JSON.parse(internals.text); }) @@ -18,15 +21,8 @@ suite }) .add('reviver', () => { JSON.parse(internals.text, internals.reviver); - }) - .on('cycle', (event) => { - console.log(String(event.target)); - }) - .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); - }) - .run({ async: true }); + }); -internals.reviver = function (key, value) { - return value; -}; +await bench.run(); + +console.table(bench.table()); diff --git a/benchmarks/no__proto__.js b/benchmarks/no__proto__.js index a4b9273..fb1a3cb 100755 --- a/benchmarks/no__proto__.js +++ b/benchmarks/no__proto__.js @@ -1,7 +1,6 @@ -'use strict'; +import { Bench } from 'tinybench'; -const Benchmark = require('benchmark'); -const Bourne = require('..'); +import * as Bourne from '../lib/index.js'; const internals = { text: '{ "a": 5, "b": 6, "proto": { "x": 7 }, "c": { "d": 0, "e": "text", "\\u005f\\u005fproto": { "y": 8 }, "f": { "g": 2 } } }', @@ -9,9 +8,17 @@ const internals = { /"(?:_|\\u005f)(?:_|\\u005f)(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006f)(?:t|\\u0074)(?:o|\\u006f)(?:_|\\u005f)(?:_|\\u005f)"/, }; -const suite = new Benchmark.Suite(); +internals.reviver = function (key, value) { + if (key.match(internals.suspectRx)) { + return undefined; + } -suite + return value; +}; + +const bench = new Bench(); + +bench .add('JSON.parse', () => { JSON.parse(internals.text); }) @@ -20,19 +27,8 @@ suite }) .add('reviver', () => { JSON.parse(internals.text, internals.reviver); - }) - .on('cycle', (event) => { - console.log(String(event.target)); - }) - .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); - }) - .run({ async: true }); + }); -internals.reviver = function (key, value) { - if (key.match(internals.suspectRx)) { - return undefined; - } +await bench.run(); - return value; -}; +console.table(bench.table()); diff --git a/benchmarks/remove.js b/benchmarks/remove.js index c7217de..9d7e58c 100755 --- a/benchmarks/remove.js +++ b/benchmarks/remove.js @@ -1,15 +1,22 @@ -'use strict'; +import { Bench } from 'tinybench'; -const Benchmark = require('benchmark'); -const Bourne = require('..'); +import * as Bourne from '../lib/index.js'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', }; -const suite = new Benchmark.Suite(); +internals.reviver = function (key, value) { + if (key === '__proto__') { + return undefined; + } -suite + return value; +}; + +const bench = new Bench(); + +bench .add('JSON.parse', () => { JSON.parse(internals.text); }) @@ -18,19 +25,8 @@ suite }) .add('reviver', () => { JSON.parse(internals.text, internals.reviver); - }) - .on('cycle', (event) => { - console.log(String(event.target)); - }) - .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); - }) - .run({ async: true }); + }); -internals.reviver = function (key, value) { - if (key === '__proto__') { - return undefined; - } +await bench.run(); - return value; -}; +console.table(bench.table()); diff --git a/benchmarks/throw.js b/benchmarks/throw.js index 4c5e5e2..4a0ac20 100755 --- a/benchmarks/throw.js +++ b/benchmarks/throw.js @@ -1,7 +1,6 @@ -'use strict'; +import { Bench } from 'tinybench'; -const Benchmark = require('benchmark'); -const Bourne = require('..'); +import * as Bourne from '../lib/index.js'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', @@ -9,9 +8,17 @@ const internals = { '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } } }', }; -const suite = new Benchmark.Suite(); +internals.reviver = function (key, value) { + if (key === '__proto__') { + throw new Error('kaboom'); + } -suite + return value; +}; + +const bench = new Bench(); + +bench .add('JSON.parse', () => { JSON.parse(internals.text); }) @@ -29,19 +36,8 @@ suite try { JSON.parse(internals.text, internals.reviver); } catch (ignoreErr) {} - }) - .on('cycle', (event) => { - console.log(String(event.target)); - }) - .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); - }) - .run({ async: true }); + }); -internals.reviver = function (key, value) { - if (key === '__proto__') { - throw new Error('kaboom'); - } +await bench.run(); - return value; -}; +console.table(bench.table()); diff --git a/package.json b/package.json index 22f19e3..99dc3ee 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@vitest/coverage-v8": "^3.2.1", "oxfmt": "^0.55.0", "oxlint": "^1.70.0", + "tinybench": "6.0.2", "typescript": "^6.0.3", "vitest": "^3.2.1" }, From 405fb18aecb17725a34f467cd49afb4d9d26164a Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 12:54:01 +0200 Subject: [PATCH 06/17] chore: update vitest --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 99dc3ee..2ac2183 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,12 @@ }, "devDependencies": { "@hapi/oxc-plugin": "^1.0.1", - "@vitest/coverage-v8": "^3.2.1", + "@vitest/coverage-v8": "^4.1.9", "oxfmt": "^0.55.0", "oxlint": "^1.70.0", "tinybench": "6.0.2", "typescript": "^6.0.3", - "vitest": "^3.2.1" + "vitest": "^4.1.9" }, "engines": { "node": ">=22" From 0457d3634143fa3db2ea1771cd22f004487721f5 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 13:14:35 +0200 Subject: [PATCH 07/17] chore: rework tests --- test/index.ts | 38 ---------------------- test/typings.ts | 47 ++++++++++++++++++++++++++++ vitest.config.js => vitest.config.ts | 15 ++++----- 3 files changed, 55 insertions(+), 45 deletions(-) delete mode 100644 test/index.ts create mode 100644 test/typings.ts rename vitest.config.js => vitest.config.ts (55%) diff --git a/test/index.ts b/test/index.ts deleted file mode 100644 index 4caec61..0000000 --- a/test/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { expectTypeOf } from 'vitest'; - -import * as Bourne from '../lib/index.js'; - -// parse — call signatures compile - -expectTypeOf(Bourne.parse).toBeFunction(); -expectTypeOf(Bourne.parse('{}')).toBeAny(); -expectTypeOf(Bourne.parse('{}', () => {})).toBeAny(); -expectTypeOf(Bourne.parse('{}', (key, value) => ({ key, value }))).toBeAny(); -expectTypeOf(Bourne.parse('{}', {})).toBeAny(); -expectTypeOf(Bourne.parse('{}', { protoAction: 'error' })).toBeAny(); -expectTypeOf(Bourne.parse('{}', () => {}, { protoAction: 'error' })).toBeAny(); - -// parse — invalid calls rejected - -// @ts-expect-error first arg must be string -Bourne.parse({}); -// @ts-expect-error reviver must be function or options object -Bourne.parse('{}', ''); -// @ts-expect-error invalid option key -Bourne.parse('{}', { protAct: 'error' }); - -// scan — returns void, accepts options - -expectTypeOf(Bourne.scan).toBeFunction(); -expectTypeOf(Bourne.scan({})).toBeVoid(); -expectTypeOf(Bourne.scan({}, {})).toBeVoid(); -expectTypeOf(Bourne.scan({}, { protoAction: 'remove' })).toBeVoid(); - -// safeParse — call signatures compile, invalid calls rejected - -expectTypeOf(Bourne.safeParse).toBeFunction(); - -// @ts-expect-error first arg must be string -Bourne.safeParse({}); -// @ts-expect-error reviver must be function -Bourne.safeParse('{}', ''); diff --git a/test/typings.ts b/test/typings.ts new file mode 100644 index 0000000..f6512a0 --- /dev/null +++ b/test/typings.ts @@ -0,0 +1,47 @@ +import { describe, expectTypeOf, it } from 'vitest'; + +import * as Bourne from '../lib/index.js'; + +describe('typings', () => { + describe('parse', () => { + it('parse — call signatures compile', () => { + expectTypeOf(Bourne.parse).toBeFunction(); + expectTypeOf(Bourne.parse('{}')).toBeAny(); + expectTypeOf(Bourne.parse('{}', () => {})).toBeAny(); + expectTypeOf(Bourne.parse('{}', (key, value) => ({ key, value }))).toBeAny(); + expectTypeOf(Bourne.parse('{}', {})).toBeAny(); + expectTypeOf(Bourne.parse('{}', { protoAction: 'error' })).toBeAny(); + expectTypeOf(Bourne.parse('{}', () => {}, { protoAction: 'error' })).toBeAny(); + }); + + it('parse — invalid calls rejected', () => { + try { + // @ts-expect-error first arg must be string + Bourne.parse({}); + } catch {} + // @ts-expect-error reviver must be function or options object + Bourne.parse('{}', ''); + // @ts-expect-error invalid option key + Bourne.parse('{}', { protAct: 'error' }); + }); + }); + + describe('scan', () => { + it('returns void, accepts options', () => { + expectTypeOf(Bourne.scan).toBeFunction(); + expectTypeOf(Bourne.scan({})).toBeVoid(); + expectTypeOf(Bourne.scan({}, {})).toBeVoid(); + expectTypeOf(Bourne.scan({}, { protoAction: 'remove' })).toBeVoid(); + }); + }); + + describe('safeParse', () => { + it('call signatures compile, invalid calls rejected', () => { + expectTypeOf(Bourne.safeParse).toBeFunction(); + // @ts-expect-error first arg must be string + Bourne.safeParse({}); + // @ts-expect-error reviver must be function + Bourne.safeParse('{}', ''); + }); + }); +}); diff --git a/vitest.config.js b/vitest.config.ts similarity index 55% rename from vitest.config.js rename to vitest.config.ts index 6036e1e..df8ecc7 100644 --- a/vitest.config.js +++ b/vitest.config.ts @@ -1,22 +1,23 @@ +import Oxc from '@hapi/oxc-plugin/vitest'; import { defineConfig } from 'vitest/config'; +import type { ViteUserConfig } from 'vitest/config'; + export default defineConfig({ + plugins: [Oxc()], test: { environment: 'node', - include: ['test/*.js'], + include: ['test/**/*.{js,ts}'], typecheck: { enabled: true, - include: ['test/**/*.ts'], + include: ['test/**/*.{js,ts}'], }, coverage: { provider: 'v8', include: ['lib/**'], thresholds: { - functions: 100, - lines: 100, - branches: 100, - statements: 100, + 100: true, }, }, }, -}); +}) as ViteUserConfig; From 355025e7dee8afba0efe5b19094911d0c71ef9b4 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 13:25:05 +0200 Subject: [PATCH 08/17] chore: add lint:fix command --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2ac2183..ee3dba0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "test": "vitest run --coverage", "typecheck": "tsc --noEmit", "lint": "oxlint", + "lint:fix": "oxlint --fix", "fmt": "oxfmt --check", "fmt:fix": "oxfmt", "check": "npm run lint && npm run fmt && npm run typecheck && npm test" From 4c85849992f9c2ff1f6a3664aa82c7a69dbb4332 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 13:26:49 +0200 Subject: [PATCH 09/17] chore: fix package meta --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ee3dba0..e350bef 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "safe" ], "license": "BSD-3-Clause", - "repository": "git://github.com/hapijs/bourne", + "repository": { + "type": "git", + "url": "git://github.com/hapijs/bourne.git" + }, "files": [ "lib" ], From 6bd3f4c501b40643d7ecb6557875e45d6d99f534 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 13:27:47 +0200 Subject: [PATCH 10/17] chore: remove stale ignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index f35fedf..af5a347 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ **/node_modules **/package-lock.json -coverage.* coverage/ **/.DS_Store From 1800d9ad70930d25aaf8e130a0f84d23a6344793 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 13:44:59 +0200 Subject: [PATCH 11/17] chore: change regex call --- lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index fa10b3a..e2b793e 100755 --- a/lib/index.js +++ b/lib/index.js @@ -26,7 +26,7 @@ export function parse(text, ...args) { // Check original string for potential exploit - if (!text.match(suspectRx)) { + if (!suspectRx.test(text)) { return obj; } From d114d41e0913869e5b6bcc329a2888db56eaa8ce Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 13:45:44 +0200 Subject: [PATCH 12/17] chore: update tsconfig --- tsconfig.json | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index f9e2047..849762f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,25 @@ { "compilerOptions": { - "target": "ESNext", + "declaration": true, + "declarationMap": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": true, + "isolatedDeclarations": true, + "isolatedModules": true, "module": "NodeNext", "moduleResolution": "NodeNext", - "strict": true, - "allowJs": true, - "checkJs": false, "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipDefaultLibCheck": true, "skipLibCheck": true, - "esModuleInterop": true, - "isolatedModules": true, - "resolveJsonModule": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["lib/**/*", "test/**/*"] + "sourceMap": true, + "strict": true, + "target": "ESNext" + } } From 8959d4ed289ae57404379373c63e90a371f8a049 Mon Sep 17 00:00:00 2001 From: Danilo Alonso Date: Fri, 26 Jun 2026 00:59:29 -0400 Subject: [PATCH 13/17] build: ship API.md + README.md in the npm package Include the human-written docs in the published tarball so consumers and LLMs inspecting the package reach usage guidance, not just the built code. --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e350bef..36783a5 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "url": "git://github.com/hapijs/bourne.git" }, "files": [ - "lib" + "lib", + "API.md", + "README.md" ], "type": "module", "types": "lib/index.d.ts", From 49a959d6d2961d9ab323a6c122c785a5a67ff861 Mon Sep 17 00:00:00 2001 From: Danilo Alonso Date: Fri, 26 Jun 2026 23:31:08 -0400 Subject: [PATCH 14/17] refactor: move source to src/ as .mjs Align with the JS-ESM archetype convention adopted across the Wave 1-2 leaves (src/ + explicit .mjs extension). - lib/ -> src/; index.js -> index.mjs; index.d.ts -> index.d.mts (NodeNext resolves a .mjs import's types from .d.mts, not .d.ts) - repoint exports/types, vitest coverage include, tests, benchmarks --- benchmarks/ignore.js | 2 +- benchmarks/no__proto__.js | 2 +- benchmarks/remove.js | 2 +- benchmarks/throw.js | 2 +- package.json | 8 ++++---- lib/index.d.ts => src/index.d.mts | 0 lib/index.js => src/index.mjs | 0 test/index.js | 2 +- test/typings.ts | 2 +- vitest.config.ts | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) rename lib/index.d.ts => src/index.d.mts (100%) rename lib/index.js => src/index.mjs (100%) diff --git a/benchmarks/ignore.js b/benchmarks/ignore.js index 627a79a..7a7b408 100755 --- a/benchmarks/ignore.js +++ b/benchmarks/ignore.js @@ -1,6 +1,6 @@ import { Bench } from 'tinybench'; -import * as Bourne from '../lib/index.js'; +import * as Bourne from '../src/index.mjs'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', diff --git a/benchmarks/no__proto__.js b/benchmarks/no__proto__.js index fb1a3cb..0175afe 100755 --- a/benchmarks/no__proto__.js +++ b/benchmarks/no__proto__.js @@ -1,6 +1,6 @@ import { Bench } from 'tinybench'; -import * as Bourne from '../lib/index.js'; +import * as Bourne from '../src/index.mjs'; const internals = { text: '{ "a": 5, "b": 6, "proto": { "x": 7 }, "c": { "d": 0, "e": "text", "\\u005f\\u005fproto": { "y": 8 }, "f": { "g": 2 } } }', diff --git a/benchmarks/remove.js b/benchmarks/remove.js index 9d7e58c..89920bb 100755 --- a/benchmarks/remove.js +++ b/benchmarks/remove.js @@ -1,6 +1,6 @@ import { Bench } from 'tinybench'; -import * as Bourne from '../lib/index.js'; +import * as Bourne from '../src/index.mjs'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', diff --git a/benchmarks/throw.js b/benchmarks/throw.js index 4a0ac20..b697a0f 100755 --- a/benchmarks/throw.js +++ b/benchmarks/throw.js @@ -1,6 +1,6 @@ import { Bench } from 'tinybench'; -import * as Bourne from '../lib/index.js'; +import * as Bourne from '../src/index.mjs'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', diff --git a/package.json b/package.json index 36783a5..63176f1 100644 --- a/package.json +++ b/package.json @@ -14,16 +14,16 @@ "url": "git://github.com/hapijs/bourne.git" }, "files": [ - "lib", + "src", "API.md", "README.md" ], "type": "module", - "types": "lib/index.d.ts", + "types": "src/index.d.mts", "exports": { ".": { - "types": "./lib/index.d.ts", - "default": "./lib/index.js" + "types": "./src/index.d.mts", + "default": "./src/index.mjs" } }, "scripts": { diff --git a/lib/index.d.ts b/src/index.d.mts similarity index 100% rename from lib/index.d.ts rename to src/index.d.mts diff --git a/lib/index.js b/src/index.mjs similarity index 100% rename from lib/index.js rename to src/index.mjs diff --git a/test/index.js b/test/index.js index 842e3eb..3ba246b 100755 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import * as Bourne from '../lib/index.js'; +import * as Bourne from '../src/index.mjs'; describe('Bourne', () => { describe('parse()', () => { diff --git a/test/typings.ts b/test/typings.ts index f6512a0..0bafa28 100644 --- a/test/typings.ts +++ b/test/typings.ts @@ -1,6 +1,6 @@ import { describe, expectTypeOf, it } from 'vitest'; -import * as Bourne from '../lib/index.js'; +import * as Bourne from '../src/index.mjs'; describe('typings', () => { describe('parse', () => { diff --git a/vitest.config.ts b/vitest.config.ts index df8ecc7..2420cb5 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ }, coverage: { provider: 'v8', - include: ['lib/**'], + include: ['src/**'], thresholds: { 100: true, }, From c028f422b6882a9568d7a1326c8fb3a3bd694f16 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 1 Jul 2026 11:38:47 +0200 Subject: [PATCH 15/17] chore: revert extension change --- benchmarks/ignore.js | 2 +- benchmarks/no__proto__.js | 2 +- benchmarks/remove.js | 2 +- benchmarks/throw.js | 2 +- package.json | 6 +++--- src/{index.d.mts => index.d.ts} | 0 src/{index.mjs => index.js} | 0 test/index.js | 2 +- test/typings.ts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) rename src/{index.d.mts => index.d.ts} (100%) rename src/{index.mjs => index.js} (100%) diff --git a/benchmarks/ignore.js b/benchmarks/ignore.js index 7a7b408..bc50df3 100755 --- a/benchmarks/ignore.js +++ b/benchmarks/ignore.js @@ -1,6 +1,6 @@ import { Bench } from 'tinybench'; -import * as Bourne from '../src/index.mjs'; +import * as Bourne from '../src/index.js'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', diff --git a/benchmarks/no__proto__.js b/benchmarks/no__proto__.js index 0175afe..5812661 100755 --- a/benchmarks/no__proto__.js +++ b/benchmarks/no__proto__.js @@ -1,6 +1,6 @@ import { Bench } from 'tinybench'; -import * as Bourne from '../src/index.mjs'; +import * as Bourne from '../src/index.js'; const internals = { text: '{ "a": 5, "b": 6, "proto": { "x": 7 }, "c": { "d": 0, "e": "text", "\\u005f\\u005fproto": { "y": 8 }, "f": { "g": 2 } } }', diff --git a/benchmarks/remove.js b/benchmarks/remove.js index 89920bb..f002d47 100755 --- a/benchmarks/remove.js +++ b/benchmarks/remove.js @@ -1,6 +1,6 @@ import { Bench } from 'tinybench'; -import * as Bourne from '../src/index.mjs'; +import * as Bourne from '../src/index.js'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', diff --git a/benchmarks/throw.js b/benchmarks/throw.js index b697a0f..9d77949 100755 --- a/benchmarks/throw.js +++ b/benchmarks/throw.js @@ -1,6 +1,6 @@ import { Bench } from 'tinybench'; -import * as Bourne from '../src/index.mjs'; +import * as Bourne from '../src/index.js'; const internals = { text: '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', diff --git a/package.json b/package.json index 63176f1..5ba6e6b 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "README.md" ], "type": "module", - "types": "src/index.d.mts", + "types": "src/index.d.ts", "exports": { ".": { - "types": "./src/index.d.mts", - "default": "./src/index.mjs" + "types": "./src/index.d.ts", + "default": "./src/index.js" } }, "scripts": { diff --git a/src/index.d.mts b/src/index.d.ts similarity index 100% rename from src/index.d.mts rename to src/index.d.ts diff --git a/src/index.mjs b/src/index.js similarity index 100% rename from src/index.mjs rename to src/index.js diff --git a/test/index.js b/test/index.js index 3ba246b..19dc0d3 100755 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import * as Bourne from '../src/index.mjs'; +import * as Bourne from '../src/index.js'; describe('Bourne', () => { describe('parse()', () => { diff --git a/test/typings.ts b/test/typings.ts index 0bafa28..2eb0ea7 100644 --- a/test/typings.ts +++ b/test/typings.ts @@ -1,6 +1,6 @@ import { describe, expectTypeOf, it } from 'vitest'; -import * as Bourne from '../src/index.mjs'; +import * as Bourne from '../src/index.js'; describe('typings', () => { describe('parse', () => { From 8664f29172bd2e7569314d49803a5e806814fcd3 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 1 Jul 2026 11:39:38 +0200 Subject: [PATCH 16/17] chore: revert implicit README.md --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 5ba6e6b..4b9edfc 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,7 @@ }, "files": [ "src", - "API.md", - "README.md" + "API.md" ], "type": "module", "types": "src/index.d.ts", From 53efcb1d1121ef9f859c161d9d59748e424de07f Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 1 Jul 2026 11:47:10 +0200 Subject: [PATCH 17/17] chore: update linting --- benchmarks/throw.js | 6 +++--- package.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benchmarks/throw.js b/benchmarks/throw.js index 9d77949..29d1b3e 100755 --- a/benchmarks/throw.js +++ b/benchmarks/throw.js @@ -25,17 +25,17 @@ bench .add('JSON.parse error', () => { try { JSON.parse(internals.invalid); - } catch (ignoreErr) {} + } catch {} }) .add('Bourne.parse', () => { try { Bourne.parse(internals.text); - } catch (ignoreErr) {} + } catch {} }) .add('reviver', () => { try { JSON.parse(internals.text, internals.reviver); - } catch (ignoreErr) {} + } catch {} }); await bench.run(); diff --git a/package.json b/package.json index 4b9edfc..21569e5 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,8 @@ "devDependencies": { "@hapi/oxc-plugin": "^1.0.1", "@vitest/coverage-v8": "^4.1.9", - "oxfmt": "^0.55.0", - "oxlint": "^1.70.0", + "oxfmt": "^0.57.0", + "oxlint": "^1.72.0", "tinybench": "6.0.2", "typescript": "^6.0.3", "vitest": "^4.1.9"