'use strict' ; import { Edit , ParseError , Node , JSONPath , Segment , ModificationOptions } from '../main' ; import { format , isEOL } from './format' ; import { parseTree , findNodeAtLocation } from './parser' ; export function removeProperty ( text , path , options ) { return setProperty ( text , path , void 0 , options ) ; } export function setProperty ( text , originalPath , value , options ) { let path = originalPath . slice ( ) ; let errors = [ ] ; let root = parseTree ( text , errors ) ; let parent = void 0 ; let lastSegment = void 0 ; while ( path . length > 0 ) { lastSegment = path . pop ( ) ; parent = findNodeAtLocation ( root , path ) ; if ( parent === void 0 && value !== void 0 ) { if ( typeof lastSegment === 'string' ) { value = { [ lastSegment ] : value } ; } else { value = [ value ] ; } } else { break ; } } if ( ! parent ) { if ( value === void 0 ) { throw new Error ( 'Can not delete in empty document' ) ; } return withFormatting ( text , { offset : root ? root . offset : 0 , length : root ? root . length : 0 , content : JSON . stringify ( value ) } , options ) ; } else if ( parent . type === 'object' && typeof lastSegment === 'string' && Array . isArray ( parent . children ) ) { let existing = findNodeAtLocation ( parent , [ lastSegment ] ) ; if ( existing !== void 0 ) { if ( value === void 0 ) { if ( ! existing . parent ) { throw new Error ( 'Malformed AST' ) ; } let propertyIndex = parent . children . indexOf ( existing . parent ) ; let removeBegin ; let removeEnd = existing . parent . offset + existing . parent . length ; if ( propertyIndex > 0 ) { let previous = parent . children [ propertyIndex - 1 ] ; removeBegin = previous . offset + previous . length ; } else { removeBegin = parent . offset + 1 ; if ( parent . children . length > 1 ) { let next = parent . children [ 1 ] ; removeEnd = next . offset ; } } return withFormatting ( text , { offset : removeBegin , length : removeEnd - removeBegin , content : '' } , options ) ; } else { return withFormatting ( text , { offset : existing . offset , length : existing . length , content : JSON . stringify ( value ) } , options ) ; } } else { if ( value === void 0 ) { return [ ] ; } let newProperty = `${ JSON . stringify ( lastSegment ) }: ${ JSON . stringify ( value ) }` ; let index = options . getInsertionIndex ? options . getInsertionIndex ( parent . children . map ( p => p . children ! [ 0 ] . value ) ) : parent . children . length ; let edit ; if ( index > 0 ) { let previous = parent . children [ index - 1 ] ; edit = { offset : previous . offset + previous . length , length : 0 , content : ',' + newProperty } ; } else if ( parent . children . length === 0 ) { edit = { offset : parent . offset + 1 , length : 0 , content : newProperty } ; } else { edit = { offset : parent . offset + 1 , length : 0 , content : newProperty + ',' } ; } return withFormatting ( text , edit , options ) ; } } else if ( parent . type === 'array' && typeof lastSegment === 'number' && Array . isArray ( parent . children ) ) { let insertIndex = lastSegment ; if ( insertIndex === - 1 ) { let newProperty = `${ JSON . stringify ( value ) }` ; let edit ; if ( parent . children . length === 0 ) { edit = { offset : parent . offset + 1 , length : 0 , content : newProperty } ; } else { let previous = parent . children [ parent . children . length - 1 ] ; edit = { offset : previous . offset + previous . length , length : 0 , content : ',' + newProperty } ; } return withFormatting ( text , edit , options ) ; } else if ( value === void 0 && parent . children . length >= 0 ) { let removalIndex = lastSegment ; let toRemove = parent . children [ removalIndex ] ; let edit ; if ( parent . children . length === 1 ) { edit = { offset : parent . offset + 1 , length : parent . length - 2 , content : '' } ; } else if ( parent . children . length - 1 === removalIndex ) { let previous = parent . children [ removalIndex - 1 ] ; let offset = previous . offset + previous . length ; let parentEndOffset = parent . offset + parent . length ; edit = { offset , length : parentEndOffset - 2 - offset , content : '' } ; } else { edit = { offset : toRemove . offset , length : parent . children [ removalIndex + 1 ] . offset - toRemove . offset , content : '' } ; } return withFormatting ( text , edit , options ) ; } else if ( value !== void 0 ) { let edit ; const newProperty = `${ JSON . stringify ( value ) }` ; if ( ! options . isArrayInsertion && parent . children . length > lastSegment ) { let toModify = parent . children [ lastSegment ] ; edit = { offset : toModify . offset , length : toModify . length , content : newProperty } ; } else if ( parent . children . length === 0 || lastSegment === 0 ) { edit = { offset : parent . offset + 1 , length : 0 , content : parent . children . length === 0 ? newProperty : newProperty + ',' } ; } else { const index = lastSegment > parent . children . length ? parent . children . length : lastSegment ; const previous = parent . children [ index - 1 ] ; edit = { offset : previous . offset + previous . length , length : 0 , content : ',' + newProperty } ; } return withFormatting ( text , edit , options ) ; } else { throw new Error ( `Can not ${ value === void 0 ? 'remove' : ( options . isArrayInsertion ? 'insert' : 'modify' ) } Array index ${ insertIndex } as length is not sufficient` ) ; } } else { throw new Error ( `Can not add ${ typeof lastSegment !== 'number' ? 'index' : 'property' } to parent of type ${ parent . type }` ) ; } } function withFormatting ( text , edit , options ) { if ( ! options . formattingOptions ) { return [ edit ] ; } let newText = applyEdit ( text , edit ) ; let begin = edit . offset ; let end = edit . offset + edit . content . length ; if ( edit . length === 0 || edit . content . length === 0 ) { while ( begin > 0 && ! isEOL ( newText , begin - 1 ) ) { begin -- ; } while ( end < newText . length && ! isEOL ( newText , end ) ) { end ++ ; } } let edits = format ( newText , { offset : begin , length : end - begin } , options . formattingOptions ) ; for ( let i = edits . length - 1 ; i >= 0 ; i -- ) { let edit = edits [ i ] ; newText = applyEdit ( newText , edit ) ; begin = Math . min ( begin , edit . offset ) ; end = Math . max ( end , edit . offset + edit . length ) ; end += edit . content . length - edit . length ; } let editLength = text . length - ( newText . length - end ) - begin ; return [ { offset : begin , length : editLength , content : newText . substring ( begin , end ) } ] ; } export function applyEdit ( text , edit ) { return text . substring ( 0 , edit . offset ) + edit . content + text . substring ( edit . offset + edit . length ) ; } export function isWS ( text , offset ) { return '\r\n \t' . indexOf ( text . charAt ( offset ) ) !== - 1 ; }