{"version":3,"file":"rename.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/rename.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;;GAOG;AAEH,MAAM,WAAW,cAAc;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,cAAc,EAAE,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,6BAA6B,EAAE,MAAM,EAAE,CAAC;IACxC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACpC,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,WAAW,EAAE,EACpB,IAAI,GAAE,oBAAyB,GAC7B;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAgC7C;AAiBD,iBAAS,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,WAAW,EAAE,GAClB,OAAO,CAAC,cAAc,CAAC,CAWzB;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAC/C,IAAI,EAAE,gBAAgB,EACtB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACb;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CAmE7C;AASD,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,oFAAoF;AACpF,wBAAsB,yBAAyB,CAC9C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EAClC,aAAa,EAAE,MAAM,GACnB,OAAO,CAAC;IACV,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CAAC,CAaD;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,gBAAgB,EACtB,aAAa,EAAE,MAAM,GACnB;IAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAsCpF","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport nodePath from \"node:path\";\nimport type { WorkspaceEdit } from \"../safety/transaction.js\";\nimport { computeLineIndex, positionToOffset } from \"./position.js\";\nimport type { LspTextEdit, LspWorkspaceEdit } from \"./types.js\";\n\n/**\n * Rename preview & transactional refactoring support.\n *\n * `lsp_rename_preview` produces a structured proposed transaction with ZERO\n * physical mutations. Applying the rename uses Jensen 1.3.0 transactional\n * mutation (policy → lease → checkpoint → deterministic edit application →\n * validation → confirm/rollback).\n */\n\nexport interface RenameFileEdit {\n\tworkspaceRelativePath: string;\n\tabsolutePath: string;\n\tnewContent: string;\n\tcontentHashBefore: string;\n\teditCount: number;\n}\n\nexport interface RenamePreview {\n\tsymbol: string;\n\toldName: string;\n\tnewName: string;\n\taffectedFiles: RenameFileEdit[];\n\ttotalEditCount: number;\n\tconflicts: string[];\n\tunsupportedResourceOperations: string[];\n\ttransactionId: string | null;\n\tzeroMutation: true;\n}\n\nexport interface ApplyTextEditOptions {\n\tpreserveBom?: boolean;\n}\n\n/** Apply LSP text edits to file content (later offsets first). UTF-16-aware. */\nexport function applyTextEdits(\n\tcontent: string,\n\tedits: LspTextEdit[],\n\topts: ApplyTextEditOptions = {},\n): { newContent: string; conflicts: string[] } {\n\tconst index = computeLineIndex(content);\n\tconst bom = opts.preserveBom === false ? \"\" : content.charCodeAt(0) === 0xfeff ? \"\\ufeff\" : \"\";\n\tconst bodyStart = bom ? 1 : 0;\n\tconst indexed = content.slice(bodyStart);\n\tconst lineIndex = bodyStart ? shiftLineIndex(index, bodyStart) : index;\n\n\tconst ordered = [...edits].sort((a, b) => {\n\t\tconst aStart = offsetAt(indexed, lineIndex, a.range.start);\n\t\tconst bStart = offsetAt(indexed, lineIndex, b.range.start);\n\t\tif (aStart !== bStart) return bStart - aStart;\n\t\treturn offsetAt(indexed, lineIndex, b.range.end) - offsetAt(indexed, lineIndex, a.range.end);\n\t});\n\n\tlet result = indexed;\n\tconst conflicts: string[] = [];\n\tconst appliedRanges: Array<{ start: number; end: number }> = [];\n\tfor (const edit of ordered) {\n\t\tconst start = offsetAt(result, computeLineIndex(result), edit.range.start);\n\t\tconst end = offsetAt(result, computeLineIndex(result), edit.range.end);\n\t\t// Overlap detection against the ORIGINAL positions.\n\t\tconst origStart = offsetAt(indexed, lineIndex, edit.range.start);\n\t\tconst origEnd = offsetAt(indexed, lineIndex, edit.range.end);\n\t\tconst overlap = appliedRanges.some((r) => origStart < r.end && origEnd > r.start);\n\t\tif (overlap) {\n\t\t\tconflicts.push(`overlapping edit at ${edit.range.start.line}:${edit.range.start.character}`);\n\t\t\tcontinue;\n\t\t}\n\t\tappliedRanges.push({ start: origStart, end: origEnd });\n\t\tresult = result.slice(0, start) + edit.newText + result.slice(end);\n\t}\n\treturn { newContent: bom + result, conflicts };\n}\n\nfunction shiftLineIndex(\n\tindex: ReturnType<typeof computeLineIndex>,\n\tshiftChars: number,\n): ReturnType<typeof computeLineIndex> {\n\treturn { lineStarts: index.lineStarts.map((s) => s - shiftChars) };\n}\n\nfunction offsetAt(\n\tcontent: string,\n\tindex: ReturnType<typeof computeLineIndex>,\n\tpos: { line: number; character: number },\n): number {\n\treturn positionToOffset(content, index, pos);\n}\n\nfunction sha256Content(content: string): string {\n\treturn createHash(\"sha256\").update(content, \"utf-8\").digest(\"hex\");\n}\n\n/**\n * Read the current content of a file (workspace path, absolute), apply edits,\n * and return the new content plus hash preconditions. Edits outside the\n * workspace root are rejected.\n */\nexport async function previewFileEdits(\n\tabsPath: string,\n\tworkspaceRoot: string,\n\tedits: LspTextEdit[],\n): Promise<RenameFileEdit> {\n\tconst content = await readFile(absPath, \"utf-8\");\n\tconst rel = nodePath.relative(workspaceRoot, absPath).replace(/\\\\/g, \"/\");\n\tconst applied = applyTextEdits(content, edits);\n\treturn {\n\t\tworkspaceRelativePath: rel,\n\t\tabsolutePath: absPath,\n\t\tnewContent: applied.newContent,\n\t\tcontentHashBefore: sha256Content(content),\n\t\teditCount: edits.length,\n\t};\n}\n\n/**\n * Normalize an LSP WorkspaceEdit into a preview. Resource operations\n * (create/rename/delete files) are reported as unsupported unless represented\n * safely; here they are surfaced but excluded from the transactional snapshot\n * so no physical change occurs.\n */\nexport function normalizeWorkspaceEditForPreview(\n\tedit: LspWorkspaceEdit,\n\tworkspaceRoot: string,\n\tsymbol: string,\n\toldName: string,\n\tnewName: string,\n): { version: string; preview: RenamePreview } {\n\tconst conflicts: string[] = [];\n\tconst unsupportedResourceOperations: string[] = [];\n\tconst byPath = new Map<string, LspTextEdit[]>();\n\n\tconst addChanges = (changes: Record<string, LspTextEdit[]> | undefined) => {\n\t\tif (!changes) return;\n\t\tfor (const [uri, edits] of Object.entries(changes)) {\n\t\t\tconst abs = uriToAbs(uri);\n\t\t\tif (!abs) {\n\t\t\t\tunsupportedResourceOperations.push(uri);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!abs.startsWith(nodePath.resolve(workspaceRoot))) {\n\t\t\t\tconflicts.push(`edit targets external path: ${uri}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst existing = byPath.get(abs) ?? [];\n\t\t\tbyPath.set(abs, [...existing, ...edits]);\n\t\t}\n\t};\n\taddChanges(edit.changes);\n\tfor (const doc of edit.documentChanges ?? []) {\n\t\tif (\"textDocument\" in doc && \"edits\" in doc) {\n\t\t\tconst abs = uriToAbs(doc.textDocument.uri);\n\t\t\tif (!abs) {\n\t\t\t\tunsupportedResourceOperations.push(doc.textDocument.uri);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!abs.startsWith(nodePath.resolve(workspaceRoot))) {\n\t\t\t\tconflicts.push(`edit targets external path: ${doc.textDocument.uri}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst existing = byPath.get(abs) ?? [];\n\t\t\tbyPath.set(abs, [...existing, ...doc.edits]);\n\t\t} else {\n\t\t\tunsupportedResourceOperations.push(\"resource-operation\");\n\t\t}\n\t}\n\n\tconst affected: RenameFileEdit[] = [];\n\tfor (const [abs, edits] of byPath) {\n\t\t// Deferred file read done at build time (async) — represented here as a\n\t\t// placeholder resolved later. This structure is populated synchronously\n\t\t// for the pure preview; the tools layer fills real content.\n\t\taffected.push({\n\t\t\tworkspaceRelativePath: nodePath.relative(workspaceRoot, abs).replace(/\\\\/g, \"/\"),\n\t\t\tabsolutePath: abs,\n\t\t\tnewContent: \"\",\n\t\t\tcontentHashBefore: \"\",\n\t\t\teditCount: edits.length,\n\t\t});\n\t}\n\n\tconst totalEditCount = affected.reduce((n, f) => n + f.editCount, 0);\n\tconst preview: RenamePreview = {\n\t\tsymbol,\n\t\toldName,\n\t\tnewName,\n\t\taffectedFiles: affected,\n\t\ttotalEditCount,\n\t\tconflicts,\n\t\tunsupportedResourceOperations,\n\t\ttransactionId: null,\n\t\tzeroMutation: true,\n\t};\n\treturn { version: sha256Content(JSON.stringify(preview)), preview };\n}\n\nfunction uriToAbs(uri: string): string | null {\n\tif (!uri.startsWith(\"file://\")) return null;\n\tconst raw = decodeURIComponent(uri.slice(\"file://\".length));\n\tif (raw.startsWith(\"/\")) return raw;\n\treturn raw.replace(/^\\//, \"\").replace(/\\//g, \"\\\\\");\n}\n\nexport { sha256Content };\n\n/** Translate a normalized LSP edit set into Jensen transactional WorkspaceEdits. */\nexport async function buildJensenWorkspaceEdits(\n\tbyPath: Map<string, LspTextEdit[]>,\n\tworkspaceRoot: string,\n): Promise<{\n\tedits: WorkspaceEdit[];\n\tcontentHashes: Record<string, string>;\n}> {\n\tconst edits: WorkspaceEdit[] = [];\n\tconst contentHashes: Record<string, string> = {};\n\tfor (const [abs, textEdits] of byPath) {\n\t\tconst rel = nodePath.relative(workspaceRoot, abs).replace(/\\\\/g, \"/\");\n\t\tconst content = await readFile(abs, \"utf-8\");\n\t\tconst before = sha256Content(content);\n\t\tconst applied = applyTextEdits(content, textEdits);\n\t\tif (applied.conflicts.length) continue;\n\t\tcontentHashes[rel] = before;\n\t\tedits.push({ kind: \"replace_file\", path: rel, content: applied.newContent, expectedSha256: before });\n\t}\n\treturn { edits, contentHashes };\n}\n\n/** Extract the per-absolute-path edit map from an LSP WorkspaceEdit. */\nexport function extractEditsByPath(\n\tedit: LspWorkspaceEdit,\n\tworkspaceRoot: string,\n): { byPath: Map<string, LspTextEdit[]>; conflicts: string[]; unsupported: string[] } {\n\tconst conflicts: string[] = [];\n\tconst unsupported: string[] = [];\n\tconst byPath = new Map<string, LspTextEdit[]>();\n\tconst root = nodePath.resolve(workspaceRoot);\n\tconst addChanges = (changes: Record<string, LspTextEdit[]> | undefined) => {\n\t\tif (!changes) return;\n\t\tfor (const [uri, edits] of Object.entries(changes)) {\n\t\t\tconst abs = uriToAbs(uri);\n\t\t\tif (!abs) {\n\t\t\t\tunsupported.push(uri);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!abs.startsWith(root)) {\n\t\t\t\tconflicts.push(`edit targets external path: ${uri}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbyPath.set(abs, [...(byPath.get(abs) ?? []), ...edits]);\n\t\t}\n\t};\n\taddChanges(edit.changes);\n\tfor (const doc of edit.documentChanges ?? []) {\n\t\tif (\"textDocument\" in doc && \"edits\" in doc) {\n\t\t\tconst abs = uriToAbs(doc.textDocument.uri);\n\t\t\tif (!abs) {\n\t\t\t\tunsupported.push(doc.textDocument.uri);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!abs.startsWith(root)) {\n\t\t\t\tconflicts.push(`edit targets external path: ${doc.textDocument.uri}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbyPath.set(abs, [...(byPath.get(abs) ?? []), ...doc.edits]);\n\t\t} else {\n\t\t\tunsupported.push(\"resource-operation\");\n\t\t}\n\t}\n\treturn { byPath, conflicts, unsupported };\n}\n"]}