sjabloon 0.11.0 → 0.13.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/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./types.js";
2
- import type { SjabloonFunctions, SjabloonRenderer, SjabloonValues, Token } from "./types.js";
2
+ import type { SjabloonRender, SjabloonTemplate, Token } from "./types.js";
3
3
 
4
4
  /**
5
5
  * Compile a template once, render it many times to a token stream.
@@ -7,18 +7,9 @@ import type { SjabloonFunctions, SjabloonRenderer, SjabloonValues, Token } from
7
7
  * `{{ expr }}` emits a value token; escaping belongs to whoever consumes the
8
8
  * stream, so `{{{ expr }}}` has no meaning here and is a compile-time
9
9
  * `SJABLOON_RAW_TAG` error.
10
- *
11
- * @see SjabloonRenderer for `names`/`functions`, SjabloonScope for `$` and `@`.
12
- * @throws {SyntaxError} On malformed tags, unclosed blocks, or bad expressions.
13
10
  */
14
- export function template(
15
- str: string,
16
- funcs?: SjabloonFunctions,
17
- opts?: { bound?: Iterable<string> },
18
- ): SjabloonRenderer<Token[]>;
19
-
20
- /** Compile and render in one go. Shorthand for `template(str, funcs)(values)`. */
21
- export function render(str: string, values?: SjabloonValues, funcs?: SjabloonFunctions): Token[];
11
+ export const template: SjabloonTemplate<Token[]>;
12
+ export const render: SjabloonRender<Token[]>;
22
13
 
23
14
  /**
24
15
  * Join a token stream into the string `sjabloon/text` would have produced:
package/lib/index.js CHANGED
@@ -9,15 +9,19 @@ import { display, make } from "./core.js";
9
9
  export { display, isDiagnostic, relocate } from "./core.js";
10
10
 
11
11
  export const { template, render } = make([
12
- // Static text is a compile-time constant: hoist and freeze one token per
13
- // text node rather than allocating a fresh object every loop iteration.
14
- (text) =>
15
- ((token) => (scope, acc) => {
16
- acc.push(token);
17
- })(Object.freeze({ literal: text })),
18
- (expr) => (scope, acc) => {
19
- acc.push({ value: expr(scope) });
20
- },
12
+ // Static text is a compile-time constant: the default parameter hoists and
13
+ // freezes one token per text node rather than allocating per loop iteration.
14
+ (text, token = Object.freeze({ literal: text })) =>
15
+ (scope, acc) =>
16
+ acc.push(token),
17
+ // A tagged interpolation spreads what the compile's `tag` returned; the
18
+ // value is written last, so the token's own key cannot be taken over. The
19
+ // branch is taken once at compile time, so an untagged template emits the
20
+ // exact closure it always did.
21
+ (expr, tagged) =>
22
+ tagged
23
+ ? (scope, acc) => acc.push({ ...tagged, value: expr(scope) })
24
+ : (scope, acc) => acc.push({ value: expr(scope) }),
21
25
  0,
22
26
  () => /** @type {import('./types.js').Token[]} */ ([]),
23
27
  (acc) => acc,
@@ -31,12 +35,11 @@ export const { template, render } = make([
31
35
  * @param {readonly import('./types.js').Token[]} tokens A render's output.
32
36
  * @returns {string} The joined text.
33
37
  */
34
- export const text = (tokens) => {
35
- let s = "";
38
+ export const text = (tokens) =>
36
39
  // A literal never stringifies and a nullish value still renders empty.
37
40
  // Widened here because each token carries one key or the other, which the
38
41
  // public union deliberately does not model.
39
- for (const t of /** @type {readonly { literal?: string, value?: unknown }[]} */ (tokens))
40
- s += t.literal ?? display(t.value);
41
- return s;
42
- };
42
+ /** @type {readonly { literal?: string, value?: unknown }[]} */ (tokens).reduce(
43
+ (s, t) => s + (t.literal ?? display(t.value)),
44
+ "",
45
+ );
package/lib/text.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./types.js";
2
- import type { SjabloonFunctions, SjabloonRenderer, SjabloonValues } from "./types.js";
2
+ import type { SjabloonRender, SjabloonTemplate } from "./types.js";
3
3
 
4
4
  /**
5
5
  * Compile a template once, render it many times to a plain string.
@@ -7,15 +7,6 @@ import type { SjabloonFunctions, SjabloonRenderer, SjabloonValues } from "./type
7
7
  * `{{ expr }}` interpolates unescaped — escaping belongs at the output edge —
8
8
  * so `{{{ expr }}}` has no meaning here and is a compile-time
9
9
  * `SJABLOON_RAW_TAG` error.
10
- *
11
- * @see SjabloonRenderer for `names`/`functions`, SjabloonScope for `$` and `@`.
12
- * @throws {SyntaxError} On malformed tags, unclosed blocks, or bad expressions.
13
10
  */
14
- export function template(
15
- str: string,
16
- funcs?: SjabloonFunctions,
17
- opts?: { bound?: Iterable<string> },
18
- ): SjabloonRenderer<string>;
19
-
20
- /** Compile and render in one go. Shorthand for `template(str, funcs)(values)`. */
21
- export function render(str: string, values?: SjabloonValues, funcs?: SjabloonFunctions): string;
11
+ export const template: SjabloonTemplate<string>;
12
+ export const render: SjabloonRender<string>;
package/lib/text.js CHANGED
@@ -6,13 +6,16 @@
6
6
  * Definitionally `text(template(str)(values))` from the root entry, but built
7
7
  * as a string accumulator so casual string users never allocate tokens.
8
8
  */
9
- import { display, litNode, make } from "./core.js";
9
+ import { display, make } from "./core.js";
10
10
 
11
11
  export { isDiagnostic, relocate } from "./core.js";
12
12
 
13
+ // The string accumulator is this edition's own shape, so its profile is stated
14
+ // here rather than named in the core: every node is shorter than an import of
15
+ // it, and the core stays the parser alone.
13
16
  export const { template, render } = make([
14
- litNode,
15
- (expr) => (scope, acc, value) => ((value = expr(scope)), (acc.text += display(value))),
17
+ (text) => (scope, acc) => (acc.text += text),
18
+ (expr) => (scope, acc) => (acc.text += display(expr(scope))),
16
19
  0,
17
20
  () => ({ text: "" }),
18
21
  (acc) => acc.text,
package/lib/types.d.ts CHANGED
@@ -1,7 +1,12 @@
1
+ import type { Diagnostic, Relocation } from "waarmerk";
1
2
  import type { XprsnErrorCode, XprsnRead } from "xprsn";
2
3
 
4
+ /**
5
+ * The codes sjabloon itself mints. An expression fault carries xprsn's instead,
6
+ * which is why the diagnostic's `code` is the wider union — the same split
7
+ * padvinder draws between its own codes and treffer's.
8
+ */
3
9
  export type SjabloonErrorCode =
4
- | XprsnErrorCode
5
10
  | "SJABLOON_EACH_SYNTAX"
6
11
  | "SJABLOON_BLOCKED_BINDING"
7
12
  | "SJABLOON_UNEXPECTED_TAG"
@@ -16,8 +21,17 @@ export interface SjabloonBlock {
16
21
  readonly end: number;
17
22
  }
18
23
 
19
- export interface SjabloonDiagnostic extends Error {
20
- readonly code: SjabloonErrorCode;
24
+ /**
25
+ * The fields and their meanings are waarmerk's; this names the code union they are
26
+ * checked against, narrows the three this module always carries from optional
27
+ * to required, and adds the block context a template fault sits in.
28
+ *
29
+ * `code` is widened with `XprsnErrorCode` because most diagnostics here are
30
+ * xprsn errors translated into template coordinates: a host can still tell a
31
+ * malformed tag from a fault in the expression inside it.
32
+ */
33
+ export interface SjabloonDiagnostic extends Diagnostic<SjabloonErrorCode | XprsnErrorCode> {
34
+ readonly code: SjabloonErrorCode | XprsnErrorCode;
21
35
  readonly start: number;
22
36
  readonly end: number;
23
37
  readonly blocks: readonly SjabloonBlock[];
@@ -81,12 +95,53 @@ export interface SjabloonRenderer<T> {
81
95
  scoped(values: SjabloonValues): T;
82
96
  }
83
97
 
98
+ /**
99
+ * `template` in every edition: compile once, render many times. `T` is what
100
+ * one render returns.
101
+ *
102
+ * @throws {SyntaxError} On malformed tags, unclosed blocks, or bad expressions.
103
+ */
104
+ export type SjabloonTemplate<T> = (
105
+ str: string,
106
+ funcs?: SjabloonFunctions,
107
+ opts?: {
108
+ bound?: Iterable<string>;
109
+ /**
110
+ * Name an interpolation by what it says. Called once per `{{ }}` while
111
+ * compiling, with that interpolation's expression source as written and
112
+ * trimmed (`{{- page.number -}}` is `"page.number"`), and never again at
113
+ * render time. Returned keys join every value token that interpolation
114
+ * emits; `value` and `literal` are the stream's own, so a tag may not
115
+ * supply either. It runs inside the parse, which is shared synchronous
116
+ * state: read the expression and return, and compile no template from
117
+ * within it. Block expressions
118
+ * (`#if` conditions, `#each` collections) emit no token and are not
119
+ * offered. The token edition alone has tokens to carry them; the string
120
+ * editions ignore this.
121
+ */
122
+ tag?: (expr: string) => object | undefined;
123
+ },
124
+ ) => SjabloonRenderer<T>;
125
+
126
+ /** Compile and render in one go. Shorthand for `template(str, funcs)(values)`. */
127
+ export type SjabloonRender<T> = (
128
+ str: string,
129
+ values?: SjabloonValues,
130
+ funcs?: SjabloonFunctions,
131
+ ) => T;
132
+
84
133
  /** One static text run of the template, verbatim. */
85
134
  export interface LiteralToken {
86
135
  literal: string;
87
136
  }
88
137
 
89
- /** One `{{ }}` interpolation, pre-stringify. Nullish values are preserved. */
138
+ /**
139
+ * One `{{ }}` interpolation, pre-stringify. Nullish values are preserved.
140
+ *
141
+ * A compile that passed `tag` also carries that interpolation's returned keys
142
+ * here, under the names the embedder chose; declare them by intersecting this
143
+ * type with your own, as `ValueToken & { field?: string }`.
144
+ */
90
145
  export interface ValueToken {
91
146
  value: unknown;
92
147
  }
@@ -113,7 +168,4 @@ export function isDiagnostic(error: unknown): error is SjabloonDiagnostic;
113
168
  *
114
169
  * @throws {TypeError} When `diag` is not a sjabloon diagnostic.
115
170
  */
116
- export function relocate(
117
- diag: unknown,
118
- opts?: { prefix?: string; offset?: number },
119
- ): SjabloonDiagnostic;
171
+ export function relocate(diag: unknown, opts?: Relocation): SjabloonDiagnostic;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sjabloon",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Tiny, CSP-safe template engine for JavaScript, powered by xprsn expressions. No eval, no new Function.",
5
5
  "keywords": [
6
6
  "csp",
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "repository": "getquario/sjabloon",
19
19
  "files": [
20
- "lib"
20
+ "lib",
21
+ "EMBEDDING.md"
21
22
  ],
22
23
  "type": "module",
23
24
  "module": "lib/index.js",
@@ -41,6 +42,7 @@
41
42
  "bench": "node --disallow-code-generation-from-strings bench/index.js",
42
43
  "bench:comparison": "npm --prefix bench/comparison run bench",
43
44
  "check": "run-s fmt:check lint fallow size test fuzz:regression test:browser",
45
+ "commitlint": "commitlint",
44
46
  "fallow": "fallow",
45
47
  "fmt": "oxfmt",
46
48
  "fmt:check": "oxfmt --check",
@@ -61,17 +63,20 @@
61
63
  "test:unit": "c8 --100 --src lib/ node --disallow-code-generation-from-strings --test --test-concurrency=1 test/*.test.js"
62
64
  },
63
65
  "dependencies": {
64
- "xprsn": "^0.11.0"
66
+ "waarmerk": "^0.1.0",
67
+ "xprsn": "^0.11.1"
65
68
  },
66
69
  "devDependencies": {
67
70
  "@arethetypeswrong/cli": "^0.18.3",
71
+ "@commitlint/cli": "^21.2.2",
72
+ "@commitlint/config-conventional": "^21.2.2",
68
73
  "@jazzer.js/bug-detectors": "^4.0.0",
69
74
  "@jazzer.js/core": "^4.0.0",
70
75
  "@size-limit/preset-small-lib": "^13.0.3",
71
76
  "c8": "^12.0.0",
72
77
  "fallow": "^3.17.0",
73
78
  "npm-run-all": "^4.1.5",
74
- "oxfmt": "^0.64.0",
79
+ "oxfmt": "^0.66.0",
75
80
  "oxlint": "^1.78.0",
76
81
  "oxlint-tsgolint": "^7.0.2001",
77
82
  "playwright": "^1.61.1",
@@ -83,6 +88,7 @@
83
88
  "name": "sjabloon",
84
89
  "path": "lib/index.js",
85
90
  "ignore": [
91
+ "waarmerk",
86
92
  "xprsn"
87
93
  ],
88
94
  "limit": "2.35 kB"
@@ -91,6 +97,7 @@
91
97
  "name": "sjabloon/text",
92
98
  "path": "lib/text.js",
93
99
  "ignore": [
100
+ "waarmerk",
94
101
  "xprsn"
95
102
  ],
96
103
  "limit": "2.35 kB"
@@ -99,6 +106,7 @@
99
106
  "name": "sjabloon/html",
100
107
  "path": "lib/html.js",
101
108
  "ignore": [
109
+ "waarmerk",
102
110
  "xprsn"
103
111
  ],
104
112
  "limit": "2.4 kB"