diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index 1d9dc48..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(npx vitest:*)" - ] - } -} diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..a61d20f --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,65 @@ +#!/bin/sh +# Blocks a commit unless the whole codebase, exactly as it will be committed, is formatted and +# passes lint. +# +# oxfmt is the formatter (.oxfmtrc.json); oxlint is the linter (.oxlintrc.json). Wired up by +# the "prepare" script in package.json (git config core.hooksPath .githooks) on npm install. +# Emergency bypass: git commit --no-verify +# +# This file is shared verbatim by jetstreamapp/sf-formula-parser, jetstreamapp/soql-parser-js, +# jetstreamapp/simple-excel and jetstreamapp/simple-xml - keep every copy in sync when changing it. + +root=$(git rev-parse --show-toplevel) || exit 1 +OXFMT="$root/node_modules/.bin/oxfmt" +OXLINT="$root/node_modules/.bin/oxlint" + +for bin in "$OXFMT" "$OXLINT"; do + if [ ! -x "$bin" ]; then + echo "pre-commit: $bin not found - run 'npm install' first." >&2 + exit 1 + fi +done + +# The checks run against a snapshot of the index rather than the working tree. The index is +# exactly what the commit will contain, so a file formatted on disk but never re-staged cannot +# slip through, and unstaged edits cannot block a clean commit. git points GIT_INDEX_FILE at the +# right index for `commit -a` and `commit ` too, and checkout-index honours it. Every +# tracked file is exported, so the whole codebase is checked, not only the files being changed. +snapshot=$(mktemp -d "${TMPDIR:-/tmp}/pre-commit.XXXXXX") || exit 1 +trap 'rm -rf "$snapshot"' EXIT +trap 'exit 1' HUP INT TERM + +git -C "$root" checkout-index --all --prefix="$snapshot/" || exit 1 + +# Some lint rules resolve imports, so the snapshot shares the installed dependencies. +ln -s "$root/node_modules" "$snapshot/node_modules" || exit 1 + +cd "$snapshot" || exit 1 + +# Both run even when the first fails, so one commit attempt reports everything to fix. +status=0 + +if ! "$OXFMT" --check; then + cat >&2 <<'MSG' + +Commit blocked: the files listed above are not formatted. + + npm run format # format the repo + git add -u # stage the result +MSG + status=1 +fi + +if ! "$OXLINT"; then + cat >&2 <<'MSG' + +Commit blocked: the files listed above have lint errors. + + npm run lint # review the full report + npm run lint:fix # apply the fixes oxlint can make automatically + git add -u # stage the result +MSG + status=1 +fi + +exit $status diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9392859..a2aad38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,9 @@ jobs: cache: npm - run: npm ci + # The pre-commit hook runs these too, but it can be bypassed with --no-verify + - run: npm run format:check + - run: npm run lint - run: npm run typecheck # build before test, matching sf-formula-parser and soql-parser-js: tests run against a # build that is known to have succeeded. diff --git a/.prettierrc b/.oxfmtrc.json similarity index 61% rename from .prettierrc rename to .oxfmtrc.json index 3dd4f06..4312a38 100644 --- a/.prettierrc +++ b/.oxfmtrc.json @@ -1,4 +1,5 @@ { + "$schema": "./node_modules/oxfmt/configuration_schema.json", "printWidth": 140, "semi": true, "tabWidth": 2, @@ -7,5 +8,6 @@ "trailingComma": "all", "bracketSpacing": true, "arrowParens": "avoid", - "insertPragma": false + "sortPackageJson": false, + "ignorePatterns": [] } diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..0da908f --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,33 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + // typescript, unicorn, oxc and import apply everywhere. vitest is enabled only in the test + // override below, so its rules cannot fire on source files. + "plugins": ["typescript", "unicorn", "oxc", "import"], + // correctness, suspicious and perf catch defects, so they are errors. style and pedantic stay + // off because oxfmt owns layout and the rest is opinion rather than defect detection. + // restriction and nursery are off by default, listed so the whole policy reads in one place. + "categories": { + "correctness": "error", + "suspicious": "error", + "perf": "error", + "pedantic": "off", + "style": "off", + "restriction": "off", + "nursery": "off" + }, + "overrides": [ + { + // The release script polls external state; each iteration must observe the result of the + // previous one, so the sequential awaits are deliberate. + "files": ["scripts/release.mjs"], + "rules": { + "no-await-in-loop": "off" + } + }, + { + "files": ["src/__tests__/**"], + "plugins": ["vitest"] + } + ], + "ignorePatterns": ["dist/**"] +} diff --git a/AGENTS.md b/AGENTS.md index 226ec27..5948322 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,46 @@ Use conventional commit style messages. +## Formatting + +**ALWAYS run `npm run format` after making any change.** This is not optional and applies to every +file you touch — source, tests, docs, config, JSON, YAML and Markdown alike. + +oxfmt is the formatter; its settings live in `.oxfmtrc.json`. It reads `.gitignore`, so build output +is skipped automatically. + +- `npm run format` — format the repo in place +- `npm run format:check` — verify without writing + +## Linting + +**ALWAYS run `npm run lint` after making any change** and fix what it reports. + +oxlint is the linter; its settings live in `.oxlintrc.json`. It lints `src/` and `scripts/` — `dist/` +is excluded via `ignorePatterns`. + +- `npm run lint` — report violations +- `npm run lint:fix` — apply the fixes oxlint can make automatically + +The `correctness`, `suspicious` and `perf` categories are errors. `style` and `pedantic` are +deliberately off: oxfmt already owns layout, and the remainder is opinion rather than defect +detection. + +Prefer fixing the code over silencing the rule. When a rule genuinely misfires, disable it at the +narrowest scope that works — an `oxlint-disable-next-line` comment or a file-glob entry in +`overrides` rather than repo-wide — and record why inline. The existing exceptions cover the +sequential polling loop in `scripts/release.mjs` and the in-place `reverse()` in `src/parse.ts`, +where the suggested `toReversed()` is above the ES2022 build target. + +## Commit hook + +A `pre-commit` hook in `.githooks/` runs both the format check and oxlint against the whole codebase +and **blocks the commit** if either fails, so skipping those steps will stop the commit rather than +slip through. It checks a snapshot of the index, not the working tree, so it sees exactly what the +commit will contain: a file formatted on disk but not re-staged still blocks, and unstaged edits +never do. The hook is wired up by the `prepare` script (`git config core.hooksPath .githooks`) on +`npm install`. Emergency bypass: `git commit --no-verify`. + ## Releasing Releases are cut from `main` by CI, kicked off from your terminal: @@ -48,4 +88,5 @@ hook failing mid-release would abort after the npm publish. `changelog.yml` workflows are shared verbatim with [sf-formula-parser](https://github.com/jetstreamapp/sf-formula-parser) and [soql-parser-js](https://github.com/jetstreamapp/soql-parser-js) — keep the copies in sync when -changing them. +changing them. `.githooks/pre-commit` and `.oxfmtrc.json` are shared the same way, with +[simple-excel](https://github.com/jetstreamapp/simple-excel) as well. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1f5b5..83ba85d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- **Replaced Prettier with [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) and added + [oxlint](https://oxc.rs/docs/guide/usage/linter) as the project linter**, matching + sf-formula-parser, soql-parser-js and simple-excel. Settings live in `.oxfmtrc.json` and + `.oxlintrc.json`, with `npm run format`, `format:check`, `lint` and `lint:fix`. oxlint runs the + `correctness`, `suspicious` and `perf` categories as errors +- **The `pre-commit` hook blocks a commit unless the whole codebase, as staged, is formatted and + passes lint.** `npm install` wires it up by pointing `core.hooksPath` at `.githooks/`. Emergency + bypass: `git commit --no-verify` +- **CI now gates on `format:check` and `lint`**, since the pre-commit hook can be bypassed +- Internal cleanups surfaced by oxlint, with no behaviour change: `addChild` moved to module scope + so it is no longer recreated on every `parse()` call, and `parseAttributes` dropped a `coerce` + parameter left unused since attribute values stopped being coerced in 1.1.0 + ## [1.2.0] - 2026-09-30 ### Added diff --git a/README.md b/README.md index 5b56c17..3b26cb0 100644 --- a/README.md +++ b/README.md @@ -51,15 +51,15 @@ const result = parse(soapResponse, { #### ParseOptions -| Option | Type | Default | Description | -| --------------------- | --------- | ------- | -------------------------------------------------------- | -| `trimValues` | `boolean` | `true` | Trim whitespace from text content | -| `ignoreAttributes` | `boolean` | `false` | Skip attributes entirely | -| `removeNSPrefix` | `boolean` | `false` | Strip namespace prefixes (`soap:Body` → `Body`) | -| `attributeNamePrefix` | `string` | `'@_'` | Prefix prepended to attribute names in the output object | -| `parseTagValue` | `boolean` | `true` | Coerce numeric/boolean text values to their JS types | -| `processEntities` | `boolean` | `true` | Decode XML entities (`&` → `&`, `<` → `<`, etc.) | -| `maxDepth` | `number` | `256` | Maximum nesting depth allowed. Throws if exceeded | +| Option | Type | Default | Description | +| --------------------- | --------- | ------- | -------------------------------------------------------------------- | +| `trimValues` | `boolean` | `true` | Trim whitespace from text content | +| `ignoreAttributes` | `boolean` | `false` | Skip attributes entirely | +| `removeNSPrefix` | `boolean` | `false` | Strip namespace prefixes (`soap:Body` → `Body`) | +| `attributeNamePrefix` | `string` | `'@_'` | Prefix prepended to attribute names in the output object | +| `parseTagValue` | `boolean` | `true` | Coerce numeric/boolean text values to their JS types | +| `processEntities` | `boolean` | `true` | Decode XML entities (`&` → `&`, `<` → `<`, etc.) | +| `maxDepth` | `number` | `256` | Maximum nesting depth allowed. Throws if exceeded | | `strict` | `boolean` | `false` | Throw on malformed XML (unclosed, mismatched, or extra closing tags) | ### `build(obj: Record, options?: BuildOptions): string` @@ -81,12 +81,12 @@ const xml = build( #### BuildOptions -| Option | Type | Default | Description | -| --------------------- | --------- | ------- | ---------------------------------------------------------- | -| `format` | `boolean` | `false` | Pretty-print with indentation | -| `ignoreAttributes` | `boolean` | `false` | Skip attributes when building XML | -| `attributeNamePrefix` | `string` | `'@_'` | Prefix used to identify attribute keys in the input object | -| `indentBy` | `string` | `' '` | Indentation string used when `format` is `true` | +| Option | Type | Default | Description | +| --------------------- | --------- | ------- | -------------------------------------------------------------- | +| `format` | `boolean` | `false` | Pretty-print with indentation | +| `ignoreAttributes` | `boolean` | `false` | Skip attributes when building XML | +| `attributeNamePrefix` | `string` | `'@_'` | Prefix used to identify attribute keys in the input object | +| `indentBy` | `string` | `' '` | Indentation string used when `format` is `true` | | `declaration` | `boolean` | `false` | Prepend `` and a newline | ## Output Shape diff --git a/package-lock.json b/package-lock.json index 305784f..7a0ac8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "@types/node": "^26.6.3", "esbuild": "^0.28.2", "npm-run-all": "^4.1.5", - "prettier": "^3.9.9", + "oxfmt": "^0.70.0", + "oxlint": "^1.85.0", "release-it": "^21.1.0", "typescript": "^7.0.2", "vitest": "^5.0.2" @@ -1011,6 +1012,700 @@ "url": "https://github.com/sponsors/oxc-project" } }, + "node_modules/@oxfmt/binding-android-arm-eabi": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.70.0.tgz", + "integrity": "sha512-Xd7YO4/T2axEj6FTLcj4Why3mTBqFMg+x24xtorT4Lb2+1g82090GH0a/4U1m0pABGYiix2bq1pqkYrmV3f0Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-android-arm64": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.70.0.tgz", + "integrity": "sha512-x9rlMYyKXdgKdYyUJzGsK1ZV8P4di/J32ipzcS6Jet6p9r9UAh28neXIMtdlSaJJycdi61Z4YkcLKLpk8ueFjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-arm64": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.70.0.tgz", + "integrity": "sha512-IUTUPvrBVYy7POh4stXzRdz4IVC/1QSaviCWoyenSlOhGu0X9j5K07vCTM9biLjAA2Zs31l0Rj5vvRpj9n95wA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-x64": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.70.0.tgz", + "integrity": "sha512-vw745q870oTd6J517O24asoX4/E+eK0nxYIFoedSLqgJ+nI5En7+ZS82iZSHZ69zevQrnOXiyHP01dA+t8xD8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-freebsd-x64": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.70.0.tgz", + "integrity": "sha512-NO14EgSM9dFkcg+MfGPxvsKqXYs9LKaxPrOKXpv1R0rLokGGFDcCq6dBMq18dE4wlpFOovX0UZY2uh1P30O7QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.70.0.tgz", + "integrity": "sha512-139OEhHarj9CYoJ/i9gXlPv4KLBGtLj2toseOWYFf09QwlhklaZk+wW3aOvlqoeZtuayvkoSNVWra7WJ21s3VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-musleabihf": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.70.0.tgz", + "integrity": "sha512-GEh2PY3IWTE0M24eNhTduountANSbWyDmMnzFSQE/nGg/bjPugbUgiGuFu+xdqcQSd/HKSwH80/F2yVVD48yhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-gnu": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.70.0.tgz", + "integrity": "sha512-En5i+UJmZSPxuSf47F2Hl5YOzKB0bicQLnGQkeTCMQ35cWLtbrSwACJKfiLqRZrk05DwSnsJkhBRaM3OURtIaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-musl": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.70.0.tgz", + "integrity": "sha512-WWOoV5W9Im3flVwOVrWn/2DUlOF8v5vcCip+kcNuaMpulRCh6nzzt1Su2vcL2F908YJIXNV3HvegbBHuyLwKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-ppc64-gnu": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.70.0.tgz", + "integrity": "sha512-YUouneIqW+5n7aE8xx/zeZ6/utr/KH7oykcGoFyd8Uz8uh591T1oKlnoWA3BsRq/ZR42oY1w4MUYvS/0e/MQOA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-gnu": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.70.0.tgz", + "integrity": "sha512-iEnMf21S5aGVa4hViDGY8sAQ/AHyCu2JPyrQF8P06wtHhSkD1YJBeT4m/KiGewgf7+a5XCYSCRIPcRQa1xwEoQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-musl": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.70.0.tgz", + "integrity": "sha512-91Sdniaj20fQzyMeCxMDzTP4c9s4RB8dGQ308xHhDR0n6U7+1Xq7N9klE7mfXq8iV3lRmIGSXi5X23Hn/0XX/g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-s390x-gnu": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.70.0.tgz", + "integrity": "sha512-uUV30M6E+2TKKGMaKiwfeL4RZrviHXlUxsrYJ/jFBb+1EZy+pnFT+hF73eeWdzh5NqPOAnw0iiMAIqjqiLZPFg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-gnu": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.70.0.tgz", + "integrity": "sha512-ivMcX6kNDPhqtbOaBt/ItFlLlTlXNHLgRuNmxP6Na6UuYXRT10llpJcAPbGeRgjjb3Qzv4jwPp3fB0hui50WNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-musl": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.70.0.tgz", + "integrity": "sha512-w+S+fERxYmlZSyZlJK/U292FjyBoH8cCEj21/tYJX6atX5kNSn+HDkhlFQKT2zcMwUW0uAtUL/bOrlwJZwqRdA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-openharmony-arm64": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.70.0.tgz", + "integrity": "sha512-Zlom1Xkx257R8bk4ZI4zJsrGno2opknz1+5v5baka3nn4FPyvNSdh8JUL4CdN1S1OWRMvJ9UJQ+RfIqGGCUEfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-arm64-msvc": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.70.0.tgz", + "integrity": "sha512-FQgPW5R17vzt7cgrJ8eG/dqX00o2xHsqFeLfw4xzA9FRHpN/DjFo9YDonvIIXGxiEuS9F/jZPnGO+KHNKuCo4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-ia32-msvc": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.70.0.tgz", + "integrity": "sha512-ZfZublNhZ+XBndMiXhkiLlPE+XyGRDa0CweeTL6t1fZypfCh1LTg7e5CvnOeTBunq15MskOcRempumSPGAaCQA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-x64-msvc": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.70.0.tgz", + "integrity": "sha512-HlIZEn+WzLQL0DszNzldiRl/DPRCX5R0Vkt6qeUPR1YHwy52hZZo4x6HoTOVmKRP2wUiwPGtKsihNY/f8KRaBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.85.0.tgz", + "integrity": "sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.85.0.tgz", + "integrity": "sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.85.0.tgz", + "integrity": "sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.85.0.tgz", + "integrity": "sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.85.0.tgz", + "integrity": "sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.85.0.tgz", + "integrity": "sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.85.0.tgz", + "integrity": "sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.85.0.tgz", + "integrity": "sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.85.0.tgz", + "integrity": "sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.85.0.tgz", + "integrity": "sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.85.0.tgz", + "integrity": "sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.85.0.tgz", + "integrity": "sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.85.0.tgz", + "integrity": "sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.85.0.tgz", + "integrity": "sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.85.0.tgz", + "integrity": "sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.85.0.tgz", + "integrity": "sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.85.0.tgz", + "integrity": "sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.85.0.tgz", + "integrity": "sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.85.0.tgz", + "integrity": "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@phun-ky/typeof": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@phun-ky/typeof/-/typeof-2.0.3.tgz", @@ -4479,6 +5174,107 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxfmt": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.70.0.tgz", + "integrity": "sha512-IsHxZ4y0wQLLMhnrJblBJgZsLDzfULrJnAw5j/QqsTlMa/m3AqsbToi+W71uhBGaqlqq/PbbjvHc09TJwdv3Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinypool": "2.1.2" + }, + "bin": { + "oxfmt": "bin/oxfmt" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxfmt/binding-android-arm-eabi": "0.70.0", + "@oxfmt/binding-android-arm64": "0.70.0", + "@oxfmt/binding-darwin-arm64": "0.70.0", + "@oxfmt/binding-darwin-x64": "0.70.0", + "@oxfmt/binding-freebsd-x64": "0.70.0", + "@oxfmt/binding-linux-arm-gnueabihf": "0.70.0", + "@oxfmt/binding-linux-arm-musleabihf": "0.70.0", + "@oxfmt/binding-linux-arm64-gnu": "0.70.0", + "@oxfmt/binding-linux-arm64-musl": "0.70.0", + "@oxfmt/binding-linux-ppc64-gnu": "0.70.0", + "@oxfmt/binding-linux-riscv64-gnu": "0.70.0", + "@oxfmt/binding-linux-riscv64-musl": "0.70.0", + "@oxfmt/binding-linux-s390x-gnu": "0.70.0", + "@oxfmt/binding-linux-x64-gnu": "0.70.0", + "@oxfmt/binding-linux-x64-musl": "0.70.0", + "@oxfmt/binding-openharmony-arm64": "0.70.0", + "@oxfmt/binding-win32-arm64-msvc": "0.70.0", + "@oxfmt/binding-win32-ia32-msvc": "0.70.0", + "@oxfmt/binding-win32-x64-msvc": "0.70.0" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/oxlint": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.85.0.tgz", + "integrity": "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.85.0", + "@oxlint/binding-android-arm64": "1.85.0", + "@oxlint/binding-darwin-arm64": "1.85.0", + "@oxlint/binding-darwin-x64": "1.85.0", + "@oxlint/binding-freebsd-x64": "1.85.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", + "@oxlint/binding-linux-arm-musleabihf": "1.85.0", + "@oxlint/binding-linux-arm64-gnu": "1.85.0", + "@oxlint/binding-linux-arm64-musl": "1.85.0", + "@oxlint/binding-linux-ppc64-gnu": "1.85.0", + "@oxlint/binding-linux-riscv64-gnu": "1.85.0", + "@oxlint/binding-linux-riscv64-musl": "1.85.0", + "@oxlint/binding-linux-s390x-gnu": "1.85.0", + "@oxlint/binding-linux-x64-gnu": "1.85.0", + "@oxlint/binding-linux-x64-musl": "1.85.0", + "@oxlint/binding-openharmony-arm64": "1.85.0", + "@oxlint/binding-win32-arm64-msvc": "1.85.0", + "@oxlint/binding-win32-ia32-msvc": "1.85.0", + "@oxlint/binding-win32-x64-msvc": "1.85.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, "node_modules/pac-proxy-agent": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.1.0.tgz", @@ -4714,22 +5510,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prettier": { - "version": "3.9.9", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", - "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/protocols": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", @@ -5592,6 +6372,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinypool": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.2.tgz", + "integrity": "sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index 83f695d..a299f30 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,11 @@ "test:watch": "vitest", "test:coverage": "vitest run --coverage", "typecheck": "tsc --noEmit -p tsconfig.typecheck.json", + "format": "oxfmt", + "format:check": "oxfmt --check", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "prepare": "git config core.hooksPath .githooks || true", "release": "node scripts/release.mjs", "release:ci": "release-it", "release:increment": "node scripts/derive-increment.mjs --explain" @@ -44,7 +49,8 @@ "@types/node": "^26.6.3", "esbuild": "^0.28.2", "npm-run-all": "^4.1.5", - "prettier": "^3.9.9", + "oxfmt": "^0.70.0", + "oxlint": "^1.85.0", "release-it": "^21.1.0", "typescript": "^7.0.2", "vitest": "^5.0.2" diff --git a/src/__tests__/build.test.ts b/src/__tests__/build.test.ts index f471770..c045336 100644 --- a/src/__tests__/build.test.ts +++ b/src/__tests__/build.test.ts @@ -146,9 +146,7 @@ describe('build', () => { describe('builder edge cases', () => { it('builds elements with special characters in text that need escaping', () => { - expect(build({ root: 'Tom & Jerry <"friends">' })).toBe( - 'Tom & Jerry <"friends">', - ); + expect(build({ root: 'Tom & Jerry <"friends">' })).toBe('Tom & Jerry <"friends">'); }); it('handles deeply nested objects', () => { @@ -159,11 +157,7 @@ describe('build', () => { it('handles arrays of objects with mixed content', () => { const obj = { root: { - item: [ - { '@_id': '1', '#text': 'first' }, - { '@_id': '2', child: 'nested' }, - 'plain', - ], + item: [{ '@_id': '1', '#text': 'first' }, { '@_id': '2', child: 'nested' }, 'plain'], }, }; const result = build(obj); diff --git a/src/__tests__/parse.test.ts b/src/__tests__/parse.test.ts index c003d61..f960458 100644 --- a/src/__tests__/parse.test.ts +++ b/src/__tests__/parse.test.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'; import { describe, it, expect } from 'vitest'; import { parse } from '../parse.js'; -const __dirname = dirname(fileURLToPath(import.meta.url)); +const fixturesDir = dirname(fileURLToPath(import.meta.url)); describe('parse', () => { describe('basic parsing', () => { @@ -285,9 +285,7 @@ describe('parse', () => { }); it('throws on mismatched closing tags', () => { - expect(() => parse('text', { strict: true })).toThrow( - 'Mismatched closing tag: expected but found ', - ); + expect(() => parse('text', { strict: true })).toThrow('Mismatched closing tag: expected but found '); }); it('does not throw for well-formed XML in strict mode', () => { @@ -295,9 +293,7 @@ describe('parse', () => { }); it('throws on extra closing tags in strict mode', () => { - expect(() => parse('text', { strict: true })).toThrow( - 'Unexpected closing tag ', - ); + expect(() => parse('text', { strict: true })).toThrow('Unexpected closing tag '); }); it('silently ignores unclosed tags when strict is false (default)', () => { @@ -328,8 +324,8 @@ describe('parse', () => { describe('Anonymous Apex', () => { it('parses an Anonymous Apex SOAP response', () => { - const xml = readFileSync(join(__dirname, 'anonymous-apex-soap-response.xml'), 'utf-8'); - const expectedLog = readFileSync(join(__dirname, 'anonymous-apex-response-body.log'), 'utf-8'); + const xml = readFileSync(join(fixturesDir, 'anonymous-apex-soap-response.xml'), 'utf-8'); + const expectedLog = readFileSync(join(fixturesDir, 'anonymous-apex-response-body.log'), 'utf-8'); const result = parse(xml, { trimValues: true, diff --git a/src/parse.ts b/src/parse.ts index 184c8d9..414bd5e 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -40,6 +40,19 @@ function parseValue(val: string, coerce: boolean): string | number | boolean { return val; } +function addChild(parent: Record, key: string, value: unknown): void { + if (key in parent) { + const existing = parent[key]; + if (Array.isArray(existing)) { + existing.push(value); + } else { + parent[key] = [existing, value]; + } + } else { + parent[key] = value; + } +} + interface StackEntry { tagName: string; children: Record; @@ -66,19 +79,6 @@ export function parse(xml: string, options?: ParseOptions): Record, key: string, value: unknown): void { - if (key in parent) { - const existing = parent[key]; - if (Array.isArray(existing)) { - existing.push(value); - } else { - parent[key] = [existing, value]; - } - } else { - parent[key] = value; - } - } - while (i < len) { if (xml[i] === '<') { // Flush any pending text content @@ -212,7 +212,7 @@ export function parse(xml: string, options?: ParseOptions): Record = {}; if (!ignoreAttributes && attrString) { - parseAttributes(attrString, attrs, attrPrefix, rmNSPrefix, processEnt, parseTagVal); + parseAttributes(attrString, attrs, attrPrefix, rmNSPrefix, processEnt); } if (selfClosing) { @@ -246,6 +246,9 @@ export function parse(xml: string, options?: ParseOptions): Record e.tagName) + // Reverses the fresh array from map(), so nothing shared is mutated. toReversed() is ES2023, + // which esbuild does not polyfill for the ES2022 target. + // oxlint-disable-next-line unicorn/no-array-reverse .reverse() .join(', '); throw new Error(`Unclosed tag(s): ${unclosed}`); @@ -269,14 +272,7 @@ function findTagEnd(xml: string, start: number): number { return -1; } -function parseAttributes( - str: string, - attrs: Record, - prefix: string, - rmNS: boolean, - processEnt: boolean, - coerce: boolean, -): void { +function parseAttributes(str: string, attrs: Record, prefix: string, rmNS: boolean, processEnt: boolean): void { const re = /([^\s=]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g; let match: RegExpExecArray | null; while ((match = re.exec(str)) !== null) {