import assert from "assert" ; import * as linesModule from "./lines" ; import * as types from "ast-types" ; const Printable = types . namedTypes . Printable ; const Expression = types . namedTypes . Expression ; const ReturnStatement = types . namedTypes . ReturnStatement ; const SourceLocation = types . namedTypes . SourceLocation ; import { comparePos , copyPos , getUnionOfKeys } from "./util" ; import FastPath from "./fast-path" ; const isObject = types . builtInTypes . object ; const isArray = types . builtInTypes . array ; const isString = types . builtInTypes . string ; const riskyAdjoiningCharExp = /[0-9a-z_$]/i ; interface PatcherType { replace ( loc , lines ) : any ; get ( loc ? ) : any ; tryToReprintComments ( newNode , oldNode , print ) : any ; deleteComments ( node ) : any ; } interface PatcherConstructor { new ( lines ) : PatcherType ; } const Patcher = ( function Patcher ( this , lines ) { assert . ok ( this instanceof Patcher ) ; assert . ok ( lines instanceof linesModule . Lines ) ; const self = this , replacements = [ ] ; self . replace = function ( loc , lines ) { if ( isString . check ( lines ) ) lines = linesModule . fromString ( lines ) ; replacements . push ( { lines : lines , start : loc . start , end : loc . end , } ) ; } ; self . get = function ( loc ) { loc = loc || { start : { line : 1 , column : 0 } , end : { line : lines . length , column : lines . getLineLength ( lines . length ) } , } ; let sliceFrom = loc . start , toConcat = [ ] ; function pushSlice ( from , to ) { assert . ok ( comparePos ( from , to ) <= 0 ) ; toConcat . push ( lines . slice ( from , to ) ) ; } replacements . sort ( ( a , b ) => comparePos ( a . start , b . start ) ) . forEach ( function ( rep ) { if ( comparePos ( sliceFrom , rep . start ) > 0 ) { } else { pushSlice ( sliceFrom , rep . start ) ; toConcat . push ( rep . lines ) ; sliceFrom = rep . end ; } } ) ; pushSlice ( sliceFrom , loc . end ) ; return linesModule . concat ( toConcat ) ; } ; } as any ) as PatcherConstructor ; export { Patcher } ; const Pp = Patcher . prototype ; Pp . tryToReprintComments = function ( newNode , oldNode , print ) { const patcher = this ; if ( ! newNode . comments && ! oldNode . comments ) { return true ; } const newPath = FastPath . from ( newNode ) ; const oldPath = FastPath . from ( oldNode ) ; newPath . stack . push ( "comments" , getSurroundingComments ( newNode ) ) ; oldPath . stack . push ( "comments" , getSurroundingComments ( oldNode ) ) ; const reprints = [ ] ; const ableToReprintComments = findArrayReprints ( newPath , oldPath , reprints ) ; if ( ableToReprintComments && reprints . length > 0 ) { reprints . forEach ( function ( reprint ) { const oldComment = reprint . oldPath . getValue ( ) ; assert . ok ( oldComment . leading || oldComment . trailing ) ; patcher . replace ( oldComment . loc , print ( reprint . newPath ) . indentTail ( oldComment . loc . indent ) ) ; } ) ; } return ableToReprintComments ; } ; function getSurroundingComments ( node ) { const result = [ ] ; if ( node . comments && node . comments . length > 0 ) { node . comments . forEach ( function ( comment ) { if ( comment . leading || comment . trailing ) { result . push ( comment ) ; } } ) ; } return result ; } Pp . deleteComments = function ( node ) { if ( ! node . comments ) { return ; } const patcher = this ; node . comments . forEach ( function ( comment ) { if ( comment . leading ) { patcher . replace ( { start : comment . loc . start , end : node . loc . lines . skipSpaces ( comment . loc . end , false , false ) , } , "" ) ; } else if ( comment . trailing ) { patcher . replace ( { start : node . loc . lines . skipSpaces ( comment . loc . start , true , false ) , end : comment . loc . end , } , "" ) ; } } ) ; } ; export function getReprinter ( path ) { assert . ok ( path instanceof FastPath ) ; const node = path . getValue ( ) ; if ( ! Printable . check ( node ) ) return ; const orig = ( node as any ) . original ; const origLoc = orig && orig . loc ; const lines = origLoc && origLoc . lines ; const reprints = [ ] ; if ( ! lines || ! findReprints ( path , reprints ) ) return ; return function ( print ) { const patcher = new Patcher ( lines ) ; reprints . forEach ( function ( reprint ) { const newNode = reprint . newPath . getValue ( ) ; const oldNode = reprint . oldPath . getValue ( ) ; SourceLocation . assert ( oldNode . loc , true ) ; const needToPrintNewPathWithComments = ! patcher . tryToReprintComments ( newNode , oldNode , print ) ; if ( needToPrintNewPathWithComments ) { patcher . deleteComments ( oldNode ) ; } let newLines = print ( reprint . newPath , { includeComments : needToPrintNewPathWithComments , avoidRootParens : oldNode . type === newNode . type && reprint . oldPath . hasParens ( ) , } ) . indentTail ( oldNode . loc . indent ) ; const nls = needsLeadingSpace ( lines , oldNode . loc , newLines ) ; const nts = needsTrailingSpace ( lines , oldNode . loc , newLines ) ; if ( nls || nts ) { const newParts = [ ] ; nls && newParts . push ( " " ) ; newParts . push ( newLines ) ; nts && newParts . push ( " " ) ; newLines = linesModule . concat ( newParts ) ; } patcher . replace ( oldNode . loc , newLines ) ; } ) ; const patchedLines = patcher . get ( origLoc ) . indentTail ( - orig . loc . indent ) ; if ( path . needsParens ( ) ) { return linesModule . concat ( [ "(" , patchedLines , ")" ] ) ; } return patchedLines ; } ; } function needsLeadingSpace ( oldLines , oldLoc , newLines ) { const posBeforeOldLoc = copyPos ( oldLoc . start ) ; const charBeforeOldLoc = oldLines . prevPos ( posBeforeOldLoc ) && oldLines . charAt ( posBeforeOldLoc ) ; const newFirstChar = newLines . charAt ( newLines . firstPos ( ) ) ; return ( charBeforeOldLoc && riskyAdjoiningCharExp . test ( charBeforeOldLoc ) && newFirstChar && riskyAdjoiningCharExp . test ( newFirstChar ) ) ; } function needsTrailingSpace ( oldLines , oldLoc , newLines ) { const charAfterOldLoc = oldLines . charAt ( oldLoc . end ) ; const newLastPos = newLines . lastPos ( ) ; const newLastChar = newLines . prevPos ( newLastPos ) && newLines . charAt ( newLastPos ) ; return ( newLastChar && riskyAdjoiningCharExp . test ( newLastChar ) && charAfterOldLoc && riskyAdjoiningCharExp . test ( charAfterOldLoc ) ) ; } function findReprints ( newPath , reprints ) { const newNode = newPath . getValue ( ) ; Printable . assert ( newNode ) ; const oldNode = newNode . original ; Printable . assert ( oldNode ) ; assert . deepEqual ( reprints , [ ] ) ; if ( newNode . type !== oldNode . type ) { return false ; } const oldPath = new FastPath ( oldNode ) ; const canReprint = findChildReprints ( newPath , oldPath , reprints ) ; if ( ! canReprint ) { reprints . length = 0 ; } return canReprint ; } function findAnyReprints ( newPath , oldPath , reprints ) { const newNode = newPath . getValue ( ) ; const oldNode = oldPath . getValue ( ) ; if ( newNode === oldNode ) return true ; if ( isArray . check ( newNode ) ) return findArrayReprints ( newPath , oldPath , reprints ) ; if ( isObject . check ( newNode ) ) return findObjectReprints ( newPath , oldPath , reprints ) ; return false ; } function findArrayReprints ( newPath , oldPath , reprints ) { const newNode = newPath . getValue ( ) ; const oldNode = oldPath . getValue ( ) ; if ( newNode === oldNode || newPath . valueIsDuplicate ( ) || oldPath . valueIsDuplicate ( ) ) { return true ; } isArray . assert ( newNode ) ; const len = newNode . length ; if ( ! ( isArray . check ( oldNode ) && oldNode . length === len ) ) return false ; for ( let i = 0 ; i < len ; ++ i ) { newPath . stack . push ( i , newNode [ i ] ) ; oldPath . stack . push ( i , oldNode [ i ] ) ; const canReprint = findAnyReprints ( newPath , oldPath , reprints ) ; newPath . stack . length -= 2 ; oldPath . stack . length -= 2 ; if ( ! canReprint ) { return false ; } } return true ; } function findObjectReprints ( newPath , oldPath , reprints ) { const newNode = newPath . getValue ( ) ; isObject . assert ( newNode ) ; if ( newNode . original === null ) { return false ; } const oldNode = oldPath . getValue ( ) ; if ( ! isObject . check ( oldNode ) ) return false ; if ( newNode === oldNode || newPath . valueIsDuplicate ( ) || oldPath . valueIsDuplicate ( ) ) { return true ; } if ( Printable . check ( newNode ) ) { if ( ! Printable . check ( oldNode ) ) { return false ; } const newParentNode = newPath . getParentNode ( ) ; const oldParentNode = oldPath . getParentNode ( ) ; if ( oldParentNode !== null && oldParentNode . type === "FunctionTypeAnnotation" && newParentNode !== null && newParentNode . type === "FunctionTypeAnnotation" ) { const oldNeedsParens = oldParentNode . params . length !== 1 || ! ! oldParentNode . params [ 0 ] . name ; const newNeedParens = newParentNode . params . length !== 1 || ! ! newParentNode . params [ 0 ] . name ; if ( ! oldNeedsParens && newNeedParens ) { return false ; } } if ( ( newNode as any ) . type === ( oldNode as any ) . type ) { const childReprints = [ ] ; if ( findChildReprints ( newPath , oldPath , childReprints ) ) { reprints . push . apply ( reprints , childReprints ) ; } else if ( oldNode . loc ) { reprints . push ( { oldPath : oldPath . copy ( ) , newPath : newPath . copy ( ) , } ) ; } else { return false ; } return true ; } if ( Expression . check ( newNode ) && Expression . check ( oldNode ) && oldNode . loc ) { reprints . push ( { oldPath : oldPath . copy ( ) , newPath : newPath . copy ( ) , } ) ; return true ; } return false ; } return findChildReprints ( newPath , oldPath , reprints ) ; } function findChildReprints ( newPath , oldPath , reprints ) { const newNode = newPath . getValue ( ) ; const oldNode = oldPath . getValue ( ) ; isObject . assert ( newNode ) ; isObject . assert ( oldNode ) ; if ( newNode . original === null ) { return false ; } if ( newPath . needsParens ( ) && ! oldPath . hasParens ( ) ) { return false ; } const keys = getUnionOfKeys ( oldNode , newNode ) ; if ( oldNode . type === "File" || newNode . type === "File" ) { delete keys . tokens ; } delete keys . loc ; const originalReprintCount = reprints . length ; for ( let k in keys ) { if ( k . charAt ( 0 ) === "_" ) { continue ; } newPath . stack . push ( k , types . getFieldValue ( newNode , k ) ) ; oldPath . stack . push ( k , types . getFieldValue ( oldNode , k ) ) ; const canReprint = findAnyReprints ( newPath , oldPath , reprints ) ; newPath . stack . length -= 2 ; oldPath . stack . length -= 2 ; if ( ! canReprint ) { return false ; } } if ( ReturnStatement . check ( newPath . getNode ( ) ) && reprints . length > originalReprintCount ) { return false ; } return true ; }