UNPKG

@oaklean/profiler-core

Version:

Part of the @oaklean suite. It provides all basic functions to work with the `.oak` file format. It allows parsing the `.oak` file format as well as tools for analyzing the measurement values. It also provides all necessary capabilities required for prec

1,031 lines 81.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parsePath = parsePath; exports.parse = parse; exports.get = get; exports.has = has; exports.getComment = getComment; exports.getTrailingComment = getTrailingComment; exports.set = set; exports.remove = remove; exports.merge = merge; exports.replace = replace; exports.patch = patch; exports.rename = rename; exports.move = move; exports.setComment = setComment; exports.setTrailingComment = setTrailingComment; exports.removeTrailingComment = removeTrailingComment; exports.removeComment = removeComment; exports.sort = sort; exports.format = format; exports.modify = modify; const jsonc_parser_1 = require("jsonc-parser"); // ============================================================================= // Path Parsing // ============================================================================= /** * Parse a dot-notation path string into a JSONPath array * Examples: * "config.database.host" -> ["config", "database", "host"] * "items.0" -> ["items", 0] * "items[0]" -> ["items", 0] */ function parsePath(pathStr) { if (!pathStr) return []; const result = []; let current = ''; let i = 0; while (i < pathStr.length) { const char = pathStr[i]; if (char === '.') { if (current) { result.push(parseSegment(current)); current = ''; } i++; } else if (char === '[') { if (current) { result.push(parseSegment(current)); current = ''; } // Find closing bracket const closeBracket = pathStr.indexOf(']', i); if (closeBracket === -1) { throw new Error(`Unclosed bracket in path: ${pathStr}`); } const indexStr = pathStr.slice(i + 1, closeBracket); result.push(parseSegment(indexStr)); i = closeBracket + 1; } else { current += char; i++; } } if (current) { result.push(parseSegment(current)); } return result; } function parseSegment(segment) { const num = Number(segment); if (!Number.isNaN(num) && Number.isInteger(num) && num >= 0) { return num; } return segment; } /** * Normalize a path to an array format * If the path is already an array, return it as-is * If it's a string, parse it using parsePath */ function normalizePath(path) { return typeof path === 'string' ? parsePath(path) : path; } // ============================================================================= // Shared Helpers // ============================================================================= /** * Get all property keys from an object node in the JSON tree */ function getObjectKeys(node) { if (!node || node.type !== 'object' || !node.children) { return []; } const keys = []; for (const child of node.children) { if (child.type === 'property' && child.children && child.children[0]) { keys.push(child.children[0].value); } } return keys; } /** * Find the property node at a given path */ function findProperty(json, path) { const normalizedPath = normalizePath(path); const tree = (0, jsonc_parser_1.parseTree)(json); if (!tree) return null; const node = (0, jsonc_parser_1.findNodeAtLocation)(tree, normalizedPath); if (!node || !node.parent) return null; return { node, propertyNode: node.parent, tree }; } /** * Get the range of a property including its line context */ function getPropertyRange(json, propertyNode) { let lineStart = propertyNode.offset; while (lineStart > 0 && json[lineStart - 1] !== '\n') { lineStart--; } const textBeforeOnLine = json.slice(lineStart, propertyNode.offset); const isSingleLine = textBeforeOnLine.includes('{'); return { lineStart, isSingleLine }; } /** * Look backwards from propertyOffset for a comment. * Returns the start position of the comment line if it should be deleted with the field. * Comments starting with "**" are preserved (returns null). */ function findAssociatedCommentStart(json, propertyOffset) { let pos = propertyOffset - 1; // Skip whitespace (spaces and tabs) while (pos >= 0 && (json[pos] === ' ' || json[pos] === '\t')) { pos--; } // Must have a newline before the property for there to be a comment above if (pos >= 0 && json[pos] === '\n') { pos--; if (pos >= 0 && json[pos] === '\r') { pos--; } } else { return null; } // Skip trailing whitespace on previous line while (pos >= 0 && (json[pos] === ' ' || json[pos] === '\t')) { pos--; } if (pos < 0) return null; // Check for block comment end */ if (pos >= 1 && json[pos] === '/' && json[pos - 1] === '*') { let i = pos - 2; while (i >= 1) { if (json[i - 1] === '/' && json[i] === '*') { const commentContent = json.slice(i + 1, pos - 1).trim(); // Comments starting with ** are preserved if (commentContent.startsWith('**')) { return null; } let commentLineStart = i - 1; while (commentLineStart > 0 && json[commentLineStart - 1] !== '\n') { commentLineStart--; } return commentLineStart; } i--; } return null; } // Check for line comment const lineEnd = pos; while (pos >= 0 && json[pos] !== '\n') { pos--; } const lineStart = pos + 1; const line = json.slice(lineStart, lineEnd + 1).trim(); if (line.startsWith('//')) { const commentContent = line.slice(2).trim(); // Comments starting with ** are preserved if (commentContent.startsWith('**')) { return null; } return lineStart; } return null; } /** * Find any comment above a property (not just associated ones) */ function findCommentAbove(json, propertyOffset) { let pos = propertyOffset - 1; // Skip whitespace while (pos >= 0 && (json[pos] === ' ' || json[pos] === '\t')) { pos--; } // Must have a newline if (pos >= 0 && json[pos] === '\n') { pos--; if (pos >= 0 && json[pos] === '\r') { pos--; } } else { return null; } // Skip trailing whitespace on previous line while (pos >= 0 && (json[pos] === ' ' || json[pos] === '\t')) { pos--; } if (pos < 0) return null; // Check for block comment end */ if (pos >= 1 && json[pos] === '/' && json[pos - 1] === '*') { let i = pos - 2; while (i >= 1) { if (json[i - 1] === '/' && json[i] === '*') { const commentStart = i - 1; const commentEnd = pos + 1; const content = json.slice(commentStart + 2, commentEnd - 2).trim(); return { start: commentStart, end: commentEnd, content }; } i--; } return null; } // Check for line comment const lineEnd = pos; while (pos >= 0 && json[pos] !== '\n') { pos--; } const lineStart = pos + 1; const line = json.slice(lineStart, lineEnd + 1).trim(); if (line.startsWith('//')) { const content = line.slice(2).trim(); return { start: lineStart, end: lineEnd + 1, content }; } return null; } /** * Find any trailing comment after a property (on the same line) */ function findTrailingComment(json, propertyEnd) { let pos = propertyEnd; // Skip comma if present if (pos < json.length && json[pos] === ',') { pos++; } // Skip whitespace (but not newlines) while (pos < json.length && (json[pos] === ' ' || json[pos] === '\t')) { pos++; } // If we hit a newline or end, no trailing comment if (pos >= json.length || json[pos] === '\n' || json[pos] === '\r') { return null; } // Check for line comment // if (pos < json.length - 1 && json[pos] === '/' && json[pos + 1] === '/') { const commentStart = pos; pos += 2; // Find end of line while (pos < json.length && json[pos] !== '\n' && json[pos] !== '\r') { pos++; } const content = json.slice(commentStart + 2, pos).trim(); return { start: commentStart, end: pos, content }; } // Check for block comment /* */ if (pos < json.length - 1 && json[pos] === '/' && json[pos + 1] === '*') { const commentStart = pos; pos += 2; // Find closing */ while (pos < json.length - 1) { if (json[pos] === '*' && json[pos + 1] === '/') { pos += 2; const content = json.slice(commentStart + 2, pos - 2).trim(); return { start: commentStart, end: pos, content }; } pos++; } // Unclosed block comment return null; } return null; } /** * Flatten a nested changes object into individual path-value pairs */ function flattenChanges(obj, prefix = []) { // contains paths to delete (explicitly set to undefined) const explicitDeletions = []; const flatChanges = []; for (const [key, value] of Object.entries(obj)) { const currentPath = [...prefix, key]; if (value === undefined) { explicitDeletions.push(currentPath); } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) { const childChanges = flattenChanges(value, currentPath); if (childChanges.flatChanges.length === 0) { // the object only contains undefined values, so we should set it to an empty object rather than deleting it flatChanges.push({ path: currentPath, value: {} }); } else { flatChanges.push(...childChanges.flatChanges); explicitDeletions.push(...childChanges.explicitDeletions); } } else { flatChanges.push({ path: currentPath, value }); } } return { explicitDeletions, flatChanges }; } /** * Compute deletions for fields that exist in currentJson but not in changes (replace semantics) */ function computeDeletions(currentJson, changes, prefix = []) { const result = []; const tree = (0, jsonc_parser_1.parseTree)(currentJson); if (!tree) return result; const currentNode = prefix.length === 0 ? tree : (0, jsonc_parser_1.findNodeAtLocation)(tree, prefix); const currentKeys = getObjectKeys(currentNode); for (const key of currentKeys) { if (!(key in changes)) { result.push({ path: [...prefix, key], value: undefined }); } else { const changeValue = changes[key]; if (changeValue !== null && changeValue !== undefined && typeof changeValue === 'object' && !Array.isArray(changeValue)) { result.push(...computeDeletions(currentJson, changeValue, [...prefix, key])); } } } return result; } // ============================================================================= // Parse Operations // ============================================================================= /** * Parse a JSONC string into a JavaScript value. * Unlike JSON.parse(), this handles comments and trailing commas. */ function parse(json) { const tree = (0, jsonc_parser_1.parseTree)(json); if (!tree) { throw new SyntaxError('Failed to parse JSON'); } return (0, jsonc_parser_1.getNodeValue)(tree); } // ============================================================================= // Path-based Operations // ============================================================================= /** * Get the value at a path in the JSON */ function get(json, path) { const normalizedPath = normalizePath(path); const tree = (0, jsonc_parser_1.parseTree)(json); if (!tree) return undefined; const node = (0, jsonc_parser_1.findNodeAtLocation)(tree, normalizedPath); if (!node) return undefined; return (0, jsonc_parser_1.getNodeValue)(node); } /** * Check if a path exists in the JSON */ function has(json, path) { const normalizedPath = normalizePath(path); const tree = (0, jsonc_parser_1.parseTree)(json); if (!tree) return false; const node = (0, jsonc_parser_1.findNodeAtLocation)(tree, normalizedPath); return node !== undefined; } /** * Get the comment associated with a field at a path. * First checks for a comment above the field, then falls back to a trailing comment. */ function getComment(json, path) { const normalizedPath = normalizePath(path); const found = findProperty(json, normalizedPath); if (!found) return null; const { propertyNode } = found; // First check for comment above const commentAbove = findCommentAbove(json, propertyNode.offset); if (commentAbove) { return commentAbove.content; } // Fall back to trailing comment const propertyEnd = propertyNode.offset + propertyNode.length; const trailingComment = findTrailingComment(json, propertyEnd); return trailingComment ? trailingComment.content : null; } /** * Get the trailing comment after a field at a path (on the same line) */ function getTrailingComment(json, path) { const normalizedPath = normalizePath(path); const found = findProperty(json, normalizedPath); if (!found) return null; const { propertyNode } = found; const propertyEnd = propertyNode.offset + propertyNode.length; const trailingComment = findTrailingComment(json, propertyEnd); return trailingComment ? trailingComment.content : null; } /** * Set a value at a path in the JSON, optionally with a comment */ function set(json, path, value, comment) { const normalizedPath = normalizePath(path); const edits = (0, jsonc_parser_1.modify)(json, normalizedPath, value, {}); let result = (0, jsonc_parser_1.applyEdits)(json, edits); // Add comment if provided if (comment !== undefined) { result = setComment(result, normalizedPath, comment); } return result; } /** * Remove a field at a path (with associated comment handling) */ function remove(json, path) { const normalizedPath = normalizePath(path); const found = findProperty(json, normalizedPath); if (!found) return json; const { propertyNode } = found; const { lineStart, isSingleLine } = getPropertyRange(json, propertyNode); // Check for associated comment above (comments starting with ** are preserved) const commentStart = !isSingleLine ? findAssociatedCommentStart(json, propertyNode.offset) : null; // Determine delete start let deleteStart; if (commentStart !== null) { deleteStart = commentStart; } else if (isSingleLine) { deleteStart = propertyNode.offset; } else { deleteStart = lineStart; } // Find the end of the property let deleteEnd = propertyNode.offset + propertyNode.length; // Check for trailing comment (unless it starts with **) const propertyEnd = propertyNode.offset + propertyNode.length; const trailingComment = findTrailingComment(json, propertyEnd); const hasPreservedTrailingComment = trailingComment === null || trailingComment === void 0 ? void 0 : trailingComment.content.startsWith('**'); // If there's a trailing comment that should be deleted, include it in the deletion if (trailingComment && !hasPreservedTrailingComment) { deleteEnd = trailingComment.end; } // Skip trailing comma (if not already past it due to trailing comment) if (deleteEnd < json.length && json[deleteEnd] === ',') { deleteEnd++; } // Skip trailing whitespace while (deleteEnd < json.length && (json[deleteEnd] === ' ' || json[deleteEnd] === '\t')) { deleteEnd++; } // For multi-line, include the trailing newline if (!isSingleLine) { if (deleteEnd < json.length && json[deleteEnd] === '\n') { deleteEnd++; } else if (deleteEnd < json.length && json[deleteEnd] === '\r') { deleteEnd++; if (deleteEnd < json.length && json[deleteEnd] === '\n') { deleteEnd++; } } } const beforeDelete = json.slice(0, deleteStart); const afterDelete = json.slice(deleteEnd); let result = beforeDelete + afterDelete; // Handle trailing comma issues const trimmedBefore = beforeDelete.trimEnd(); const afterTrimmed = afterDelete.trimStart(); if (trimmedBefore.endsWith(',') && afterTrimmed.startsWith('}')) { const commaPos = beforeDelete.lastIndexOf(','); result = beforeDelete.slice(0, commaPos) + beforeDelete.slice(commaPos + 1) + afterDelete; } return result; } // ============================================================================= // Merge Strategies // ============================================================================= /** * Merge changes into JSON (update/add only, never delete) */ function merge(json, changes) { let result = json; const { explicitDeletions, flatChanges } = flattenChanges(changes); // remove fields explicitly set to undefined in changes for (const path of explicitDeletions) { result = remove(result, path); } for (const { path, value } of flatChanges) { const edits = (0, jsonc_parser_1.modify)(result, path, value, {}); result = (0, jsonc_parser_1.applyEdits)(result, edits); } return result; } /** * Replace JSON with changes (delete fields not in changes) */ function replace(json, changes) { let result = json; // Compute deletions for fields not in changes const deletions = computeDeletions(json, changes); const { explicitDeletions, flatChanges } = flattenChanges(changes); // Process deletions first (from deepest to shallowest) const sortedDeletions = deletions.sort((a, b) => b.path.length - a.path.length); for (const { path } of sortedDeletions) { result = remove(result, path); } // remove fields explicitly set to undefined in changes for (const path of explicitDeletions) { result = remove(result, path); } // Then apply updates for (const { path, value } of flatChanges) { const edits = (0, jsonc_parser_1.modify)(result, path, value, {}); result = (0, jsonc_parser_1.applyEdits)(result, edits); } return result; } /** * Patch JSON with explicit deletes via undefined */ function patch(json, changes) { return merge(json, changes); } // ============================================================================= // Key Operations // ============================================================================= /** * Rename a key while preserving its value and associated comment */ function rename(json, path, newKey) { const normalizedPath = normalizePath(path); const found = findProperty(json, normalizedPath); if (!found) return json; const { node, propertyNode } = found; // Get the value const value = (0, jsonc_parser_1.getNodeValue)(node); // Check for associated comment const { isSingleLine } = getPropertyRange(json, propertyNode); const commentInfo = !isSingleLine ? findCommentAbove(json, propertyNode.offset) : null; // Build new path const newPath = [...normalizedPath.slice(0, -1), newKey]; // Remove old key let result = remove(json, normalizedPath); // Add new key with the value result = set(result, newPath, value); // If there was a comment, preserve it if (commentInfo) { result = setComment(result, newPath, commentInfo.content); } return result; } /** * Move a field to a different location */ function move(json, fromPath, toPath) { const normalizedFromPath = normalizePath(fromPath); const normalizedToPath = normalizePath(toPath); const found = findProperty(json, normalizedFromPath); if (!found) return json; const { node } = found; const value = (0, jsonc_parser_1.getNodeValue)(node); // Remove from old location let result = remove(json, normalizedFromPath); // Add to new location result = set(result, normalizedToPath, value); return result; } // ============================================================================= // Comment Operations // ============================================================================= /** * Set or update a comment above a field */ function setComment(json, path, comment) { const normalizedPath = normalizePath(path); const found = findProperty(json, normalizedPath); if (!found) return json; const { propertyNode } = found; const { lineStart, isSingleLine } = getPropertyRange(json, propertyNode); if (isSingleLine) { // Can't add comment to single-line JSON return json; } // Check for existing comment const existingComment = findCommentAbove(json, propertyNode.offset); // Determine indentation const indentation = json.slice(lineStart, propertyNode.offset); const commentLine = `${indentation}// ${comment}\n`; if (existingComment) { // Find the line start of the existing comment let commentLineStart = existingComment.start; while (commentLineStart > 0 && json[commentLineStart - 1] !== '\n') { commentLineStart--; } // Find the end of the comment line (including newline) let commentLineEnd = existingComment.end; while (commentLineEnd < json.length && json[commentLineEnd] !== '\n') { commentLineEnd++; } if (commentLineEnd < json.length) { commentLineEnd++; // Include the newline } // Replace the existing comment return (json.slice(0, commentLineStart) + commentLine + json.slice(commentLineEnd)); } else { // Insert new comment before the property return json.slice(0, lineStart) + commentLine + json.slice(lineStart); } } /** * Set or update a trailing comment after a field (on the same line) */ function setTrailingComment(json, path, comment) { const normalizedPath = normalizePath(path); const found = findProperty(json, normalizedPath); if (!found) return json; const { propertyNode } = found; const propertyEnd = propertyNode.offset + propertyNode.length; // Check for existing trailing comment const existingComment = findTrailingComment(json, propertyEnd); // Find where to insert the comment (after the value, before any comma) const insertPos = propertyEnd; // Check if there's a comma after the property let pos = propertyEnd; while (pos < json.length && (json[pos] === ' ' || json[pos] === '\t')) { pos++; } const hasComma = pos < json.length && json[pos] === ','; if (existingComment) { // Replace existing trailing comment return `${json.slice(0, existingComment.start)}// ${comment}${json.slice(existingComment.end)}`; } // Insert new trailing comment // If there's a comma, insert before it; otherwise insert after value if (hasComma) { // Insert before the comma return `${json.slice(0, pos)} // ${comment}${json.slice(pos)}`; } // Insert after the value return `${json.slice(0, insertPos)} // ${comment}${json.slice(insertPos)}`; } /** * Remove the trailing comment after a field */ function removeTrailingComment(json, path) { const normalizedPath = normalizePath(path); const found = findProperty(json, normalizedPath); if (!found) return json; const { propertyNode } = found; const propertyEnd = propertyNode.offset + propertyNode.length; const existingComment = findTrailingComment(json, propertyEnd); if (!existingComment) return json; // Remove the comment and any whitespace before it let removeStart = existingComment.start; while (removeStart > propertyEnd && (json[removeStart - 1] === ' ' || json[removeStart - 1] === '\t')) { removeStart--; } return json.slice(0, removeStart) + json.slice(existingComment.end); } /** * Remove the comment above a field */ function removeComment(json, path) { const normalizedPath = normalizePath(path); const found = findProperty(json, normalizedPath); if (!found) return json; const { propertyNode } = found; const existingComment = findCommentAbove(json, propertyNode.offset); if (!existingComment) return json; // Find the full line range of the comment let commentLineStart = existingComment.start; while (commentLineStart > 0 && json[commentLineStart - 1] !== '\n') { commentLineStart--; } let commentLineEnd = existingComment.end; while (commentLineEnd < json.length && json[commentLineEnd] !== '\n') { commentLineEnd++; } if (commentLineEnd < json.length) { commentLineEnd++; // Include the newline } return json.slice(0, commentLineStart) + json.slice(commentLineEnd); } /** * Extract information about all properties in an object node */ function getPropertyInfos(json, objectNode) { if (!objectNode.children) return []; const infos = []; for (const child of objectNode.children) { if (child.type !== 'property' || !child.children || !child.children[0]) { continue; } const keyNode = child.children[0]; const key = keyNode.value; // Find line start for indentation let lineStart = child.offset; while (lineStart > 0 && json[lineStart - 1] !== '\n') { lineStart--; } const indentation = json.slice(lineStart, child.offset); // Check for associated comment above const commentStart = findAssociatedCommentStart(json, child.offset); const fullStart = commentStart !== null ? commentStart : lineStart; // Check for trailing comment const propertyEnd = child.offset + child.length; const trailingComment = findTrailingComment(json, propertyEnd); const fullEnd = trailingComment ? trailingComment.end : propertyEnd; const info = { key, keyNode, propertyNode: child, fullStart, propertyEnd, fullEnd, indentation }; if (trailingComment) { info.trailingComment = trailingComment; } infos.push(info); } return infos; } /** * Sort object keys while preserving comments */ function sort(json, path = [], options = {}) { const { comparator = (a, b) => a.localeCompare(b), deep = true } = options; const normalizedPath = normalizePath(path); const tree = (0, jsonc_parser_1.parseTree)(json); if (!tree) return json; // Find the target node at the given path const targetNode = normalizedPath.length === 0 ? tree : (0, jsonc_parser_1.findNodeAtLocation)(tree, normalizedPath); if (!targetNode || targetNode.type !== 'object') return json; // Collect all objects that need sorting within the target (process deepest first) const objectsToSort = []; function collectObjects(node, depth) { if (node.type === 'object' && node.children && node.children.length > 0) { objectsToSort.push({ node, depth }); } if (node.children) { for (const child of node.children) { collectObjects(child, depth + 1); } } } collectObjects(targetNode, 0); // Sort deepest objects first so offsets remain valid objectsToSort.sort((a, b) => b.depth - a.depth); // If not deep, only process the target object itself const objectsToProcess = deep ? objectsToSort : objectsToSort.filter((o) => o.depth === 0); let result = json; for (const { node: objectNode } of objectsToProcess) { result = sortSingleObject(result, objectNode, comparator); // Re-parse after each modification to get updated offsets if (objectsToProcess.length > 1) { const newTree = (0, jsonc_parser_1.parseTree)(result); if (!newTree) break; } } // If we processed multiple objects, we need to re-process with fresh offsets // since sorting one object invalidates offsets for others if (deep && objectsToSort.length > 1) { // Re-parse and process again from scratch with updated positions result = sortDeepAtPath(result, normalizedPath, comparator); } return result; } /** * Recursively sort all objects in the JSON starting at a path */ function sortDeepAtPath(json, path, comparator) { const tree = (0, jsonc_parser_1.parseTree)(json); if (!tree) return json; const targetNode = path.length === 0 ? tree : (0, jsonc_parser_1.findNodeAtLocation)(tree, path); if (!targetNode || targetNode.type !== 'object') return json; // Find the deepest object and sort it, repeat until no changes let result = json; let changed = true; while (changed) { changed = false; const currentTree = (0, jsonc_parser_1.parseTree)(result); if (!currentTree) break; const currentTarget = path.length === 0 ? currentTree : (0, jsonc_parser_1.findNodeAtLocation)(currentTree, path); if (!currentTarget) break; // Find deepest object within target let deepestObject = null; let maxDepth = -1; function findDeepest(node, depth) { if (node.type === 'object' && node.children && node.children.length > 1) { // Check if this object needs sorting const infos = getPropertyInfos(result, node); const keys = infos.map((i) => i.key); const sortedKeys = [...keys].sort(comparator); const needsSort = keys.some((k, i) => k !== sortedKeys[i]); if (needsSort && depth > maxDepth) { deepestObject = node; maxDepth = depth; } } if (node.children) { for (const child of node.children) { findDeepest(child, depth + 1); } } } findDeepest(currentTarget, 0); if (deepestObject) { result = sortSingleObject(result, deepestObject, comparator); changed = true; } } return result; } /** * Sort a single object's keys */ function sortSingleObject(json, objectNode, comparator) { const infos = getPropertyInfos(json, objectNode); if (infos.length <= 1) return json; // Check if already sorted const keys = infos.map((i) => i.key); const sortedKeys = [...keys].sort(comparator); if (keys.every((k, i) => k === sortedKeys[i])) { return json; } // Sort the property infos const sortedInfos = [...infos].sort((a, b) => comparator(a.key, b.key)); // Find the object's content boundaries (inside the braces) const objectStart = objectNode.offset; const objectEnd = objectNode.offset + objectNode.length; // Find where content starts (after opening brace and any whitespace) let contentStart = objectStart + 1; while (contentStart < objectEnd && (json[contentStart] === ' ' || json[contentStart] === '\t' || json[contentStart] === '\n' || json[contentStart] === '\r')) { contentStart++; } // Find where content ends (before closing brace) let contentEnd = objectEnd - 1; while (contentEnd > contentStart && (json[contentEnd - 1] === ' ' || json[contentEnd - 1] === '\t' || json[contentEnd - 1] === '\n' || json[contentEnd - 1] === '\r')) { contentEnd--; } // Check if it's a single-line object const objectContent = json.slice(objectStart, objectEnd); const isSingleLine = !objectContent.includes('\n'); // Detect if original has trailing comma (check after last property, accounting for trailing comments) const lastInfo = infos[infos.length - 1]; let hasTrailingComma = false; if (lastInfo) { // Start after the property (or trailing comment if present) let pos = lastInfo.fullEnd; while (pos < objectEnd && (json[pos] === ' ' || json[pos] === '\t')) { pos++; } // Check for comma if (pos < objectEnd && json[pos] === ',') { hasTrailingComma = true; } else { // Also check if comma comes before trailing comment pos = lastInfo.propertyEnd; while (pos < objectEnd && (json[pos] === ' ' || json[pos] === '\t')) { pos++; } hasTrailingComma = pos < objectEnd && json[pos] === ','; } } if (isSingleLine) { // For single-line objects, rebuild simply but preserve trailing comments const sortedPairs = sortedInfos.map((info) => { var _a; const valueNode = (_a = info.propertyNode.children) === null || _a === void 0 ? void 0 : _a[1]; const valueStr = valueNode ? json.slice(valueNode.offset, valueNode.offset + valueNode.length) : 'null'; // Include trailing comment if present const trailingCommentStr = info.trailingComment ? ` // ${info.trailingComment.content}` : ''; return `"${info.key}": ${valueStr}${trailingCommentStr}`; }); const trailingComma = hasTrailingComma ? ',' : ''; return (json.slice(0, objectStart) + '{ ' + sortedPairs.join(', ') + trailingComma + ' }' + json.slice(objectEnd)); } // For multi-line objects, preserve formatting // Extract each property with its full content (including comments above and trailing) const propertyTexts = []; for (const sortedInfo of sortedInfos) { // Find the original info to get proper text boundaries const originalInfo = infos.find((x) => x.key === sortedInfo.key); if (!originalInfo) continue; // Build the property text including comment above and trailing comment let propText; // Start with comment above if present if (originalInfo.fullStart < originalInfo.propertyNode.offset) { // There's a comment before this property const commentText = json.slice(originalInfo.fullStart, originalInfo.propertyNode.offset); // Get just the property part (without trailing comment) const propOnlyText = json.slice(originalInfo.propertyNode.offset, originalInfo.propertyEnd); // Reconstruct with consistent indentation propText = commentText + propOnlyText; } else { // No comment above, just the property with indentation propText = sortedInfo.indentation + json.slice(originalInfo.propertyNode.offset, originalInfo.propertyEnd); } // Add trailing comment if present if (originalInfo.trailingComment) { propText += ` // ${originalInfo.trailingComment.content}`; } propertyTexts.push(propText); } // Find closing brace indentation const closingBracePos = objectEnd - 1; let closingIndentStart = closingBracePos; while (closingIndentStart > 0 && json[closingIndentStart - 1] !== '\n') { closingIndentStart--; } const closingIndent = json.slice(closingIndentStart, closingBracePos); // Build the new object content const newContent = propertyTexts .map((text, i) => { // Add comma except for last item (unless trailing comma is preserved) const isLast = i === propertyTexts.length - 1; if (isLast) { return hasTrailingComma ? `${text},` : text; } return `${text},`; }) .join('\n'); // Reconstruct the object const beforeObject = json.slice(0, objectStart); const afterObject = json.slice(objectEnd); return `${beforeObject}{\n${newContent}\n${closingIndent}}${afterObject}`; } // ============================================================================= // Format Operations // ============================================================================= /** * Format a JSONC document with consistent indentation. * Preserves comments while normalizing whitespace. */ function format(json, options = {}) { const { tabSize = 2, insertSpaces = true, eol = '\n' } = options; const edits = (0, jsonc_parser_1.format)(json, undefined, { tabSize, insertSpaces, eol }); return (0, jsonc_parser_1.applyEdits)(json, edits); } // ============================================================================= // High-level API // ============================================================================= /** * Modify JSON with replace semantics — fields not in changes are deleted */ function modify(json, changes) { return replace(json, changes); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9saWIvYXl3c29uL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBMkJBLDhCQXdDQztBQXlXRCxzQkFNQztBQVNELGtCQVNDO0FBS0Qsa0JBT0M7QUFNRCxnQ0FpQkM7QUFLRCxnREFhQztBQUtELGtCQWdCQztBQUtELHdCQStFQztBQVNELHNCQWVDO0FBS0QsMEJBK0JDO0FBS0Qsc0JBRUM7QUFTRCx3QkErQkM7QUFLRCxvQkFvQkM7QUFTRCxnQ0ErQ0M7QUFLRCxnREF1Q0M7QUFLRCxzREFxQkM7QUFLRCxzQ0F5QkM7QUFzRkQsb0JBNkRDO0FBa1BELHdCQWVDO0FBU0Qsd0JBRUM7QUFqeUNELCtDQVFxQjtBQVFyQixnRkFBZ0Y7QUFDaEYsZUFBZTtBQUNmLGdGQUFnRjtBQUVoRjs7Ozs7O0dBTUc7QUFDSCxTQUFnQixTQUFTLENBQUMsT0FBZTtJQUN4QyxJQUFJLENBQUMsT0FBTztRQUFFLE9BQU8sRUFBRSxDQUFBO0lBRXZCLE1BQU0sTUFBTSxHQUF3QixFQUFFLENBQUE7SUFDdEMsSUFBSSxPQUFPLEdBQUcsRUFBRSxDQUFBO0lBQ2hCLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUVULE9BQU8sQ0FBQyxHQUFHLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUMzQixNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFFdkIsSUFBSSxJQUFJLEtBQUssR0FBRyxFQUFFLENBQUM7WUFDbEIsSUFBSSxPQUFPLEVBQUUsQ0FBQztnQkFDYixNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFBO2dCQUNsQyxPQUFPLEdBQUcsRUFBRSxDQUFBO1lBQ2IsQ0FBQztZQUNELENBQUMsRUFBRSxDQUFBO1FBQ0osQ0FBQzthQUFNLElBQUksSUFBSSxLQUFLLEdBQUcsRUFBRSxDQUFDO1lBQ3pCLElBQUks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