preprocessor.ts
1 import { HelperNames } from "../helpers" 2 import { swapStrings, isAlphaNumeric } from "../utilities" 3 import { ProcessOptions } from "../types" 4 5 const FUNCTION_CASES = ["#", "else", "/"] 6 7 export enum PreprocessorNames { 8 SWAP_TO_DOT = "swap-to-dot-notation", 9 FIX_FUNCTIONS = "fix-functions", 10 FINALISE = "finalise", 11 NORMALIZE_SPACES = "normalize-spaces", 12 } 13 14 export type PreprocessorFn = ( 15 statement: string, 16 opts?: ProcessOptions 17 ) => string 18 19 export class Preprocessor { 20 name: string 21 private readonly fn: PreprocessorFn 22 23 constructor(name: PreprocessorNames, fn: PreprocessorFn) { 24 this.name = name 25 this.fn = fn 26 } 27 28 process(fullString: string, statement: string, opts: ProcessOptions) { 29 const output = this.fn(statement, opts) 30 const idx = fullString.indexOf(statement) 31 return swapStrings(fullString, idx, statement.length, output) 32 } 33 } 34 35 export const processors = [ 36 new Preprocessor(PreprocessorNames.SWAP_TO_DOT, (statement: string) => { 37 let startBraceIdx = statement.indexOf("[") 38 let lastIdx = 0 39 while (startBraceIdx !== -1) { 40 // if the character previous to the literal specifier is alphanumeric this should happen 41 if (isAlphaNumeric(statement.charAt(startBraceIdx - 1))) { 42 statement = swapStrings(statement, startBraceIdx + lastIdx, 1, ".[") 43 } 44 lastIdx = startBraceIdx + 1 45 const nextBraceIdx = statement.substring(lastIdx + 1).indexOf("[") 46 startBraceIdx = nextBraceIdx > 0 ? lastIdx + 1 + nextBraceIdx : -1 47 } 48 return statement 49 }), 50 51 new Preprocessor(PreprocessorNames.FIX_FUNCTIONS, (statement: string) => { 52 for (let specialCase of FUNCTION_CASES) { 53 const toFind = `{ ${specialCase}`, 54 replacement = `{${specialCase}` 55 statement = statement.replace(new RegExp(toFind, "g"), replacement) 56 } 57 return statement 58 }), 59 60 new Preprocessor(PreprocessorNames.NORMALIZE_SPACES, (statement: string) => { 61 return statement.replace(/{{(\s{2,})/g, "{{ ") 62 }), 63 new Preprocessor( 64 PreprocessorNames.FINALISE, 65 (statement: string, opts?: ProcessOptions) => { 66 const noHelpers = opts?.noHelpers 67 const helpersEnabled = !noHelpers 68 let insideStatement = statement.slice(2, statement.length - 2) 69 if (insideStatement.charAt(0) === " ") { 70 insideStatement = insideStatement.slice(1) 71 } 72 if (insideStatement.charAt(insideStatement.length - 1) === " ") { 73 insideStatement = insideStatement.slice(0, insideStatement.length - 1) 74 } 75 const possibleHelper = insideStatement.split(" ")[0] 76 // function helpers can't be wrapped 77 for (let specialCase of FUNCTION_CASES) { 78 if (possibleHelper.includes(specialCase)) { 79 return statement 80 } 81 } 82 const testHelper = possibleHelper.trim().toLowerCase() 83 if ( 84 helpersEnabled && 85 !opts?.disabledHelpers?.includes(testHelper) && 86 HelperNames().some(option => testHelper === option.toLowerCase()) 87 ) { 88 insideStatement = `(${insideStatement})` 89 } 90 return `{{ all ${insideStatement} }}` 91 } 92 ), 93 ]