import { parse , print } from 'recast' ; import traverse from 'ast-traverse' ; import * as espree from 'espree' ; import merge from 'lodash.merge' ; import selectorsLibrary from '../selectorsLibrary' ; import replaceString from './string' ; import cssVariablesLibrary from '../cssVariablesLibrary' ; import allRegex from './regex' ; import { Source } from '../allWarnings' ; import { EspreeOptions } from './html' ; export interface ReplaceJsOptions { sourceFile ? : string ; espreeOptions ? : EspreeOptions ; triggerClassAttributes ? : string [ ] ; triggerIdAttributes ? : string [ ] ; } const makeSource = ( node , file ) : Source => { const Position = node . loc . start . constructor ; const currentLine = node . loc . start . line ; const sourceLine = node . loc . lines . sliceString ( new Position ( currentLine , 0 ) , new Position ( currentLine , node . loc . lines . getLineLength ( currentLine ) ) ) ; return { file , line : currentLine , text : sourceLine } ; } ; const replaceJs = ( code , espreeOptions = { } ) : string => { const regex = selectorsLibrary . getAllRegex ( ) ; const options = merge ( { ecmaVersion : 'latest' , sourceType : 'module' , ecmaFeatures : { jsx : true , } , } , espreeOptions ) ; const ast = parse ( code , { parser : { parse : ( source ) => ( espree . parse ( source , { ... options , range : true , loc : true , comment : true , attachComment : true , tokens : true , } ) ) , } , } ) ; traverse ( ast , { pre : ( node , parentNode ) => { let isJSX = false ; if ( node . type === 'BinaryExpression' && node . operator === 'in' ) { return false ; } if ( parentNode && parentNode . type === 'JSXAttribute' && [ 'class' , 'id' , 'for' ] . includes ( parentNode . name . name ) ) { isJSX = true ; } if ( node . type === 'TemplateElement' && 'raw' in node . value ) { const source = makeSource ( node , options . sourceFile ) ; const raw = node . value . raw . replace ( allRegex . templateSelectors , ( txt , p1 , p2 , p3 ) => { const newValue = ` ${ p3 } ` ; const selectorLib = p1 === 'class' ? selectorsLibrary . getClassSelector ( ) : selectorsLibrary . getIdSelector ( ) ; const selectorLibRegex = selectorLib . getAll ( { regex : true } ) ; if ( ! ( selectorLibRegex instanceof RegExp ) ) { return newValue ; } const replacedAttr = newValue . replace ( newValue , ( match ) => replaceString ( match , selectorLibRegex , { isJSX , classOnly : p1 === 'class' , countStats : false , source , } ) ) ; return `${ p1 }=${ p2 + replacedAttr . slice ( 1 , replacedAttr . length - 1 ) + p2 }` ; } ) ; node . value . raw = raw ; } else if ( node . type === 'Literal' && typeof node . value === 'string' ) { const source = makeSource ( node , options . sourceFile ) ; node . raw = node . raw . replace ( node . raw , ( match ) => replaceString ( match , regex , { isJSX , source } ) ) ; const newValue = ` ${ node . value } ` ; const replacedCssSelectors = newValue . replace ( newValue , ( match ) => replaceString ( match , regex , { isJSX , countStats : false , source } ) ) ; const replacedCssVariables = replacedCssSelectors . replace ( allRegex . cssVariables , ( match ) => ( cssVariablesLibrary . get ( match , { source } ) ) ) ; node . value = replacedCssVariables . slice ( 1 , replacedCssVariables . length - 1 ) ; } return true ; } , } ) ; return print ( ast ) . code ; } ; export default replaceJs ;