🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

comment-json

Package Overview
Dependencies
Maintainers
1
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

comment-json - npm Package Compare versions

Comparing version
4.4.1
to
4.5.0
+80
-11
index.d.ts

@@ -9,13 +9,39 @@ // Original from DefinitelyTyped. Thanks a million

export type CommentPrefix = 'before'
| 'after-prop'
| 'after-colon'
| 'after-value'
| 'after'
// Define comment prefix constants first to avoid magic strings
declare const PREFIX_BEFORE: 'before'
declare const PREFIX_AFTER_PROP: 'after-prop'
declare const PREFIX_AFTER_COLON: 'after-colon'
declare const PREFIX_AFTER_VALUE: 'after-value'
declare const PREFIX_AFTER: 'after'
declare const PREFIX_BEFORE_ALL: 'before-all'
declare const PREFIX_AFTER_ALL: 'after-all'
export type CommentDescriptor = `${CommentPrefix}:${string}`
| 'before'
| 'before-all'
| 'after-all'
// Export the constants
export {
PREFIX_BEFORE,
PREFIX_AFTER_PROP,
PREFIX_AFTER_COLON,
PREFIX_AFTER_VALUE,
PREFIX_AFTER,
PREFIX_BEFORE_ALL,
PREFIX_AFTER_ALL
}
// Use the constant types to build CommentPrefix
export type PropertyCommentPrefix = typeof PREFIX_BEFORE
| typeof PREFIX_AFTER_PROP
| typeof PREFIX_AFTER_COLON
| typeof PREFIX_AFTER_VALUE
| typeof PREFIX_AFTER
export type NonPropertyCommentPrefix = typeof PREFIX_BEFORE
| typeof PREFIX_AFTER
| typeof PREFIX_BEFORE_ALL
| typeof PREFIX_AFTER_ALL
export type CommentPrefix = PropertyCommentPrefix | NonPropertyCommentPrefix
export type CommentDescriptor = `${PropertyCommentPrefix}:${string}`
| NonPropertyCommentPrefix
export type CommentSymbol = typeof commentSymbol

@@ -64,3 +90,7 @@

export type Reviver = (k: number | string, v: unknown) => unknown
export type Reviver = (
k: number | string,
v: unknown,
context?: { source?: string }
) => unknown

@@ -109,8 +139,47 @@ /**

/**
* Assign properties and comments from source to target
* @param target The target object to assign to
* @param source The source object to assign from
* @param keys Optional array of keys to assign. If not provided, all keys and non-property comments are assigned
* @returns The target object
*/
export function assign<TTarget, TSource>(
target: TTarget,
source: TSource,
// Although it actually accepts more key types and filters then`,
// Although it actually accepts more key types and filters then,
// we set the type of `keys` stricter
keys?: readonly (number | string)[]
): TTarget
interface CommentPosition {
where: CommentPrefix
key?: string
}
/**
* Move comments from one location to another
* @param source The source object containing comments
* @param target The target object to move comments to (defaults to source if not provided)
* @param from The source comment location
* @param to The target comment location
* @param override Whether to override existing comments at the target location
*/
export function moveComments(
source: CommentJSONValue,
target: CommentJSONValue | undefined,
from: CommentPosition,
to: CommentPosition,
override?: boolean
): void
/**
* Remove comments from a specific location
* @param target The target object to remove comments from
* @param location The comment location to remove
*/
export function removeComments(
target: CommentJSONValue,
location: CommentPosition
): void
+1
-1
{
"name": "comment-json",
"version": "4.4.1",
"version": "4.5.0",
"description": "Parse and stringify JSON with comments. It will retain comments even after saved!",

@@ -5,0 +5,0 @@ "main": "src/index.js",

+176
-3

@@ -33,2 +33,4 @@ [![Build Status](https://github.com/kaelzhang/node-comment-json/actions/workflows/nodejs.yml/badge.svg)](https://github.com/kaelzhang/node-comment-json/actions/workflows/nodejs.yml)

- [assign](#assigntarget-object-source-object-keys-array)
- [moveComments](#movecommentssource-object-target-object-from-object-to-object-override-boolean)
- [removeComments](#removecommentstarget-object-location-object)
- [CommentArray](#commentarray)

@@ -76,3 +78,5 @@ - [Change Logs](https://github.com/kaelzhang/node-comment-json/releases)

stringify,
assign
assign,
moveComments,
removeComments
} = require('comment-json')

@@ -238,3 +242,3 @@ const fs = require('fs')

There are **EIGHT** kinds of symbol properties:
There are **NINE** kinds of symbol properties:

@@ -272,2 +276,6 @@ ```js

// Always at the inner end of an object or an array,
// only used for stringification
Symbol.for('after')
// Comments after everything

@@ -464,3 +472,3 @@ Symbol.for('after-all')

But if argument `keys` is specified and is not empty, then comment ` before all`, which belongs to no properties, will **NOT** be copied.
But if argument `keys` is specified and is not empty, then comment ` before all`, which belongs to non-properties, will **NOT** be copied.

@@ -499,5 +507,170 @@ ```js

Symbol.for('before')
Symbol.for('after') // only for stringify
Symbol.for('after-all')
```
## moveComments(source: object, target?: object, from: object, to: object, override?: boolean)
- **source** `object` The source object containing comments to move.
- **target?** `object` The target object to move comments to. If not provided, defaults to source (move within same object).
- **from** `object` The source comment location.
- **from.where** `CommentPrefix` The comment position (e.g., 'before', 'after', 'before-all', etc.).
- **from.key?** `string` The property key for property-specific comments. Omit for non-property comments.
- **to** `object` The target comment location.
- **to.where** `CommentPrefix` The comment position (e.g., 'before', 'after', 'before-all', etc.).
- **to.key?** `string` The property key for property-specific comments. Omit for non-property comments.
- **override?** `boolean = false` Whether to override existing comments at the target location. If false, comments will be appended.
This method is used to move comments from one location to another within objects. It's particularly useful when you need to reorganize comments or move them between different comment positions.
```js
const {parse, stringify, moveComments} = require('comment-json')
const obj = parse(`{
"foo": 1, // comment after foo
"bar": 2
}`)
// Move comment from `after 'foo'` to `after`
moveComments(obj, obj,
{ where: 'after', key: 'foo' },
{ where: 'after' }
)
obj.baz = 3
console.log(stringify(obj, null, 2))
// {
// "foo": 1,
// "bar": 2,
// "baz": 3
// // comment after foo
// }
```
### Moving non-property comments
```js
const obj = parse(`// top comment
{
"foo": 1
}`)
// Move top comment to bottom
moveComments(obj, obj,
{ kind: 'before-all' },
{ kind: 'after-all' }
)
console.log(stringify(obj, null, 2))
// {
// "foo": 1
// }
// // top comment
```
### Moving comments between objects
```js
const source = parse(`{
"foo": 1 // source comment
}`)
const target = { bar: 2 }
// Move comment from source to target
moveComments(source, target,
{ kind: 'after-value', key: 'foo' },
{ kind: 'before', key: 'bar' }
)
console.log(stringify(target, null, 2))
// {
// // source comment
// "bar": 2
// }
```
### Appending vs overriding comments
```js
const obj = parse(`{
// existing comment
"foo": 1, // another comment
"bar": 2
}`)
// By default, comments are appended (override = false)
moveComments(obj, obj,
{ kind: 'after-value', key: 'foo' },
{ kind: 'before', key: 'foo' }
)
console.log(stringify(obj, null, 2))
// {
// // existing comment
// // another comment
// "foo": 1,
// "bar": 2
// }
// With override = true, existing comments are replaced
moveComments(obj, obj,
{ kind: 'before', key: 'bar' },
{ kind: 'before', key: 'foo' },
true // override existing comments
)
```
## removeComments(target: object, location: object)
- **target** `object` The target object to remove comments from.
- **location** `object` The comment location to remove.
- **location.where** `CommentPrefix` The comment position (e.g., 'before', 'after', 'before-all', etc.).
- **location.key?** `string` The property key for property-specific comments. Omit for non-property comments.
This method is used to remove comments from a specific location within objects. It's useful for cleaning up comments or removing unwanted comment annotations.
### Basic usage
```js
const {parse, stringify, removeComments} = require('comment-json')
const obj = parse(`{
// comment before foo
"foo": 1, // comment after foo
"bar": 2
}`)
// Remove comment before 'foo'
removeComments(obj, { where: 'before', key: 'foo' })
console.log(stringify(obj, null, 2))
// {
// "foo": 1, // comment after foo
// "bar": 2
// }
```
### Removing non-property comments
```js
const obj = parse(`// top comment
{
"foo": 1
}
// bottom comment`)
// Remove top comment
removeComments(obj, { where: 'before-all' })
// Remove bottom comment
removeComments(obj, { where: 'after-all' })
console.log(stringify(obj, null, 2))
// {
// "foo": 1
// }
```
## `CommentArray`

@@ -504,0 +677,0 @@

@@ -5,3 +5,3 @@ const {isArray} = require('core-util-is')

const {
SYMBOL_PREFIXES,
PROP_SYMBOL_PREFIXES,

@@ -72,3 +72,3 @@ UNDEFINED,

const remove_comments = (array, key) => {
SYMBOL_PREFIXES.forEach(prefix => {
PROP_SYMBOL_PREFIXES.forEach(prefix => {
const prop = symbol(prefix, key)

@@ -89,2 +89,17 @@ delete array[prop]

/**
* An Array subclass that preserves comments when array operations are performed.
*
* CommentArray extends the native Array class and automatically handles comment
* preservation during array mutations like splice, slice, push, pop, etc.
* Comments are stored as symbol properties and are moved/copied appropriately
* when the array structure changes.
*
* @extends Array
*
* @example
* const arr = parse('[1, 2, 3]') // with comments
* // arr is a CommentArray instance
* arr.splice(1, 1) // Comments are preserved and repositioned correctly
*/
class CommentArray extends Array {

@@ -97,2 +112,10 @@ // - deleteCount + items.length

// - slice
/**
* Changes the contents of an array by removing or replacing existing
* elements and/or adding new elements in place.
* Comments are automatically preserved and repositioned during the operation.
*
* @param {...*} args Arguments passed to Array.prototype.splice
* @returns {CommentArray} A new CommentArray containing the deleted elements.
*/
splice (...args) {

@@ -143,2 +166,10 @@ const {length} = this

/**
* Returns a shallow copy of a portion of an array into a new CommentArray object.
* Comments are copied to the appropriate positions in the new array.
*
* @param {...*} args Arguments passed to Array.prototype.slice
* @returns {CommentArray} A new CommentArray containing the extracted
* elements with their comments.
*/
slice (...args) {

@@ -145,0 +176,0 @@ const {length} = this

@@ -26,3 +26,3 @@ const {

const SYMBOL_PREFIXES = [
const PROP_SYMBOL_PREFIXES = [
PREFIX_BEFORE,

@@ -35,8 +35,11 @@ PREFIX_AFTER_PROP,

const NON_PROP_SYMBOL_KEYS = [
const NON_PROP_SYMBOL_PREFIXES = [
PREFIX_BEFORE,
PREFIX_AFTER,
PREFIX_BEFORE_ALL,
PREFIX_AFTER_ALL
].map(Symbol.for)
]
const NON_PROP_SYMBOL_KEYS = NON_PROP_SYMBOL_PREFIXES.map(Symbol.for)
const COLON = ':'

@@ -46,3 +49,20 @@ const UNDEFINED = undefined

const symbol = (prefix, key) => Symbol.for(prefix + COLON + key)
const symbol_checked = (prefix, key) => {
if (key) {
if (PROP_SYMBOL_PREFIXES.includes(prefix)) {
return symbol(prefix, key)
}
throw new RangeError(
`Unsupported comment position ${prefix} with key ${key}`
)
}
if (NON_PROP_SYMBOL_PREFIXES.includes(prefix)) {
return Symbol.for(prefix)
}
throw new RangeError(`Unsupported comment position ${prefix}`)
}
const define = (target, key, value) => Object.defineProperty(target, key, {

@@ -76,3 +96,3 @@ value,

) => {
SYMBOL_PREFIXES.forEach(prefix => {
PROP_SYMBOL_PREFIXES.forEach(prefix => {
copy_comments_by_kind(

@@ -89,3 +109,3 @@ target, source, target_key, source_key, prefix, remove_source

SYMBOL_PREFIXES.forEach(prefix => {
PROP_SYMBOL_PREFIXES.forEach(prefix => {
const target_prop = symbol(prefix, to)

@@ -141,3 +161,3 @@ if (!Object.hasOwn(array, target_prop)) {

module.exports = {
SYMBOL_PREFIXES,
PROP_SYMBOL_PREFIXES,

@@ -173,2 +193,30 @@ PREFIX_BEFORE,

/**
* Assign properties and comments from source to target object.
*
* @param {Object} target The target object to assign properties and comments
* to.
* @param {Object} source The source object to copy properties and comments
* from.
* @param {Array<string|number>} [keys] Optional array of keys to assign. If
* not provided, all keys and non-property comments are assigned. If empty
* array, only non-property comments are assigned.
* @returns {Object} The target object with assigned properties and comments.
*
* @throws {TypeError} If target cannot be converted to object or keys is not
* array or undefined.
*
* @example
* const source = parse('{"a": 1 // comment a, "b": 2 // comment b}')
* const target = {}
*
* // Copy all properties and comments
* assign(target, source)
*
* // Copy only specific properties and their comments
* assign(target, source, ['a'])
*
* // Copy only non-property comments
* assign(target, source, [])
*/
assign (target, source, keys) {

@@ -184,2 +232,6 @@ if (!isObject(target)) {

if (keys === UNDEFINED) {
// Copy all comments from source to target, including:
// - non-property comments
// - property comments
keys = Object.keys(source)

@@ -192,2 +244,4 @@ // We assign non-property comments

} else if (keys.length === 0) {
// Copy all non-property comments from source to target
// Or argument `keys` is an empty array

@@ -197,4 +251,125 @@ assign_non_prop_comments(target, source)

// Copy specified property comments from source to target
return assign(target, source, keys)
},
/**
* Move comments from one location to another within objects.
*
* @param {Object} source The source object containing comments to move.
* @param {Object} [target] The target object to move comments to. If not
* provided, defaults to source (move within same object).
* @param {Object} from The source comment location.
* @param {string} from.where The comment position (e.g., 'before',
* 'after', 'before-all', etc.).
* @param {string} [from.key] The property key for property-specific comments.
* Omit for non-property comments.
* @param {Object} to The target comment location.
* @param {string} to.where The comment position (e.g., 'before',
* 'after', 'before-all', etc.).
* @param {string} [to.key] The property key for property-specific comments.
* Omit for non-property comments.
* @param {boolean} [override=false] Whether to override existing comments at
* the target location. If false, comments will be appended.
*
* @throws {TypeError} If source is not an object.
* @throws {RangeError} If where parameter is invalid or incompatible with key.
*
* @example
* const obj = parse('{"a": 1 // comment on a}')
*
* // Move comment from after 'a' to before 'a'
* moveComments(obj, obj,
* { where: 'after', key: 'a' },
* { where: 'before', key: 'a' }
* )
*
* @example
* // Move non-property comment
* moveComments(obj, obj,
* { where: 'before-all' },
* { where: 'after-all' }
* )
*/
moveComments (source, target, {
where: from_where,
key: from_key
}, {
where: to_where,
key: to_key
}, override = false) {
if (!isObject(source)) {
throw new TypeError('source must be an object')
}
if (!target) {
target = source
}
if (!isObject(target)) {
// No target to move to
return
}
const from_prop = symbol_checked(from_where, from_key)
const to_prop = symbol_checked(to_where, to_key)
if (!Object.hasOwn(source, from_prop)) {
return
}
const source_comments = source[from_prop]
delete source[from_prop]
if (override || !Object.hasOwn(target, to_prop)) {
// Override
// or the target has no existing comments
define(target, to_prop, source_comments)
return
}
const target_comments = target[to_prop]
if (target_comments) {
target_comments.push(...source_comments)
}
},
/**
* Remove comments from a specific location within an object.
*
* @param {Object} target The target object to remove comments from.
* @param {Object} location The comment location to remove.
* @param {string} location.where The comment position (e.g., 'before',
* 'after', 'before-all', etc.).
* @param {string} [location.key] The property key for property-specific
* comments. Omit for non-property comments.
*
* @throws {TypeError} If target is not an object.
* @throws {RangeError} If where parameter is invalid or incompatible with key.
*
* @example
* const obj = parse('{"a": 1 // comment on a}')
*
* // Remove comment after 'a'
* removeComments(obj, { where: 'after', key: 'a' })
*
* @example
* // Remove non-property comment
* removeComments(obj, { where: 'before-all' })
*/
removeComments (target, {
where,
key
}) {
if (!isObject(target)) {
throw new TypeError('target must be an object')
}
const prop = symbol_checked(where, key)
if (!Object.hasOwn(target, prop)) {
return
}
delete target[prop]
}
}

@@ -14,3 +14,5 @@ const {parse, tokenize} = require('./parse')

assign
assign,
moveComments,
removeComments
} = require('./common')

@@ -33,3 +35,5 @@

CommentArray,
assign
assign,
moveComments,
removeComments
}

@@ -35,2 +35,13 @@ // JSON formatting

/**
* Tokenize JSON string with comments into an array of tokens.
*
* @param {string} code The JSON string with comments to tokenize.
* @returns {Array} Array of token objects containing type, value, and location
* information.
*
* @example
* const tokens = tokenize('{"a": 1 // comment}')
* // Returns array of tokens including comment tokens
*/
const tokenize = code => esprima.tokenize(code, {

@@ -421,2 +432,31 @@ comment: true,

/**
* Converts a JavaScript Object Notation (JSON) string with comments into an
* object.
*
* @param {string} code A valid JSON string with comments.
* @param {function} [rev] A function that transforms the results. This function
* is called for each member of the object. If a member contains nested
* objects, the nested objects are transformed before the parent object is.
* @param {boolean} [no_comments=false] If true, the comments won't be
* maintained, which is often used when we want to get a clean object.
* @returns {*} The JavaScript object corresponding to the given JSON text with
* comments preserved as symbol properties.
*
* @example
* const result = parse('{"a": 1 // This is a comment}')
* // result.a === 1
* // Comments are stored in symbol properties
*
* @example
* // With reviver function
* const result = parse('{"a": "1"}', (key, value) => {
* return typeof value === 'string' ? parseInt(value) : value
* })
*
* @example
* // Without comments
* const clean = parse('{"a": 1 // comment}', null, true)
* // Returns clean object without comment symbols
*/
const parse = (code, rev, no_comments) => {

@@ -423,0 +463,0 @@ // Clean variables in closure

@@ -335,4 +335,29 @@ const {

// @param {function()|Array} replacer
// @param {string|number} space
/**
* Converts a JavaScript value to a JavaScript Object Notation (JSON) string
* with comments preserved.
*
* @param {*} value A JavaScript value, usually an object or array, to be
* converted.
* @param {function|Array|null} [replacer_] A function that transforms the
* results or an array of strings and numbers that acts as an approved list
* for selecting the object properties that will be stringified.
* @param {string|number} [space] Adds indentation, white space, and line
* break characters to the return-value JSON text to make it easier to read.
* @returns {string} A JSON string representing the given value with comments
* preserved.
*
* @example
* const obj = parse('{"a": 1 // comment}')
* stringify(obj, null, 2)
* // Returns: '{\n "a": 1 // comment\n}'
*
* @example
* // With replacer function
* stringify(obj, (key, value) => typeof value === 'number' ? value * 2 : value)
*
* @example
* // With replacer array
* stringify(obj, ['a', 'b']) // Only include 'a' and 'b' properties
*/
module.exports = (value, replacer_, space) => {

@@ -339,0 +364,0 @@ // The stringify method takes a value and an optional replacer, and an optional