/ packages / string-templates / src / processors / preprocessor.ts
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  ]