import { arrayify , last } from './helpers' ; import { parser } from './parser' ; import { isCloseTag , isComment , isOpenCloseTag , isOpenTag , isSelfClosingTag , } from './regexp' ; import type { Node , Parent , TraverseOptions , Traverser } from './types' ; export class Traverse { code : string ; private readonly _enter : Traverser ; constructor ( { code , enter } ) { this . code = code ; this . _enter = enter ; } combineLeftJsxNodes ( jsxNodes , parent ? ) { const start = jsxNodes [ 0 ] . position . start ; const end = { ... last ( jsxNodes ) . position . end } ; if ( parent && parent . position . indent ?. length > 0 ) { end . offset += parent . position . indent . reduce ( ( acc , indent , index ) => acc + ( index ? indent + 1 : 0 ) , 0 ) ; } return { type : 'jsx' , data : jsxNodes [ 0 ] . data , value : this . code . slice ( start . offset , end . offset ) , position : { start , end , } , } ; } combineJsxNodes ( nodes , parent ? ) { let offset = 0 ; let hasOpenTag = false ; const jsxNodes = [ ] ; const { length } = nodes ; return nodes . reduce < Node [ ] > ( ( acc , node , index ) => { if ( node . type === 'jsx' ) { const value = node . value ; if ( isOpenTag ( value ) ) { offset ++ ; hasOpenTag = true ; jsxNodes . push ( node ) ; } else { if ( isCloseTag ( value ) ) { offset -- ; jsxNodes . push ( node ) ; } else if ( isComment ( value ) || isSelfClosingTag ( value ) || isOpenCloseTag ( value ) ) { jsxNodes . push ( node ) ; } else { try { jsxNodes . push ( ... arrayify ( parser . normalizeJsxNode ( node , parent ) ) ) ; } catch { if ( ! index ) { offset ++ ; hasOpenTag = true ; } if ( offset ) { jsxNodes . push ( node ) ; } else { const { start } = node . position ; throw Object . assign ( new SyntaxError ( 'unknown jsx node: ' + JSON . stringify ( value ) ) , { lineNumber : start . line , column : start . column , index : start . offset , } ) ; } } } if ( ! offset ) { const firstOpenTagIndex = jsxNodes . findIndex ( node => typeof node . value === 'string' && isOpenTag ( node . value ) ) ; if ( firstOpenTagIndex === - 1 ) { if ( hasOpenTag ) { acc . push ( this . combineLeftJsxNodes ( jsxNodes , parent ) ) ; } else { acc . push ( ... jsxNodes ) ; } } else { acc . push ( ... jsxNodes . slice ( 0 , firstOpenTagIndex ) , this . combineLeftJsxNodes ( jsxNodes . slice ( firstOpenTagIndex ) , parent ) ) ; } jsxNodes . length = 0 ; } } } else if ( offset ) { jsxNodes . push ( node ) ; } else { acc . push ( node ) ; } if ( index === length - 1 && jsxNodes . length > 0 ) { acc . push ( this . combineLeftJsxNodes ( jsxNodes , parent ) ) ; } return acc ; } , [ ] ) ; } traverse ( node , parent ? ) { if ( ! node ) { return ; } if ( 'children' in node ) { const parent = node as Parent ; parent . children = this . combineJsxNodes ( parent . children , parent ) ; for ( const child of parent . children ) { this . traverse ( child , parent ) ; } } this . _enter ( node , parent ) ; } } export const traverse = ( root , options ) => new Traverse ( options ) . traverse ( root ) ;