diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index b72c09f88d..7cea43afd8 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -12,15 +12,16 @@ "test": "jest" }, "dependencies": { - "handlebars": "^4.7.6" + "handlebars": "^4.7.6", + "lodash": "^4.17.20" }, "devDependencies": { + "jest": "^26.6.3", "rollup": "^2.36.2", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-globals": "^1.4.0", "rollup-plugin-node-resolve": "^5.2.0", - "typescript": "^4.1.3", - "jest": "^26.6.3" + "typescript": "^4.1.3" } } diff --git a/packages/string-templates/src/custom/postprocessor.js b/packages/string-templates/src/custom/postprocessor.js index e69de29bb2..4dad27ab98 100644 --- a/packages/string-templates/src/custom/postprocessor.js +++ b/packages/string-templates/src/custom/postprocessor.js @@ -0,0 +1,6 @@ +class Postprocessor { + constructor(name, fn) { + this.name = name + this.fn = fn + } +} diff --git a/packages/string-templates/src/custom/preprocessor.js b/packages/string-templates/src/custom/preprocessor.js index ffd81f1693..4793cd2099 100644 --- a/packages/string-templates/src/custom/preprocessor.js +++ b/packages/string-templates/src/custom/preprocessor.js @@ -6,6 +6,12 @@ const { includesAny, } = require("../utilities") +const PreprocessorNames = { + SWAP_TO_DOT: "swap-to-dot-notation", + HANDLE_SPACES: "handle-spaces-in-properties", + FINALISE: "finalise", +} + class Preprocessor { constructor(name, fn) { this.name = name @@ -20,7 +26,7 @@ class Preprocessor { } const PROCESSORS = [ - new Preprocessor("swap-to-dot-notation", statement => { + new Preprocessor(PreprocessorNames.SWAP_TO_DOT, statement => { let startBraceIdx = statement.indexOf("[") let lastIdx = 0 while (startBraceIdx !== -1) { @@ -34,7 +40,7 @@ const PROCESSORS = [ return statement }), - new Preprocessor("handle-spaces-in-properties", statement => { + new Preprocessor(PreprocessorNames.HANDLE_SPACES, statement => { // exclude helpers and brackets, regex will only find double brackets const exclusions = HelperFunctions.concat(["{{", "}}"]) // find all the parts split by spaces @@ -62,7 +68,7 @@ const PROCESSORS = [ return statement.replace(/\[\[/g, "[").replace(/]]/g, "]") }), - new Preprocessor("finalise", statement => { + new Preprocessor(Preprocessor.FINALISE, statement => { let insideStatement = statement.slice(2, statement.length - 2) if (insideStatement.charAt(0) === " ") { insideStatement = insideStatement.slice(1) @@ -74,7 +80,7 @@ const PROCESSORS = [ insideStatement = `(${insideStatement})` } return `{{ all ${insideStatement} }}` - }) + }), ] /** diff --git a/packages/string-templates/src/helpers/index.js b/packages/string-templates/src/helpers/index.js index 768fe297af..b31bc8bb0d 100644 --- a/packages/string-templates/src/helpers/index.js +++ b/packages/string-templates/src/helpers/index.js @@ -27,6 +27,10 @@ const HELPERS = [ }), // this help is applied to all statements new Helper(HelperFunctionNames.ALL, value => { + // null/undefined values produce bad results + if (value == null) { + return "" + } let text = new SafeString(unescape(value).replace(/&/g, "&")) if (text == null || typeof text !== "string") { return text diff --git a/packages/string-templates/src/index.js b/packages/string-templates/src/index.js index 58ae887e20..c04bbf64f1 100644 --- a/packages/string-templates/src/index.js +++ b/packages/string-templates/src/index.js @@ -1,6 +1,8 @@ const handlebars = require("handlebars") const { registerAll } = require("./helpers/index") const { preprocess } = require("./custom/preprocessor") +const { cloneDeep } = require("lodash/fp") +const { removeNull } = require("./utilities") const hbsInstance = handlebars.create() registerAll(hbsInstance) @@ -78,6 +80,8 @@ module.exports.processObjectSync = (object, context) => { * @returns {string} The enriched string, all templates should have been replaced if they can be. */ module.exports.processStringSync = (string, context) => { + const clonedContext = removeNull(cloneDeep(context)) + // remove any null/undefined properties if (typeof string !== "string") { throw "Cannot process non-string types." } @@ -85,7 +89,7 @@ module.exports.processStringSync = (string, context) => { string = preprocess(string) // this does not throw an error when template can't be fulfilled, have to try correct beforehand template = hbsInstance.compile(string) - return template(context) + return template(clonedContext) } /** diff --git a/packages/string-templates/src/utilities.js b/packages/string-templates/src/utilities.js index bb35e94567..fe430a235d 100644 --- a/packages/string-templates/src/utilities.js +++ b/packages/string-templates/src/utilities.js @@ -13,3 +13,15 @@ module.exports.swapStrings = (string, start, length, swap) => { module.exports.includesAny = (string, options) => { return options.some(option => string.includes(option)) } + +// removes null and undefined +module.exports.removeNull = obj => { + return Object.fromEntries( + Object.entries(obj) + .filter(([key, value]) => value != null) + .map(([key, value]) => [ + key, + value === Object(value) ? module.exports.removeNull(value) : value, + ]) + ) +} diff --git a/packages/string-templates/test/basic.spec.js b/packages/string-templates/test/basic.spec.js index 6babce8f8c..55dc63a995 100644 --- a/packages/string-templates/test/basic.spec.js +++ b/packages/string-templates/test/basic.spec.js @@ -27,6 +27,14 @@ describe("Test that the string processing works correctly", () => { } expect(error).not.toBeNull() }) + + it("confirm that null properties are handled correctly", async () => { + const output = await processString("hello {{ name }} I am {{ name2 }}", { + name: undefined, + name2: null, + }) + expect(output).toBe("hello I am ") + }) }) describe("Test that the object processing works correctly", () => { diff --git a/packages/string-templates/yarn.lock b/packages/string-templates/yarn.lock index 36999ca6c6..96973ea41a 100644 --- a/packages/string-templates/yarn.lock +++ b/packages/string-templates/yarn.lock @@ -2799,7 +2799,7 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash@^4.17.19: +lodash@^4.17.19, lodash@^4.17.20: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==