import assert from "assert" ; import * as types from "ast-types" ; const b = types . builders ; const isObject = types . builtInTypes . object ; const isArray = types . builtInTypes . array ; import { normalize as normalizeOptions } from "./options" ; import { fromString } from "./lines" ; import { attach as attachComments } from "./comments" ; import * as util from "./util" ; import { Options } from "./options" ; export function parse ( source , options ? ) { options = normalizeOptions ( options ) ; const lines = fromString ( source , options ) ; const sourceWithoutTabs = lines . toString ( { tabWidth : options . tabWidth , reuseWhitespace : false , useTabs : false , } ) ; let comments = [ ] ; const ast = options . parser . parse ( sourceWithoutTabs , { jsx : true , loc : true , locations : true , range : options . range , comment : true , onComment : comments , tolerant : util . getOption ( options , "tolerant" , true ) , ecmaVersion : 6 , sourceType : util . getOption ( options , "sourceType" , "module" ) , } ) ; const tokens = Array . isArray ( ast . tokens ) ? ast . tokens : require ( "esprima" ) . tokenize ( sourceWithoutTabs , { loc : true , } ) ; delete ast . tokens ; tokens . forEach ( function ( token ) { if ( typeof token . value !== "string" ) { token . value = lines . sliceString ( token . loc . start , token . loc . end ) ; } } ) ; if ( Array . isArray ( ast . comments ) ) { comments = ast . comments ; delete ast . comments ; } if ( ast . loc ) { util . fixFaultyLocations ( ast , lines ) ; } else { ast . loc = { start : lines . firstPos ( ) , end : lines . lastPos ( ) , } ; } ast . loc . lines = lines ; ast . loc . indent = 0 ; let file ; let program ; if ( ast . type === "Program" ) { program = ast ; file = b . file ( ast , options . sourceFileName || null ) ; file . loc = { start : lines . firstPos ( ) , end : lines . lastPos ( ) , lines : lines , indent : 0 , } as any ; } else if ( ast . type === "File" ) { file = ast ; program = file . program ; } if ( options . tokens ) { file . tokens = tokens ; } const trueProgramLoc = util . getTrueLoc ( { type : program . type , loc : program . loc , body : [ ] , comments , } , lines ) ; program . loc . start = trueProgramLoc . start ; program . loc . end = trueProgramLoc . end ; attachComments ( comments , program . body . length ? file . program : file , lines ) ; return new TreeCopier ( lines , tokens ) . copy ( file ) ; } interface TreeCopierType { lines : any ; tokens : any [ ] ; startTokenIndex : number ; endTokenIndex : number ; indent : number ; seen : Map < any , any > ; copy ( node ) : any ; findTokenRange ( loc ) : any ; } interface TreeCopierConstructor { new ( lines , tokens ) : TreeCopierType ; } const TreeCopier = ( function TreeCopier ( this , lines , tokens ) { assert . ok ( this instanceof TreeCopier ) ; this . lines = lines ; this . tokens = tokens ; this . startTokenIndex = 0 ; this . endTokenIndex = tokens . length ; this . indent = 0 ; this . seen = new Map ( ) ; } as any ) as TreeCopierConstructor ; const TCp = TreeCopier . prototype ; TCp . copy = function ( node ) { if ( this . seen . has ( node ) ) { return this . seen . get ( node ) ; } if ( isArray . check ( node ) ) { const copy = new Array ( node . length ) ; this . seen . set ( node , copy ) ; node . forEach ( function ( this , item , i ) { copy [ i ] = this . copy ( item ) ; } , this ) ; return copy ; } if ( ! isObject . check ( node ) ) { return node ; } util . fixFaultyLocations ( node , this . lines ) ; const copy = Object . create ( Object . getPrototypeOf ( node ) , { original : { value : node , configurable : false , enumerable : false , writable : true , } , } ) ; this . seen . set ( node , copy ) ; const loc = node . loc ; const oldIndent = this . indent ; let newIndent = oldIndent ; const oldStartTokenIndex = this . startTokenIndex ; const oldEndTokenIndex = this . endTokenIndex ; if ( loc ) { if ( node . type === "Block" || node . type === "Line" || node . type === "CommentBlock" || node . type === "CommentLine" || this . lines . isPrecededOnlyByWhitespace ( loc . start ) ) { newIndent = this . indent = loc . start . column ; } loc . lines = this . lines ; loc . tokens = this . tokens ; loc . indent = newIndent ; this . findTokenRange ( loc ) ; } const keys = Object . keys ( node ) ; const keyCount = keys . length ; for ( let i = 0 ; i < keyCount ; ++ i ) { const key = keys [ i ] ; if ( key === "loc" ) { copy [ key ] = node [ key ] ; } else if ( key === "tokens" && node . type === "File" ) { copy [ key ] = node [ key ] ; } else { copy [ key ] = this . copy ( node [ key ] ) ; } } this . indent = oldIndent ; this . startTokenIndex = oldStartTokenIndex ; this . endTokenIndex = oldEndTokenIndex ; return copy ; } ; TCp . findTokenRange = function ( loc ) { while ( this . startTokenIndex > 0 ) { const token = loc . tokens [ this . startTokenIndex ] ; if ( util . comparePos ( loc . start , token . loc . start ) < 0 ) { -- this . startTokenIndex ; } else break ; } while ( this . endTokenIndex < loc . tokens . length ) { const token = loc . tokens [ this . endTokenIndex ] ; if ( util . comparePos ( token . loc . end , loc . end ) < 0 ) { ++ this . endTokenIndex ; } else break ; } while ( this . startTokenIndex < this . endTokenIndex ) { const token = loc . tokens [ this . startTokenIndex ] ; if ( util . comparePos ( token . loc . start , loc . start ) < 0 ) { ++ this . startTokenIndex ; } else break ; } loc . start . token = this . startTokenIndex ; while ( this . endTokenIndex > this . startTokenIndex ) { const token = loc . tokens [ this . endTokenIndex - 1 ] ; if ( util . comparePos ( loc . end , token . loc . end ) < 0 ) { -- this . endTokenIndex ; } else break ; } loc . end . token = this . endTokenIndex ; } ;