From 7c7c9bf23ce90e6c3fb8ccfdd55330db470cc487 Mon Sep 17 00:00:00 2001 From: Dimitri Benin Date: Sun, 28 Apr 2019 14:00:27 +0200 Subject: [PATCH] Require Node.js 8, add TypeScript definition --- .gitattributes | 3 +-- .travis.yml | 4 +-- index.d.ts | 36 ++++++++++++++++++++++++++ index.js | 46 ++++++++++++++++----------------- index.test-d.ts | 9 +++++++ package.json | 7 ++--- readme.md | 9 ++++--- test.js | 68 ++++++++++++++++++++++++------------------------- 8 files changed, 114 insertions(+), 68 deletions(-) create mode 100644 index.d.ts create mode 100644 index.test-d.ts diff --git a/.gitattributes b/.gitattributes index 391f0a4..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -* text=auto -*.js text eol=lf +* text=auto eol=lf diff --git a/.travis.yml b/.travis.yml index 7d69d74..f98fed0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: node_js node_js: + - '12' + - '10' - '8' - - '6' - - '4' diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..cc5655d --- /dev/null +++ b/index.d.ts @@ -0,0 +1,36 @@ +declare namespace stripJsonComments { + interface Options { + /** + Replace comments with whitespace instead of stripping them entirely. + + @default true + */ + readonly whitespace?: boolean; + } +} + +/** +Strip comments from JSON. Lets you use comments in your JSON files! + +It will replace single-line comments `//` and multi-line comments `/**\/` with whitespace. This allows JSON error positions to remain as close as possible to the original source. + +@param jsonString - Accepts a string with JSON. +@returns A JSON string without comments. + +@example +``` +const json = `{ + // rainbows + "unicorn": "cake" +}`; + +JSON.parse(stripJsonComments(json)); +//=> {unicorn: 'cake'} +``` +*/ +declare function stripJsonComments( + jsonString: string, + options?: stripJsonComments.Options +): string; + +export = stripJsonComments; diff --git a/index.js b/index.js index ce65022..c8eb3cc 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,23 @@ 'use strict'; -const singleComment = 1; -const multiComment = 2; +const singleComment = Symbol('singleComment'); +const multiComment = Symbol('multiComment'); const stripWithoutWhitespace = () => ''; -const stripWithWhitespace = (str, start, end) => str.slice(start, end).replace(/\S/g, ' '); +const stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/\S/g, ' '); -module.exports = (str, opts) => { - opts = opts || {}; - - const strip = opts.whitespace === false ? stripWithoutWhitespace : stripWithWhitespace; +module.exports = (jsonString, options = {}) => { + const strip = options.whitespace === false ? stripWithoutWhitespace : stripWithWhitespace; let insideString = false; let insideComment = false; let offset = 0; - let ret = ''; + let result = ''; - for (let i = 0; i < str.length; i++) { - const currentChar = str[i]; - const nextChar = str[i + 1]; + for (let i = 0; i < jsonString.length; i++) { + const currentCharacter = jsonString[i]; + const nextCharacter = jsonString[i + 1]; - if (!insideComment && currentChar === '"') { - const escaped = str[i - 1] === '\\' && str[i - 2] !== '\\'; + if (!insideComment && currentCharacter === '"') { + const escaped = jsonString[i - 1] === '\\' && jsonString[i - 2] !== '\\'; if (!escaped) { insideString = !insideString; } @@ -29,35 +27,35 @@ module.exports = (str, opts) => { continue; } - if (!insideComment && currentChar + nextChar === '//') { - ret += str.slice(offset, i); + if (!insideComment && currentCharacter + nextCharacter === '//') { + result += jsonString.slice(offset, i); offset = i; insideComment = singleComment; i++; - } else if (insideComment === singleComment && currentChar + nextChar === '\r\n') { + } else if (insideComment === singleComment && currentCharacter + nextCharacter === '\r\n') { i++; insideComment = false; - ret += strip(str, offset, i); + result += strip(jsonString, offset, i); offset = i; continue; - } else if (insideComment === singleComment && currentChar === '\n') { + } else if (insideComment === singleComment && currentCharacter === '\n') { insideComment = false; - ret += strip(str, offset, i); + result += strip(jsonString, offset, i); offset = i; - } else if (!insideComment && currentChar + nextChar === '/*') { - ret += str.slice(offset, i); + } else if (!insideComment && currentCharacter + nextCharacter === '/*') { + result += jsonString.slice(offset, i); offset = i; insideComment = multiComment; i++; continue; - } else if (insideComment === multiComment && currentChar + nextChar === '*/') { + } else if (insideComment === multiComment && currentCharacter + nextCharacter === '*/') { i++; insideComment = false; - ret += strip(str, offset, i + 1); + result += strip(jsonString, offset, i + 1); offset = i + 1; continue; } } - return ret + (insideComment ? strip(str.substr(offset)) : str.substr(offset)); + return result + (insideComment ? strip(jsonString.substr(offset)) : jsonString.substr(offset)); }; diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..2d14af9 --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,9 @@ +import {expectType} from 'tsd'; +import stripJsonComments = require('.'); + +const options: stripJsonComments.Options = {}; + +const json = '{/*rainbows*/"unicorn":"cake"}'; + +expectType(stripJsonComments(json)); +expectType(stripJsonComments(json, {whitespace: true})); diff --git a/package.json b/package.json index 601f888..c66d98d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "url": "sindresorhus.com" }, "engines": { - "node": ">=4" + "node": ">=8" }, "scripts": { "test": "xo && ava", @@ -37,8 +37,9 @@ "environment" ], "devDependencies": { - "ava": "^0.20.0", + "ava": "^1.4.1", "matcha": "^0.7.0", - "xo": "^0.18.0" + "tsd": "^0.7.2", + "xo": "^0.24.0" } } diff --git a/readme.md b/readme.md index 41e3e5f..b1438e9 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,10 @@ $ npm install strip-json-comments ## Usage ```js -const json = '{/*rainbows*/"unicorn":"cake"}'; +const json = `{ + // rainbows + "unicorn": /* ❤ */ "cake" +}`; JSON.parse(stripJsonComments(json)); //=> {unicorn: 'cake'} @@ -35,9 +38,9 @@ JSON.parse(stripJsonComments(json)); ## API -### stripJsonComments(input, [options]) +### stripJsonComments(jsonString, [options]) -#### input +#### jsonString Type: `string` diff --git a/test.js b/test.js index 186bdb7..aaba3a5 100644 --- a/test.js +++ b/test.js @@ -1,66 +1,66 @@ import test from 'ava'; -import m from '.'; +import stripJsonComments from '.'; test('replace comments with whitespace', t => { - t.is(m('//comment\n{"a":"b"}'), ' \n{"a":"b"}'); - t.is(m('/*//comment*/{"a":"b"}'), ' {"a":"b"}'); - t.is(m('{"a":"b"//comment\n}'), '{"a":"b" \n}'); - t.is(m('{"a":"b"/*comment*/}'), '{"a":"b" }'); - t.is(m('{"a"/*\n\n\ncomment\r\n*/:"b"}'), '{"a" \n\n\n \r\n :"b"}'); - t.is(m('/*!\n * comment\n */\n{"a":"b"}'), ' \n \n \n{"a":"b"}'); - t.is(m('{/*comment*/"a":"b"}'), '{ "a":"b"}'); + t.is(stripJsonComments('//comment\n{"a":"b"}'), ' \n{"a":"b"}'); + t.is(stripJsonComments('/*//comment*/{"a":"b"}'), ' {"a":"b"}'); + t.is(stripJsonComments('{"a":"b"//comment\n}'), '{"a":"b" \n}'); + t.is(stripJsonComments('{"a":"b"/*comment*/}'), '{"a":"b" }'); + t.is(stripJsonComments('{"a"/*\n\n\ncomment\r\n*/:"b"}'), '{"a" \n\n\n \r\n :"b"}'); + t.is(stripJsonComments('/*!\n * comment\n */\n{"a":"b"}'), ' \n \n \n{"a":"b"}'); + t.is(stripJsonComments('{/*comment*/"a":"b"}'), '{ "a":"b"}'); }); test('remove comments', t => { - const opts = {whitespace: false}; - t.is(m('//comment\n{"a":"b"}', opts), '\n{"a":"b"}'); - t.is(m('/*//comment*/{"a":"b"}', opts), '{"a":"b"}'); - t.is(m('{"a":"b"//comment\n}', opts), '{"a":"b"\n}'); - t.is(m('{"a":"b"/*comment*/}', opts), '{"a":"b"}'); - t.is(m('{"a"/*\n\n\ncomment\r\n*/:"b"}', opts), '{"a":"b"}'); - t.is(m('/*!\n * comment\n */\n{"a":"b"}', opts), '\n{"a":"b"}'); - t.is(m('{/*comment*/"a":"b"}', opts), '{"a":"b"}'); + const options = {whitespace: false}; + t.is(stripJsonComments('//comment\n{"a":"b"}', options), '\n{"a":"b"}'); + t.is(stripJsonComments('/*//comment*/{"a":"b"}', options), '{"a":"b"}'); + t.is(stripJsonComments('{"a":"b"//comment\n}', options), '{"a":"b"\n}'); + t.is(stripJsonComments('{"a":"b"/*comment*/}', options), '{"a":"b"}'); + t.is(stripJsonComments('{"a"/*\n\n\ncomment\r\n*/:"b"}', options), '{"a":"b"}'); + t.is(stripJsonComments('/*!\n * comment\n */\n{"a":"b"}', options), '\n{"a":"b"}'); + t.is(stripJsonComments('{/*comment*/"a":"b"}', options), '{"a":"b"}'); }); test('doesn\'t strip comments inside strings', t => { - t.is(m('{"a":"b//c"}'), '{"a":"b//c"}'); - t.is(m('{"a":"b/*c*/"}'), '{"a":"b/*c*/"}'); - t.is(m('{"/*a":"b"}'), '{"/*a":"b"}'); - t.is(m('{"\\"/*a":"b"}'), '{"\\"/*a":"b"}'); + t.is(stripJsonComments('{"a":"b//c"}'), '{"a":"b//c"}'); + t.is(stripJsonComments('{"a":"b/*c*/"}'), '{"a":"b/*c*/"}'); + t.is(stripJsonComments('{"/*a":"b"}'), '{"/*a":"b"}'); + t.is(stripJsonComments('{"\\"/*a":"b"}'), '{"\\"/*a":"b"}'); }); test('consider escaped slashes when checking for escaped string quote', t => { - t.is(m('{"\\\\":"https://foobar.com"}'), '{"\\\\":"https://foobar.com"}'); - t.is(m('{"foo\\"":"https://foobar.com"}'), '{"foo\\"":"https://foobar.com"}'); + t.is(stripJsonComments('{"\\\\":"https://foobar.com"}'), '{"\\\\":"https://foobar.com"}'); + t.is(stripJsonComments('{"foo\\"":"https://foobar.com"}'), '{"foo\\"":"https://foobar.com"}'); }); test('line endings - no comments', t => { - t.is(m('{"a":"b"\n}'), '{"a":"b"\n}'); - t.is(m('{"a":"b"\r\n}'), '{"a":"b"\r\n}'); + t.is(stripJsonComments('{"a":"b"\n}'), '{"a":"b"\n}'); + t.is(stripJsonComments('{"a":"b"\r\n}'), '{"a":"b"\r\n}'); }); test('line endings - single line comment', t => { - t.is(m('{"a":"b"//c\n}'), '{"a":"b" \n}'); - t.is(m('{"a":"b"//c\r\n}'), '{"a":"b" \r\n}'); + t.is(stripJsonComments('{"a":"b"//c\n}'), '{"a":"b" \n}'); + t.is(stripJsonComments('{"a":"b"//c\r\n}'), '{"a":"b" \r\n}'); }); test('line endings - single line block comment', t => { - t.is(m('{"a":"b"/*c*/\n}'), '{"a":"b" \n}'); - t.is(m('{"a":"b"/*c*/\r\n}'), '{"a":"b" \r\n}'); + t.is(stripJsonComments('{"a":"b"/*c*/\n}'), '{"a":"b" \n}'); + t.is(stripJsonComments('{"a":"b"/*c*/\r\n}'), '{"a":"b" \r\n}'); }); test('line endings - multi line block comment', t => { - t.is(m('{"a":"b",/*c\nc2*/"x":"y"\n}'), '{"a":"b", \n "x":"y"\n}'); - t.is(m('{"a":"b",/*c\r\nc2*/"x":"y"\r\n}'), '{"a":"b", \r\n "x":"y"\r\n}'); + t.is(stripJsonComments('{"a":"b",/*c\nc2*/"x":"y"\n}'), '{"a":"b", \n "x":"y"\n}'); + t.is(stripJsonComments('{"a":"b",/*c\r\nc2*/"x":"y"\r\n}'), '{"a":"b", \r\n "x":"y"\r\n}'); }); test('line endings - works at EOF', t => { - const opts = {whitespace: false}; - t.is(m('{\r\n\t"a":"b"\r\n} //EOF'), '{\r\n\t"a":"b"\r\n} '); - t.is(m('{\r\n\t"a":"b"\r\n} //EOF', opts), '{\r\n\t"a":"b"\r\n} '); + const options = {whitespace: false}; + t.is(stripJsonComments('{\r\n\t"a":"b"\r\n} //EOF'), '{\r\n\t"a":"b"\r\n} '); + t.is(stripJsonComments('{\r\n\t"a":"b"\r\n} //EOF', options), '{\r\n\t"a":"b"\r\n} '); }); test.failing('handles weird escaping', t => { // eslint-disable-next-line no-useless-escape - t.is(m('{"x":"x \"sed -e \\\"s/^.\\\\{46\\\\}T//\\\" -e \\\"s/#033/\\\\x1b/g\\\"\""}'), '{"x":"x \"sed -e \\\"s/^.\\\\{46\\\\}T//\\\" -e \\\"s/#033/\\\\x1b/g\\\"\""}'); + t.is(stripJsonComments('{"x":"x \"sed -e \\\"s/^.\\\\{46\\\\}T//\\\" -e \\\"s/#033/\\\\x1b/g\\\"\""}'), '{"x":"x \"sed -e \\\"s/^.\\\\{46\\\\}T//\\\" -e \\\"s/#033/\\\\x1b/g\\\"\""}'); });