diff --git a/.github/issue_template.md b/.github/issue_template.md index 3fd3598c..de5b728a 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,6 +1,10 @@ -If you are submitting a bug because you are receiving an error or because this -project is incompatible with the [official JSON5 -specification][spec], please continue. +If you are reporting a security vulnerability, please do not submit an issue. +Instead, follow the guidelines described in our +[security policy](../blob/main/SECURITY.md). + +If you are submitting a bug report because you are receiving an error or because +this project is incompatible with the [official JSON5 specification][spec], +please continue. If you are submitting a feature request or code improvement that is compatible with the [official JSON5 specification][spec], please continue. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 647597ea..29da2850 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,7 @@ +If you are patching a security vulnerability, please do not submit a pull +request. Instead, follow the guidelines described in our +[security policy](../blob/main/SECURITY.md). + If you are submitting a bug fix for an an error or fixing an incompatibility with the [official JSON5 specification][spec], please continue. diff --git a/.travis.yml b/.travis.yml index 7716b7b5..931847ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,3 @@ node_js: - "11" - "10" - "8" - - "6" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b9aa0d2..5d04cf06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,31 @@ ### Unreleased [[code][c-unreleased], [diff][d-unreleased]] -[c-unreleased]: https://github.com/json5/json5/tree/master -[d-unreleased]: https://github.com/json5/json5/compare/v2.2.0...HEAD +[c-unreleased]: https://github.com/json5/json5/tree/main +[d-unreleased]: https://github.com/json5/json5/compare/v2.2.3...HEAD + +### v2.2.3 [[code][c2.2.3], [diff][d2.2.3]] + +[c2.2.3]: https://github.com/json5/json5/tree/v2.2.3 +[d2.2.3]: https://github.com/json5/json5/compare/v2.2.2...v2.2.3 + +- Fix: json5@2.2.3 is now the 'latest' release according to npm instead of + v1.0.2. ([#299]) + +### v2.2.2 [[code][c2.2.2], [diff][d2.2.2]] + +[c2.2.2]: https://github.com/json5/json5/tree/v2.2.2 +[d2.2.2]: https://github.com/json5/json5/compare/v2.2.1...v2.2.2 + +- Fix: Properties with the name `__proto__` are added to objects and arrays. + ([#199]) This also fixes a prototype pollution vulnerability reported by + Jonathan Gregson! ([#295]). + +### v2.2.1 [[code][c2.2.1], [diff][d2.2.1]] + +[c2.2.1]: https://github.com/json5/json5/tree/v2.2.1 +[d2.2.1]: https://github.com/json5/json5/compare/v2.2.0...v2.2.1 + +- Fix: Removed dependence on minimist to patch CVE-2021-44906. ([#266]) ### v2.2.0 [[code][c2.2.0], [diff][d2.2.0]] @@ -353,6 +377,7 @@ parser for the regular JSON format. [#182]: https://github.com/json5/json5/issues/182 [#187]: https://github.com/json5/json5/issues/187 [#196]: https://github.com/json5/json5/issues/196 +[#199]: https://github.com/json5/json5/issues/199 [#208]: https://github.com/json5/json5/issues/208 [#210]: https://github.com/json5/json5/issues/210 [#222]: https://github.com/json5/json5/issues/222 @@ -360,3 +385,6 @@ parser for the regular JSON format. [#229]: https://github.com/json5/json5/issues/229 [#236]: https://github.com/json5/json5/issues/236 [#244]: https://github.com/json5/json5/issues/244 +[#266]: https://github.com/json5/json5/issues/266 +[#295]: https://github.com/json5/json5/issues/295 +[#299]: https://github.com/json5/json5/issues/299 diff --git a/README.md b/README.md index 6049d02c..00ee7939 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,39 @@ # JSON5 – JSON for Humans -[![Build Status](https://travis-ci.com/json5/json5.svg)][Build Status] -[![Coverage +[![Build Status](https://app.travis-ci.com/json5/json5.svg?branch=main)][Build +Status] [![Coverage Status](https://coveralls.io/repos/github/json5/json5/badge.svg)][Coverage Status] -The JSON5 Data Interchange Format (JSON5) is a superset of [JSON] that aims to -alleviate some of the limitations of JSON by expanding its syntax to include -some productions from [ECMAScript 5.1]. - -This JavaScript library is the official reference implementation for JSON5 -parsing and serialization libraries. - -[Build Status]: https://travis-ci.com/json5/json5 +JSON5 is an extension to the popular [JSON] file format that aims to be +easier to **write and maintain _by hand_ (e.g. for config files)**. +It is _not intended_ to be used for machine-to-machine communication. +(Keep using JSON or other file formats for that. 🙂) + +JSON5 was started in 2012, and as of 2022, now gets **[>65M downloads/week](https://www.npmjs.com/package/json5)**, +ranks in the **[top 0.1%](https://gist.github.com/anvaka/8e8fa57c7ee1350e3491)** of the most depended-upon packages on npm, +and has been adopted by major projects like +**[Chromium](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/runtime_enabled_features.json5;drc=5de823b36e68fd99009a29281b17bc3a1d6b329c), +[Next.js](https://github.com/vercel/next.js/blob/b88f20c90bf4659b8ad5cb2a27956005eac2c7e8/packages/next/lib/find-config.ts#L43-L46), +[Babel](https://babeljs.io/docs/en/config-files#supported-file-extensions), +[Retool](https://community.retool.com/t/i-am-attempting-to-append-several-text-fields-to-a-google-sheet-but-receiving-a-json5-invalid-character-error/7626), +[WebStorm](https://www.jetbrains.com/help/webstorm/json.html), +and [more](https://github.com/json5/json5/wiki/In-the-Wild)**. +It's also natively supported on **[Apple platforms](https://developer.apple.com/documentation/foundation/jsondecoder/3766916-allowsjson5)** +like **MacOS** and **iOS**. + +Formally, the **[JSON5 Data Interchange Format](https://spec.json5.org/)** is a superset of JSON +(so valid JSON files will always be valid JSON5 files) +that expands its syntax to include some productions from [ECMAScript 5.1] (ES5). +It's also a strict _subset_ of ES5, so valid JSON5 files will always be valid ES5. + +This JavaScript library is a reference implementation for JSON5 parsing and serialization, +and is directly used in many of the popular projects mentioned above +(where e.g. extreme performance isn't necessary), +but others have created [many other libraries](https://github.com/json5/json5/wiki/In-the-Wild) +across many other platforms. + +[Build Status]: https://app.travis-ci.com/json5/json5 [Coverage Status]: https://coveralls.io/github/json5/json5 @@ -52,7 +73,9 @@ been extended to JSON5. [IEEE 754]: http://ieeexplore.ieee.org/servlet/opac?punumber=4610933 -## Short Example +## Example +Kitchen-sink example: + ```js { // comments @@ -68,26 +91,42 @@ No \\n's!", } ``` +A more real-world example is [this config file](https://github.com/chromium/chromium/blob/feb3c9f670515edf9a88f185301cbd7794ee3e52/third_party/blink/renderer/platform/runtime_enabled_features.json5) +from the Chromium/Blink project. + ## Specification For a detailed explanation of the JSON5 format, please read the [official specification](https://json5.github.io/json5-spec/). -## Installation +## Installation and Usage ### Node.js ```sh npm install json5 ``` +#### CommonJS ```js const JSON5 = require('json5') ``` +#### Modules +```js +import JSON5 from 'json5' +``` + ### Browsers +#### UMD ```html - + + ``` -This will create a global `JSON5` variable. +#### Modules +```html + +``` ## API The JSON5 API is compatible with the [JSON API]. @@ -195,18 +234,27 @@ run lint` before submitting pull requests. Please use an editor that supports [EditorConfig](http://editorconfig.org/). ### Issues -To report bugs or request features regarding the JSON5 data format, please -submit an issue to the [official specification -repository](https://github.com/json5/json5-spec). +To report bugs or request features regarding the JSON5 **data format**, +please submit an issue to the official +**[_specification_ repository](https://github.com/json5/json5-spec)**. + +Note that we will never add any features that make JSON5 incompatible with ES5; +that compatibility is a fundamental premise of JSON5. + +To report bugs or request features regarding this **JavaScript implementation** +of JSON5, please submit an issue to **_this_ repository**. -To report bugs or request features regarding the JavaScript implementation of -JSON5, please submit an issue to this repository. +### Security Vulnerabilities and Disclosures +To report a security vulnerability, please follow the follow the guidelines +described in our [security policy](./SECURITY.md). ## License MIT. See [LICENSE.md](./LICENSE.md) for details. ## Credits -[Assem Kishore](https://github.com/aseemk) founded this project. +[Aseem Kishore](https://github.com/aseemk) founded this project. +He wrote a [blog post](https://aseemk.substack.com/p/ignore-the-f-ing-haters-json5) +about the journey and lessons learned 10 years in. [Michael Bolin](http://bolinfest.com/) independently arrived at and published some of these same ideas with awesome explanations and detail. Recommended diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..2eaadb65 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,45 @@ +# JSON5 Security Policy + +We take security seriously. Responsible reporting and disclosure of security +vulnerabilities is important for the protection and privacy of our users. If you +discover any security vulnerabilities, please follow these guidelines. + +Published security advisories are available on our [GitHub Security Advisories] +page. + +To report a vulnerability, please draft a [new security advisory on GitHub]. Any +fields that you are unsure of or don't understand can be left at their default +values. The important part is that the vulnerability is reported. Once the +security advisory draft has been created, we will validate the vulnerability and +coordinate with you to fix it, release a patch, and responsibly disclose the +vulnerability to the public. Read GitHub's documentation on [privately reporting +a security vulnerability] for details. + +If you are unable to draft a security advisory, or if you need help or have +security related questions, please send an email to [security@json5.org]. + +Please do not report undisclosed vulnerabilities on public sites or forums, +including GitHub issues and pull requests. Reporting vulnerabilities to the +public could allow attackers to exploit vulnerable applications before we have +been able to release a patch and before applications have had time to install +the patch. Once we have released a patch and sufficient time has passed for +applications to install the patch, we will disclose the vulnerability to the +public, at which time you will be free to publish details of the vulnerability +on public sites and forums. + +If you have a fix for a security vulnerability, please do not submit a GitHub +pull request. Instead, report the vulnerability as described in this policy. +Once we have verified the vulnerability, we can create a [temporary private +fork] to collaborate on a patch. + +We appreciate your cooperation in helping keep our users safe by following this +policy. + +[github security advisories]: https://github.com/json5/json5/security/advisories +[new security advisory on github]: + https://github.com/json5/json5/security/advisories/new +[privately reporting a security vulnerability]: + https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability +[security@json5.org]: mailto:security@json5.org +[temporary private fork]: + https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/collaborating-in-a-temporary-private-fork-to-resolve-a-repository-security-vulnerability diff --git a/lib/cli.js b/lib/cli.js index de852f15..93cb8092 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -2,37 +2,17 @@ const fs = require('fs') const path = require('path') -const minimist = require('minimist') const pkg = require('../package.json') const JSON5 = require('./') -const argv = minimist(process.argv.slice(2), { - alias: { - 'convert': 'c', - 'space': 's', - 'validate': 'v', - 'out-file': 'o', - 'version': 'V', - 'help': 'h', - }, - boolean: [ - 'convert', - 'validate', - 'version', - 'help', - ], - string: [ - 'space', - 'out-file', - ], -}) +const argv = parseArgs() if (argv.version) { version() } else if (argv.help) { usage() } else { - const inFilename = argv._[0] + const inFilename = argv.defaults[0] let readStream if (inFilename) { @@ -65,7 +45,7 @@ if (argv.version) { // --convert is for backward compatibility with v0.5.1. If // specified with and not --out-file, then a file with // the same name but with a .json extension will be written. - if (argv.convert && inFilename && !argv.o) { + if (argv.convert && inFilename && !argv.outFile) { const parsedFilename = path.parse(inFilename) const outFilename = path.format( Object.assign( @@ -75,8 +55,8 @@ if (argv.version) { ) writeStream = fs.createWriteStream(outFilename) - } else if (argv.o) { - writeStream = fs.createWriteStream(argv.o) + } else if (argv.outFile) { + writeStream = fs.createWriteStream(argv.outFile) } else { writeStream = process.stdout } @@ -90,6 +70,66 @@ if (argv.version) { }) } +function parseArgs () { + let convert + let space + let validate + let outFile + let version + let help + const defaults = [] + + const args = process.argv.slice(2) + for (let i = 0; i < args.length; i++) { + const arg = args[i] + switch (arg) { + case '--convert': + case '-c': + convert = true + break + + case '--space': + case '-s': + space = args[++i] + break + + case '--validate': + case '-v': + validate = true + break + + case '--out-file': + case '-o': + outFile = args[++i] + break + + case '--version': + case '-V': + version = true + break + + case '--help': + case '-h': + help = true + break + + default: + defaults.push(arg) + break + } + } + + return { + convert, + space, + validate, + outFile, + version, + help, + defaults, + } +} + function version () { console.log(pkg.version) } diff --git a/lib/parse.js b/lib/parse.js index c01646fc..da2078a6 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -42,12 +42,34 @@ module.exports = function parse (text, reviver) { function internalize (holder, name, reviver) { const value = holder[name] if (value != null && typeof value === 'object') { - for (const key in value) { - const replacement = internalize(value, key, reviver) - if (replacement === undefined) { - delete value[key] - } else { - value[key] = replacement + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + const key = String(i) + const replacement = internalize(value, key, reviver) + if (replacement === undefined) { + delete value[key] + } else { + Object.defineProperty(value, key, { + value: replacement, + writable: true, + enumerable: true, + configurable: true, + }) + } + } + } else { + for (const key in value) { + const replacement = internalize(value, key, reviver) + if (replacement === undefined) { + delete value[key] + } else { + Object.defineProperty(value, key, { + value: replacement, + writable: true, + enumerable: true, + configurable: true, + }) + } } } } @@ -973,7 +995,12 @@ function push () { if (Array.isArray(parent)) { parent.push(value) } else { - parent[key] = value + Object.defineProperty(parent, key, { + value, + writable: true, + enumerable: true, + configurable: true, + }) } } diff --git a/package-lock.json b/package-lock.json index 56c0f4de..ac2e4549 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "json5", - "version": "2.2.0", + "version": "2.2.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -563,6 +563,16 @@ "unset-value": "^1.0.0" } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "callsites": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", @@ -801,6 +811,16 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -894,6 +914,48 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1542,12 +1604,51 @@ "integrity": "sha1-gHa7MF6OajzO7ikgdl8zDRkPNAw=", "dev": true }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -1620,12 +1721,42 @@ "function-bind": "^1.1.1" } }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -1771,6 +1902,17 @@ } } }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -1797,12 +1939,37 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -1823,6 +1990,15 @@ } } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -1860,6 +2036,12 @@ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -1880,6 +2062,15 @@ } } }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -1895,12 +2086,58 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -1980,6 +2217,12 @@ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -2130,6 +2373,12 @@ "object-visit": "^1.0.0" } }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -2184,7 +2433,8 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true }, "minipass": { "version": "2.3.5", @@ -2310,6 +2560,73 @@ "validate-npm-package-license": "^3.0.1" } }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + } + } + }, "nyc": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", @@ -3381,6 +3698,18 @@ } } }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -3390,6 +3719,18 @@ "isobject": "^3.0.0" } }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -3568,6 +3909,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -3694,6 +4041,17 @@ "safe-regex": "^1.1.0" } }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", @@ -3977,6 +4335,23 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -4281,6 +4656,39 @@ "strip-ansi": "^4.0.0" } }, + "string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4654,6 +5062,18 @@ "integrity": "sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==", "dev": true }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, "unicode-10.0.0": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/unicode-10.0.0/-/unicode-10.0.0-0.7.5.tgz", @@ -4860,6 +5280,19 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 31c43e5f..60c51d93 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "json5", - "version": "2.2.0", - "description": "JSON for humans.", + "version": "2.2.3", + "description": "JSON for Humans", "main": "lib/index.js", "module": "dist/index.mjs", "bin": "lib/cli.js", @@ -20,10 +20,12 @@ "build-unicode": "node build/unicode.js", "coverage": "tap --coverage-report html test", "lint": "eslint --fix .", + "lint-report": "eslint .", "prepublishOnly": "npm run production", "preversion": "npm run production", - "production": "npm run lint && npm test && npm run build", - "test": "tap -Rspec --100 test", + "production": "run-s test build", + "tap": "tap -Rspec --100 test", + "test": "run-s lint-report tap", "version": "npm run build-package && git add package.json5" }, "repository": { @@ -48,9 +50,6 @@ "url": "https://github.com/json5/json5/issues" }, "homepage": "http://json5.org/", - "dependencies": { - "minimist": "^1.2.5" - }, "devDependencies": { "core-js": "^2.6.5", "eslint": "^5.15.3", @@ -59,6 +58,7 @@ "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", + "npm-run-all": "^4.1.5", "regenerate": "^1.4.0", "rollup": "^0.64.1", "rollup-plugin-buble": "^0.19.6", diff --git a/package.json5 b/package.json5 index afcfbe26..322bed55 100644 --- a/package.json5 +++ b/package.json5 @@ -1,8 +1,8 @@ // This is a generated file. Do not edit. { name: 'json5', - version: '2.2.0', - description: 'JSON for humans.', + version: '2.2.3', + description: 'JSON for Humans', main: 'lib/index.js', module: 'dist/index.mjs', bin: 'lib/cli.js', @@ -21,10 +21,12 @@ 'build-unicode': 'node build/unicode.js', coverage: 'tap --coverage-report html test', lint: 'eslint --fix .', + 'lint-report': 'eslint .', prepublishOnly: 'npm run production', preversion: 'npm run production', - production: 'npm run lint && npm test && npm run build', - test: 'tap -Rspec --100 test', + production: 'run-s test build', + tap: 'tap -Rspec --100 test', + test: 'run-s lint-report tap', version: 'npm run build-package && git add package.json5', }, repository: { @@ -49,9 +51,6 @@ url: 'https://github.com/json5/json5/issues', }, homepage: 'http://json5.org/', - dependencies: { - minimist: '^1.2.5', - }, devDependencies: { 'core-js': '^2.6.5', eslint: '^5.15.3', @@ -60,6 +59,7 @@ 'eslint-plugin-node': '^8.0.1', 'eslint-plugin-promise': '^4.0.1', 'eslint-plugin-standard': '^4.0.0', + 'npm-run-all': '^4.1.5', regenerate: '^1.4.0', rollup: '^0.64.1', 'rollup-plugin-buble': '^0.19.6', diff --git a/test/.eslintrc.json b/test/.eslintrc.json deleted file mode 100644 index 4668ae79..00000000 --- a/test/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "mocha": true - } -} diff --git a/test/cli.js b/test/cli.js index 8ddef73a..3d1ca827 100644 --- a/test/cli.js +++ b/test/cli.js @@ -2,12 +2,13 @@ const assert = require('assert') const child = require('child_process') const fs = require('fs') const path = require('path') -const tap = require('tap') const pkg = require('../package.json') const cliPath = path.resolve(__dirname, '../lib/cli.js') -tap.test('CLI', t => { +const t = require('tap') + +t.test('CLI', t => { t.test('converts JSON5 to JSON from stdin to stdout', t => { const proc = child.spawn(process.execPath, [cliPath]) let output = '' @@ -43,7 +44,7 @@ tap.test('CLI', t => { }) }) - t.test('indents output with the number of spaces specified', t => { + t.test('indents output with the number of spaces specified with -s', t => { const proc = child.spawn( process.execPath, [ @@ -65,7 +66,29 @@ tap.test('CLI', t => { }) }) - t.test('indents output with tabs when specified', t => { + t.test('indents output with the number of spaces specified with --space', t => { + const proc = child.spawn( + process.execPath, + [ + cliPath, + path.resolve(__dirname, 'test.json5'), + '--space', + '4', + ] + ) + + let output = '' + proc.stdout.on('data', data => { + output += data + }) + + proc.stdout.on('end', () => { + assert.strictEqual(output, '{\n "a": 1,\n "b": 2\n}') + t.end() + }) + }) + + t.test('indents output with tabs when specified with -s', t => { const proc = child.spawn( process.execPath, [ @@ -87,7 +110,7 @@ tap.test('CLI', t => { }) }) - t.test('outputs to the specified file', t => { + t.test('outputs to the specified file with -o', t => { const proc = child.spawn( process.execPath, [ @@ -116,7 +139,36 @@ tap.test('CLI', t => { }) }) - t.test('validates valid JSON5 files', t => { + t.test('outputs to the specified file with --out-file', t => { + const proc = child.spawn( + process.execPath, + [ + cliPath, + path.resolve(__dirname, 'test.json5'), + '--out-file', + path.resolve(__dirname, 'output.json'), + ] + ) + + proc.on('exit', () => { + assert.strictEqual( + fs.readFileSync( + path.resolve(__dirname, 'output.json'), + 'utf8' + ), + '{"a":1,"b":2}' + ) + t.end() + }) + + t.tearDown(() => { + try { + fs.unlinkSync(path.resolve(__dirname, 'output.json')) + } catch (err) {} + }) + }) + + t.test('validates valid JSON5 files with -v', t => { const proc = child.spawn( process.execPath, [ @@ -132,7 +184,23 @@ tap.test('CLI', t => { }) }) - t.test('validates invalid JSON5 files', t => { + t.test('validates valid JSON5 files with --validate', t => { + const proc = child.spawn( + process.execPath, + [ + cliPath, + path.resolve(__dirname, 'test.json5'), + '--validate', + ] + ) + + proc.on('exit', code => { + assert.strictEqual(code, 0) + t.end() + }) + }) + + t.test('validates invalid JSON5 files with -v', t => { const proc = child.spawn( process.execPath, [ @@ -157,7 +225,7 @@ tap.test('CLI', t => { }) }) - t.test('outputs the version number when specified', t => { + t.test('outputs the version number when specified with -V', t => { const proc = child.spawn(process.execPath, [cliPath, '-V']) let output = '' @@ -171,7 +239,21 @@ tap.test('CLI', t => { }) }) - t.test('outputs usage information when specified', t => { + t.test('outputs the version number when specified with --version', t => { + const proc = child.spawn(process.execPath, [cliPath, '--version']) + + let output = '' + proc.stdout.on('data', data => { + output += data + }) + + proc.stdout.on('end', () => { + assert.strictEqual(output, pkg.version + '\n') + t.end() + }) + }) + + t.test('outputs usage information when specified with -h', t => { const proc = child.spawn(process.execPath, [cliPath, '-h']) let output = '' @@ -185,7 +267,21 @@ tap.test('CLI', t => { }) }) - t.test('is backward compatible with v0.5.1', t => { + t.test('outputs usage information when specified with --help', t => { + const proc = child.spawn(process.execPath, [cliPath, '--help']) + + let output = '' + proc.stdout.on('data', data => { + output += data + }) + + proc.stdout.on('end', () => { + assert(/Usage/.test(output)) + t.end() + }) + }) + + t.test('is backward compatible with v0.5.1 with -c', t => { const proc = child.spawn( process.execPath, [ @@ -213,5 +309,33 @@ tap.test('CLI', t => { }) }) + t.test('is backward compatible with v0.5.1 with --convert', t => { + const proc = child.spawn( + process.execPath, + [ + cliPath, + '--convert', + path.resolve(__dirname, 'test.json5'), + ] + ) + + proc.on('exit', () => { + assert.strictEqual( + fs.readFileSync( + path.resolve(__dirname, 'test.json'), + 'utf8' + ), + '{"a":1,"b":2}' + ) + t.end() + }) + + t.tearDown(() => { + try { + fs.unlinkSync(path.resolve(__dirname, 'test.json')) + } catch (err) {} + }) + }) + t.end() }) diff --git a/test/errors.js b/test/errors.js index 1d1657c6..50f590cf 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1,444 +1,377 @@ -const assert = require('assert') const JSON5 = require('../lib') -require('tap').mochaGlobals() - -describe('JSON5', () => { - describe('#parse()', () => { - describe('errors', () => { - it('throws on empty documents', () => { - assert.throws(() => { - JSON5.parse('') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 1 - )) - }) - - it('throws on documents with only comments', () => { - assert.throws(() => { - JSON5.parse('//a') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 4 - )) - }) - - it('throws on incomplete single line comments', () => { - assert.throws(() => { - JSON5.parse('/a') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'a'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 2 - )) - }) - - it('throws on unterminated multiline comments', () => { - assert.throws(() => { - JSON5.parse('/*') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on unterminated multiline comment closings', () => { - assert.throws(() => { - JSON5.parse('/**') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 4 - )) - }) - - it('throws on invalid characters in values', () => { - assert.throws(() => { - JSON5.parse('a') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'a'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 1 - )) - }) - - it('throws on invalid characters in identifier start escapes', () => { - assert.throws(() => { - JSON5.parse('{\\a:1}') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'a'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on invalid identifier start characters', () => { - assert.throws(() => { - JSON5.parse('{\\u0021:1}') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid identifier character/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 2 - )) - }) - - it('throws on invalid characters in identifier continue escapes', () => { - assert.throws(() => { - JSON5.parse('{a\\a:1}') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'a'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 4 - )) - }) - - it('throws on invalid identifier continue characters', () => { - assert.throws(() => { - JSON5.parse('{a\\u0021:1}') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid identifier character/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on invalid characters following a sign', () => { - assert.throws(() => { - JSON5.parse('-a') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'a'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 2 - )) - }) - - it('throws on invalid characters following a leading decimal point', () => { - assert.throws(() => { - JSON5.parse('.a') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'a'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 2 - )) - }) - - it('throws on invalid characters following an exponent indicator', () => { - assert.throws(() => { - JSON5.parse('1ea') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'a'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on invalid characters following an exponent sign', () => { - assert.throws(() => { - JSON5.parse('1e-a') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'a'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 4 - )) - }) - - it('throws on invalid characters following a hexadecimal indicator', () => { - assert.throws(() => { - JSON5.parse('0xg') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'g'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on invalid new lines in strings', () => { - assert.throws(() => { - JSON5.parse('"\n"') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '\\n'/.test(err.message) && - err.lineNumber === 2 && - err.columnNumber === 0 - )) - }) - - it('throws on unterminated strings', () => { - assert.throws(() => { - JSON5.parse('"') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 2 - )) - }) - - it('throws on invalid identifier start characters in property names', () => { - assert.throws(() => { - JSON5.parse('{!:1}') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '!'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 2 - )) - }) - - it('throws on invalid characters following a property name', () => { - assert.throws(() => { - JSON5.parse('{a!1}') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '!'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on invalid characters following a property value', () => { - assert.throws(() => { - JSON5.parse('{a:1!}') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '!'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 5 - )) - }) - - it('throws on invalid characters following an array value', () => { - assert.throws(() => { - JSON5.parse('[1!]') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '!'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on invalid characters in literals', () => { - assert.throws(() => { - JSON5.parse('tru!') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '!'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 4 - )) - }) - - it('throws on unterminated escapes', () => { - assert.throws(() => { - JSON5.parse('"\\') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on invalid first digits in hexadecimal escapes', () => { - assert.throws(() => { - JSON5.parse('"\\xg"') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'g'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 4 - )) - }) - - it('throws on invalid second digits in hexadecimal escapes', () => { - assert.throws(() => { - JSON5.parse('"\\x0g"') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'g'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 5 - )) - }) - - it('throws on invalid unicode escapes', () => { - assert.throws(() => { - JSON5.parse('"\\u000g"') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character 'g'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 7 - )) - }) - - it('throws on escaped digits other than 0', () => { - for (let i = 1; i <= 9; i++) { - assert.throws(() => { - JSON5.parse(`'\\${i}'`) +const t = require('tap') + +t.test('JSON5', t => { + t.test('#parse()', t => { + t.test('errors', t => { + t.throws( + () => { JSON5.parse('') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 1, + }, + 'throws on empty documents' + ) + + t.throws( + () => { JSON5.parse('//a') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 4, + }, + 'throws on documents with only comments' + ) + + t.throws( + () => { JSON5.parse('/a') }, + { + message: /^JSON5: invalid character 'a'/, + lineNumber: 1, + columnNumber: 2, + }, + 'throws on incomplete single line comments' + ) + + t.throws( + () => { JSON5.parse('/*') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on unterminated multiline comments' + ) + + t.throws( + () => { JSON5.parse('/**') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 4, + }, + 'throws on unterminated multiline comment closings' + ) + + t.throws( + () => { JSON5.parse('a') }, + { + message: /^JSON5: invalid character 'a'/, + lineNumber: 1, + columnNumber: 1, + }, + 'throws on invalid characters in values' + ) + + t.throws( + () => { JSON5.parse('{\\a:1}') }, + { + message: /^JSON5: invalid character 'a'/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on invalid characters in identifier start escapes' + ) + + t.throws( + () => { JSON5.parse('{\\u0021:1}') }, + { + message: /^JSON5: invalid identifier character/, + lineNumber: 1, + columnNumber: 2, + }, + 'throws on invalid identifier start characters' + ) + + t.throws( + () => { JSON5.parse('{a\\a:1}') }, + { + message: /^JSON5: invalid character 'a'/, + lineNumber: 1, + columnNumber: 4, + }, + 'throws on invalid characters in identifier continue escapes' + ) + + t.throws( + () => { JSON5.parse('{a\\u0021:1}') }, + { + message: /^JSON5: invalid identifier character/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on invalid identifier continue characters' + ) + + t.throws( + () => { JSON5.parse('-a') }, + { + message: /^JSON5: invalid character 'a'/, + lineNumber: 1, + columnNumber: 2, + }, + 'throws on invalid characters following a sign' + ) + + t.throws( + () => { JSON5.parse('.a') }, + { + message: /^JSON5: invalid character 'a'/, + lineNumber: 1, + columnNumber: 2, + }, + 'throws on invalid characters following a leading decimal point' + ) + + t.throws( + () => { JSON5.parse('1ea') }, + { + message: /^JSON5: invalid character 'a'/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on invalid characters following an exponent indicator' + ) + + t.throws( + () => { JSON5.parse('1e-a') }, + { + message: /^JSON5: invalid character 'a'/, + lineNumber: 1, + columnNumber: 4, + }, + 'throws on invalid characters following an exponent sign' + ) + + t.throws( + () => { JSON5.parse('0xg') }, + { + message: /^JSON5: invalid character 'g'/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on invalid characters following a hexadecimal indicator' + ) + + t.throws( + () => { JSON5.parse('"\n"') }, + { + message: /^JSON5: invalid character '\\n'/, + lineNumber: 2, + columnNumber: 0, + }, + 'throws on invalid new lines in strings' + ) + + t.throws( + () => { JSON5.parse('"') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 2, + }, + 'throws on unterminated strings' + ) + + t.throws( + () => { JSON5.parse('{!:1}') }, + { + message: /^JSON5: invalid character '!'/, + lineNumber: 1, + columnNumber: 2, + }, + 'throws on invalid identifier start characters in property names' + ) + + t.throws( + () => { JSON5.parse('{a!1}') }, + { + message: /^JSON5: invalid character '!'/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on invalid characters following a property name' + ) + + t.throws( + () => { JSON5.parse('{a:1!}') }, + { + message: /^JSON5: invalid character '!'/, + lineNumber: 1, + columnNumber: 5, + }, + 'throws on invalid characters following a property value' + ) + + t.throws( + () => { JSON5.parse('[1!]') }, + { + message: /^JSON5: invalid character '!'/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on invalid characters following an array value' + ) + + t.throws( + () => { JSON5.parse('tru!') }, + { + message: /^JSON5: invalid character '!'/, + lineNumber: 1, + columnNumber: 4, + }, + 'throws on invalid characters in literals' + ) + + t.throws( + () => { JSON5.parse('"\\') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on unterminated escapes' + ) + + t.throws( + () => { JSON5.parse('"\\xg"') }, + { + message: /^JSON5: invalid character 'g'/, + lineNumber: 1, + columnNumber: 4, + }, + 'throws on invalid first digits in hexadecimal escapes' + ) + + t.throws( + () => { JSON5.parse('"\\x0g"') }, + { + message: /^JSON5: invalid character 'g'/, + lineNumber: 1, + columnNumber: 5, + }, + 'throws on invalid second digits in hexadecimal escapes' + ) + + t.throws( + () => { JSON5.parse('"\\u000g"') }, + { + message: /^JSON5: invalid character 'g'/, + lineNumber: 1, + columnNumber: 7, + }, + 'throws on invalid unicode escapes' + ) + + for (let i = 1; i <= 9; i++) { + t.throws( + () => { JSON5.parse(`'\\${i}'`) }, + { + message: /^JSON5: invalid character '\d'/, + lineNumber: 1, + columnNumber: 3, }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '\d'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - } - }) - - it('throws on octal escapes', () => { - assert.throws(() => { - JSON5.parse("'\\01'") - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '1'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 4 - )) - }) - - it('throws on multiple values', () => { - assert.throws(() => { - JSON5.parse('1 2') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '2'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws with control characters escaped in the message', () => { - assert.throws(() => { - JSON5.parse('\x01') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid character '\\x01'/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 1 - )) - }) - - it('throws on unclosed objects before property names', () => { - assert.throws(() => { - JSON5.parse('{') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 2 - )) - }) - - it('throws on unclosed objects after property names', () => { - assert.throws(() => { - JSON5.parse('{a') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) - - it('throws on unclosed objects before property values', () => { - assert.throws(() => { - JSON5.parse('{a:') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 4 - )) - }) - - it('throws on unclosed objects after property values', () => { - assert.throws(() => { - JSON5.parse('{a:1') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 5 - )) - }) - - it('throws on unclosed arrays before values', () => { - assert.throws(() => { - JSON5.parse('[') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 2 - )) - }) - - it('throws on unclosed arrays after values', () => { - assert.throws(() => { - JSON5.parse('[1') - }, - err => ( - err instanceof SyntaxError && - /^JSON5: invalid end of input/.test(err.message) && - err.lineNumber === 1 && - err.columnNumber === 3 - )) - }) + `throws on escaped digit ${i}` + ) + } + + t.throws( + () => { JSON5.parse("'\\01'") }, + { + message: /^JSON5: invalid character '1'/, + lineNumber: 1, + columnNumber: 4, + }, + 'throws on octal escapes' + ) + + t.throws( + () => { JSON5.parse('1 2') }, + { + message: /^JSON5: invalid character '2'/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on multiple values' + ) + + t.throws( + () => { JSON5.parse('\x01') }, + { + message: /^JSON5: invalid character '\\x01'/, + lineNumber: 1, + columnNumber: 1, + }, + 'throws with control characters escaped in the message' + ) + + t.throws( + () => { JSON5.parse('{') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 2, + }, + 'throws on unclosed objects before property names' + ) + + t.throws( + () => { JSON5.parse('{a') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on unclosed objects after property names' + ) + + t.throws( + () => { JSON5.parse('{a:') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 4, + }, + 'throws on unclosed objects before property values' + ) + + t.throws( + () => { JSON5.parse('{a:1') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 5, + }, + 'throws on unclosed objects after property values' + ) + + t.throws( + () => { JSON5.parse('[') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 2, + }, + 'throws on unclosed arrays before values' + ) + + t.throws( + () => { JSON5.parse('[1') }, + { + message: /^JSON5: invalid end of input/, + lineNumber: 1, + columnNumber: 3, + }, + 'throws on unclosed arrays after values' + ) + + t.end() }) + + t.end() }) + + t.end() }) diff --git a/test/parse.js b/test/parse.js index 59fbdd15..e305767e 100644 --- a/test/parse.js +++ b/test/parse.js @@ -2,8 +2,6 @@ const assert = require('assert') const sinon = require('sinon') const JSON5 = require('../lib') -require('tap').mochaGlobals() - const t = require('tap') t.test('parse(text)', t => { @@ -50,6 +48,13 @@ t.test('parse(text)', t => { 'parses escaped property names' ) + t.strictSame( + // eslint-disable-next-line no-proto + JSON5.parse('{"__proto__":1}').__proto__, + 1, + 'preserves __proto__ property names' + ) + t.strictSame( JSON5.parse('{abc:1,def:2}'), {abc: 1, def: 2}, diff --git a/test/require.js b/test/require.js index b30f74c7..86b9b470 100644 --- a/test/require.js +++ b/test/require.js @@ -1,24 +1,29 @@ const assert = require('assert') const sinon = require('sinon') -require('tap').mochaGlobals() +const t = require('tap') -describe('require(*.json5)', () => { - it('parses a JSON5 document', () => { +t.test('require(*.json5)', t => { + t.test('parses a JSON5 document', t => { require('../lib/register') assert.deepStrictEqual({a: 1, b: 2}, require('./test.json5')) + t.end() }) - it('is backward compatible with v0.5.1, but gives a deprecation warning', () => { + t.test('is backward compatible with v0.5.1, but gives a deprecation warning', t => { const mock = sinon.mock(console) mock.expects('warn').once().withExactArgs("'json5/require' is deprecated. Please use 'json5/register' instead.") require('../lib/require') assert.deepStrictEqual({a: 1, b: 2}, require('./test.json5')) mock.verify() + t.end() }) - it('throws on invalid JSON5', () => { + t.test('throws on invalid JSON5', t => { require('../lib/register') assert.throws(() => { require('./invalid.json5') }, SyntaxError) + t.end() }) + + t.end() }) diff --git a/test/stringify.js b/test/stringify.js index a0c494f0..e256a314 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -1,340 +1,482 @@ const assert = require('assert') const JSON5 = require('../lib') -require('tap').mochaGlobals() - -describe('JSON5', () => { - describe('#stringify', () => { - describe('objects', () => { - it('stringifies empty objects', () => { - assert.strictEqual(JSON5.stringify({}), '{}') - }) - - it('stringifies unquoted property names', () => { - assert.strictEqual(JSON5.stringify({a: 1}), '{a:1}') - }) - - it('stringifies single quoted string property names', () => { - assert.strictEqual(JSON5.stringify({'a-b': 1}), "{'a-b':1}") - }) - - it('stringifies double quoted string property names', () => { - assert.strictEqual(JSON5.stringify({"a'": 1}), `{"a'":1}`) - }) +const t = require('tap') + +t.test('JSON5', t => { + t.test('#stringify', t => { + t.test('objects', t => { + t.strictSame( + JSON5.stringify({}), + '{}', + 'stringifies empty objects' + ) - it('stringifies empty string property names', () => { - assert.strictEqual(JSON5.stringify({'': 1}), "{'':1}") - }) + t.strictSame( + JSON5.stringify({a: 1}), + '{a:1}', + 'stringifies unquoted property names' + ) - it('stringifies special character property names', () => { - assert.strictEqual(JSON5.stringify({$_: 1, _$: 2, 'a\u200C': 3}), '{$_:1,_$:2,a\u200C:3}') - }) + t.strictSame( + JSON5.stringify({'a-b': 1}), + "{'a-b':1}", + 'stringifies single quoted string property names' + ) - it('stringifies unicode property names', () => { - assert.strictEqual(JSON5.stringify({'ùńîċõďë': 9}), '{ùńîċõďë:9}') - }) + t.strictSame( + JSON5.stringify({"a'": 1}), + `{"a'":1}`, + 'stringifies double quoted string property names' + ) - it('stringifies escaped property names', () => { - assert.strictEqual(JSON5.stringify({'\\\b\f\n\r\t\v\0\x01': 1}), "{'\\\\\\b\\f\\n\\r\\t\\v\\0\\x01':1}") - }) + t.strictSame( + JSON5.stringify({'': 1}), + "{'':1}", + 'stringifies empty string property names' + ) - it('stringifies escaped null character property names', () => { - assert.strictEqual(JSON5.stringify({'\0\x001': 1}), "{'\\0\\x001':1}") - }) + t.strictSame( + JSON5.stringify({$_: 1, _$: 2, 'a\u200C': 3}), + '{$_:1,_$:2,a\u200C:3}', + 'stringifies special character property names' + ) - it('stringifies multiple properties', () => { - assert.strictEqual(JSON5.stringify({abc: 1, def: 2}), '{abc:1,def:2}') - }) + t.strictSame( + JSON5.stringify({'ùńîċõďë': 9}), + '{ùńîċõďë:9}', + 'stringifies unicode property names' + ) - it('stringifies nested objects', () => { - assert.strictEqual(JSON5.stringify({a: {b: 2}}), '{a:{b:2}}') - }) - }) + t.strictSame( + JSON5.stringify({'\\\b\f\n\r\t\v\0\x01': 1}), + "{'\\\\\\b\\f\\n\\r\\t\\v\\0\\x01':1}", + 'stringifies escaped property names' + ) - describe('arrays', () => { - it('stringifies empty arrays', () => { - assert.strictEqual(JSON5.stringify([]), '[]') - }) + t.strictSame( + JSON5.stringify({'\0\x001': 1}), + "{'\\0\\x001':1}", + 'stringifies escaped null character property names' + ) - it('stringifies array values', () => { - assert.strictEqual(JSON5.stringify([1]), '[1]') - }) + t.strictSame( + JSON5.stringify({abc: 1, def: 2}), + '{abc:1,def:2}', + 'stringifies multiple properties' + ) - it('stringifies multiple array values', () => { - assert.strictEqual(JSON5.stringify([1, 2]), '[1,2]') - }) + t.strictSame( + JSON5.stringify({a: {b: 2}}), + '{a:{b:2}}', + 'stringifies nested objects' + ) - it('stringifies nested arrays', () => { - assert.strictEqual(JSON5.stringify([1, [2, 3]]), '[1,[2,3]]') - }) + t.end() }) - it('stringifies nulls', () => { - assert.strictEqual(JSON5.stringify(null), 'null') - }) + t.test('arrays', t => { + t.strictSame( + JSON5.stringify([]), + '[]', + 'stringifies empty arrays' + ) - it('returns undefined for functions', () => { - assert.strictEqual(JSON5.stringify(() => {}), undefined) - }) + t.strictSame( + JSON5.stringify([1]), + '[1]', + 'stringifies array values' + ) - it('ignores function properties', () => { - assert.strictEqual(JSON5.stringify({a () {}}), '{}') - }) + t.strictSame( + JSON5.stringify([1, 2]), + '[1,2]', + 'stringifies multiple array values' + ) - it('returns null for functions in arrays', () => { - assert.strictEqual(JSON5.stringify([() => {}]), '[null]') - }) + t.strictSame( + JSON5.stringify([1, [2, 3]]), + '[1,[2,3]]', + 'stringifies nested arrays' + ) - describe('Booleans', () => { - it('stringifies true', () => { - assert.strictEqual(JSON5.stringify(true), 'true') - }) + t.end() + }) + + t.strictSame( + JSON5.stringify(null), + 'null', + 'stringifies nulls' + ) + + t.strictSame( + JSON5.stringify(() => {}), + undefined, + 'returns undefined for functions' + ) + + t.strictSame( + JSON5.stringify({a () {}}), + '{}', + 'ignores function properties' + ) + + t.strictSame( + JSON5.stringify([() => {}]), + '[null]', + 'returns null for functions in arrays' + ) + + t.test('Booleans', t => { + t.strictSame( + JSON5.stringify(true), + 'true', + 'stringifies true' + ) - it('stringifies false', () => { - assert.strictEqual(JSON5.stringify(false), 'false') - }) + t.strictSame( + JSON5.stringify(false), + 'false', + 'stringifies false' + ) - it('stringifies true Boolean objects', () => { + t.strictSame( // eslint-disable-next-line no-new-wrappers - assert.strictEqual(JSON5.stringify(new Boolean(true)), 'true') - }) + JSON5.stringify(new Boolean(true)), + 'true', + 'stringifies true Boolean objects' + ) - it('stringifies false Boolean objects', () => { + t.strictSame( // eslint-disable-next-line no-new-wrappers - assert.strictEqual(JSON5.stringify(new Boolean(false)), 'false') - }) + JSON5.stringify(new Boolean(false)), + 'false', + 'stringifies false Boolean objects' + ) + + t.end() }) - describe('numbers', () => { - it('stringifies numbers', () => { - assert.strictEqual(JSON5.stringify(-1.2), '-1.2') - }) + t.test('numbers', t => { + t.strictSame( + JSON5.stringify(-1.2), + '-1.2', + 'stringifies numbers' + ) - it('stringifies non-finite numbers', () => { - assert.strictEqual(JSON5.stringify([Infinity, -Infinity, NaN]), '[Infinity,-Infinity,NaN]') - }) + t.strictSame( + JSON5.stringify([Infinity, -Infinity, NaN]), + '[Infinity,-Infinity,NaN]', + 'stringifies non-finite numbers' + ) - it('stringifies Number objects', () => { + t.strictSame( // eslint-disable-next-line no-new-wrappers - assert.strictEqual(JSON5.stringify(new Number(-1.2)), '-1.2') - }) + JSON5.stringify(new Number(-1.2)), + '-1.2', + 'stringifies Number objects' + ) + + t.end() }) - describe('strings', () => { - it('stringifies single quoted strings', () => { - assert.strictEqual(JSON5.stringify('abc'), "'abc'") - }) + t.test('strings', t => { + t.strictSame( + JSON5.stringify('abc'), + "'abc'", + 'stringifies single quoted strings' + ) - it('stringifies double quoted strings', () => { - assert.strictEqual(JSON5.stringify("abc'"), `"abc'"`) - }) + t.strictSame( + JSON5.stringify("abc'"), + `"abc'"`, + 'stringifies double quoted strings' + ) - it('stringifies escaped characters', () => { - assert.strictEqual(JSON5.stringify('\\\b\f\n\r\t\v\0\x0f'), "'\\\\\\b\\f\\n\\r\\t\\v\\0\\x0f'") - }) + t.strictSame( + JSON5.stringify('\\\b\f\n\r\t\v\0\x0f'), + "'\\\\\\b\\f\\n\\r\\t\\v\\0\\x0f'", + 'stringifies escaped characters' + ) - it('stringifies escaped null characters', () => { - assert.strictEqual(JSON5.stringify('\0\x001'), "'\\0\\x001'") - }) + t.strictSame( + JSON5.stringify('\0\x001'), + "'\\0\\x001'", + 'stringifies escaped null characters' + ) - it('stringifies escaped single quotes', () => { - assert.strictEqual(JSON5.stringify(`'"`), `'\\'"'`) - }) + t.strictSame( + JSON5.stringify(`'"`), + `'\\'"'`, + 'stringifies escaped single quotes' + ) - it('stringifies escaped double quotes', () => { - assert.strictEqual(JSON5.stringify(`''"`), `"''\\""`) - }) + t.strictSame( + JSON5.stringify(`''"`), + `"''\\""`, + 'stringifies escaped double quotes' + ) - it('stringifies escaped line and paragraph separators', () => { - assert.strictEqual(JSON5.stringify('\u2028\u2029'), "'\\u2028\\u2029'") - }) + t.strictSame( + JSON5.stringify('\u2028\u2029'), + "'\\u2028\\u2029'", + 'stringifies escaped line and paragraph separators' + ) - it('stringifies String objects', () => { + t.strictSame( // eslint-disable-next-line no-new-wrappers - assert.strictEqual(JSON5.stringify(new String('abc')), "'abc'") - }) - }) + JSON5.stringify(new String('abc')), + "'abc'", + 'stringifies String objects' + ) - it('stringifies using built-in toJSON methods', () => { - assert.strictEqual(JSON5.stringify(new Date('2016-01-01T00:00:00.000Z')), "'2016-01-01T00:00:00.000Z'") + t.end() }) - it('stringifies using user defined toJSON methods', () => { + t.strictSame( + JSON5.stringify(new Date('2016-01-01T00:00:00.000Z')), + "'2016-01-01T00:00:00.000Z'", + 'stringifies using built-in toJSON methods' + ) + + t.test('stringifies using user defined toJSON methods', t => { function C () {} Object.assign(C.prototype, {toJSON () { return {a: 1, b: 2} }}) assert.strictEqual(JSON5.stringify(new C()), '{a:1,b:2}') + t.end() }) - it('stringifies using user defined toJSON(key) methods', () => { + t.test('stringifies using user defined toJSON(key) methods', t => { function C () {} Object.assign(C.prototype, {toJSON (key) { return (key === 'a') ? 1 : 2 }}) assert.strictEqual(JSON5.stringify({a: new C(), b: new C()}), '{a:1,b:2}') + t.end() }) - it('stringifies using toJSON5 methods', () => { + t.test('stringifies using toJSON5 methods', t => { function C () {} Object.assign(C.prototype, {toJSON5 () { return {a: 1, b: 2} }}) assert.strictEqual(JSON5.stringify(new C()), '{a:1,b:2}') + t.end() }) - it('stringifies using toJSON5(key) methods', () => { + t.test('stringifies using toJSON5(key) methods', t => { function C () {} Object.assign(C.prototype, {toJSON5 (key) { return (key === 'a') ? 1 : 2 }}) assert.strictEqual(JSON5.stringify({a: new C(), b: new C()}), '{a:1,b:2}') + t.end() }) - it('calls toJSON5 instead of toJSON if both are defined', () => { + t.test('calls toJSON5 instead of toJSON if both are defined', t => { function C () {} Object.assign(C.prototype, { toJSON () { return {a: 1, b: 2} }, toJSON5 () { return {a: 2, b: 2} }, }) assert.strictEqual(JSON5.stringify(new C()), '{a:2,b:2}') + t.end() }) - it('throws on circular objects', () => { + t.test('throws on circular objects', t => { let a = {} a.a = a assert.throws(() => { JSON5.stringify(a) }, TypeError, 'Converting circular structure to JSON5') + t.end() }) - it('throws on circular arrays', () => { + t.test('throws on circular arrays', t => { let a = [] a[0] = a assert.throws(() => { JSON5.stringify(a) }, TypeError, 'Converting circular structure to JSON5') - }) - }) - - describe('#stringify(value, null, space)', () => { - it('does not indent when no value is provided', () => { - assert.strictEqual(JSON5.stringify([1]), '[1]') - }) - - it('does not indent when 0 is provided', () => { - assert.strictEqual(JSON5.stringify([1], null, 0), '[1]') - }) - - it('does not indent when an empty string is provided', () => { - assert.strictEqual(JSON5.stringify([1], null, ''), '[1]') - }) - - it('indents n spaces when a number is provided', () => { - assert.strictEqual(JSON5.stringify([1], null, 2), '[\n 1,\n]') - }) - - it('does not indent more than 10 spaces when a number is provided', () => { - assert.strictEqual(JSON5.stringify([1], null, 11), '[\n 1,\n]') - }) - - it('indents with the string provided', () => { - assert.strictEqual(JSON5.stringify([1], null, '\t'), '[\n\t1,\n]') - }) - - it('does not indent more than 10 characters of the string provided', () => { - assert.strictEqual(JSON5.stringify([1], null, ' '), '[\n 1,\n]') - }) - - it('indents in arrays', () => { - assert.strictEqual(JSON5.stringify([1], null, 2), '[\n 1,\n]') - }) - - it('indents in nested arrays', () => { - assert.strictEqual(JSON5.stringify([1, [2], 3], null, 2), '[\n 1,\n [\n 2,\n ],\n 3,\n]') + t.end() }) - it('indents in objects', () => { - assert.strictEqual(JSON5.stringify({a: 1}, null, 2), '{\n a: 1,\n}') - }) - - it('indents in nested objects', () => { - assert.strictEqual(JSON5.stringify({a: {b: 2}}, null, 2), '{\n a: {\n b: 2,\n },\n}') - }) + t.end() + }) - it('accepts Number objects', () => { + t.test('#stringify(value, null, space)', t => { + t.strictSame( + JSON5.stringify([1]), + '[1]', + 'does not indent when no value is provided' + ) + + t.strictSame( + JSON5.stringify([1], null, 0), + '[1]', + 'does not indent when 0 is provided' + ) + + t.strictSame( + JSON5.stringify([1], null, ''), + '[1]', + 'does not indent when an empty string is provided' + ) + + t.strictSame( + JSON5.stringify([1], null, 2), + '[\n 1,\n]', + 'indents n spaces when a number is provided' + ) + + t.strictSame( + JSON5.stringify([1], null, 11), + '[\n 1,\n]', + 'does not indent more than 10 spaces when a number is provided' + ) + + t.strictSame( + JSON5.stringify([1], null, '\t'), + '[\n\t1,\n]', + 'indents with the string provided' + ) + + t.strictSame( + JSON5.stringify([1], null, ' '), + '[\n 1,\n]', + 'does not indent more than 10 characters of the string provided' + ) + + t.strictSame( + JSON5.stringify([1], null, 2), + '[\n 1,\n]', + 'indents in arrays' + ) + + t.strictSame( + JSON5.stringify([1, [2], 3], null, 2), + '[\n 1,\n [\n 2,\n ],\n 3,\n]', + 'indents in nested arrays' + ) + + t.strictSame( + JSON5.stringify({a: 1}, null, 2), + '{\n a: 1,\n}', + 'indents in objects' + ) + + t.strictSame( + JSON5.stringify({a: {b: 2}}, null, 2), + '{\n a: {\n b: 2,\n },\n}', + 'indents in nested objects' + ) + + t.strictSame( // eslint-disable-next-line no-new-wrappers - assert.strictEqual(JSON5.stringify([1], null, new Number(2)), '[\n 1,\n]') - }) + JSON5.stringify([1], null, new Number(2)), + '[\n 1,\n]', + 'accepts Number objects' + ) - it('accepts String objects', () => { + t.strictSame( // eslint-disable-next-line no-new-wrappers - assert.strictEqual(JSON5.stringify([1], null, new String('\t')), '[\n\t1,\n]') - }) + JSON5.stringify([1], null, new String('\t')), + '[\n\t1,\n]', + 'accepts String objects' + ) + + t.end() }) - describe('#stringify(value, replacer)', () => { - it('filters keys when an array is provided', () => { - assert.strictEqual(JSON5.stringify({a: 1, b: 2, 3: 3}, ['a', 3]), "{a:1,'3':3}") - }) + t.test('#stringify(value, replacer)', t => { + t.strictSame( + JSON5.stringify({a: 1, b: 2, 3: 3}, ['a', 3]), + "{a:1,'3':3}", + 'filters keys when an array is provided' + ) - it('only filters string and number keys when an array is provided', () => { - assert.strictEqual(JSON5.stringify({a: 1, b: 2, 3: 3, false: 4}, ['a', 3, false]), "{a:1,'3':3}") - }) + t.strictSame( + JSON5.stringify({a: 1, b: 2, 3: 3, false: 4}, ['a', 3, false]), + "{a:1,'3':3}", + 'only filters string and number keys when an array is provided' + ) - it('accepts String and Number objects when an array is provided', () => { + t.strictSame( // eslint-disable-next-line no-new-wrappers - assert.strictEqual(JSON5.stringify({a: 1, b: 2, 3: 3}, [new String('a'), new Number(3)]), "{a:1,'3':3}") - }) - - it('replaces values when a function is provided', () => { - assert.strictEqual( - JSON5.stringify({a: 1, b: 2}, (key, value) => (key === 'a') ? 2 : value), - '{a:2,b:2}' - ) - }) - - it('sets `this` to the parent value', () => { - assert.strictEqual( - JSON5.stringify({a: {b: 1}}, function (k, v) { return (k === 'b' && this.b) ? 2 : v }), - '{a:{b:2}}' - ) - }) - - it('is called after toJSON', () => { + JSON5.stringify({a: 1, b: 2, 3: 3}, [new String('a'), new Number(3)]), + "{a:1,'3':3}", + 'accepts String and Number objects when an array is provided' + ) + + t.strictSame( + JSON5.stringify({a: 1, b: 2}, (key, value) => (key === 'a') ? 2 : value), + '{a:2,b:2}', + 'replaces values when a function is provided' + ) + + t.strictSame( + JSON5.stringify({a: {b: 1}}, function (k, v) { return (k === 'b' && this.b) ? 2 : v }), + '{a:{b:2}}', + 'sets `this` to the parent value' + ) + + t.test('is called after toJSON', t => { function C () {} Object.assign(C.prototype, {toJSON () { return {a: 1, b: 2} }}) assert.strictEqual( JSON5.stringify(new C(), (key, value) => (key === 'a') ? 2 : value), '{a:2,b:2}' ) + t.end() }) - it('is called after toJSON5', () => { + t.test('is called after toJSON5', t => { function C () {} Object.assign(C.prototype, {toJSON5 () { return {a: 1, b: 2} }}) assert.strictEqual( JSON5.stringify(new C(), (key, value) => (key === 'a') ? 2 : value), '{a:2,b:2}' ) + t.end() }) - it('does not affect space when calls are nested', () => { - assert.strictEqual( - JSON5.stringify({a: 1}, (key, value) => { + t.strictSame( + JSON5.stringify( + {a: 1}, + (key, value) => { JSON5.stringify({}, null, 4) return value - }, 2), - '{\n a: 1,\n}' - ) - }) + }, + 2 + ), + '{\n a: 1,\n}', + 'does not affect space when calls are nested' + ) + + t.end() }) - describe('#stringify(value, options)', () => { - it('accepts replacer as an option', () => { - assert.strictEqual(JSON5.stringify({a: 1, b: 2, 3: 3}, {replacer: ['a', 3]}), "{a:1,'3':3}") - }) + t.test('#stringify(value, options)', t => { + t.strictSame( + JSON5.stringify({a: 1, b: 2, 3: 3}, {replacer: ['a', 3]}), + "{a:1,'3':3}", + 'accepts replacer as an option' + ) - it('accepts space as an option', () => { - assert.strictEqual(JSON5.stringify([1], {space: 2}), '[\n 1,\n]') - }) + t.strictSame( + JSON5.stringify([1], {space: 2}), + '[\n 1,\n]', + 'accepts space as an option' + ) + + t.end() }) - describe('#stringify(value, {quote})', () => { - it('uses double quotes if provided', () => { - assert.strictEqual(JSON5.stringify({'a"': '1"'}, {quote: '"'}), '{"a\\"":"1\\""}') - }) + t.test('#stringify(value, {quote})', t => { + t.strictSame( + JSON5.stringify({'a"': '1"'}, {quote: '"'}), + '{"a\\"":"1\\""}', + 'uses double quotes if provided' + ) - it('uses single quotes if provided', () => { - assert.strictEqual(JSON5.stringify({"a'": "1'"}, {quote: "'"}), "{'a\\'':'1\\''}") - }) + t.strictSame( + JSON5.stringify({"a'": "1'"}, {quote: "'"}), + "{'a\\'':'1\\''}", + 'uses single quotes if provided' + ) + + t.end() }) + + t.end() })