diff --git a/changelog.md b/changelog.md index 534a457..a190c82 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +# [1.11.0](https://github.com/posthtml/posthtml-expressions/compare/v1.10.0...v1.11.0) (2022-12-20) + + + # [1.10.0](https://github.com/posthtml/posthtml-expressions/compare/v1.9.0...v1.10.0) (2022-09-26) diff --git a/lib/index.js b/lib/index.js index 50375a0..b2abbce 100644 --- a/lib/index.js +++ b/lib/index.js @@ -161,7 +161,15 @@ module.exports = function postHTMLExpressions (options) { return function (tree) { const { locals } = scriptDataLocals(tree, options) - return normalizeTree(clearRawTag(walk({ locals: { ...options.locals, ...locals }, strictMode: options.strictMode }, tree)), tree.options) + return normalizeTree( + clearRawTag( + walk( + { + locals: { ...options.locals, ...locals }, + strictMode: options.strictMode, + missingLocal: options.missingLocal + }, tree) + ), tree.options) } } diff --git a/lib/placeholders.js b/lib/placeholders.js index 52368dd..b5d2d13 100644 --- a/lib/placeholders.js +++ b/lib/placeholders.js @@ -66,13 +66,20 @@ function placeholders (input, ctx, settings, opts) { value = ctx[expression] } + // Not found local + if (value === null || value === undefined) { + if (opts.missingLocal === undefined) { + if (opts.strictMode) { + throw new ReferenceError(`'${expression}' is not defined`) + } + } else if (typeof opts.missingLocal === 'string') { + value = opts.missingLocal.replace('{local}', match) + } + } // Escape html if necessary if (settings[i].escape && typeof value === 'string') { value = escapeHTML(value) - } - - // Stringify if value object - if (typeof value === 'object') { + } else if (typeof value === 'object') { // Stringify if value object value = JSON.stringify(value) } diff --git a/package-lock.json b/package-lock.json index 2bd83e0..e9bcb7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "posthtml-expressions", - "version": "1.10.0", + "version": "1.11.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -474,6 +474,28 @@ "defer-to-connect": "^1.0.1" } }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -1819,30 +1841,42 @@ } }, "dmd": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.0.0.tgz", - "integrity": "sha512-PwWZlqZnJPETwqZZ70haRa+UDZcD5jeBD3ywW1Kf+jYYv0MHu/S7Ri9jsSoeTMwkcMVW9hXOMA1IZUMEufBhOg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.1.0.tgz", + "integrity": "sha512-0zQIJ873gay1scCTFZvHPWM9mVJBnaylB2NQDI8O9u8O32m00Jb6uxDKexZm8hjTRM7RiWe0FJ32pExHoXdwoQ==", "dev": true, "requires": { - "array-back": "^5.0.0", + "array-back": "^6.2.2", "cache-point": "^2.0.0", - "common-sequence": "^2.0.0", - "file-set": "^4.0.1", + "common-sequence": "^2.0.2", + "file-set": "^4.0.2", "handlebars": "^4.7.7", - "marked": "^2.0.0", + "marked": "^4.0.12", "object-get": "^2.1.1", - "reduce-flatten": "^3.0.0", + "reduce-flatten": "^3.0.1", "reduce-unique": "^2.0.1", "reduce-without": "^1.0.1", "test-value": "^3.0.0", - "walk-back": "^5.0.0" + "walk-back": "^5.1.0" }, "dependencies": { + "array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "dev": true + }, "reduce-flatten": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz", "integrity": "sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==", "dev": true + }, + "walk-back": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", + "integrity": "sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==", + "dev": true } } }, @@ -3573,48 +3607,101 @@ "esprima": "^4.0.0" } }, - "js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", - "dev": true, - "requires": { - "xmlcreate": "^2.0.3" - } - }, "jsdoc": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", - "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", "dev": true, "requires": { "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", + "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^2.0.3", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", "taffydb": "2.6.2", - "underscore": "~1.13.1" + "underscore": "~1.13.2" }, "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true + }, "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.4" + } + }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", + "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==", + "dev": true + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true + }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true } } }, @@ -3788,15 +3875,6 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, "load-json-file": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", @@ -3927,37 +4005,10 @@ "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", "dev": true }, - "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "dependencies": { - "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true - } - } - }, - "markdown-it-anchor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", - "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", - "dev": true - }, "marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", + "integrity": "sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==", "dev": true }, "matcher": { @@ -6110,12 +6161,6 @@ "which-boxed-primitive": "^1.0.2" } }, - "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", - "dev": true - }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -6339,12 +6384,6 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true }, - "xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", - "dev": true - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 1d4ecea..e7f1412 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "posthtml-expressions", - "version": "1.10.0", + "version": "1.11.0", "description": "Expressions Plugin for PostHTML", "engines": { "node": ">=10" diff --git a/readme.md b/readme.md index b3f80d4..51a23e5 100644 --- a/readme.md +++ b/readme.md @@ -37,17 +37,18 @@ You have full control over the delimiters used for injecting locals, as well as |Option|Default|Description| |:----:|:-----:|:----------| -| **delimiters** | `['{{', '}}']` | Array containing beginning and ending delimiters for escaped locals | -| **unescapeDelimiters** | `['{{{', '}}}']` | Array containing beginning and ending delimiters for unescaped locals | -| **locals** | `{}` | Object containing any local variables you want to be available inside your expressions | +| **delimiters** | `['{{', '}}']` | Array containing beginning and ending delimiters for escaped locals os [expressions](#expressions) | +| [**unescapeDelimiters**](#unescaped-locals) | `['{{{', '}}}']` | Array containing beginning and ending delimiters for unescaped locals | +| [**locals**](#locals) | `{}` | Object containing any local variables you want to be available inside your expressions | | **localsAttr** | `locals` | Attribute name for the tag `script` which contains ***[locals](#locals)***| | **removeScriptLocals** | `false` | Will remove tag `script` which contains ***[locals](#locals)***| -| **conditionalTags** | `['if', 'elseif', 'else']` | Array containing names for tags used for `if/else if/else` statements | -| **switchTags** | `['switch', 'case', 'default']` | Array containing names for tags used for `switch/case/default` statements | +| [**conditionalTags**](#conditionals) | `['if', 'elseif', 'else']` | Array containing names for tags used for [*`if/else` statements*](#conditionals) | +| [**switchTags**](#switchtags) | `['switch', 'case', 'default']` | Array containing names for tags used for [*`switch/case/default` statements*](#switch-statement) | | **loopTags** | `['each']` | Array containing names for `for` loops | | **scopeTags** | `['scope']` | Array containing names for scopes | -| **ignoredTag** | `'raw'` | String containing name of tag inside which parsing is disabled | -| **strictMode** | `true` | Boolean value set to `false` will not throw an exception | +| [**ignoredTag**](#ignored-tag) | `'raw'` | String containing name of tag inside which parsing is disabled | +| **strictMode** | `true` | Boolean value set to `false` will not throw an exception if a value in locals not found or expression could not be evaluated| +| [**missingLocal**](#missing-locals) | `undefined` | string defining the replacement value in case value not found in locals. May contain `{expression}` placeholder| ### Locals @@ -124,6 +125,23 @@ posthtml(expressions({ locals: { foo: 'bar' } }))
undefinedundefined
\ No newline at end of file diff --git a/test/expect/local_missing_keep.html b/test/expect/local_missing_keep.html new file mode 100644 index 0000000..083fa61 --- /dev/null +++ b/test/expect/local_missing_keep.html @@ -0,0 +1 @@ +{{foo}}
\ No newline at end of file diff --git a/test/expect/local_missing_remove.html b/test/expect/local_missing_remove.html new file mode 100644 index 0000000..138f2d6 --- /dev/null +++ b/test/expect/local_missing_remove.html @@ -0,0 +1 @@ +\ No newline at end of file diff --git a/test/expect/local_missing_replace.html b/test/expect/local_missing_replace.html new file mode 100644 index 0000000..a48a9c5 --- /dev/null +++ b/test/expect/local_missing_replace.html @@ -0,0 +1 @@ +
Error: {{foo}} undefined
\ No newline at end of file diff --git a/test/fixtures/local_missing.html b/test/fixtures/local_missing.html new file mode 100644 index 0000000..fb189f6 --- /dev/null +++ b/test/fixtures/local_missing.html @@ -0,0 +1 @@ +{{foo}}{{foo? "bar": ""}}
\ No newline at end of file diff --git a/test/fixtures/local_missing_keep.html b/test/fixtures/local_missing_keep.html new file mode 100644 index 0000000..083fa61 --- /dev/null +++ b/test/fixtures/local_missing_keep.html @@ -0,0 +1 @@ +{{foo}}
\ No newline at end of file diff --git a/test/fixtures/local_missing_remove.html b/test/fixtures/local_missing_remove.html new file mode 100644 index 0000000..083fa61 --- /dev/null +++ b/test/fixtures/local_missing_remove.html @@ -0,0 +1 @@ +{{foo}}
\ No newline at end of file diff --git a/test/fixtures/local_missing_replace.html b/test/fixtures/local_missing_replace.html new file mode 100644 index 0000000..083fa61 --- /dev/null +++ b/test/fixtures/local_missing_replace.html @@ -0,0 +1 @@ +{{foo}}
\ No newline at end of file diff --git a/test/test-core.js b/test/test-core.js index 598b3be..a64d661 100644 --- a/test/test-core.js +++ b/test/test-core.js @@ -28,8 +28,8 @@ function process (t, name, options, log = false, plugins = [expressions(options) }) } -function error (name, cb) { - return posthtml([expressions()]) +function error (name, cb, opts) { + return posthtml([expressions(opts)]) .process(fixture(name)) .catch(cb) } @@ -103,3 +103,25 @@ test('Directives options', (t) => { }] }) }) + +test('local - missing - error', (t) => { + return error('local_missing', (err) => { + t.is(err.message, "'foo' is not defined") + }) +}) + +test('local - missing - undefined', (t) => { + return process(t, 'local_missing', { strictMode: false }) +}) + +test('local - missing - keep', (t) => { + return process(t, 'local_missing_keep', { missingLocal: '{local}' }) +}) + +test('local - missing - keep / strictMode:false', (t) => { + return process(t, 'local_missing_keep', { missingLocal: '{local}', strictMode: false }) +}) + +test('local - missing - replace', (t) => { + return process(t, 'local_missing_replace', { missingLocal: 'Error: {local} undefined' }) +})