UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

242 lines (241 loc) 9.91 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommentArrayClass = void 0; exports.parseJsonWithComments = parseJsonWithComments; exports.stringifyJsonWithComments = stringifyJsonWithComments; exports.assignJsonPreservingComments = assignJsonPreservingComments; exports.jsonContentsSemanticallyEqual = jsonContentsSemanticallyEqual; exports.jsonObjectsSemanticallyEqual = jsonObjectsSemanticallyEqual; exports.mergeJsonPreservingComments = mergeJsonPreservingComments; exports.cloneJsonWithComments = cloneJsonWithComments; exports.hasCommentMetadata = hasCommentMetadata; /** * # JsonUtilities - Comment-Preserving JSON Handling * * This module provides centralized utilities for parsing and stringifying JSON * while preserving C-style comments (// and /* *\/). * * ## Key Concepts * * - Uses the `comment-json` library which stores comments as Symbol properties * - Comments are attached to the parsed object and survive mutations * - To preserve comments during round-trip, you must: * 1. Parse once with `parseJsonWithComments()` * 2. Mutate the returned object (don't create a new one) * 3. Stringify with `stringifyJsonWithComments()` * * ## Important Limitations * * - Array reassignment loses comments - use `commentJson.assign()` or in-place mutations * - Copying objects with spread/Object.assign loses comment symbols * - The parsed object must be retained to preserve comments * * ## Usage Pattern * * ```typescript * // Parse and cache the result * const obj = JsonUtilities.parseJsonWithComments(jsonString); * * // Modify properties directly on the parsed object * obj.someProperty = "new value"; * * // Stringify preserves comments * const output = JsonUtilities.stringifyJsonWithComments(obj); * ``` */ const commentJson = __importStar(require("comment-json")); const Utilities_1 = __importDefault(require("./Utilities")); // Re-export CommentArray as a value (it's a class) exports.CommentArrayClass = commentJson.CommentArray; /** * Parses a JSON string while preserving comments. * The returned object contains Symbol properties that store comment metadata. * * @param jsonString The JSON string to parse (may contain // and /* *\/ comments) * @param fixContent If true, applies Utilities.fixJsonContentForCommentJson to handle * trailing commas and other non-standard JSON before parsing * @returns The parsed object with comment metadata preserved as Symbol properties */ function parseJsonWithComments(jsonString, fixContent = true) { if (fixContent) { jsonString = Utilities_1.default.fixJsonContentForCommentJson(jsonString); } return commentJson.parse(jsonString); } /** * Stringifies an object to JSON, preserving any comments stored as Symbol properties. * * @param value The object to stringify (should be one returned from parseJsonWithComments * or created with comment-json utilities to have comments) * @param space Indentation (default: 2 spaces) * @returns JSON string with comments restored in their original positions */ function stringifyJsonWithComments(value, space = 2) { return commentJson.stringify(value, null, space); } /** * Assigns properties from source to target while preserving comment metadata. * Use this instead of Object.assign or spread to maintain comments. * * @param target The target object to assign properties to * @param source The source object to copy properties from * @param keys Optional array of keys to assign. If not provided, all keys are assigned. * @returns The target object with properties and comments from source */ function assignJsonPreservingComments(target, source, keys) { return commentJson.assign(target, source, keys); } /** * Checks if two JSON strings are semantically equal (same data, ignoring whitespace/formatting). * This does NOT consider comments - it only compares the actual data values. * * @param contentA First JSON string * @param contentB Second JSON string * @returns true if the JSON data is semantically equivalent */ function jsonContentsSemanticallyEqual(contentA, contentB) { try { // Parse without preserving comments for semantic comparison const objA = commentJson.parse(contentA, null, true); // true = remove comments const objB = commentJson.parse(contentB, null, true); return Utilities_1.default.consistentStringify(objA) === Utilities_1.default.consistentStringify(objB); } catch (e) { // If parsing fails, fall back to string comparison return contentA === contentB; } } /** * Compares two JSON objects for semantic equality, ignoring comment metadata. * Use this when you have already-parsed objects and want to check if their * actual data (not comments) is the same. * * @param objA First object (may have comment metadata) * @param objB Second object (may have comment metadata) * @returns true if the JSON data is semantically equivalent */ function jsonObjectsSemanticallyEqual(objA, objB) { try { return Utilities_1.default.consistentStringify(objA) === Utilities_1.default.consistentStringify(objB); } catch (e) { return false; } } /** * Merges new values into an existing comment-json object, preserving comments. * This is the recommended way to update a JSON object while keeping its comments. * * @param original The original parsed object with comments * @param updates An object containing the properties to update * @returns The original object with updates applied (mutates in place) */ function mergeJsonPreservingComments(original, updates) { // Use comment-json's assign to copy properties while preserving comment symbols for (const key of Object.keys(updates)) { // Guard against prototype pollution if (key === "__proto__" || key === "constructor" || key === "prototype") { continue; } const value = updates[key]; if (value !== undefined) { // For nested objects, we need to recursively merge if (typeof value === "object" && value !== null && !Array.isArray(value) && typeof original[key] === "object" && original[key] !== null && !Array.isArray(original[key])) { mergeJsonPreservingComments(original[key], value); } else { // For primitives and arrays, just assign // Note: Array assignment will lose comments on the array itself // For arrays, consider using CommentArray methods like splice/push original[key] = value; } } } return original; } /** * Creates a deep clone of a comment-json object, preserving comments. * Use this when you need a copy that retains comment metadata. * * @param obj The object to clone * @returns A new object with the same data and comments */ function cloneJsonWithComments(obj) { // The simplest way to clone while preserving comments is to stringify and re-parse const jsonString = commentJson.stringify(obj, null, 2); return commentJson.parse(jsonString); } /** * Checks if an object was parsed with comment-json and contains comment metadata. * * @param obj The object to check * @returns true if the object has comment Symbol properties */ function hasCommentMetadata(obj) { if (obj === null || typeof obj !== "object") { return false; } // Check for any comment-json symbols const symbols = Object.getOwnPropertySymbols(obj); return symbols.some((sym) => sym.description?.startsWith("before") || sym.description?.startsWith("after")); } /** * Default export providing all JsonUtilities functions as a namespace-like object. * This allows both `import JsonUtilities from "./JsonUtilities"` and * `import { parseJsonWithComments } from "./JsonUtilities"` usage patterns. */ const JsonUtilities = { parseJsonWithComments, stringifyJsonWithComments, assignJsonPreservingComments, mergeJsonPreservingComments, jsonContentsSemanticallyEqual, jsonObjectsSemanticallyEqual, cloneJsonWithComments, hasCommentMetadata, CommentArrayClass: exports.CommentArrayClass, }; exports.default = JsonUtilities;