diff --git a/packages/string-templates/src/conversion/index.ts b/packages/string-templates/src/conversion/index.ts index 1fb8d97467..d69576e940 100644 --- a/packages/string-templates/src/conversion/index.ts +++ b/packages/string-templates/src/conversion/index.ts @@ -1,11 +1,11 @@ import { getJsHelperList } from "../helpers" -function getLayers(fullBlock) { +function getLayers(fullBlock: string): string[] { let layers = [] while (fullBlock.length) { const start = fullBlock.lastIndexOf("("), end = fullBlock.indexOf(")") - let layer + let layer: string if (start === -1 || end === -1) { layer = fullBlock.trim() fullBlock = "" @@ -21,7 +21,7 @@ function getLayers(fullBlock) { return layers } -function getVariable(variableName) { +function getVariable(variableName: string) { if (!variableName || typeof variableName !== "string") { return variableName } @@ -47,10 +47,12 @@ function getVariable(variableName) { return `$("${variableName}")` } -function buildList(parts, value) { +function buildList(parts: any[], value: any) { function build() { return parts - .map(part => (part.startsWith("helper") ? part : getVariable(part))) + .map((part: string) => + part.startsWith("helper") ? part : getVariable(part) + ) .join(", ") } if (!value) { @@ -60,12 +62,12 @@ function buildList(parts, value) { } } -function splitBySpace(layer) { +function splitBySpace(layer: string) { const parts = [] let started = null, endChar = null, last = 0 - function add(str) { + function add(str: string) { const startsWith = ["]"] while (startsWith.indexOf(str.substring(0, 1)) !== -1) { str = str.substring(1, str.length) @@ -103,7 +105,7 @@ function splitBySpace(layer) { return parts } -export function convertHBSBlock(block, blockNumber) { +export function convertHBSBlock(block: string, blockNumber: number) { const braceLength = block[2] === "{" ? 3 : 2 block = block.substring(braceLength, block.length - braceLength).trim() const layers = getLayers(block) diff --git a/packages/string-templates/src/index.ts b/packages/string-templates/src/index.ts index 7d0f198772..67ec194e84 100644 --- a/packages/string-templates/src/index.ts +++ b/packages/string-templates/src/index.ts @@ -1,4 +1,4 @@ -import { createContext, runInNewContext } from "vm" +import { Context, createContext, runInNewContext } from "vm" import { create } from "handlebars" import { registerAll, registerMinimum } from "./helpers/index" import { preprocess, postprocess } from "./processors" @@ -15,6 +15,7 @@ import { setJSRunner, removeJSRunner } from "./helpers/javascript" import { helpersToRemoveForJs } from "./helpers/list" import manifest from "../manifest.json" +import { ProcessOptions } from "./types" export { setJSRunner, setOnErrorLog } from "./helpers/javascript" @@ -22,7 +23,7 @@ const hbsInstance = create() registerAll(hbsInstance) const hbsInstanceNoHelpers = create() registerMinimum(hbsInstanceNoHelpers) -const defaultOpts = { +const defaultOpts: ProcessOptions = { noHelpers: false, cacheTemplates: false, noEscaping: false, @@ -33,7 +34,7 @@ const defaultOpts = { /** * Utility function to check if the object is valid. */ -function testObject(object) { +function testObject(object: any) { // JSON stringify will fail if there are any cycles, stops infinite recursion try { JSON.stringify(object) @@ -46,7 +47,7 @@ function testObject(object) { * Creates a HBS template function for a given string, and optionally caches it. */ let templateCache = {} -function createTemplate(string, opts) { +function createTemplate(string: string, opts: ProcessOptions) { opts = { ...defaultOpts, ...opts } // Finalising adds a helper, can't do this with no helpers @@ -82,7 +83,11 @@ function createTemplate(string, opts) { * @param {object|undefined} [opts] optional - specify some options for processing. * @returns {Promise} The structure input, as fully updated as possible. */ -export async function processObject(object, context, opts?) { +export async function processObject( + object: { [x: string]: any }, + context: object, + opts?: { noHelpers?: boolean; escapeNewlines?: boolean; onlyFound?: boolean } +) { testObject(object) for (let key of Object.keys(object || {})) { if (object[key] != null) { @@ -123,7 +128,11 @@ export async function processString( * @param {object|undefined} [opts] optional - specify some options for processing. * @returns {object|array} The structure input, as fully updated as possible. */ -export function processObjectSync(object, context, opts) { +export function processObjectSync( + object: { [x: string]: any }, + context: any, + opts: any +) { testObject(object) for (let key of Object.keys(object || {})) { let val = object[key] @@ -144,13 +153,17 @@ export function processObjectSync(object, context, opts) { * @param {object|undefined} [opts] optional - specify some options for processing. * @returns {string} The enriched string, all templates should have been replaced if they can be. */ -export function processStringSync(string, context, opts?) { +export function processStringSync( + string: string, + context: object, + opts?: { noHelpers?: boolean; escapeNewlines?: boolean; onlyFound: any } +) { // Take a copy of input in case of error const input = string if (typeof string !== "string") { throw "Cannot process non-string types." } - function process(stringPart) { + function process(stringPart: string) { const template = createTemplate(stringPart, opts) const now = Math.floor(Date.now() / 1000) * 1000 return postprocess( @@ -186,7 +199,7 @@ export function processStringSync(string, context, opts?) { * this function will find any double braces and switch to triple. * @param string the string to have double HBS statements converted to triple. */ -export function disableEscaping(string) { +export function disableEscaping(string: string) { const matches = findDoubleHbsInstances(string) if (matches == null) { return string @@ -207,7 +220,7 @@ export function disableEscaping(string) { * @param {string} property The property which is to be wrapped. * @returns {string} The wrapped property ready to be added to a templating string. */ -export function makePropSafe(property) { +export function makePropSafe(property: any) { return `[${property}]`.replace("[[", "[").replace("]]", "]") } @@ -217,7 +230,7 @@ export function makePropSafe(property) { * @param [opts] optional - specify some options for processing. * @returns {boolean} Whether or not the input string is valid. */ -export function isValid(string, opts?) { +export function isValid(string: any, opts?: any) { const validCases = [ "string", "number", @@ -268,7 +281,7 @@ export function getManifest() { * @param handlebars the HBS expression to check * @returns {boolean} whether the expression is JS or not */ -export function isJSBinding(handlebars) { +export function isJSBinding(handlebars: any) { return decodeJSBinding(handlebars) != null } @@ -277,7 +290,7 @@ export function isJSBinding(handlebars) { * @param javascript the JS code to encode * @returns {string} the JS HBS expression */ -export function encodeJSBinding(javascript) { +export function encodeJSBinding(javascript: string) { return `{{ js "${btoa(javascript)}" }}` } @@ -286,7 +299,7 @@ export function encodeJSBinding(javascript) { * @param handlebars the JS HBS expression * @returns {string|null} the raw JS code */ -export function decodeJSBinding(handlebars) { +export function decodeJSBinding(handlebars: string) { if (!handlebars || typeof handlebars !== "string") { return null } @@ -311,7 +324,7 @@ export function decodeJSBinding(handlebars) { * @param {string[]} strings The strings to look for. * @returns {boolean} Will return true if all strings found in HBS statement. */ -export function doesContainStrings(template, strings) { +export function doesContainStrings(template: string, strings: any[]) { let regexp = new RegExp(FIND_HBS_REGEX) let matches = template.match(regexp) if (matches == null) { @@ -341,7 +354,7 @@ export function doesContainStrings(template, strings) { * @param {string} string The string to search within. * @return {string[]} The found HBS blocks. */ -export function findHBSBlocks(string) { +export function findHBSBlocks(string: string) { if (!string || typeof string !== "string") { return [] } @@ -362,11 +375,11 @@ export function findHBSBlocks(string) { * @param {string} string The word or sentence to search for. * @returns {boolean} The this return true if the string is found, false if not. */ -export function doesContainString(template, string) { +export function doesContainString(template: any, string: any) { return doesContainStrings(template, [string]) } -export function convertToJS(hbs) { +export function convertToJS(hbs: string) { const blocks = findHBSBlocks(hbs) let js = "return `", prevBlock: string | null = null @@ -407,7 +420,7 @@ function defaultJSSetup() { /** * Use polyfilled vm to run JS scripts in a browser Env */ - setJSRunner((js, context) => { + setJSRunner((js: string, context: Context) => { context = { ...context, alert: undefined, diff --git a/packages/string-templates/src/processors/index.ts b/packages/string-templates/src/processors/index.ts index e1743b2f4c..308ac5adf4 100644 --- a/packages/string-templates/src/processors/index.ts +++ b/packages/string-templates/src/processors/index.ts @@ -1,8 +1,9 @@ import { FIND_HBS_REGEX } from "../utilities" import * as preprocessor from "./preprocessor" import * as postprocessor from "./postprocessor" +import { ProcessOptions } from "../types" -function process(output, processors, opts?) { +function process(output: string, processors: any[], opts?: ProcessOptions) { for (let processor of processors) { // if a literal statement has occurred stop if (typeof output !== "string") { @@ -21,7 +22,7 @@ function process(output, processors, opts?) { return output } -export function preprocess(string, opts) { +export function preprocess(string: string, opts: ProcessOptions) { let processors = preprocessor.processors if (opts.noFinalise) { processors = processors.filter( @@ -30,7 +31,7 @@ export function preprocess(string, opts) { } return process(string, processors, opts) } -export function postprocess(string) { +export function postprocess(string: string) { let processors = postprocessor.processors return process(string, processors) } diff --git a/packages/string-templates/src/processors/postprocessor.ts b/packages/string-templates/src/processors/postprocessor.ts index 39125b225a..6f7260718b 100644 --- a/packages/string-templates/src/processors/postprocessor.ts +++ b/packages/string-templates/src/processors/postprocessor.ts @@ -6,45 +6,51 @@ export const PostProcessorNames = { /* eslint-disable no-unused-vars */ class Postprocessor { - name - private fn + name: string + private fn: any - constructor(name, fn) { + constructor(name: string, fn: any) { this.name = name this.fn = fn } - process(statement) { + process(statement: any) { return this.fn(statement) } } export const processors = [ - new Postprocessor(PostProcessorNames.CONVERT_LITERALS, statement => { - if (typeof statement !== "string" || !statement.includes(LITERAL_MARKER)) { - return statement + new Postprocessor( + PostProcessorNames.CONVERT_LITERALS, + (statement: string) => { + if ( + typeof statement !== "string" || + !statement.includes(LITERAL_MARKER) + ) { + return statement + } + const splitMarkerIndex = statement.indexOf("-") + const type = statement.substring(12, splitMarkerIndex) + const value = statement.substring( + splitMarkerIndex + 1, + statement.length - 2 + ) + switch (type) { + case "string": + return value + case "number": + return parseFloat(value) + case "boolean": + return value === "true" + case "object": + return JSON.parse(value) + case "js_result": + // We use the literal helper to process the result of JS expressions + // as we want to be able to return any types. + // We wrap the value in an abject to be able to use undefined properly. + return JSON.parse(value).data + } + return value } - const splitMarkerIndex = statement.indexOf("-") - const type = statement.substring(12, splitMarkerIndex) - const value = statement.substring( - splitMarkerIndex + 1, - statement.length - 2 - ) - switch (type) { - case "string": - return value - case "number": - return parseFloat(value) - case "boolean": - return value === "true" - case "object": - return JSON.parse(value) - case "js_result": - // We use the literal helper to process the result of JS expressions - // as we want to be able to return any types. - // We wrap the value in an abject to be able to use undefined properly. - return JSON.parse(value).data - } - return value - }), + ), ] diff --git a/packages/string-templates/src/processors/preprocessor.ts b/packages/string-templates/src/processors/preprocessor.ts index 1fcbac629f..141b2be3a9 100644 --- a/packages/string-templates/src/processors/preprocessor.ts +++ b/packages/string-templates/src/processors/preprocessor.ts @@ -14,7 +14,7 @@ class Preprocessor { name: string private fn: any - constructor(name, fn) { + constructor(name: string, fn: any) { this.name = name this.fn = fn } @@ -27,7 +27,7 @@ class Preprocessor { } export const processors = [ - new Preprocessor(PreprocessorNames.SWAP_TO_DOT, statement => { + new Preprocessor(PreprocessorNames.SWAP_TO_DOT, (statement: string) => { let startBraceIdx = statement.indexOf("[") let lastIdx = 0 while (startBraceIdx !== -1) { @@ -42,7 +42,7 @@ export const processors = [ return statement }), - new Preprocessor(PreprocessorNames.FIX_FUNCTIONS, statement => { + new Preprocessor(PreprocessorNames.FIX_FUNCTIONS, (statement: string) => { for (let specialCase of FUNCTION_CASES) { const toFind = `{ ${specialCase}`, replacement = `{${specialCase}` @@ -51,29 +51,32 @@ export const processors = [ return statement }), - new Preprocessor(PreprocessorNames.FINALISE, (statement, opts) => { - const noHelpers = opts && opts.noHelpers - let insideStatement = statement.slice(2, statement.length - 2) - if (insideStatement.charAt(0) === " ") { - insideStatement = insideStatement.slice(1) - } - if (insideStatement.charAt(insideStatement.length - 1) === " ") { - insideStatement = insideStatement.slice(0, insideStatement.length - 1) - } - const possibleHelper = insideStatement.split(" ")[0] - // function helpers can't be wrapped - for (let specialCase of FUNCTION_CASES) { - if (possibleHelper.includes(specialCase)) { - return statement + new Preprocessor( + PreprocessorNames.FINALISE, + (statement: string, opts: { noHelpers: any }) => { + const noHelpers = opts && opts.noHelpers + let insideStatement = statement.slice(2, statement.length - 2) + if (insideStatement.charAt(0) === " ") { + insideStatement = insideStatement.slice(1) } + if (insideStatement.charAt(insideStatement.length - 1) === " ") { + insideStatement = insideStatement.slice(0, insideStatement.length - 1) + } + const possibleHelper = insideStatement.split(" ")[0] + // function helpers can't be wrapped + for (let specialCase of FUNCTION_CASES) { + if (possibleHelper.includes(specialCase)) { + return statement + } + } + const testHelper = possibleHelper.trim().toLowerCase() + if ( + !noHelpers && + HelperNames().some(option => testHelper === option.toLowerCase()) + ) { + insideStatement = `(${insideStatement})` + } + return `{{ all ${insideStatement} }}` } - const testHelper = possibleHelper.trim().toLowerCase() - if ( - !noHelpers && - HelperNames().some(option => testHelper === option.toLowerCase()) - ) { - insideStatement = `(${insideStatement})` - } - return `{{ all ${insideStatement} }}` - }), + ), ] diff --git a/packages/string-templates/src/types.ts b/packages/string-templates/src/types.ts new file mode 100644 index 0000000000..fc029f3b6a --- /dev/null +++ b/packages/string-templates/src/types.ts @@ -0,0 +1,7 @@ +export interface ProcessOptions { + cacheTemplates?: boolean + noEscaping?: boolean + noHelpers?: boolean + noFinalise?: boolean + escapeNewlines?: boolean +}