1const parseJSON = require('json-parse-even-better-errors') 2const { diff } = require('just-diff') 3const { diffApply } = require('just-diff-apply') 4 5const globalObjectProperties = Object.getOwnPropertyNames(Object.prototype) 6 7const stripBOM = content => { 8 content = content.toString() 9 // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) 10 // because the buffer-to-string conversion in `fs.readFileSync()` 11 // translates it to FEFF, the UTF-16 BOM. 12 if (content.charCodeAt(0) === 0xFEFF) { 13 content = content.slice(1) 14 } 15 return content 16} 17 18const PARENT_RE = /\|{7,}/g 19const OURS_RE = /<{7,}/g 20const THEIRS_RE = /={7,}/g 21const END_RE = />{7,}/g 22 23const isDiff = str => 24 str.match(OURS_RE) && str.match(THEIRS_RE) && str.match(END_RE) 25 26const parseConflictJSON = (str, reviver, prefer) => { 27 prefer = prefer || 'ours' 28 if (prefer !== 'theirs' && prefer !== 'ours') { 29 throw new TypeError('prefer param must be "ours" or "theirs" if set') 30 } 31 32 str = stripBOM(str) 33 34 if (!isDiff(str)) { 35 return parseJSON(str) 36 } 37 38 const pieces = str.split(/[\n\r]+/g).reduce((acc, line) => { 39 if (line.match(PARENT_RE)) { 40 acc.state = 'parent' 41 } else if (line.match(OURS_RE)) { 42 acc.state = 'ours' 43 } else if (line.match(THEIRS_RE)) { 44 acc.state = 'theirs' 45 } else if (line.match(END_RE)) { 46 acc.state = 'top' 47 } else { 48 if (acc.state === 'top' || acc.state === 'ours') { 49 acc.ours += line 50 } 51 if (acc.state === 'top' || acc.state === 'theirs') { 52 acc.theirs += line 53 } 54 if (acc.state === 'top' || acc.state === 'parent') { 55 acc.parent += line 56 } 57 } 58 return acc 59 }, { 60 state: 'top', 61 ours: '', 62 theirs: '', 63 parent: '', 64 }) 65 66 // this will throw if either piece is not valid JSON, that's intended 67 const parent = parseJSON(pieces.parent, reviver) 68 const ours = parseJSON(pieces.ours, reviver) 69 const theirs = parseJSON(pieces.theirs, reviver) 70 71 return prefer === 'ours' 72 ? resolve(parent, ours, theirs) 73 : resolve(parent, theirs, ours) 74} 75 76const isObj = obj => obj && typeof obj === 'object' 77 78const copyPath = (to, from, path, i) => { 79 const p = path[i] 80 if (isObj(to[p]) && isObj(from[p]) && 81 Array.isArray(to[p]) === Array.isArray(from[p])) { 82 return copyPath(to[p], from[p], path, i + 1) 83 } 84 to[p] = from[p] 85} 86 87// get the diff from parent->ours and applying our changes on top of theirs. 88// If they turned an object into a non-object, then put it back. 89const resolve = (parent, ours, theirs) => { 90 const dours = diff(parent, ours) 91 for (let i = 0; i < dours.length; i++) { 92 if (globalObjectProperties.find(prop => dours[i].path.includes(prop))) { 93 continue 94 } 95 try { 96 diffApply(theirs, [dours[i]]) 97 } catch (e) { 98 copyPath(theirs, ours, dours[i].path, 0) 99 } 100 } 101 return theirs 102} 103 104module.exports = Object.assign(parseConflictJSON, { isDiff }) 105