sjabloon 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sjabloon
2
2
 
3
- A tiny, CSP-safe template engine for JavaScript. **~0.8KB min+gzip (~2KB with [xprsn](https://www.npmjs.com/package/xprsn)), one dependency.**
3
+ A tiny, CSP-safe template engine for JavaScript. **~1.8KB min+gzip (~3.6KB with [xprsn](https://www.npmjs.com/package/xprsn)), one dependency.**
4
4
 
5
5
  [![NPM version](https://img.shields.io/npm/v/sjabloon.svg)](https://www.npmjs.com/package/sjabloon)
6
6
  [![Build Status](https://github.com/robinvdvleuten/sjabloon/actions/workflows/test.yml/badge.svg)](https://github.com/robinvdvleuten/sjabloon/actions/workflows/test.yml)
@@ -59,6 +59,21 @@ tpl.functions; // => ['fmt']
59
59
 
60
60
  Shorthand for `template(str, functions)(values)`.
61
61
 
62
+ ### Error diagnostics
63
+
64
+ Sjabloon errors keep their native `SyntaxError` or `TypeError` class and expose:
65
+
66
+ - `code`: a stable `SJABLOON_*` parser category or the original `XPRSN_*` expression category;
67
+ - `start`: a zero-based offset in the original template;
68
+ - `end`: the exclusive template offset;
69
+ - `blocks`: a frozen, outermost-first array of `{ type, start, end }` opener spans.
70
+
71
+ Parser codes are `SJABLOON_EACH_SYNTAX`, `SJABLOON_BLOCKED_BINDING`, `SJABLOON_UNEXPECTED_TAG`, `SJABLOON_UNKNOWN_BLOCK`, and `SJABLOON_UNCLOSED_BLOCK`. A missing closer uses an empty span at the end of the template. Expression offsets refer to the original template, so surrounding braces, whitespace, and trim markers contribute to their absolute position.
72
+
73
+ Unauthenticated errors thrown by registered functions, getters, methods, or value coercion hooks are host errors. Sjabloon passes them through unchanged and does not attach template diagnostic fields.
74
+
75
+ Use `isDiagnostic(error)` when a host needs to distinguish those errors. It returns `true` only for errors produced or translated by the same sjabloon module instance. Copying a documented `code`, `start`, `end`, and `blocks` onto another error does not authenticate it. A diagnostic from another installed copy or module instance also returns `false`.
76
+
62
77
  ## Syntax
63
78
 
64
79
  | Tag | Meaning |
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- var t=require("xprsn");function r(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,n=Array(r);e<r;e++)n[e]=t[e];return n}function e(t,e){var n="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(n)return(n=n.call(t)).next.bind(n);if(Array.isArray(t)||(n=function(t,e){if(t){if("string"==typeof t)return r(t,e);var n={}.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(t,e):void 0}}(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var a=0;return function(){return a>=t.length?{done:!0}:{done:!1,value:t[a++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var n,a,i,u,o,l,c,f={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},s=function(t){return String(t).replace(/[&<>"']/g,function(t){return f[t]})},h=/\{\{\{(-)?\s*([\s\S]*?)\s*(-)?\}\}\}|\{\{(-)?\s*([\s\S]*?)\s*(-)?\}\}/,g=function(t){throw SyntaxError(t)},p=function(t,r){return t.map(function(t){return t(r)}).join("")},v=function(t,r){return e=d(t),function(t){var n;return r(null!=(n=e(t))?n:"")};var e},d=function(r){for(var n,a=t.compile(r,i),u=e(a.names);!(n=u()).done;){var f=n.value;o.has(f)||l.add(f)}for(var s,h=e(a.functions);!(s=h()).done;)c.add(s.value);return a},m=function(t){var r=y(["#elif","#else","/if"]),e=u.startsWith("#elif ")?[m(d(u.slice(6)))]:"#else"===u?y(["/if"]):[];return function(n){return p(t(n)?r:e,n)}},y=function(t){for(var r,e,i=[],l=function(){if(null!=e.text)i.push((x=e.text,function(){return x}));else if(null!=e.raw)i.push(v(e.raw,String));else{if(t.includes(e.tag.split(" ")[0]))return u=e.tag,{v:i};if("!"===e.tag[0]);else if(e.tag.startsWith("#if "))i.push(m(d(e.tag.slice(4))));else if(e.tag.startsWith("#each ")){var r=/^#each ([\s\S]+) as (\w+)(?:\s*,\s*(\w+))?$/.exec(e.tag)||g("Bad {{"+e.tag+"}}"),n=d(r[1]),a=r[2],l=r[3],c=function(){var t=[].slice.call(arguments).filter(function(t){return t&&!o.has(t)});return t.forEach(function(t){return o.add(t)}),function(){return t.forEach(function(t){return o.delete(t)})}}(a,l,"loop"),f=y(["#else","/each"]);c();var h="#else"===u?y(["/each"]):[];i.push(function(t){var r,e=(r=n(t),Array.isArray(r)?r.map(function(t,r){return[t,r]}):r&&"object"==typeof r?Object.keys(r).map(function(t){return[r[t],t]}):[]);return e.length?e.map(function(r,n){var i=r[0],u=r[1],o=Object.create(t);return o[a]=i,l&&(o[l]=u),o["@"]=i,o.loop={index:n+1,index0:n,first:!n,last:n===e.length-1,length:e.length},p(f,o)}).join(""):p(h,t)})}else"#"===e.tag[0]||"/"===e.tag[0]?g("Unexpected {{"+e.tag+"}}"):i.push(v(e.tag,s))}var x};e=n[a++];)if(r=l())return r.v;return t.length&&g("Missing {{"+t[t.length-1]+"}}"),i};function x(t,r){i=r,o=new Set(["$","@"]),l=new Set,c=new Set,n=[];for(var e=String(t).split(h),u=0;u<e.length&&(e[u]&&n.push({text:e[u]}),!(u+1>=e.length));u+=7){var f=null!=e[u+2],s=f?{raw:e[u+2]}:{tag:e[u+5]};s.l="-"===e[u+(f?1:4)],s.r="-"===e[u+(f?3:6)],n.push(s)}n.forEach(function(t,r){var e,a;t.l&&null!=(e=n[r-1])&&e.text&&(n[r-1].text=n[r-1].text.replace(/\s+$/,"")),t.r&&null!=(a=n[r+1])&&a.text&&(n[r+1].text=n[r+1].text.replace(/^\s+/,""))}),a=0;var g=y([]),v=function(t,r){t=t||{};var e=Object.create(t);return e.$=r?r.root:t,r?"item"in r&&(e["@"]=r.item):e["@"]=t,p(g,e)};return v.names=Array.from(l),v.functions=Array.from(c),v}exports.render=function(t,r,e){return x(t,e)(r)},exports.template=x;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("xprsn");const t={"&":`&amp;`,"<":`&lt;`,">":`&gt;`,'"':`&quot;`,"'":`&#39;`},n=e=>String(e).replace(/[&<>"']/g,e=>t[e]),r=/^(?:__proto__|constructor|prototype)$/,i=new WeakSet,a=i.add.bind(i),o=i.has.bind(i),s=e=>o(e);let c=e=>{let t=[];for(let n=0,r=1;n<e.length;){let i=e.indexOf(`{{`,n);if(i<0){t.push([0,e.slice(n)]);break}i>n&&t.push([0,e.slice(n,i)]);let a=e[i+2]===`{`,o=i+2+a,s=e[o]===`-`,c=-1;if(s&&o++,a&&r&&(c=e.indexOf(`}}}`,o),c<0&&(r=0)),c<0&&(a&&(a=!1,o=i+2,s=e[o]===`-`,s&&o++),c=e.indexOf(`}}`,o)),c<0){t.push([0,e.slice(i)]);break}let l=c>o&&e[c-1]===`-`,u=l?c-1:c,d=e.slice(o,u),f=d.trim(),p=o+d.length-d.trimStart().length,m=c+2+a,h=[a?1:2,f,i,m,o,u,p,p+f.length,s,l],g=t.at(-1);if(s&&g?.[0]===0&&g[1]&&(g[1]=g[1].trimEnd()),t.push(h),n=m,l)for(;/\s/.test(e[n]);)n++}return t},l,u,d,f,p,m,h,g,_,v=()=>Object.freeze(_.slice()),y=(e,t)=>Object.freeze({type:e,start:t[2],end:t[3]}),b=(e,t)=>(Object.defineProperty(e,"blocks",{value:t,enumerable:!0}),a(e),e),x=(e,t,n,r=n?.[2]??g.length,i=n?.[3]??g.length)=>{let a=SyntaxError(e);throw a.code=t,a.start=r,a.end=i,b(a,v())},S=(t,n,r,i=e.isDiagnostic)=>{throw i(t)?(t.start+=n,t.end+=n,b(t,r)):t},C=e=>x(`Unexpected {{`+e[1]+`}}`,`SJABLOON_UNEXPECTED_TAG`,e),w=(e,t)=>e.map(e=>e(t)).join(``),T=(e,t)=>(e=>n=>t(e(n)??``))(E(e[1],e[6],v())),E=(t,n,r)=>{let i;try{i=(0,e.compile)(t,d)}catch(e){S(e,n,r)}for(let e of i.names)p.includes(e)||m.add(e);for(let e of i.functions)h.add(e);return e=>{try{return i(e)}catch(e){S(e,n,r,i.isDiagnostic)}}},D=e=>{let t=O([`#elif`,`#else`,`/if`]),n=f[1],r=[];return n.startsWith(`#elif `)?r=[D(E(n.slice(6),f[6]+6,v()))]:n===`#else`?(r=O([`/if`]),f[1]===`/if`||C(f)):n!==`/if`&&C(f),n=>w(e(n)?t:r,n)},O=e=>{let t=[];for(let i;i=l[u++];){let a=i[1];if(!i[0])t.push((e=>()=>e)(a));else if(i[0]===1)t.push(T(i,String));else if(e.includes(a.split(` `)[0]))return f=i,t;else if(a[0]!==`!`)if(a.startsWith(`#if `))_.push(y(`if`,i)),t.push(D(E(a.slice(4),i[6]+4,v()))),_.pop();else if(/^#each(?:\s|$)/.test(a)){_.push(y(`each`,i));let e=/^#each ([\s\S]+) as ((\w+)(?:\s*,\s*(\w+))?)$/.exec(a);e||x(`Bad {{`+a+`}}`,`SJABLOON_EACH_SYNTAX`,i);let n=e[3],o=e[4],s=i[6]+a.length-e[2].length;if(r.test(n)&&x(`Bad {{`+a+`}}`,`SJABLOON_BLOCKED_BINDING`,i,s,s+n.length),o&&r.test(o)){let e=i[6]+a.length-o.length;x(`Bad {{`+a+`}}`,`SJABLOON_BLOCKED_BINDING`,i,e,e+o.length)}let c=E(e[1],i[6]+6,v()),l=p.length;p.push(n),o&&p.push(o),p.push(`loop`);let u=O([`#else`,`/each`]);p.length=l;let d=[];f[1]===`#else`?(d=O([`/each`]),f[1]===`/each`||C(f)):f[1]!==`/each`&&C(f),_.pop(),t.push(e=>{let t=c(e),r=Array.isArray(t),i=r?t.slice():t&&typeof t==`object`?Object.keys(t).map(e=>[t[e],e]):[];return i.length?i.map((t,a)=>{let s=r?t:t[0],c=r?a:t[1],l=Object.create(e);return l[n]=s,o&&(l[o]=c),l[`@`]=s,l.loop={index:a+1,index0:a,first:!a,last:a===i.length-1,length:i.length},w(u,l)}).join(``):w(d,e)})}else/^#(?:if|elif|else)(?:\s|$)/.test(a)||a[0]===`/`?C(i):a[0]===`#`?x(`Unknown {{`+a+`}}`,`SJABLOON_UNKNOWN_BLOCK`,i):t.push(T(i,n))}return e.length&&x(`Missing {{`+e[e.length-1]+`}}`,`SJABLOON_UNCLOSED_BLOCK`),t};function k(e,t){d=t,p=[`$`,`@`],m=new Set,h=new Set,g=String(e),_=[],l=c(g),u=0;let n=O([]),r=(e,t)=>{e||={};let r=Object.create(e);return r.$=t?t.root:e,t?`item`in t&&(r[`@`]=t.item):r[`@`]=e,w(n,r)};return r.names=Array.from(m),r.functions=Array.from(h),r}function A(e,t,n){return k(e,n)(t)}exports.isDiagnostic=s,exports.render=A,exports.template=k;
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{compile as e,isDiagnostic as t}from"xprsn";const n={"&":`&amp;`,"<":`&lt;`,">":`&gt;`,'"':`&quot;`,"'":`&#39;`},r=e=>String(e).replace(/[&<>"']/g,e=>n[e]),i=/^(?:__proto__|constructor|prototype)$/,a=new WeakSet,o=a.add.bind(a),s=a.has.bind(a),c=e=>s(e);let l=e=>{let t=[];for(let n=0,r=1;n<e.length;){let i=e.indexOf(`{{`,n);if(i<0){t.push([0,e.slice(n)]);break}i>n&&t.push([0,e.slice(n,i)]);let a=e[i+2]===`{`,o=i+2+a,s=e[o]===`-`,c=-1;if(s&&o++,a&&r&&(c=e.indexOf(`}}}`,o),c<0&&(r=0)),c<0&&(a&&(a=!1,o=i+2,s=e[o]===`-`,s&&o++),c=e.indexOf(`}}`,o)),c<0){t.push([0,e.slice(i)]);break}let l=c>o&&e[c-1]===`-`,u=l?c-1:c,d=e.slice(o,u),f=d.trim(),p=o+d.length-d.trimStart().length,m=c+2+a,h=[a?1:2,f,i,m,o,u,p,p+f.length,s,l],g=t.at(-1);if(s&&g?.[0]===0&&g[1]&&(g[1]=g[1].trimEnd()),t.push(h),n=m,l)for(;/\s/.test(e[n]);)n++}return t},u,d,f,p,m,h,g,_,v,y=()=>Object.freeze(v.slice()),b=(e,t)=>Object.freeze({type:e,start:t[2],end:t[3]}),x=(e,t)=>(Object.defineProperty(e,"blocks",{value:t,enumerable:!0}),o(e),e),S=(e,t,n,r=n?.[2]??_.length,i=n?.[3]??_.length)=>{let a=SyntaxError(e);throw a.code=t,a.start=r,a.end=i,x(a,y())},C=(e,n,r,i=t)=>{throw i(e)?(e.start+=n,e.end+=n,x(e,r)):e},w=e=>S(`Unexpected {{`+e[1]+`}}`,`SJABLOON_UNEXPECTED_TAG`,e),T=(e,t)=>e.map(e=>e(t)).join(``),E=(e,t)=>(e=>n=>t(e(n)??``))(D(e[1],e[6],y())),D=(t,n,r)=>{let i;try{i=e(t,f)}catch(e){C(e,n,r)}for(let e of i.names)m.includes(e)||h.add(e);for(let e of i.functions)g.add(e);return e=>{try{return i(e)}catch(e){C(e,n,r,i.isDiagnostic)}}},O=e=>{let t=k([`#elif`,`#else`,`/if`]),n=p[1],r=[];return n.startsWith(`#elif `)?r=[O(D(n.slice(6),p[6]+6,y()))]:n===`#else`?(r=k([`/if`]),p[1]===`/if`||w(p)):n!==`/if`&&w(p),n=>T(e(n)?t:r,n)},k=e=>{let t=[];for(let n;n=u[d++];){let a=n[1];if(!n[0])t.push((e=>()=>e)(a));else if(n[0]===1)t.push(E(n,String));else if(e.includes(a.split(` `)[0]))return p=n,t;else if(a[0]!==`!`)if(a.startsWith(`#if `))v.push(b(`if`,n)),t.push(O(D(a.slice(4),n[6]+4,y()))),v.pop();else if(/^#each(?:\s|$)/.test(a)){v.push(b(`each`,n));let e=/^#each ([\s\S]+) as ((\w+)(?:\s*,\s*(\w+))?)$/.exec(a);e||S(`Bad {{`+a+`}}`,`SJABLOON_EACH_SYNTAX`,n);let r=e[3],o=e[4],s=n[6]+a.length-e[2].length;if(i.test(r)&&S(`Bad {{`+a+`}}`,`SJABLOON_BLOCKED_BINDING`,n,s,s+r.length),o&&i.test(o)){let e=n[6]+a.length-o.length;S(`Bad {{`+a+`}}`,`SJABLOON_BLOCKED_BINDING`,n,e,e+o.length)}let c=D(e[1],n[6]+6,y()),l=m.length;m.push(r),o&&m.push(o),m.push(`loop`);let u=k([`#else`,`/each`]);m.length=l;let d=[];p[1]===`#else`?(d=k([`/each`]),p[1]===`/each`||w(p)):p[1]!==`/each`&&w(p),v.pop(),t.push(e=>{let t=c(e),n=Array.isArray(t),i=n?t.slice():t&&typeof t==`object`?Object.keys(t).map(e=>[t[e],e]):[];return i.length?i.map((t,a)=>{let s=n?t:t[0],c=n?a:t[1],l=Object.create(e);return l[r]=s,o&&(l[o]=c),l[`@`]=s,l.loop={index:a+1,index0:a,first:!a,last:a===i.length-1,length:i.length},T(u,l)}).join(``):T(d,e)})}else/^#(?:if|elif|else)(?:\s|$)/.test(a)||a[0]===`/`?w(n):a[0]===`#`?S(`Unknown {{`+a+`}}`,`SJABLOON_UNKNOWN_BLOCK`,n):t.push(E(n,r))}return e.length&&S(`Missing {{`+e[e.length-1]+`}}`,`SJABLOON_UNCLOSED_BLOCK`),t};function A(e,t){f=t,m=[`$`,`@`],h=new Set,g=new Set,_=String(e),v=[],u=l(_),d=0;let n=k([]),r=(e,t)=>{e||={};let r=Object.create(e);return r.$=t?t.root:e,t?`item`in t&&(r[`@`]=t.item):r[`@`]=e,T(n,r)};return r.names=Array.from(h),r.functions=Array.from(g),r}function j(e,t,n){return A(e,n)(t)}export{c as isDiagnostic,j as render,A as template};
package/index.d.ts CHANGED
@@ -1,40 +1,68 @@
1
- /**
2
- * Compile a template once, render it many times.
3
- *
4
- * The returned renderer exposes `names`: the variables the template reads
5
- * from your values, deduplicated. Loop variables the template introduces are
6
- * not included. It also exposes `functions`: the registry functions the
7
- * template calls, deduplicated.
8
- *
9
- * Two anchors are always in scope: `$` is the root values, and `@` is the
10
- * current `#each` item (the root outside any loop). They let a nested loop
11
- * reach the root (`$.company`) or the current item (`@.total`) explicitly,
12
- * past any shadowing. Neither counts as a `name`.
13
- *
14
- * An embedder with its own scope model can override the anchors per render by
15
- * passing `{ root, item }` as the renderer's second argument: `$` becomes
16
- * `root` and `@` becomes `item` (two distinct objects). Omit `item` to leave
17
- * `@` unbound, so reading `@.x` throws through xprsn's guard.
18
- *
19
- * @param {string} str The template, e.g. `'Hello {{ user.name }}!'`.
20
- * @param {Record<string, Function>} [funcs] Functions callable inside expressions.
21
- * @returns {{(values?: Record<string, any>, scope?: { root?: any, item?: any }): string, names: string[], functions: string[]}} Renderer for the compiled template.
22
- * @throws {SyntaxError} On malformed tags, unclosed blocks, or bad expressions.
23
- */
24
- export function template(str: string, funcs?: Record<string, Function>): {
25
- (values?: Record<string, any>, scope?: {
26
- root?: any;
27
- item?: any;
28
- }): string;
29
- names: string[];
30
- functions: string[];
31
- };
32
- /**
33
- * Compile and render a template in one go.
34
- *
35
- * @param {string} str The template to render.
36
- * @param {Record<string, any>} [values] Variables available to the template.
37
- * @param {Record<string, Function>} [funcs] Functions callable inside expressions.
38
- * @returns {string} The rendered output.
39
- */
40
- export function render(str: string, values?: Record<string, any>, funcs?: Record<string, Function>): string;
1
+ import type { XprsnErrorCode } from 'xprsn';
2
+
3
+ export type SjabloonErrorCode =
4
+ | XprsnErrorCode
5
+ | 'SJABLOON_EACH_SYNTAX'
6
+ | 'SJABLOON_BLOCKED_BINDING'
7
+ | 'SJABLOON_UNEXPECTED_TAG'
8
+ | 'SJABLOON_UNKNOWN_BLOCK'
9
+ | 'SJABLOON_UNCLOSED_BLOCK';
10
+
11
+ export interface SjabloonBlock {
12
+ readonly type: 'if' | 'each';
13
+ readonly start: number;
14
+ readonly end: number;
15
+ }
16
+
17
+ export interface SjabloonDiagnostic extends Error {
18
+ readonly code: SjabloonErrorCode;
19
+ readonly start: number;
20
+ readonly end: number;
21
+ readonly blocks: readonly SjabloonBlock[];
22
+ }
23
+
24
+ /**
25
+ * Check whether an error was produced or translated by this sjabloon module instance.
26
+ */
27
+ export function isDiagnostic(error: unknown): error is SjabloonDiagnostic;
28
+
29
+ /**
30
+ * Compile a template once, render it many times.
31
+ *
32
+ * The returned renderer exposes `names`: the variables the template reads
33
+ * from your values, deduplicated. Loop variables the template introduces are
34
+ * not included. It also exposes `functions`: the registry functions the
35
+ * template calls, deduplicated.
36
+ *
37
+ * Two anchors are always in scope: `$` is the root values, and `@` is the
38
+ * current `#each` item (the root outside any loop). They let a nested loop
39
+ * reach the root (`$.company`) or the current item (`@.total`) explicitly,
40
+ * past any shadowing. Neither counts as a `name`.
41
+ *
42
+ * An embedder with its own scope model can override the anchors per render by
43
+ * passing `{ root, item }` as the renderer's second argument: `$` becomes
44
+ * `root` and `@` becomes `item` (two distinct objects). Omit `item` to leave
45
+ * `@` unbound, so reading `@.x` throws through xprsn's guard.
46
+ *
47
+ * @param {string} str The template, e.g. `'Hello {{ user.name }}!'`.
48
+ * @param {Record<string, Function>} [funcs] Functions callable inside expressions.
49
+ * @returns {{(values?: Record<string, any>, scope?: { root?: any, item?: any }): string, names: string[], functions: string[]}} Renderer for the compiled template.
50
+ * @throws {SyntaxError} On malformed tags, unclosed blocks, or bad expressions.
51
+ */
52
+ export function template(str: string, funcs?: Record<string, Function>): {
53
+ (values?: Record<string, any>, scope?: {
54
+ root?: any;
55
+ item?: any;
56
+ }): string;
57
+ names: string[];
58
+ functions: string[];
59
+ };
60
+ /**
61
+ * Compile and render a template in one go.
62
+ *
63
+ * @param {string} str The template to render.
64
+ * @param {Record<string, any>} [values] Variables available to the template.
65
+ * @param {Record<string, Function>} [funcs] Functions callable inside expressions.
66
+ * @returns {string} The rendered output.
67
+ */
68
+ export function render(str: string, values?: Record<string, any>, funcs?: Record<string, Function>): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sjabloon",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Tiny, CSP-safe template engine for JavaScript, powered by xprsn expressions. No eval, no new Function.",
5
5
  "repository": "robinvdvleuten/sjabloon",
6
6
  "license": "MIT",
@@ -11,16 +11,14 @@
11
11
  },
12
12
  "type": "module",
13
13
  "source": "src/index.js",
14
- "main": "./dist/index.cjs",
15
- "module": "./dist/index.module.js",
16
- "unpkg": "./dist/index.umd.js",
17
- "types": "./index.d.ts",
14
+ "main": "dist/index.cjs",
15
+ "module": "dist/index.js",
16
+ "types": "index.d.ts",
18
17
  "exports": {
19
18
  ".": {
20
19
  "types": "./index.d.ts",
21
- "import": "./dist/index.module.js",
22
- "require": "./dist/index.cjs",
23
- "default": "./dist/index.module.js"
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.cjs"
24
22
  }
25
23
  },
26
24
  "files": [
@@ -28,11 +26,32 @@
28
26
  "src",
29
27
  "index.d.ts"
30
28
  ],
29
+ "size-limit": [
30
+ {
31
+ "path": "dist/index.js",
32
+ "limit": "1.7 kB"
33
+ },
34
+ {
35
+ "path": "dist/index.cjs",
36
+ "limit": "1.7 kB"
37
+ }
38
+ ],
31
39
  "scripts": {
32
- "build": "microbundle --no-sourcemap",
40
+ "bench": "node --disallow-code-generation-from-strings bench/index.js",
41
+ "build": "tsdown",
42
+ "fuzz": "npm run fuzz:prepare && run-s fuzz:compile fuzz:render fuzz:structured",
43
+ "fuzz:prepare": "node -e \"for (const x of ['compile','render','structured']) require('fs').mkdirSync('.fuzz-corpus/'+x,{recursive:true})\"",
44
+ "fuzz:compile": "NODE_OPTIONS=--disallow-code-generation-from-strings jazzer fuzz/compile.fuzz.js -i src/ --customHooks fuzz/hooks.js --disableBugDetectors='command-injection|path-traversal|ssrf' --sync .fuzz-corpus/compile fuzz/corpus/compile -- -max_total_time=60 -use_value_profile=1 -dict=fuzz/sjabloon.dict -artifact_prefix=fuzz/",
45
+ "fuzz:render": "NODE_OPTIONS=--disallow-code-generation-from-strings jazzer fuzz/render.fuzz.js -i src/ --customHooks fuzz/hooks.js --disableBugDetectors='command-injection|path-traversal|ssrf' --sync .fuzz-corpus/render fuzz/corpus/render -- -max_total_time=60 -use_value_profile=1 -dict=fuzz/sjabloon.dict -artifact_prefix=fuzz/",
46
+ "fuzz:structured": "NODE_OPTIONS=--disallow-code-generation-from-strings jazzer fuzz/structured.fuzz.js -i src/ --customHooks fuzz/hooks.js --disableBugDetectors='command-injection|path-traversal|ssrf' --sync .fuzz-corpus/structured fuzz/corpus/structured -- -max_total_time=60 -use_value_profile=1 -dict=fuzz/sjabloon.dict -artifact_prefix=fuzz/",
47
+ "fuzz:regression": "run-s fuzz:regression:compile fuzz:regression:render fuzz:regression:structured",
48
+ "fuzz:regression:compile": "NODE_OPTIONS=--disallow-code-generation-from-strings jazzer fuzz/compile.fuzz.js -i src/ --customHooks fuzz/hooks.js --disableBugDetectors='command-injection|path-traversal|ssrf' --sync --mode=regression fuzz/corpus/compile -- -artifact_prefix=fuzz/",
49
+ "fuzz:regression:render": "NODE_OPTIONS=--disallow-code-generation-from-strings jazzer fuzz/render.fuzz.js -i src/ --customHooks fuzz/hooks.js --disableBugDetectors='command-injection|path-traversal|ssrf' --sync --mode=regression fuzz/corpus/render -- -artifact_prefix=fuzz/",
50
+ "fuzz:regression:structured": "NODE_OPTIONS=--disallow-code-generation-from-strings jazzer fuzz/structured.fuzz.js -i src/ --customHooks fuzz/hooks.js --disableBugDetectors='command-injection|path-traversal|ssrf' --sync --mode=regression fuzz/corpus/structured -- -artifact_prefix=fuzz/",
33
51
  "prepublishOnly": "npm run build",
34
- "test": "node --disallow-code-generation-from-strings node_modules/tape/bin/tape test/*.test.js",
35
- "bench": "node bench/index.js"
52
+ "size": "size-limit",
53
+ "test": "node --disallow-code-generation-from-strings --test --test-concurrency=1 test/*.test.js && npm run typecheck",
54
+ "typecheck": "tsc --noEmit --strict test/types.check.ts"
36
55
  },
37
56
  "keywords": [
38
57
  "template",
@@ -42,10 +61,15 @@
42
61
  "handlebars"
43
62
  ],
44
63
  "dependencies": {
45
- "xprsn": "^0.4.0"
64
+ "xprsn": "^0.8.0"
46
65
  },
47
66
  "devDependencies": {
48
- "microbundle": "^0.15.1",
49
- "tape": "^5.9.0"
67
+ "@jazzer.js/bug-detectors": "^4.0.0",
68
+ "@jazzer.js/core": "^4.0.0",
69
+ "@size-limit/file": "^12.1.0",
70
+ "npm-run-all": "^4.1.5",
71
+ "size-limit": "^12.1.0",
72
+ "tsdown": "^0.22.12",
73
+ "typescript": "^7.0.2"
50
74
  }
51
75
  }
package/src/index.js CHANGED
@@ -3,92 +3,172 @@
3
3
  * Templates compile to a composition of closures; template text is never
4
4
  * turned into JavaScript, so strict CSP is satisfied.
5
5
  */
6
- import { compile } from 'xprsn';
6
+ import { compile, isDiagnostic as isXprsnDiagnostic } from 'xprsn';
7
7
 
8
8
  const ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
9
9
  const esc = s => String(s).replace(/[&<>"']/g, c => ESC[c]);
10
+ const BLOCKED = /^(?:__proto__|constructor|prototype)$/;
11
+ const DIAGNOSTICS = new WeakSet();
12
+ const mark = DIAGNOSTICS.add.bind(DIAGNOSTICS);
13
+ const owns = DIAGNOSTICS.has.bind(DIAGNOSTICS);
10
14
 
11
- // What `#each` walks: [value, key] pairs — array indexes or own object keys.
12
- const pairs = lv => Array.isArray(lv) ? lv.map((x, j) => [x, j])
13
- : lv && typeof lv === 'object' ? Object.keys(lv).map(k => [lv[k], k])
14
- : [];
15
+ /**
16
+ * Check whether an error was produced or translated by sjabloon.
17
+ *
18
+ * @param {unknown} error Any thrown value.
19
+ * @returns {boolean} Whether `error` is an authentic sjabloon diagnostic.
20
+ */
21
+ export const isDiagnostic = error => owns(error);
15
22
 
16
- // Split into [text, rawL, raw, rawR, tagL, tag, tagR, ...] strides of 7.
17
- // The dash captures hug the braces, so `{{ -price }}` stays a unary minus
18
- // while `{{- price -}}` trims the whitespace touching the tag.
19
- const TAGS = /\{\{\{(-)?\s*([\s\S]*?)\s*(-)?\}\}\}|\{\{(-)?\s*([\s\S]*?)\s*(-)?\}\}/;
23
+ // Linear scan into text/tag/raw tokens. Dashes hug braces (`{{- x -}}` trims;
24
+ // `{{ -x }}` stays unary minus). Prefer {{{ }}} over {{ }}. `triple` latches
25
+ // off once }}} is gone so {{{...}}×N does not rescan to EOF (stays O(n)).
26
+ let lex = s => {
27
+ const out = [];
28
+ for (let i = 0, triple = 1; i < s.length; ) {
29
+ const a = s.indexOf('{{', i);
30
+ if (a < 0) { out.push([0, s.slice(i)]); break; }
31
+ if (a > i) out.push([0, s.slice(i, a)]);
32
+ let raw = s[a + 2] === '{', p = a + 2 + raw, l = s[p] === '-', b = -1;
33
+ if (l) p++;
34
+ if (raw && triple) { b = s.indexOf('}}}', p); if (b < 0) triple = 0; }
35
+ if (b < 0) {
36
+ if (raw) { raw = !1; p = a + 2; l = s[p] === '-'; if (l) p++; }
37
+ b = s.indexOf('}}', p);
38
+ }
39
+ if (b < 0) { out.push([0, s.slice(a)]); break; }
40
+ const r = b > p && s[b - 1] === '-';
41
+ const q = r ? b - 1 : b, whole = s.slice(p, q), body = whole.trim();
42
+ const start = p + whole.length - whole.trimStart().length, end = b + 2 + raw;
43
+ const t = [raw ? 1 : 2, body, a, end, p, q, start, start + body.length, l, r];
44
+ const prev = out.at(-1);
45
+ if (l && prev?.[0] === 0 && prev[1]) prev[1] = prev[1].trimEnd();
46
+ out.push(t);
47
+ i = end;
48
+ if (r) while (/\s/.test(s[i])) i++;
49
+ }
50
+ return out;
51
+ };
20
52
 
21
53
  // Shared parser state; parsing is synchronous so this is safe.
22
54
  // `nms` collects free variables, `fnms` the registry functions called.
23
- let toks, i, fns, last, bound, nms, fnms;
55
+ let toks, i, fns, last, bound, nms, fnms, src, blocks;
24
56
 
25
- let err = msg => { throw SyntaxError(msg) };
57
+ let snap = () => Object.freeze(blocks.slice());
58
+ let opener = (type, t) => Object.freeze({ type, start: t[2], end: t[3] });
59
+ let attach = (e, context) => {
60
+ Object.defineProperty(e, 'blocks', { value: context, enumerable: true });
61
+ mark(e);
62
+ return e;
63
+ };
64
+ let fault = (msg, code, t, start = t?.[2] ?? src.length, end = t?.[3] ?? src.length) => {
65
+ const e = SyntaxError(msg);
66
+ e.code = code;
67
+ e.start = start;
68
+ e.end = end;
69
+ throw attach(e, snap());
70
+ };
71
+ let translated = (e, start, context, owns = isXprsnDiagnostic) => {
72
+ if (!owns(e)) throw e;
73
+ e.start += start;
74
+ e.end += start;
75
+ throw attach(e, context);
76
+ };
77
+ let unexpected = t => fault('Unexpected {{' + t[1] + '}}', 'SJABLOON_UNEXPECTED_TAG', t);
26
78
 
27
79
  // Render a list of nodes against a scope.
28
80
  let run = (nodes, v) => nodes.map(n => n(v)).join('');
29
81
 
30
82
  // A leaf interpolation node: compile `src`, render nullish as '', apply `wrap`
31
83
  // (`esc` for `{{ }}`, `String` for the raw `{{{ }}}` form).
32
- let interp = (src, wrap) => (e => v => wrap(e(v) ?? ''))(cp(src));
33
-
34
- // Bind `names` for a block body; returns a restore that unbinds only the names
35
- // this block introduced, leaving an outer scope's bindings in place.
36
- let scope = (...names) => {
37
- const fresh = names.filter(n => n && !bound.has(n));
38
- fresh.forEach(n => bound.add(n));
39
- return () => fresh.forEach(n => bound.delete(n));
40
- };
84
+ let interp = (t, wrap) => (e => v => wrap(e(v) ?? ''))(cp(t[1], t[6], snap()));
41
85
 
42
86
  // Compile one expression and collect its free variables (minus the loop
43
87
  // variables currently in scope, which belong to the template) and the registry
44
88
  // functions it calls.
45
- let cp = s => {
46
- const e = compile(s, fns);
47
- for (const n of e.names) bound.has(n) || nms.add(n);
89
+ let cp = (s, start, context) => {
90
+ let e;
91
+ try {
92
+ e = compile(s, fns);
93
+ } catch (x) {
94
+ translated(x, start, context);
95
+ }
96
+ for (const n of e.names) bound.includes(n) || nms.add(n);
48
97
  for (const fn of e.functions) fnms.add(fn);
49
- return e;
98
+ return v => {
99
+ try {
100
+ return e(v);
101
+ } catch (x) {
102
+ translated(x, start, context, e.isDiagnostic);
103
+ }
104
+ };
50
105
  };
51
106
 
52
107
  // One `#if`/`#elif` link: parse its branch, then recurse on the chain tail.
53
108
  let branch = cond => {
54
109
  const then = parse(['#elif', '#else', '/if']);
55
- const els = last.startsWith('#elif ') ? [branch(cp(last.slice(6)))]
56
- : last === '#else' ? parse(['/if'])
57
- : [];
110
+ const tag = last[1];
111
+ let els = [];
112
+ if (tag.startsWith('#elif ')) els = [branch(cp(tag.slice(6), last[6] + 6, snap()))];
113
+ else if (tag === '#else') {
114
+ els = parse(['/if']);
115
+ last[1] === '/if' || unexpected(last);
116
+ } else if (tag !== '/if') unexpected(last);
58
117
  return v => run(cond(v) ? then : els, v);
59
118
  };
60
119
 
61
120
  let parse = stops => {
62
121
  const nodes = [];
63
122
  for (let t; (t = toks[i++]); ) {
64
- if (t.text != null) {
65
- nodes.push((s => () => s)(t.text));
66
- } else if (t.raw != null) {
67
- nodes.push(interp(t.raw, String));
68
- } else if (stops.includes(t.tag.split(' ')[0])) {
69
- last = t.tag;
123
+ const tag = t[1];
124
+ if (!t[0]) {
125
+ nodes.push((s => () => s)(tag));
126
+ } else if (t[0] === 1) {
127
+ nodes.push(interp(t, String));
128
+ } else if (stops.includes(tag.split(' ')[0])) {
129
+ last = t;
70
130
  return nodes;
71
- } else if (t.tag[0] === '!') {
131
+ } else if (tag[0] === '!') {
72
132
  // comment
73
- } else if (t.tag.startsWith('#if ')) {
74
- nodes.push(branch(cp(t.tag.slice(4))));
75
- } else if (t.tag.startsWith('#each ')) {
76
- const m = /^#each ([\s\S]+) as (\w+)(?:\s*,\s*(\w+))?$/.exec(t.tag) || err('Bad {{' + t.tag + '}}');
77
- const list = cp(m[1]), name = m[2], idx = m[3];
133
+ } else if (tag.startsWith('#if ')) {
134
+ blocks.push(opener('if', t));
135
+ nodes.push(branch(cp(tag.slice(4), t[6] + 4, snap())));
136
+ blocks.pop();
137
+ } else if (/^#each(?:\s|$)/.test(tag)) {
138
+ blocks.push(opener('each', t));
139
+ const m = /^#each ([\s\S]+) as ((\w+)(?:\s*,\s*(\w+))?)$/.exec(tag);
140
+ m || fault('Bad {{' + tag + '}}', 'SJABLOON_EACH_SYNTAX', t);
141
+ const name = m[3], idx = m[4], at = t[6] + tag.length - m[2].length;
142
+ if (BLOCKED.test(name)) fault('Bad {{' + tag + '}}', 'SJABLOON_BLOCKED_BINDING', t, at, at + name.length);
143
+ if (idx && BLOCKED.test(idx)) {
144
+ const p = t[6] + tag.length - idx.length;
145
+ fault('Bad {{' + tag + '}}', 'SJABLOON_BLOCKED_BINDING', t, p, p + idx.length);
146
+ }
147
+ const list = cp(m[1], t[6] + 6, snap());
78
148
  // `name`, `idx`, and `loop` are engine-bound inside the body, so
79
149
  // exclude them from names there and restore outer bindings after.
80
- const restore = scope(name, idx, 'loop');
150
+ const mark = bound.length;
151
+ bound.push(name);
152
+ if (idx) bound.push(idx);
153
+ bound.push('loop');
81
154
  const body = parse(['#else', '/each']);
82
- restore();
83
- const empty = last === '#else' ? parse(['/each']) : [];
155
+ bound.length = mark;
156
+ let empty = [];
157
+ if (last[1] === '#else') {
158
+ empty = parse(['/each']);
159
+ last[1] === '/each' || unexpected(last);
160
+ } else if (last[1] !== '/each') unexpected(last);
161
+ blocks.pop();
84
162
  // Child scopes inherit the parent via the prototype chain, so outer
85
163
  // variables stay visible inside the loop body. `@` re-points to the
86
164
  // current item at each level, `$` (root) rides the chain, and `loop`
87
165
  // carries the iteration metadata (index/first/last/length).
88
166
  nodes.push(v => {
89
- const ps = pairs(list(v));
167
+ const lv = list(v), arr = Array.isArray(lv);
168
+ const ps = arr ? lv.slice() : lv && typeof lv === 'object' ? Object.keys(lv).map(k => [lv[k], k]) : [];
90
169
  if (!ps.length) return run(empty, v);
91
- return ps.map(([item, key], j) => {
170
+ return ps.map((x, j) => {
171
+ const item = arr ? x : x[0], key = arr ? j : x[1];
92
172
  const s = Object.create(v);
93
173
  s[name] = item;
94
174
  if (idx) s[idx] = key;
@@ -97,13 +177,15 @@ let parse = stops => {
97
177
  return run(body, s);
98
178
  }).join('');
99
179
  });
100
- } else if (t.tag[0] === '#' || t.tag[0] === '/') {
101
- err('Unexpected {{' + t.tag + '}}');
180
+ } else if (/^#(?:if|elif|else)(?:\s|$)/.test(tag) || tag[0] === '/') {
181
+ unexpected(t);
182
+ } else if (tag[0] === '#') {
183
+ fault('Unknown {{' + tag + '}}', 'SJABLOON_UNKNOWN_BLOCK', t);
102
184
  } else {
103
- nodes.push(interp(t.tag, esc));
185
+ nodes.push(interp(t, esc));
104
186
  }
105
187
  }
106
- stops.length && err('Missing {{' + stops[stops.length - 1] + '}}');
188
+ stops.length && fault('Missing {{' + stops[stops.length - 1] + '}}', 'SJABLOON_UNCLOSED_BLOCK');
107
189
  return nodes;
108
190
  };
109
191
 
@@ -134,25 +216,12 @@ export function template(str, funcs) {
134
216
  fns = funcs;
135
217
  // `$` (root) and `@` (current item) are engine-bound anchors, always in
136
218
  // scope, so they never count as caller-supplied `names`.
137
- bound = new Set(['$', '@']);
219
+ bound = ['$', '@'];
138
220
  nms = new Set();
139
221
  fnms = new Set();
140
- toks = [];
141
- const parts = String(str).split(TAGS);
142
- for (let j = 0; j < parts.length; j += 7) {
143
- if (parts[j]) toks.push({ text: parts[j] });
144
- if (j + 1 >= parts.length) break;
145
- const raw = parts[j + 2] != null;
146
- const t = raw ? { raw: parts[j + 2] } : { tag: parts[j + 5] };
147
- t.l = parts[j + (raw ? 1 : 4)] === '-';
148
- t.r = parts[j + (raw ? 3 : 6)] === '-';
149
- toks.push(t);
150
- }
151
- // `{{-` / `-}}` eat the whitespace touching that side of the tag.
152
- toks.forEach((t, k) => {
153
- if (t.l && toks[k - 1]?.text) toks[k - 1].text = toks[k - 1].text.replace(/\s+$/, '');
154
- if (t.r && toks[k + 1]?.text) toks[k + 1].text = toks[k + 1].text.replace(/^\s+/, '');
155
- });
222
+ src = String(str);
223
+ blocks = [];
224
+ toks = lex(src);
156
225
  i = 0;
157
226
  const nodes = parse([]);
158
227
  // Wrap the values in a root scope carrying the anchors, without mutating
@@ -1 +0,0 @@
1
- import{compile as t}from"xprsn";function r(t,r){(null==r||r>t.length)&&(r=t.length);for(var n=0,e=Array(r);n<r;n++)e[n]=t[n];return e}function n(t,n){var e="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(e)return(e=e.call(t)).next.bind(e);if(Array.isArray(t)||(e=function(t,n){if(t){if("string"==typeof t)return r(t,n);var e={}.toString.call(t).slice(8,-1);return"Object"===e&&t.constructor&&(e=t.constructor.name),"Map"===e||"Set"===e?Array.from(t):"Arguments"===e||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e)?r(t,n):void 0}}(t))||n&&t&&"number"==typeof t.length){e&&(t=e);var a=0;return function(){return a>=t.length?{done:!0}:{done:!1,value:t[a++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var e,a,i,u,o,l,f,c={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},s=function(t){return String(t).replace(/[&<>"']/g,function(t){return c[t]})},h=/\{\{\{(-)?\s*([\s\S]*?)\s*(-)?\}\}\}|\{\{(-)?\s*([\s\S]*?)\s*(-)?\}\}/,g=function(t){throw SyntaxError(t)},p=function(t,r){return t.map(function(t){return t(r)}).join("")},v=function(t,r){return n=d(t),function(t){var e;return r(null!=(e=n(t))?e:"")};var n},d=function(r){for(var e,a=t(r,i),u=n(a.names);!(e=u()).done;){var c=e.value;o.has(c)||l.add(c)}for(var s,h=n(a.functions);!(s=h()).done;)f.add(s.value);return a},m=function(t){var r=y(["#elif","#else","/if"]),n=u.startsWith("#elif ")?[m(d(u.slice(6)))]:"#else"===u?y(["/if"]):[];return function(e){return p(t(e)?r:n,e)}},y=function(t){for(var r,n,i=[],l=function(){if(null!=n.text)i.push((x=n.text,function(){return x}));else if(null!=n.raw)i.push(v(n.raw,String));else{if(t.includes(n.tag.split(" ")[0]))return u=n.tag,{v:i};if("!"===n.tag[0]);else if(n.tag.startsWith("#if "))i.push(m(d(n.tag.slice(4))));else if(n.tag.startsWith("#each ")){var r=/^#each ([\s\S]+) as (\w+)(?:\s*,\s*(\w+))?$/.exec(n.tag)||g("Bad {{"+n.tag+"}}"),e=d(r[1]),a=r[2],l=r[3],f=function(){var t=[].slice.call(arguments).filter(function(t){return t&&!o.has(t)});return t.forEach(function(t){return o.add(t)}),function(){return t.forEach(function(t){return o.delete(t)})}}(a,l,"loop"),c=y(["#else","/each"]);f();var h="#else"===u?y(["/each"]):[];i.push(function(t){var r,n=(r=e(t),Array.isArray(r)?r.map(function(t,r){return[t,r]}):r&&"object"==typeof r?Object.keys(r).map(function(t){return[r[t],t]}):[]);return n.length?n.map(function(r,e){var i=r[0],u=r[1],o=Object.create(t);return o[a]=i,l&&(o[l]=u),o["@"]=i,o.loop={index:e+1,index0:e,first:!e,last:e===n.length-1,length:n.length},p(c,o)}).join(""):p(h,t)})}else"#"===n.tag[0]||"/"===n.tag[0]?g("Unexpected {{"+n.tag+"}}"):i.push(v(n.tag,s))}var x};n=e[a++];)if(r=l())return r.v;return t.length&&g("Missing {{"+t[t.length-1]+"}}"),i};function x(t,r){i=r,o=new Set(["$","@"]),l=new Set,f=new Set,e=[];for(var n=String(t).split(h),u=0;u<n.length&&(n[u]&&e.push({text:n[u]}),!(u+1>=n.length));u+=7){var c=null!=n[u+2],s=c?{raw:n[u+2]}:{tag:n[u+5]};s.l="-"===n[u+(c?1:4)],s.r="-"===n[u+(c?3:6)],e.push(s)}e.forEach(function(t,r){var n,a;t.l&&null!=(n=e[r-1])&&n.text&&(e[r-1].text=e[r-1].text.replace(/\s+$/,"")),t.r&&null!=(a=e[r+1])&&a.text&&(e[r+1].text=e[r+1].text.replace(/^\s+/,""))}),a=0;var g=y([]),v=function(t,r){t=t||{};var n=Object.create(t);return n.$=r?r.root:t,r?"item"in r&&(n["@"]=r.item):n["@"]=t,p(g,n)};return v.names=Array.from(l),v.functions=Array.from(f),v}function S(t,r,n){return x(t,n)(r)}export{S as render,x as template};
package/dist/index.umd.js DELETED
@@ -1 +0,0 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("xprsn")):"function"==typeof define&&define.amd?define(["exports","xprsn"],e):e((t||self).sjabloon={},t.xprsn)}(this,function(t,e){function n(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=Array(e);n<e;n++)r[n]=t[n];return r}function r(t,e){var r="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(r)return(r=r.call(t)).next.bind(r);if(Array.isArray(t)||(r=function(t,e){if(t){if("string"==typeof t)return n(t,e);var r={}.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?n(t,e):void 0}}(t))||e&&t&&"number"==typeof t.length){r&&(t=r);var a=0;return function(){return a>=t.length?{done:!0}:{done:!1,value:t[a++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,i,o,u,l,f,s,c={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},p=function(t){return String(t).replace(/[&<>"']/g,function(t){return c[t]})},h=/\{\{\{(-)?\s*([\s\S]*?)\s*(-)?\}\}\}|\{\{(-)?\s*([\s\S]*?)\s*(-)?\}\}/,g=function(t){throw SyntaxError(t)},d=function(t,e){return t.map(function(t){return t(e)}).join("")},v=function(t,e){return n=m(t),function(t){var r;return e(null!=(r=n(t))?r:"")};var n},m=function(t){for(var n,a=e.compile(t,o),i=r(a.names);!(n=i()).done;){var u=n.value;l.has(u)||f.add(u)}for(var c,p=r(a.functions);!(c=p()).done;)s.add(c.value);return a},y=function(t){var e=x(["#elif","#else","/if"]),n=u.startsWith("#elif ")?[y(m(u.slice(6)))]:"#else"===u?x(["/if"]):[];return function(r){return d(t(r)?e:n,r)}},x=function(t){for(var e,n,r=[],o=function(){if(null!=n.text)r.push((h=n.text,function(){return h}));else if(null!=n.raw)r.push(v(n.raw,String));else{if(t.includes(n.tag.split(" ")[0]))return u=n.tag,{v:r};if("!"===n.tag[0]);else if(n.tag.startsWith("#if "))r.push(y(m(n.tag.slice(4))));else if(n.tag.startsWith("#each ")){var e=/^#each ([\s\S]+) as (\w+)(?:\s*,\s*(\w+))?$/.exec(n.tag)||g("Bad {{"+n.tag+"}}"),a=m(e[1]),i=e[2],o=e[3],f=function(){var t=[].slice.call(arguments).filter(function(t){return t&&!l.has(t)});return t.forEach(function(t){return l.add(t)}),function(){return t.forEach(function(t){return l.delete(t)})}}(i,o,"loop"),s=x(["#else","/each"]);f();var c="#else"===u?x(["/each"]):[];r.push(function(t){var e,n=(e=a(t),Array.isArray(e)?e.map(function(t,e){return[t,e]}):e&&"object"==typeof e?Object.keys(e).map(function(t){return[e[t],t]}):[]);return n.length?n.map(function(e,r){var a=e[0],u=e[1],l=Object.create(t);return l[i]=a,o&&(l[o]=u),l["@"]=a,l.loop={index:r+1,index0:r,first:!r,last:r===n.length-1,length:n.length},d(s,l)}).join(""):d(c,t)})}else"#"===n.tag[0]||"/"===n.tag[0]?g("Unexpected {{"+n.tag+"}}"):r.push(v(n.tag,p))}var h};n=a[i++];)if(e=o())return e.v;return t.length&&g("Missing {{"+t[t.length-1]+"}}"),r};function b(t,e){o=e,l=new Set(["$","@"]),f=new Set,s=new Set,a=[];for(var n=String(t).split(h),r=0;r<n.length&&(n[r]&&a.push({text:n[r]}),!(r+1>=n.length));r+=7){var u=null!=n[r+2],c=u?{raw:n[r+2]}:{tag:n[r+5]};c.l="-"===n[r+(u?1:4)],c.r="-"===n[r+(u?3:6)],a.push(c)}a.forEach(function(t,e){var n,r;t.l&&null!=(n=a[e-1])&&n.text&&(a[e-1].text=a[e-1].text.replace(/\s+$/,"")),t.r&&null!=(r=a[e+1])&&r.text&&(a[e+1].text=a[e+1].text.replace(/^\s+/,""))}),i=0;var p=x([]),g=function(t,e){t=t||{};var n=Object.create(t);return n.$=e?e.root:t,e?"item"in e&&(n["@"]=e.item):n["@"]=t,d(p,n)};return g.names=Array.from(f),g.functions=Array.from(s),g}t.render=function(t,e,n){return b(t,n)(e)},t.template=b});