@stll/template-conditions 0.6.2 → 0.6.3

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/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@ import { CompareNode, CompareOp, Condition, ConditionNode, ConditionNode as Cond
4
4
  export declare const resolvePath: (path: string, data: Record<string, unknown>) => unknown;
5
5
  //#endregion
6
6
  //#region src/parse.d.ts
7
+ /** Maximum combined nesting of parentheses and unary negation. */
8
+ export declare const MAX_CONDITION_NESTING = 256;
7
9
  /** Bare words the tokenizer reads as operators or literals; a data path can
8
10
  * never be one of them. */
9
11
  export declare const CONDITION_RESERVED_WORDS: readonly ["and", "or", "not", "in", "is", "defined", "true", "false"];
@@ -349,7 +351,8 @@ type InvalidMarker = {
349
351
  */
350
352
  export declare const scanInvalidMarkers: (text: string) => InvalidMarker[];
351
353
  /** The loop alias the codemod (and the legacy-marker message) generates for a
352
- * `{{#each path}}`: the singular of the last plain-word segment, else `item`. */
354
+ * `{{#each path}}`: the singular of the last plain-word segment, else `item`.
355
+ * A fresh `{% for %}` inserted by the Studio names its item by the same rule. */
353
356
  export declare const legacyLoopAlias: (path: string) => string;
354
357
  /** Translate an old-dialect condition expression to the Jinja surface:
355
358
  * `!a` becomes `not a`, `a contains "x"` becomes `"x" in a`. */
package/dist/index.js CHANGED
@@ -191,6 +191,8 @@ const evaluateNumericExpression = (expression, data) => {
191
191
  };
192
192
  //#endregion
193
193
  //#region src/parse.ts
194
+ /** Maximum combined nesting of parentheses and unary negation. */
195
+ const MAX_CONDITION_NESTING = 256;
194
196
  const COMPARE_SYMBOL_TO_OP = {
195
197
  "==": "eq",
196
198
  "!=": "neq",
@@ -325,13 +327,16 @@ const parseCondition = (expression) => {
325
327
  if (tokens.length === 0) return null;
326
328
  let pos = 0;
327
329
  const peek = () => tokens[pos];
328
- const parseOr = () => {
329
- const first = parseAnd();
330
+ const parseOr = (depth) => {
331
+ const first = parseAnd(depth);
332
+ if (first === null) return null;
330
333
  if (peek()?.type !== "or") return first;
331
334
  const children = [first];
332
335
  while (peek()?.type === "or") {
333
336
  pos += 1;
334
- children.push(parseAnd());
337
+ const child = parseAnd(depth);
338
+ if (child === null) return null;
339
+ children.push(child);
335
340
  }
336
341
  return {
337
342
  type: "group",
@@ -339,13 +344,16 @@ const parseCondition = (expression) => {
339
344
  children
340
345
  };
341
346
  };
342
- const parseAnd = () => {
343
- const first = parseNot();
347
+ const parseAnd = (depth) => {
348
+ const first = parseNot(depth);
349
+ if (first === null) return null;
344
350
  if (peek()?.type !== "and") return first;
345
351
  const children = [first];
346
352
  while (peek()?.type === "and") {
347
353
  pos += 1;
348
- children.push(parseNot());
354
+ const child = parseNot(depth);
355
+ if (child === null) return null;
356
+ children.push(child);
349
357
  }
350
358
  return {
351
359
  type: "group",
@@ -353,22 +361,27 @@ const parseCondition = (expression) => {
353
361
  children
354
362
  };
355
363
  };
356
- const parseNot = () => {
364
+ const parseNot = (depth) => {
357
365
  if (peek()?.type === "not") {
366
+ if (depth >= 256) return null;
358
367
  pos += 1;
368
+ const child = parseNot(depth + 1);
369
+ if (child === null) return null;
359
370
  return {
360
371
  type: "group",
361
372
  combinator: "and",
362
373
  negated: true,
363
- children: [parseNot()]
374
+ children: [child]
364
375
  };
365
376
  }
366
- return parseComparison();
377
+ return parseComparison(depth);
367
378
  };
368
- const parseComparison = () => {
379
+ const parseComparison = (depth) => {
369
380
  if (peek()?.type === "lparen") {
381
+ if (depth >= 256) return null;
370
382
  pos += 1;
371
- const inner = parseOr();
383
+ const inner = parseOr(depth + 1);
384
+ if (inner === null) return null;
372
385
  if (peek()?.type === "rparen") pos += 1;
373
386
  return inner;
374
387
  }
@@ -437,7 +450,7 @@ const parseCondition = (expression) => {
437
450
  right: operandFromToken(rightTok)
438
451
  };
439
452
  };
440
- return parseOr();
453
+ return parseOr(0);
441
454
  };
442
455
  //#endregion
443
456
  //#region src/referenced-paths.ts
@@ -1183,7 +1196,8 @@ const singularize = (segment) => {
1183
1196
  return segment;
1184
1197
  };
1185
1198
  /** The loop alias the codemod (and the legacy-marker message) generates for a
1186
- * `{{#each path}}`: the singular of the last plain-word segment, else `item`. */
1199
+ * `{{#each path}}`: the singular of the last plain-word segment, else `item`.
1200
+ * A fresh `{% for %}` inserted by the Studio names its item by the same rule. */
1187
1201
  const legacyLoopAlias = (path) => {
1188
1202
  const segment = path.split(".").at(-1) ?? "";
1189
1203
  if (!IDENTIFIER_RE.test(segment)) return "item";
@@ -1713,4 +1727,4 @@ const evaluateCondition = (expression, data, namedConditions, _resolved = /* @__
1713
1727
  return evaluateCondition$1(node, makeResolver(data, namedConditions, _resolved, _depth));
1714
1728
  };
1715
1729
  //#endregion
1716
- export { ARRAY_FILTER_NAMES, BLOCK_DIRECTIVE_KINDS, CONDITION_RESERVED_WORDS, DATE_FORMAT_EXAMPLE_ISO, DATE_FORMAT_STYLES, DIRECTIVE_KINDS, FILTER_NAMES, LOOP_PROPERTIES, MARKER_DEFECT_KINDS, MARKER_OUTPUT_BODY, MARKER_STATEMENT_BODY, MAX_CONDITION_DEPTH, NUMERIC_FUNCTION_NAMES, arrayFiltersFromFieldConfig, assertNever, blockDirectiveLinePattern, classifyMarker, classifyMarkerDefect, clauseSlotKey, clauseSlotPattern, conditionHasFormula, conditionNodeSchema, conditionSchema, detectRowBlockPair, emptyCondition, evaluateCondition, evaluateNumericExpression, filtersFromFieldConfig, formatDate, hasBlockDirectivePattern, hasNumberingPattern, isArrayFilterName, isBlockDirectiveKind, isClauseSlotName, isFieldPath, isSafeFieldPath, legacyLoopAlias, legacyMarkerReplacement, loopPattern, markerPattern, normalizeMarkerInner, numPattern, parseCondition, placeholderPattern, qualifyLoopPath, qualifyRowScopedPath, qualifyRowScopedPlaceholder, refPattern, referencedConditionPaths, renderConditionTag, renderDeterministicFieldValue, renderFilterChain, renderForOpener, renderValueMarker, replaceOutputMarkers, resolvePath, rowScopePaths, scanInvalidMarkers, scanMarkers, serializeCondition, substitutionKey, translateLegacyExpression, unwritableFilterValues, unwritableMarkerLiteral };
1730
+ export { ARRAY_FILTER_NAMES, BLOCK_DIRECTIVE_KINDS, CONDITION_RESERVED_WORDS, DATE_FORMAT_EXAMPLE_ISO, DATE_FORMAT_STYLES, DIRECTIVE_KINDS, FILTER_NAMES, LOOP_PROPERTIES, MARKER_DEFECT_KINDS, MARKER_OUTPUT_BODY, MARKER_STATEMENT_BODY, MAX_CONDITION_DEPTH, MAX_CONDITION_NESTING, NUMERIC_FUNCTION_NAMES, arrayFiltersFromFieldConfig, assertNever, blockDirectiveLinePattern, classifyMarker, classifyMarkerDefect, clauseSlotKey, clauseSlotPattern, conditionHasFormula, conditionNodeSchema, conditionSchema, detectRowBlockPair, emptyCondition, evaluateCondition, evaluateNumericExpression, filtersFromFieldConfig, formatDate, hasBlockDirectivePattern, hasNumberingPattern, isArrayFilterName, isBlockDirectiveKind, isClauseSlotName, isFieldPath, isSafeFieldPath, legacyLoopAlias, legacyMarkerReplacement, loopPattern, markerPattern, normalizeMarkerInner, numPattern, parseCondition, placeholderPattern, qualifyLoopPath, qualifyRowScopedPath, qualifyRowScopedPlaceholder, refPattern, referencedConditionPaths, renderConditionTag, renderDeterministicFieldValue, renderFilterChain, renderForOpener, renderValueMarker, replaceOutputMarkers, resolvePath, rowScopePaths, scanInvalidMarkers, scanMarkers, serializeCondition, substitutionKey, translateLegacyExpression, unwritableFilterValues, unwritableMarkerLiteral };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/template-conditions",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "A document-template marker and condition engine: {{...}} marker grammar, parse, compute, serialize, field values.",
5
5
  "keywords": [
6
6
  "condition",
@@ -43,7 +43,7 @@
43
43
  "test": "bun test",
44
44
  "test:property": "bun test --preload @stll/property-testing/preload $(grep -rlF 'fc.assert' src --include='*.test.ts')",
45
45
  "typecheck": "bun ../../packages/scripts/src/tsc-native.ts --noEmit",
46
- "lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --type-aware packages/template-conditions",
46
+ "lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --type-aware --type-check packages/template-conditions",
47
47
  "lint:fix": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --type-aware --fix packages/template-conditions",
48
48
  "format": "bun ../../scripts/run-oxfmt.ts .",
49
49
  "prepack": "bun run build"