comment-json
Advanced tools
Comparing version 2.0.9 to 2.1.0
{ | ||
"name": "comment-json", | ||
"version": "2.0.9", | ||
"version": "2.1.0", | ||
"description": "Parse and stringify JSON with comments. It will retain comments even after saved!", | ||
@@ -60,4 +60,5 @@ "main": "src/index.js", | ||
"esprima": "^4.0.1", | ||
"has-own-prop": "^2.0.0", | ||
"repeat-string": "^1.6.1" | ||
} | ||
} |
109
README.md
@@ -35,2 +35,8 @@ [![Build Status](https://travis-ci.org/kaelzhang/node-comment-json.svg?branch=master)](https://travis-ci.org/kaelzhang/node-comment-json) | ||
## How? | ||
`comment-json` parse JSON strings with comments and save comment tokens into [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) properties. | ||
For JSON array with comments, `comment-json` extends the vanilla `Array` object into [`CommentArray`](#commentarray) whose instances could handle comments changes even after a comment array is modified. | ||
## Install | ||
@@ -58,3 +64,4 @@ | ||
parse, | ||
stringify | ||
stringify, | ||
assign | ||
} = require('comment-json') | ||
@@ -316,4 +323,104 @@ const fs = require('fs') | ||
## assign(target: object, source?: object, keys?: Array<string>) | ||
- **target** `object` the target object | ||
- **source?** `object` the source object. This parameter is optional but it is silly to not pass this argument. | ||
- **keys?** `Array<string>` If not specified, all enumerable own properties of `source` will be used. | ||
This method is used to copy the enumerable own properties and their corresponding comment symbol properties to the target object. | ||
```js | ||
const parsed = parse(`{ | ||
// This is a comment | ||
"foo": "bar" | ||
}`) | ||
const obj = assign({ | ||
bar: 'baz' | ||
}, parsed) | ||
stringify(obj, null, 2) | ||
// { | ||
// "bar": "baz", | ||
// // This is a comment | ||
// "foo": "bar" | ||
// } | ||
``` | ||
## `CommentArray` | ||
> Advanced Section | ||
All arrays of the parsed object are `CommentArray`s. | ||
The constructor of `CommentArray` could be accessed by: | ||
```js | ||
const {CommentArray} = require('comment-json') | ||
``` | ||
If we modify a comment array, its comment symbol properties could be handled automatically. | ||
```js | ||
const parsed = parse(`{ | ||
"foo": [ | ||
// bar | ||
"bar", | ||
// baz, | ||
"baz" | ||
] | ||
}`) | ||
parsed.foo.unshift('qux') | ||
stringify(parsed, null, 2) | ||
// { | ||
// "foo": [ | ||
// "qux", | ||
// // bar | ||
// "bar", | ||
// // baz | ||
// "baz" | ||
// ] | ||
// } | ||
``` | ||
Oh yeah! 😆 | ||
But pay attention, if you reassign the property of a comment array with a normal array, all comments will be gone: | ||
```js | ||
parsed.foo = ['quux'].concat(parsed.foo) | ||
stringify(parsed, null, 2) | ||
// { | ||
// "foo": [ | ||
// "quux", | ||
// "qux", | ||
// "bar", | ||
// "baz" | ||
// ] | ||
// } | ||
// Whoooops!! 😩 Comments are gone | ||
``` | ||
Instead, we should: | ||
```js | ||
parsed.foo = new CommentArray('quux').concat(parsed.foo) | ||
stringify(parsed, null, 2) | ||
// { | ||
// "foo": [ | ||
// "quux", | ||
// "qux", | ||
// // bar | ||
// "bar", | ||
// // baz | ||
// "baz" | ||
// ] | ||
// } | ||
``` | ||
## License | ||
[MIT](LICENSE) |
306
src/array.js
@@ -1,3 +0,307 @@ | ||
const splice = (array) => { | ||
const hasOwnProperty = require('has-own-prop') | ||
const {isObject, isArray} = require('core-util-is') | ||
const PREFIX_BEFORE = 'before' | ||
const PREFIX_AFTER_PROP = 'after-prop' | ||
const PREFIX_AFTER_COLON = 'after-colon' | ||
const PREFIX_AFTER_VALUE = 'after-value' | ||
const SYMBOL_PREFIXES = [ | ||
PREFIX_BEFORE, | ||
PREFIX_AFTER_PROP, | ||
PREFIX_AFTER_COLON, | ||
PREFIX_AFTER_VALUE | ||
] | ||
const COLON = ':' | ||
const UNDEFINED = undefined | ||
const symbol = (prefix, key) => Symbol.for(prefix + COLON + key) | ||
const assign_comments = ( | ||
target, source, target_key, source_key, prefix, remove_source | ||
) => { | ||
const source_prop = symbol(prefix, source_key) | ||
if (!hasOwnProperty(source, source_prop)) { | ||
return | ||
} | ||
const target_prop = target_key === source_key | ||
? source_prop | ||
: symbol(prefix, target_key) | ||
target[target_prop] = source[source_prop] | ||
if (remove_source) { | ||
delete source[source_prop] | ||
} | ||
} | ||
// Assign keys and comments | ||
const assign = (target, source, keys) => { | ||
keys.forEach(key => { | ||
if (!hasOwnProperty(source, key)) { | ||
return | ||
} | ||
target[key] = source[key] | ||
SYMBOL_PREFIXES.forEach(prefix => { | ||
assign_comments(target, source, key, key, prefix) | ||
}) | ||
}) | ||
return target | ||
} | ||
const swap_comments = (array, from, to) => { | ||
if (from === to) { | ||
return | ||
} | ||
SYMBOL_PREFIXES.forEach(prefix => { | ||
const target_prop = symbol(prefix, to) | ||
if (!hasOwnProperty(array, target_prop)) { | ||
assign_comments(array, array, to, from, prefix) | ||
return | ||
} | ||
const comments = array[target_prop] | ||
assign_comments(array, array, to, from, prefix) | ||
array[symbol(prefix, from)] = comments | ||
}) | ||
} | ||
const reverse_comments = array => { | ||
const {length} = array | ||
let i = 0 | ||
const max = length / 2 | ||
for (; i < max; i ++) { | ||
swap_comments(array, i, length - i - 1) | ||
} | ||
} | ||
const move_comment = (target, source, i, offset, remove) => { | ||
SYMBOL_PREFIXES.forEach(prefix => { | ||
assign_comments(target, source, i + offset, i, prefix, remove) | ||
}) | ||
} | ||
const move_comments = ( | ||
// `Array` target array | ||
target, | ||
// `Array` source array | ||
source, | ||
// `number` start index | ||
start, | ||
// `number` number of indexes to move | ||
count, | ||
// `number` offset to move | ||
offset, | ||
// `boolean` whether should remove the comments from source | ||
remove | ||
) => { | ||
if (offset > 0) { | ||
let i = count | ||
// | count | offset | | ||
// source: ------------- | ||
// target: ------------- | ||
// | remove | | ||
// => remove === offset | ||
// From [count - 1, 0] | ||
while (i -- > 0) { | ||
move_comment(target, source, start + i, offset, remove && i < offset) | ||
} | ||
return | ||
} | ||
let i = 0 | ||
const min_remove = count + offset | ||
// | remove | count | | ||
// ------------- | ||
// ------------- | ||
// | offset | | ||
// From [0, count - 1] | ||
while (i < count) { | ||
const ii = i ++ | ||
move_comment(target, source, start + ii, offset, remove && i >= min_remove) | ||
} | ||
} | ||
class CommentArray extends Array { | ||
// - deleteCount + items.length | ||
// We should avoid `splice(begin, deleteCount, ...items)`, | ||
// because `splice(0, undefined)` is not equivalent to `splice(0)`, | ||
// as well as: | ||
// - slice | ||
splice (...args) { | ||
const {length} = this | ||
const ret = super.splice(...args) | ||
// If no element removed, just skip moving comments. | ||
// This is also used as argument type checking | ||
if (!ret.length) { | ||
return ret | ||
} | ||
// JavaScript syntax is silly | ||
// eslint-disable-next-line prefer-const | ||
let [begin, deleteCount, ...items] = args | ||
if (begin < 0) { | ||
begin += length | ||
} | ||
if (arguments.length === 1) { | ||
deleteCount = length - begin | ||
} else { | ||
deleteCount = Math.min(length - begin, deleteCount) | ||
} | ||
const { | ||
length: item_length | ||
} = items | ||
// itemsToDelete: - | ||
// itemsToAdd: + | ||
// | dc | count | | ||
// =======-------------============ | ||
// =======++++++============ | ||
// | il | | ||
const offset = item_length - deleteCount | ||
const start = begin + deleteCount | ||
const count = length - start | ||
move_comments(this, this, start, count, offset, true) | ||
return ret | ||
} | ||
slice (...args) { | ||
const {length} = this | ||
const array = super.slice(...args) | ||
if (!array.length) { | ||
return new CommentArray() | ||
} | ||
let [begin, before] = args | ||
// Ref: | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice | ||
if (before === UNDEFINED) { | ||
before = length | ||
} else if (before < 0) { | ||
before += length | ||
} | ||
if (begin < 0) { | ||
begin += length | ||
} else if (begin === UNDEFINED) { | ||
begin = 0 | ||
} | ||
move_comments(array, this, begin, before - begin, - begin) | ||
return array | ||
} | ||
unshift (...items) { | ||
const {length} = this | ||
const ret = super.unshift(...items) | ||
const { | ||
length: items_length | ||
} = items | ||
if (items_length > 0) { | ||
move_comments(this, this, 0, length, items_length, true) | ||
} | ||
return ret | ||
} | ||
shift () { | ||
const ret = super.shift() | ||
const {length} = this | ||
move_comments(this, this, 1, length, - 1, true) | ||
return ret | ||
} | ||
reverse () { | ||
super.reverse() | ||
reverse_comments(this) | ||
return this | ||
} | ||
pop () { | ||
const ret = super.pop() | ||
// Removes comments | ||
const {length} = this | ||
SYMBOL_PREFIXES.forEach(prefix => { | ||
const prop = symbol(prefix, length) | ||
delete this[prop] | ||
}) | ||
return ret | ||
} | ||
concat (...items) { | ||
let {length} = this | ||
const ret = super.concat(...items) | ||
if (!items.length) { | ||
return ret | ||
} | ||
items.forEach(item => { | ||
const prev = length | ||
length += isArray(item) | ||
? item.length | ||
: 1 | ||
if (!(item instanceof CommentArray)) { | ||
return | ||
} | ||
move_comments(ret, item, 0, item.length, prev) | ||
}) | ||
return ret | ||
} | ||
} | ||
module.exports = { | ||
CommentArray, | ||
assign (target, source, keys) { | ||
if (!isObject(target)) { | ||
throw new TypeError('Cannot convert undefined or null to object') | ||
} | ||
if (!isObject(source)) { | ||
return target | ||
} | ||
if (keys === UNDEFINED) { | ||
keys = Object.keys(source) | ||
} else if (!isArray(keys)) { | ||
throw new TypeError('keys must be array or undefined') | ||
} | ||
return assign(target, source, keys) | ||
}, | ||
PREFIX_BEFORE, | ||
PREFIX_AFTER_PROP, | ||
PREFIX_AFTER_COLON, | ||
PREFIX_AFTER_VALUE, | ||
COLON, | ||
UNDEFINED | ||
} |
const {parse, tokenize} = require('./parse') | ||
const stringify = require('./stringify') | ||
const {CommentArray, assign} = require('./array') | ||
@@ -7,3 +8,6 @@ module.exports = { | ||
stringify, | ||
tokenize | ||
tokenize, | ||
CommentArray, | ||
assign | ||
} |
@@ -5,2 +5,13 @@ // JSON formatting | ||
const { | ||
CommentArray, | ||
PREFIX_BEFORE, | ||
PREFIX_AFTER_PROP, | ||
PREFIX_AFTER_COLON, | ||
PREFIX_AFTER_VALUE, | ||
COLON, | ||
UNDEFINED | ||
} = require('./array') | ||
const tokenize = code => esprima.tokenize(code, { | ||
@@ -11,4 +22,2 @@ comment: true, | ||
const UNDEFINED = undefined | ||
const previous_hosts = [] | ||
@@ -51,6 +60,2 @@ let comments_host = null | ||
const PREFIX_BEFORE_ALL = 'before-all' | ||
const PREFIX_BEFORE = 'before' | ||
const PREFIX_AFTER_PROP = 'after-prop' | ||
const PREFIX_AFTER_COLON = 'after-colon' | ||
const PREFIX_AFTER_VALUE = 'after-value' | ||
const PREFIX_AFTER = 'after' | ||
@@ -63,3 +68,2 @@ const PREFIX_AFTER_ALL = 'after-all' | ||
const CURLY_BRACKET_CLOSE = '}' | ||
const COLON = ':' | ||
const COMMA = ',' | ||
@@ -250,3 +254,3 @@ const EMPTY = '' | ||
const parse_array = () => { | ||
const array = [] | ||
const array = new CommentArray() | ||
set_comments_host(array) | ||
@@ -253,0 +257,0 @@ set_prop(UNDEFINED, true) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
33858
821
424
4
+ Addedhas-own-prop@^2.0.0
+ Addedhas-own-prop@2.0.0(transitive)