@rohal12/spindle-lsp 0.15.27 → 0.15.29

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/bin.js CHANGED
@@ -55,7 +55,7 @@ var VERSION;
55
55
  var init_version = __esm({
56
56
  "src/version.ts"() {
57
57
  "use strict";
58
- VERSION = false ? pkg.version : "0.15.27";
58
+ VERSION = false ? pkg.version : "0.15.29";
59
59
  }
60
60
  });
61
61
 
@@ -10640,6 +10640,11 @@ var init_file_inputs = __esm({
10640
10640
  });
10641
10641
 
10642
10642
  // node_modules/@rohal12/spindle/dist/pkg/story-variables.js
10643
+ function effectiveNamedDefinitions(definitions) {
10644
+ const latest = new NameMap();
10645
+ for (const definition2 of definitions) latest.set(definition2.name, definition2);
10646
+ return [...latest.values()];
10647
+ }
10643
10648
  function isSigil(c) {
10644
10649
  return c !== void 0 && SIGIL_CHARS.has(c);
10645
10650
  }
@@ -14051,11 +14056,66 @@ function peg$parse(input2, options) {
14051
14056
  if (peg$success) return peg$result;
14052
14057
  else peg$throw();
14053
14058
  }
14059
+ function consumesOptionClauses(macro) {
14060
+ const name = macro.name.toLowerCase();
14061
+ return macro.source === "builtin" && !macro.widget && (name === "listbox" || name === "cycle");
14062
+ }
14063
+ function checkParameterTypes(macro, parameters) {
14064
+ const holdsFix = `Give a \`string\` or \`text\` parameter one of ${STRING_HOLDS.join(", ")} to hold (see docs/custom-macros.md#what-a-string-holds).`;
14065
+ for (const param of parameters) {
14066
+ const type = param.type;
14067
+ if (!PARAMETER_TYPES.includes(type)) throw parameterError(macro, param, type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`, `Give it one of the types ${PARAMETER_TYPES.join(", ")} (see docs/custom-macros.md#parameter-types), or declare no parameters and read props.rawArgs.`);
14068
+ const holds = param.holds;
14069
+ if (holds !== void 0 && type !== "string" && type !== "text") throw parameterError(macro, param, `is of the type ${type}, which holds what it is`, holdsFix);
14070
+ if (holds !== void 0 && !STRING_HOLDS.includes(holds)) throw parameterError(macro, param, `holds the unknown ${JSON.stringify(holds)}`, holdsFix);
14071
+ if (param.parameters) checkParameterTypes(macro, param.parameters);
14072
+ }
14073
+ }
14074
+ function parameterError(macro, param, problem, fix) {
14075
+ return /* @__PURE__ */ new Error(`spindle: The parameter "${param.name}" of the macro {${macro}} ${problem}. ${fix}`);
14076
+ }
14077
+ function indexCompanionParents(index, macro) {
14078
+ const name = macro.name.toLowerCase();
14079
+ for (const sub of macro.subMacros) {
14080
+ const parents = index.get(sub) ?? /* @__PURE__ */ new Set();
14081
+ parents.add(name);
14082
+ index.set(sub, parents);
14083
+ }
14084
+ }
14085
+ function branchParents(name, macros) {
14086
+ if (!macros) return [...companionParents.get(name) ?? []].filter((parent) => !consumesOptionClauses(metadataRegistry.get(parent)));
14087
+ return [...branchParentLookup(macros)(name)];
14088
+ }
14089
+ function branchParentLookup(macros) {
14090
+ const parents = new NameMap();
14091
+ for (const macro of effectiveNamedDefinitions(macros)) {
14092
+ if (consumesOptionClauses(macro)) continue;
14093
+ indexCompanionParents(parents, macro);
14094
+ }
14095
+ const index = new NameMap();
14096
+ for (const [sub, names] of parents) index.set(sub, [...names]);
14097
+ return (name) => index.get(name) ?? [];
14098
+ }
14099
+ function getMacroParameters(name) {
14100
+ return metadataRegistry.get(name)?.parameters;
14101
+ }
14102
+ function getMacroRegistry() {
14103
+ return Array.from(metadataRegistry.values());
14104
+ }
14054
14105
  function isRawMacro(name, widget = WIDGETS.has(name)) {
14055
14106
  return RAW_BODY_MACROS.has(name.toLowerCase()) && !widget;
14056
14107
  }
14057
14108
  function isBlockMacro(name) {
14058
- return BLOCK_MACROS.has(name) || DEFINED_BLOCK_MACROS.has(name) || BLOCK_WIDGETS.has(name);
14109
+ if (REGISTERED_BLOCK_MACROS.has(name)) return true;
14110
+ if (WIDGETS.has(name)) return BLOCK_WIDGETS.has(name);
14111
+ if (DEFINED_MACROS.has(name)) return DEFINED_BLOCK_MACROS.has(name);
14112
+ return isBuiltinBlockMacro(name);
14113
+ }
14114
+ function isBuiltinBlockMacro(name) {
14115
+ return BLOCK_MACROS.has(name);
14116
+ }
14117
+ function isRegisteredBlockMacro(name) {
14118
+ return REGISTERED_BLOCK_MACROS.has(name);
14059
14119
  }
14060
14120
  function buildAst(nodes) {
14061
14121
  return nodes.map(astNode);
@@ -14106,7 +14166,7 @@ function astNode({ token, body }) {
14106
14166
  };
14107
14167
  if (!body) return withSelectors$1(node2, token);
14108
14168
  const children = buildAst(body.children);
14109
- const branching = BRANCHING.has(name) && !WIDGETS.has(name);
14169
+ const branching = BRANCHING.has(name) && !WIDGETS.has(name) && getMacroParameters.source(name) !== "user";
14110
14170
  if (!branching && body.branches.length === 0) {
14111
14171
  node2.children = children;
14112
14172
  return withSelectors$1(node2, token);
@@ -14121,29 +14181,15 @@ function astNode({ token, body }) {
14121
14181
  }
14122
14182
  }
14123
14183
  }
14124
- function checkParameterTypes(macro, parameters) {
14125
- const holdsFix = `Give a \`string\` or \`text\` parameter one of ${STRING_HOLDS.join(", ")} to hold (see docs/custom-macros.md#what-a-string-holds).`;
14126
- for (const param of parameters) {
14127
- const type = param.type;
14128
- if (!PARAMETER_TYPES.includes(type)) throw parameterError(macro, param, type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`, `Give it one of the types ${PARAMETER_TYPES.join(", ")} (see docs/custom-macros.md#parameter-types), or declare no parameters and read props.rawArgs.`);
14129
- const holds = param.holds;
14130
- if (holds !== void 0 && type !== "string" && type !== "text") throw parameterError(macro, param, `is of the type ${type}, which holds what it is`, holdsFix);
14131
- if (holds !== void 0 && !STRING_HOLDS.includes(holds)) throw parameterError(macro, param, `holds the unknown ${JSON.stringify(holds)}`, holdsFix);
14132
- if (param.parameters) checkParameterTypes(macro, param.parameters);
14184
+ function upperBound(sorted, value) {
14185
+ let low = 0;
14186
+ let high = sorted.length;
14187
+ while (low < high) {
14188
+ const mid = low + high >>> 1;
14189
+ if (sorted[mid] <= value) low = mid + 1;
14190
+ else high = mid;
14133
14191
  }
14134
- }
14135
- function parameterError(macro, param, problem, fix) {
14136
- return /* @__PURE__ */ new Error(`spindle: The parameter "${param.name}" of the macro {${macro}} ${problem}. ${fix}`);
14137
- }
14138
- function branchParents(name, macros) {
14139
- if (!macros) return [...companionParents.get(name) ?? []].filter((parent) => parent !== "listbox");
14140
- return macros.filter((macro) => macro.name.toLowerCase() !== "listbox" && macro.subMacros.some((sub) => sub.toLowerCase() === name.toLowerCase())).map((macro) => macro.name.toLowerCase());
14141
- }
14142
- function getMacroParameters(name) {
14143
- return metadataRegistry.get(name)?.parameters;
14144
- }
14145
- function getMacroRegistry() {
14146
- return Array.from(metadataRegistry.values());
14192
+ return low;
14147
14193
  }
14148
14194
  function lineColumn(text3, offset2) {
14149
14195
  let line = 1;
@@ -14162,14 +14208,7 @@ function sourcePositions(text3) {
14162
14208
  const starts = [0];
14163
14209
  for (let i2 = 0; i2 < text3.length; i2++) if (text3[i2] === "\n") starts.push(i2 + 1);
14164
14210
  return (offset2) => {
14165
- let low = 0;
14166
- let high = starts.length;
14167
- while (low < high) {
14168
- const mid = low + high >>> 1;
14169
- if (starts[mid] <= offset2) low = mid + 1;
14170
- else high = mid;
14171
- }
14172
- const line = Math.max(1, low);
14211
+ const line = Math.max(1, upperBound(starts, offset2));
14173
14212
  return {
14174
14213
  line,
14175
14214
  column: offset2 - starts[line - 1] + 1
@@ -14183,9 +14222,11 @@ function pairMarkup(tokens2, options = {}) {
14183
14222
  const isBlock = options.isBlock ?? isBlockMacro;
14184
14223
  const isRaw = options.isRaw ?? isRawMacro;
14185
14224
  const parentsOf = (name) => {
14225
+ if (options.branchParents) return options.branchParents(name);
14226
+ const registered = branchParents(name);
14186
14227
  const lower = name.toLowerCase();
14187
14228
  const builtin = Object.prototype.hasOwnProperty.call(BRANCH_PARENT, lower) ? BRANCH_PARENT[lower] : void 0;
14188
- return builtin ? [builtin] : options.branchParents?.(name) ?? branchParents(name);
14229
+ return builtin && getMacroParameters.source(builtin) === void 0 ? [...registered, builtin] : registered;
14189
14230
  };
14190
14231
  const errors = [];
14191
14232
  const noticedAtEnd = (tokens2[tokens2.length - 1]?.end ?? 0) + 1;
@@ -14284,6 +14325,10 @@ function pairMarkup(tokens2, options = {}) {
14284
14325
  add(node2);
14285
14326
  continue;
14286
14327
  }
14328
+ if (stack.length === MAX_MARKUP_DEPTH) report2("nesting-limit", token, `Markup nesting exceeds the limit of ${MAX_MARKUP_DEPTH} elements`, token.start, {
14329
+ name: nameOf(token),
14330
+ limit: String(MAX_MARKUP_DEPTH)
14331
+ });
14287
14332
  const body = {
14288
14333
  children: [],
14289
14334
  branches: []
@@ -14692,6 +14737,28 @@ function parseMemberIdentifier(src) {
14692
14737
  if (object3.type !== "Identifier" || object3.start !== 0 || object3.end !== 1 || property.type !== "Identifier" || property.start !== 2 || property.end !== src.length + 2) return null;
14693
14738
  return property.name;
14694
14739
  }
14740
+ function readMemberPath(src, end) {
14741
+ const path2 = [];
14742
+ const p = tokenizerAt(src, end, "expression", { afterOperand: true });
14743
+ try {
14744
+ for (; ; ) {
14745
+ p.nextToken();
14746
+ if (p.type !== types$1.dot || p.start !== end) break;
14747
+ const dotEnd = p.end;
14748
+ p.nextToken();
14749
+ if (p.start !== dotEnd || p.type !== types$1.name && !p.type.keyword) break;
14750
+ if (!/^\w+$/.test(String(p.value))) break;
14751
+ path2.push(String(p.value));
14752
+ end = p.end;
14753
+ }
14754
+ } catch (error62) {
14755
+ if (!(error62 instanceof SyntaxError)) throw error62;
14756
+ }
14757
+ return {
14758
+ path: path2,
14759
+ end
14760
+ };
14761
+ }
14695
14762
  function parseAst(src, goal, percentAt, transientsRead) {
14696
14763
  try {
14697
14764
  const p = tokenizerAt(src, 0, goal);
@@ -14719,121 +14786,147 @@ function propertyName(node2, src) {
14719
14786
  const c = src.charAt(node2.start);
14720
14787
  if (c === "@" || c === "%") throw new CodeSyntaxError(`"${node2.name}" can't be a property name`, src, node2.start);
14721
14788
  }
14722
- function walk(node2, src, out, binding, shorthand, access = "read") {
14723
- if (Array.isArray(node2)) {
14724
- for (const n7 of node2) walk(n7, src, out, binding, false, access);
14725
- return;
14789
+ function walk(root, src, out, binding, shorthand, access = "read") {
14790
+ const pending = [[
14791
+ root,
14792
+ binding,
14793
+ shorthand,
14794
+ access
14795
+ ]];
14796
+ while (pending.length) {
14797
+ const [node2, bind, short, role] = pending.pop();
14798
+ const children = [];
14799
+ visit2(node2, bind, short, role, children);
14800
+ for (let i2 = children.length - 1; i2 >= 0; i2--) pending.push(children[i2]);
14726
14801
  }
14727
- if (!isNode(node2)) return;
14728
- const sub = (child, bind = false, short = false, role = "read") => walk(child, src, out, bind, short, role);
14729
- switch (node2.type) {
14730
- case "Identifier": {
14731
- const sigil = sigilOf(node2, src);
14732
- if (!sigil) return;
14733
- if (binding) throw new CodeSyntaxError(`"${node2.name}" is a ${NAMESPACE_NAME[sigil[0]]} variable and can't be declared`, src, node2.start);
14734
- out.refs.push({
14735
- sigil: sigil[0],
14736
- name: sigil[1],
14737
- start: node2.start,
14738
- end: node2.end,
14739
- shorthand,
14740
- read: access !== "write",
14741
- write: access !== "read"
14742
- });
14802
+ function visit2(node2, binding2, shorthand2, access2, children) {
14803
+ if (Array.isArray(node2)) {
14804
+ for (const n7 of node2) children.push([
14805
+ n7,
14806
+ binding2,
14807
+ false,
14808
+ access2
14809
+ ]);
14743
14810
  return;
14744
14811
  }
14745
- case "Literal":
14746
- if (typeof node2.value === "string") out.strings.push(src.slice(node2.start + 1, node2.end - 1));
14747
- return;
14748
- case "TemplateElement":
14749
- out.strings.push(node2.value.raw);
14750
- return;
14751
- case "MemberExpression":
14752
- sub(node2.object);
14753
- if (node2.computed) sub(node2.property);
14754
- else propertyName(node2.property, src);
14755
- return;
14756
- case "Property":
14757
- if (node2.computed) sub(node2.key);
14758
- else if (!node2.shorthand) propertyName(node2.key, src);
14759
- if (node2.shorthand) {
14760
- const value = node2.value;
14761
- if (value.type === "AssignmentPattern") {
14762
- sub(value.left, binding, true, access);
14763
- sub(value.right);
14764
- } else sub(value, binding, true, access);
14765
- } else sub(node2.value, binding, false, access);
14766
- return;
14767
- case "MethodDefinition":
14768
- case "PropertyDefinition":
14769
- if (node2.computed) sub(node2.key);
14770
- else propertyName(node2.key, src);
14771
- sub(node2.value);
14772
- return;
14773
- case "LabeledStatement":
14774
- sub(node2.body);
14775
- return;
14776
- case "BreakStatement":
14777
- case "ContinueStatement":
14778
- case "MetaProperty":
14779
- case "PrivateIdentifier":
14780
- return;
14781
- case "VariableDeclarator":
14782
- sub(node2.id, true);
14783
- sub(node2.init);
14784
- return;
14785
- case "FunctionDeclaration":
14786
- case "FunctionExpression":
14787
- case "ArrowFunctionExpression":
14788
- sub(node2.id, true);
14789
- for (const p of node2.params) sub(p, true);
14790
- sub(node2.body);
14791
- return;
14792
- case "ClassDeclaration":
14793
- case "ClassExpression":
14794
- sub(node2.id, true);
14795
- sub(node2.superClass);
14796
- sub(node2.body);
14797
- return;
14798
- case "CatchClause":
14799
- sub(node2.param, true);
14800
- sub(node2.body);
14801
- return;
14802
- case "AssignmentExpression":
14803
- sub(node2.left, false, false, node2.operator === "=" ? "write" : "readwrite");
14804
- sub(node2.right);
14805
- return;
14806
- case "UnaryExpression":
14807
- sub(node2.argument, false, false, node2.operator === "delete" ? "write" : "read");
14808
- return;
14809
- case "UpdateExpression":
14810
- sub(node2.argument, false, false, "readwrite");
14811
- return;
14812
- case "ForInStatement":
14813
- case "ForOfStatement":
14814
- sub(node2.left, false, false, "write");
14815
- sub(node2.right);
14816
- sub(node2.body);
14817
- return;
14818
- case "AssignmentPattern":
14819
- sub(node2.left, binding, false, access);
14820
- sub(node2.right);
14821
- return;
14822
- case "ObjectPattern":
14823
- case "ArrayPattern":
14824
- case "RestElement":
14825
- for (const key of [
14826
- "properties",
14827
- "elements",
14828
- "argument"
14829
- ]) sub(node2[key], binding, false, access);
14830
- return;
14831
- default:
14832
- for (const key in node2) {
14833
- if (key === "type" || key === "start" || key === "end") continue;
14834
- const v2 = node2[key];
14835
- if (typeof v2 === "object" && v2 !== null) sub(v2);
14812
+ if (!isNode(node2)) return;
14813
+ const sub = (child, bind = false, short = false, role = "read") => {
14814
+ children.push([
14815
+ child,
14816
+ bind,
14817
+ short,
14818
+ role
14819
+ ]);
14820
+ };
14821
+ switch (node2.type) {
14822
+ case "Identifier": {
14823
+ const sigil = sigilOf(node2, src);
14824
+ if (!sigil) return;
14825
+ if (binding2) throw new CodeSyntaxError(`"${node2.name}" is a ${NAMESPACE_NAME[sigil[0]]} variable and can't be declared`, src, node2.start);
14826
+ out.refs.push({
14827
+ sigil: sigil[0],
14828
+ name: sigil[1],
14829
+ start: node2.start,
14830
+ end: node2.end,
14831
+ shorthand: shorthand2,
14832
+ read: access2 !== "write",
14833
+ write: access2 !== "read"
14834
+ });
14835
+ return;
14836
14836
  }
14837
+ case "Literal":
14838
+ if (typeof node2.value === "string") out.strings.push(src.slice(node2.start + 1, node2.end - 1));
14839
+ return;
14840
+ case "TemplateElement":
14841
+ out.strings.push(node2.value.raw);
14842
+ return;
14843
+ case "MemberExpression":
14844
+ sub(node2.object);
14845
+ if (node2.computed) sub(node2.property);
14846
+ else propertyName(node2.property, src);
14847
+ return;
14848
+ case "Property":
14849
+ if (node2.computed) sub(node2.key);
14850
+ else if (!node2.shorthand) propertyName(node2.key, src);
14851
+ if (node2.shorthand) {
14852
+ const value = node2.value;
14853
+ if (value.type === "AssignmentPattern") {
14854
+ sub(value.left, binding2, true, access2);
14855
+ sub(value.right);
14856
+ } else sub(value, binding2, true, access2);
14857
+ } else sub(node2.value, binding2, false, access2);
14858
+ return;
14859
+ case "MethodDefinition":
14860
+ case "PropertyDefinition":
14861
+ if (node2.computed) sub(node2.key);
14862
+ else propertyName(node2.key, src);
14863
+ sub(node2.value);
14864
+ return;
14865
+ case "LabeledStatement":
14866
+ sub(node2.body);
14867
+ return;
14868
+ case "BreakStatement":
14869
+ case "ContinueStatement":
14870
+ case "MetaProperty":
14871
+ case "PrivateIdentifier":
14872
+ return;
14873
+ case "VariableDeclarator":
14874
+ sub(node2.id, true);
14875
+ sub(node2.init);
14876
+ return;
14877
+ case "FunctionDeclaration":
14878
+ case "FunctionExpression":
14879
+ case "ArrowFunctionExpression":
14880
+ sub(node2.id, true);
14881
+ for (const p of node2.params) sub(p, true);
14882
+ sub(node2.body);
14883
+ return;
14884
+ case "ClassDeclaration":
14885
+ case "ClassExpression":
14886
+ sub(node2.id, true);
14887
+ sub(node2.superClass);
14888
+ sub(node2.body);
14889
+ return;
14890
+ case "CatchClause":
14891
+ sub(node2.param, true);
14892
+ sub(node2.body);
14893
+ return;
14894
+ case "AssignmentExpression":
14895
+ sub(node2.left, false, false, node2.operator === "=" ? "write" : "readwrite");
14896
+ sub(node2.right);
14897
+ return;
14898
+ case "UnaryExpression":
14899
+ sub(node2.argument, false, false, node2.operator === "delete" ? "write" : "read");
14900
+ return;
14901
+ case "UpdateExpression":
14902
+ sub(node2.argument, false, false, "readwrite");
14903
+ return;
14904
+ case "ForInStatement":
14905
+ case "ForOfStatement":
14906
+ sub(node2.left, false, false, "write");
14907
+ sub(node2.right);
14908
+ sub(node2.body);
14909
+ return;
14910
+ case "AssignmentPattern":
14911
+ sub(node2.left, binding2, false, access2);
14912
+ sub(node2.right);
14913
+ return;
14914
+ case "ObjectPattern":
14915
+ case "ArrayPattern":
14916
+ case "RestElement":
14917
+ for (const key of [
14918
+ "properties",
14919
+ "elements",
14920
+ "argument"
14921
+ ]) sub(node2[key], binding2, false, access2);
14922
+ return;
14923
+ default:
14924
+ for (const key in node2) {
14925
+ if (key === "type" || key === "start" || key === "end") continue;
14926
+ const v2 = node2[key];
14927
+ if (typeof v2 === "object" && v2 !== null) sub(v2);
14928
+ }
14929
+ }
14837
14930
  }
14838
14931
  }
14839
14932
  function findCodeEnd(src, start, { goal = "expression", stop, onRunOut } = {}) {
@@ -15276,7 +15369,11 @@ function splitSigilTemplate(template, incomplete = false) {
15276
15369
  if (text3) parts.push({ text: text3 });
15277
15370
  text3 = "";
15278
15371
  const block = template.slice(i2, end);
15279
- parts.push(/^.\w/.test(block) || incomplete && block.length === 1 && end === template.length ? { expr: block } : { verbatim: `{${block}}` });
15372
+ let reference = /^.\w/.test(block);
15373
+ if (!reference && block[1] === "\\") lexJs(block, { variable(_sigil, _name, start) {
15374
+ if (start === 0) reference = true;
15375
+ } });
15376
+ parts.push(reference || incomplete && block.length === 1 && end === template.length ? { expr: block } : { verbatim: `{${block}}` });
15280
15377
  i2 = end + 1;
15281
15378
  }
15282
15379
  if (text3) parts.push({ text: text3 });
@@ -15325,7 +15422,12 @@ function analyzeMarkup(source, options = {}) {
15325
15422
  nodes: paired.nodes,
15326
15423
  errors
15327
15424
  };
15328
- const inDamage = (start) => errors.some((e, i2) => start >= damage[i2] && start <= e.offset);
15425
+ const damageEnds = [];
15426
+ for (const e of errors) damageEnds.push(Math.max(damageEnds[damageEnds.length - 1] ?? -1, e.offset));
15427
+ const inDamage = (start) => {
15428
+ const index = upperBound(damage, start) - 1;
15429
+ return index >= 0 && start <= damageEnds[index];
15430
+ };
15329
15431
  const noticed = errors.map((e, i2) => [damage[i2] + 0.5, e]);
15330
15432
  const position = paired.errors.length ? sourcePositions(source) : void 0;
15331
15433
  for (const error62 of paired.errors) if (!inDamage(error62.start)) noticed.push([error62.noticedAt, pairingError(position, error62)]);
@@ -15395,7 +15497,7 @@ function scanMarkupTolerant(source, options = {}, completion = false) {
15395
15497
  const rest = source.slice(base);
15396
15498
  const result2 = attempt(rest, base);
15397
15499
  if (Array.isArray(result2)) {
15398
- tokens2.push(...result2);
15500
+ for (const token of result2) tokens2.push(token);
15399
15501
  break;
15400
15502
  }
15401
15503
  const at3 = base + result2.offset;
@@ -15408,7 +15510,7 @@ function scanMarkupTolerant(source, options = {}, completion = false) {
15408
15510
  cut = prefix.offset;
15409
15511
  prefix = attempt(rest.slice(0, cut), base);
15410
15512
  }
15411
- tokens2.push(...prefix);
15513
+ for (const token of prefix) tokens2.push(token);
15412
15514
  damage.push(base + cut);
15413
15515
  if (cut < rest.length) {
15414
15516
  const start = base + cut;
@@ -15897,6 +15999,29 @@ function widgetParameters(params, holds = {}) {
15897
15999
  ...hasOwn(holds, name) ? { holds: holds[name] } : {}
15898
16000
  }));
15899
16001
  }
16002
+ function macroArgumentLookup(tokens2, parametersOf2) {
16003
+ let owners;
16004
+ const parentsOf = parametersOf2.pairing?.branchParents ?? branchParents;
16005
+ return (token) => {
16006
+ const name = token.name.toLowerCase();
16007
+ if (parentsOf(name).length > 0 && !owners) {
16008
+ owners = /* @__PURE__ */ new Map();
16009
+ const pending = [...pairMarkup(tokens2, parametersOf2.pairing).nodes];
16010
+ while (pending.length) {
16011
+ const node2 = pending.pop();
16012
+ if (!node2.body) continue;
16013
+ for (const branch of node2.body.branches) {
16014
+ if (node2.token.type === "macro") owners.set(branch.tag, node2.token.name.toLowerCase());
16015
+ for (const child of branch.children) pending.push(child);
16016
+ }
16017
+ for (const child of node2.body.children) pending.push(child);
16018
+ }
16019
+ }
16020
+ const owner = owners?.get(token);
16021
+ if (owner !== void 0) return CONDITION_MACROS.has(name) && owner === BRANCH_PARENT[name] && usesBuiltinSemantics(owner, parametersOf2) ? "condition" : "branch";
16022
+ return CONDITION_MACROS.has(name) && usesBuiltinSemantics(name, parametersOf2) ? "condition" : "invocation";
16023
+ };
16024
+ }
15900
16025
  function usesBuiltinSemantics(name, parametersOf2) {
15901
16026
  return parametersOf2.widget?.(name) === void 0 && parametersOf2.source?.(name) !== "user";
15902
16027
  }
@@ -15916,10 +16041,11 @@ function withHolds(params, holds) {
15916
16041
  });
15917
16042
  }
15918
16043
  function parameterLookup(macros) {
16044
+ const list3 = [...macros];
15919
16045
  const parameters = new NameMap();
15920
16046
  const widgets = new NameMap();
15921
16047
  const sources = new NameMap();
15922
- for (const { name, parameters: params, interpolate, widget, source } of macros) {
16048
+ for (const { name, parameters: params, interpolate, widget, source } of list3) {
15923
16049
  sources.set(name, source ?? "user");
15924
16050
  if (params) parameters.set(name, withHolds(params, holdsByDefault(interpolate)));
15925
16051
  else parameters.delete(name);
@@ -15936,6 +16062,15 @@ function parameterLookup(macros) {
15936
16062
  };
15937
16063
  if (widgets.size > 0) lookup.widget = (name) => widgets.get(name);
15938
16064
  lookup.source = (name) => sources.get(name);
16065
+ const definitions = new NameMap(effectiveNamedDefinitions([...list3.filter((macro) => !macro.widget), ...list3.filter((macro) => macro.widget)]).map((macro) => [macro.name, macro]));
16066
+ lookup.pairing = {
16067
+ isBlock: (name) => definitions.get(name)?.block ?? isBuiltinBlockMacro(name),
16068
+ isRaw: (name) => isRawMacro(name, widgets.has(name)),
16069
+ branchParents: branchParentLookup([...definitions.values()].map((macro) => ({
16070
+ ...macro,
16071
+ subMacros: [...macro.subMacros ?? []]
16072
+ })))
16073
+ };
15939
16074
  return lookup;
15940
16075
  }
15941
16076
  function codeGoal(param) {
@@ -15947,6 +16082,7 @@ function stringHolds(param) {
15947
16082
  return param.holds ?? "markup";
15948
16083
  }
15949
16084
  function* codeAndText(src, tokens2, parametersOf2) {
16085
+ const argumentRole = macroArgumentLookup(tokens2, parametersOf2);
15950
16086
  const closeOfTextBody = (t, name) => {
15951
16087
  for (let k2 = t + 1; k2 < tokens2.length; k2++) {
15952
16088
  const c = tokens2[k2];
@@ -15964,6 +16100,7 @@ function* codeAndText(src, tokens2, parametersOf2) {
15964
16100
  offset: token.targetStart,
15965
16101
  length: token.targetEnd - token.targetStart,
15966
16102
  macro: "link",
16103
+ ...usesBuiltinSemantics("link", parametersOf2) && { navigation: true },
15967
16104
  label: src.slice(token.start, token.end)
15968
16105
  };
15969
16106
  if (token.display.includes("{")) yield textPiece(token.display, token.displayStart, "In the label of a link: ");
@@ -15989,6 +16126,8 @@ function* codeAndText(src, tokens2, parametersOf2) {
15989
16126
  }
15990
16127
  else if (token.type === "macro" && !token.isClose) {
15991
16128
  const name = token.name.toLowerCase();
16129
+ const role = argumentRole(token);
16130
+ if (role === "branch") continue;
15992
16131
  const widget = parametersOf2.widget?.(name);
15993
16132
  if (executesRawBody(name, parametersOf2)) {
15994
16133
  const close = closeOfTextBody(t, name);
@@ -16022,7 +16161,7 @@ function* codeAndText(src, tokens2, parametersOf2) {
16022
16161
  if (!args2) continue;
16023
16162
  const label2 = `{${token.name} ${args2}}`;
16024
16163
  const argsAt = token.argsStart;
16025
- if (CONDITION_MACROS.has(name) && usesBuiltinSemantics(name, parametersOf2)) {
16164
+ if (role === "condition") {
16026
16165
  yield {
16027
16166
  kind: "code",
16028
16167
  code: args2,
@@ -16033,7 +16172,10 @@ function* codeAndText(src, tokens2, parametersOf2) {
16033
16172
  continue;
16034
16173
  }
16035
16174
  const params = parametersOf2(name);
16036
- if (params) yield* argPieces(args2, argsAt, params, token.name, label2);
16175
+ if (params) {
16176
+ const navigation = usesBuiltinSemantics(name, parametersOf2) ? name === "goto" || name === "link" ? ["passage"] : name === "watch" ? ["goto"] : [] : [];
16177
+ yield* argPieces(args2, argsAt, params, token.name, label2, navigation);
16178
+ }
16037
16179
  if (widget) yield* widgetPieces(args2, argsAt, widget, token.name, label2);
16038
16180
  }
16039
16181
  }
@@ -16041,32 +16183,46 @@ function* codeAndText(src, tokens2, parametersOf2) {
16041
16183
  function* passagePiecesOf(src, tokens2, parametersOf2, options = {}) {
16042
16184
  let position;
16043
16185
  const locate2 = (offset2) => (position ?? (position = sourcePositions(src)))(offset2);
16044
- yield* expand(src, src, tokens2, parametersOf2, options, (i2) => i2, void 0, locate2);
16186
+ yield* expand(src, src, tokens2, parametersOf2, options, (i2) => i2, void 0, locate2, new MarkupExpansionBudget(src.length));
16045
16187
  }
16046
- function* expand(root, src, tokens2, parametersOf2, options, toRoot, where2, locate2) {
16188
+ function* expand(root, src, tokens2, parametersOf2, options, toRoot, where2, locate2, budget) {
16047
16189
  for (const flat of codeAndText(src, tokens2, parametersOf2)) {
16048
16190
  const piece = where2 ? nestedPiece(flat, toRoot, where2) : flat;
16049
16191
  if (flat.kind !== "text" || !flat.text.includes("{")) {
16050
16192
  yield piece;
16051
16193
  continue;
16052
16194
  }
16053
- const toText = (i2) => toRoot(pieceOffset(flat, i2));
16054
- const inner = analyzeMarkup(flat.text, {
16055
- text: true,
16056
- hooks: {
16057
- ...options.isBlock && { isBlock: options.isBlock },
16058
- isRaw: (name) => isRawMacro(name, parametersOf2.widget?.(name) !== void 0)
16059
- }
16060
- });
16061
- yield {
16062
- ...piece,
16063
- tokens: inner.tokens.map((t) => mapOffsets(t, toText)),
16064
- errors: inner.errors.map((e) => {
16065
- const { line, column } = locate2(toText(e.offset));
16066
- return new MarkupError(e.reason, toText(e.offset), line, column, e.code, toText(e.end), e.data);
16067
- })
16068
- };
16069
- yield* expand(root, flat.text, inner.tokens, parametersOf2, options, toText, flat.where, locate2);
16195
+ const toText = (i2) => pieceOffset(piece, i2);
16196
+ if (!budget.enter(flat.text.length)) {
16197
+ const at3 = toText(0);
16198
+ const { line, column } = locate2(at3);
16199
+ yield {
16200
+ ...piece,
16201
+ errors: [new MarkupError("Markup in labels and attributes exceeds the nesting or work limit", at3, line, column, "nesting-limit", toText(flat.text.length), { limit: String(MarkupExpansionBudget.maxDepth) })]
16202
+ };
16203
+ continue;
16204
+ }
16205
+ try {
16206
+ const inner = analyzeMarkup(flat.text, {
16207
+ text: true,
16208
+ hooks: {
16209
+ ...parametersOf2.pairing,
16210
+ ...options.isBlock && { isBlock: options.isBlock },
16211
+ isRaw: (name) => isRawMacro(name, parametersOf2.widget?.(name) !== void 0)
16212
+ }
16213
+ });
16214
+ yield {
16215
+ ...piece,
16216
+ tokens: inner.tokens.map((t) => mapOffsets(t, toText)),
16217
+ errors: inner.errors.map((e) => {
16218
+ const { line, column } = locate2(toText(e.offset));
16219
+ return new MarkupError(e.reason, toText(e.offset), line, column, e.code, toText(e.end), e.data);
16220
+ })
16221
+ };
16222
+ yield* expand(root, flat.text, inner.tokens, parametersOf2, options, toText, flat.where, locate2, budget);
16223
+ } finally {
16224
+ budget.leave();
16225
+ }
16070
16226
  }
16071
16227
  }
16072
16228
  function nestedPiece(piece, toRoot, where2) {
@@ -16130,7 +16286,7 @@ function escapeOffsets(raw, at3, value, escapes) {
16130
16286
  offsets.push(at3 + i2);
16131
16287
  return escaped ? offsets : void 0;
16132
16288
  }
16133
- function* argPieces(args2, offset2, params, macro, label2) {
16289
+ function* argPieces(args2, offset2, params, macro, label2, navigation = []) {
16134
16290
  let values;
16135
16291
  const spans = /* @__PURE__ */ new Map();
16136
16292
  try {
@@ -16147,7 +16303,7 @@ function* argPieces(args2, offset2, params, macro, label2) {
16147
16303
  };
16148
16304
  return;
16149
16305
  }
16150
- yield* valuePieces(args2, offset2, params, values, spans, macro, label2, "quotes");
16306
+ yield* valuePieces(args2, offset2, params, values, spans, macro, label2, "quotes", navigation);
16151
16307
  }
16152
16308
  function* widgetPieces(args2, offset2, params, macro, label2) {
16153
16309
  const written2 = splitArgSpans(args2);
@@ -16169,7 +16325,7 @@ function* widgetPieces(args2, offset2, params, macro, label2) {
16169
16325
  });
16170
16326
  yield* valuePieces(args2, offset2, declared, values, spans, macro, label2, "javascript");
16171
16327
  }
16172
- function* valuePieces(args2, offset2, params, values, spans, macro, label2, escapes) {
16328
+ function* valuePieces(args2, offset2, params, values, spans, macro, label2, escapes, navigation = []) {
16173
16329
  const name = macro.toLowerCase();
16174
16330
  function* visit2(list3, from) {
16175
16331
  for (const param of list3) {
@@ -16188,7 +16344,8 @@ function* valuePieces(args2, offset2, params, values, spans, macro, label2, esca
16188
16344
  offset: offset2 + start,
16189
16345
  length: end - start,
16190
16346
  macro: name,
16191
- label: label2
16347
+ label: label2,
16348
+ ...navigation.includes(param.name) && { navigation: true }
16192
16349
  });
16193
16350
  const goal = codeGoal(param);
16194
16351
  if (param.type === "passage") {
@@ -16348,11 +16505,9 @@ function completionContext(source, cursor, macros = []) {
16348
16505
  const name = token.name.toLowerCase();
16349
16506
  const args2 = token.rawArgs;
16350
16507
  const widget = parametersOf2.widget?.(name);
16351
- if (usesBuiltinSemantics(name, parametersOf2) && [
16352
- "if",
16353
- "elseif",
16354
- "case"
16355
- ].includes(name)) return codeAt(args2, token.argsStart);
16508
+ const role = macroArgumentLookup(tokens2, parametersOf2)(token);
16509
+ if (role === "branch") return LITERAL;
16510
+ if (role === "condition") return codeAt(args2, token.argsStart);
16356
16511
  const stringContext = (raw2, at4, holds, escapes = "quotes") => {
16357
16512
  const quoted = readQuoted(raw2, 0, true);
16358
16513
  if (!quoted) {
@@ -16400,25 +16555,28 @@ function completionContext(source, cursor, macros = []) {
16400
16555
  };
16401
16556
  return read(source.slice(0, cursor), (i2) => i2);
16402
16557
  }
16403
- function textContainsChildren(text3, parametersOf2, hooks) {
16558
+ function textContainsChildren(text3, parametersOf2, hooks, budget = new MarkupExpansionBudget(text3.length)) {
16404
16559
  if (!text3.includes("{")) return false;
16405
- let nodes;
16560
+ if (!budget.enter(text3.length)) return false;
16406
16561
  try {
16407
- nodes = parseMarkup(text3, {
16562
+ return astContainsChildren(parseMarkup(text3, {
16408
16563
  text: true,
16409
16564
  hooks
16410
- });
16565
+ }), parametersOf2, hooks, budget);
16411
16566
  } catch {
16412
16567
  return false;
16568
+ } finally {
16569
+ budget.leave();
16413
16570
  }
16414
- return astContainsChildren(nodes, parametersOf2, hooks);
16415
16571
  }
16416
- function tokenTextContainsChildren(token, parametersOf2 = registeredParameters, src = "", hooks) {
16417
- for (const piece of codeAndText(src, [token], parametersOf2)) if (piece.kind === "text" && textContainsChildren(piece.text, parametersOf2, hooks)) return true;
16572
+ function tokenTextContainsChildren(token, parametersOf2 = registeredParameters, src = "", hooks, budget) {
16573
+ for (const piece of codeAndText(src, [token], parametersOf2)) if (piece.kind === "text" && textContainsChildren(piece.text, parametersOf2, hooks, budget)) return true;
16418
16574
  return false;
16419
16575
  }
16420
- function astContainsChildren(nodes, parametersOf2 = registeredParameters, hooks) {
16421
- for (const node2 of nodes) {
16576
+ function astContainsChildren(nodes, parametersOf2 = registeredParameters, hooks, budget) {
16577
+ const pending = [...nodes];
16578
+ while (pending.length) {
16579
+ const node2 = pending.pop();
16422
16580
  if (node2.type === "variable" && node2.scope === "local" && node2.name === "children") return true;
16423
16581
  if (node2.type !== "html" && node2.type !== "macro") continue;
16424
16582
  if (node2.type === "macro" && node2.name.toLowerCase() === "widget") continue;
@@ -16441,10 +16599,9 @@ function astContainsChildren(nodes, parametersOf2 = registeredParameters, hooks)
16441
16599
  nameEnd: 0,
16442
16600
  argsStart: 0,
16443
16601
  argsEnd: 0
16444
- }, parametersOf2, "", hooks) || astContainsChildren(node2.children, parametersOf2, hooks)) return true;
16445
- if (node2.type === "macro" && node2.branches) {
16446
- for (const branch of node2.branches) if (astContainsChildren(branch.children, parametersOf2, hooks)) return true;
16447
- }
16602
+ }, parametersOf2, "", hooks, budget)) return true;
16603
+ for (const child of node2.children) pending.push(child);
16604
+ if (node2.type === "macro" && node2.branches) for (const branch of node2.branches) for (const child of branch.children) pending.push(child);
16448
16605
  }
16449
16606
  return false;
16450
16607
  }
@@ -16487,6 +16644,7 @@ function widgetDefinitions$1(passages, parametersOf2 = registeredParameters) {
16487
16644
  const hooks = { isRaw: (name) => isRawMacro(name, replacesDo || parametersOf2.widget?.(name) !== void 0) };
16488
16645
  for (const passage of list3) {
16489
16646
  if (passage.name !== "StoryInit" && !passage.tags?.includes("widget")) continue;
16647
+ const budget = new MarkupExpansionBudget(passage.content.length);
16490
16648
  const open2 = [];
16491
16649
  const done = [];
16492
16650
  for (const token of tokenizeMarkupTolerant$1(passage.content, { hooks }).tokens) if (token.type === "macro" && token.name.toLowerCase() === "widget") {
@@ -16501,7 +16659,7 @@ function widgetDefinitions$1(passages, parametersOf2 = registeredParameters) {
16501
16659
  closeStart: token.start
16502
16660
  });
16503
16661
  }
16504
- } else if (open2.length > 0 && (token.type === "variable" && token.scope === "local" && token.name === "children" || tokenTextContainsChildren(token, parametersOf2, passage.content, hooks))) open2[open2.length - 1].isBlock = true;
16662
+ } else if (open2.length > 0 && (token.type === "variable" && token.scope === "local" && token.name === "children" || tokenTextContainsChildren(token, parametersOf2, passage.content, hooks, budget))) open2[open2.length - 1].isBlock = true;
16505
16663
  done.push(...open2);
16506
16664
  const defs = [];
16507
16665
  for (const { token, isBlock, closeStart } of done) try {
@@ -16531,6 +16689,9 @@ function effectiveWidgetDefinitions(definitions) {
16531
16689
  for (const def of [...definitions.filter((def2) => !inInit(def2)), ...definitions.filter(inInit)]) if (def.closeStart !== void 0) effective.set(def.name, def);
16532
16690
  return [...effective.values()];
16533
16691
  }
16692
+ function isSpecialPassage(name) {
16693
+ return SPECIAL_PASSAGES.has(name);
16694
+ }
16534
16695
  function distance(a, b) {
16535
16696
  let prev = Array.from({ length: b.length + 1 }, (_2, j2) => j2);
16536
16697
  for (let i2 = 1; i2 <= a.length; i2++) {
@@ -16664,6 +16825,13 @@ function validateMarkup(passages, options) {
16664
16825
  report2(passage, at3, at3 + piece.length, "argument-error", `${where2}${piece.message} in ${piece.label}`, { macro: piece.macro });
16665
16826
  break;
16666
16827
  case "passage":
16828
+ if (piece.navigation && isSpecialPassage(piece.name)) {
16829
+ report2(passage, at3, at3 + piece.length, "special-passage", `${where2}Cannot navigate to special passage ${JSON.stringify(piece.name)} in ${piece.label}.`, {
16830
+ name: piece.name,
16831
+ macro: piece.macro
16832
+ });
16833
+ break;
16834
+ }
16667
16835
  if (options.checkPassageNames !== false && !passageNames.has(piece.name)) {
16668
16836
  const best = closestName(piece.name, passageNames);
16669
16837
  report2(passage, at3, at3 + piece.length, "unknown-passage", `${where2}No passage named ${JSON.stringify(piece.name)} in ${piece.label}.${didYouMean(best, JSON.stringify)}`, {
@@ -16868,28 +17036,14 @@ function checkReference(ref2, declared) {
16868
17036
  }
16869
17037
  return null;
16870
17038
  }
16871
- function referenceAt(code, sigil, name, start, end, at3) {
16872
- FIELD_PATH_RE.lastIndex = end;
16873
- const fields = FIELD_PATH_RE.exec(code)[0];
17039
+ function referenceAt(code, { sigil, name, start, end }, at3) {
17040
+ const fields = readMemberPath(code, end);
16874
17041
  return {
16875
17042
  sigil,
16876
17043
  name,
16877
- path: fields ? fields.slice(1).split(".") : [],
17044
+ path: fields.path,
16878
17045
  start: at3(start),
16879
- end: at3(end + fields.length)
16880
- };
16881
- }
16882
- function termReferenceAt(term, index, at3) {
16883
- VAR_PATH_RE.lastIndex = index;
16884
- const match2 = VAR_PATH_RE.exec(term);
16885
- if (!match2) return void 0;
16886
- const [name, ...path2] = match2[2].split(".");
16887
- return {
16888
- sigil: match2[1],
16889
- name,
16890
- path: path2,
16891
- start: at3(index),
16892
- end: at3(index + match2[0].length)
17046
+ end: at3(fields.end)
16893
17047
  };
16894
17048
  }
16895
17049
  function scanSigilReferences(value, at3, emit) {
@@ -16897,7 +17051,15 @@ function scanSigilReferences(value, at3, emit) {
16897
17051
  }
16898
17052
  function scanInterpolations(text3, at3, emit, options) {
16899
17053
  if (!text3.includes("{")) return;
16900
- collectTokenRefs(text3, tokensOf(text3, true, options), at3, emit, options);
17054
+ const budget = options.expansionBudget;
17055
+ if (!budget.enter(text3.length)) return;
17056
+ try {
17057
+ const offset2 = at3(0);
17058
+ const sourceOffsets = at3(text3.length) - offset2 === text3.length ? void 0 : Array.from({ length: text3.length + 1 }, (_2, i2) => at3(i2));
17059
+ collectTokenRefs(text3, tokensOf(text3, true, options), (i2) => sourceOffsets?.[i2] ?? offset2 + i2, emit, options);
17060
+ } finally {
17061
+ budget.leave();
17062
+ }
16901
17063
  }
16902
17064
  function scanCode(code, at3, emit, goal = "expression") {
16903
17065
  const parsed = parseOrError(code, goal);
@@ -16907,14 +17069,19 @@ function scanCode(code, at3, emit, goal = "expression") {
16907
17069
  }
16908
17070
  for (const ref2 of parsed.refs) {
16909
17071
  if (ref2.sigil !== "$" && ref2.sigil !== "%") continue;
16910
- emit(referenceAt(code, ref2.sigil, ref2.name, ref2.start, ref2.end, at3));
17072
+ emit(referenceAt(code, ref2, at3));
16911
17073
  }
16912
17074
  }
16913
17075
  function scanCodeLeniently(code, at3, emit, goal) {
16914
17076
  lexJs(code, {
16915
17077
  variable(sigil, name, index, _nesting, end) {
16916
17078
  if (sigil !== "$" && sigil !== "%") return;
16917
- emit(referenceAt(code, sigil, name, index, end, at3));
17079
+ emit(referenceAt(code, {
17080
+ sigil,
17081
+ name,
17082
+ start: index,
17083
+ end
17084
+ }, at3));
16918
17085
  },
16919
17086
  literal() {
16920
17087
  }
@@ -16935,7 +17102,7 @@ function scanArgs(token, parametersOf2, at3, emit, options) {
16935
17102
  const params = parametersOf2(token.name);
16936
17103
  let byParameters = false;
16937
17104
  if (params) {
16938
- if (options.all && !options.storeVarMacros.has(token.name.toLowerCase())) scanVariableParameters(token, params, at3, emit);
17105
+ if (!options.storeVarMacros.has(token.name.toLowerCase())) scanVariableParameters(token, params, at3, emit, options.all);
16939
17106
  const pieces = [...argPieces(token.rawArgs, token.argsStart, params, token.name, "")];
16940
17107
  byParameters = !pieces.some((piece) => piece.kind === "argument-error");
16941
17108
  if (byParameters) scanPieces(pieces, at3, emit, options);
@@ -16944,7 +17111,7 @@ function scanArgs(token, parametersOf2, at3, emit, options) {
16944
17111
  const widget = parametersOf2.widget?.(token.name);
16945
17112
  if (widget) scanPieces(widgetPieces(token.rawArgs, token.argsStart, widget, token.name, ""), at3, emit, options);
16946
17113
  }
16947
- function scanVariableParameters(token, params, at3, emit) {
17114
+ function scanVariableParameters(token, params, at3, emit, all) {
16948
17115
  const spans = /* @__PURE__ */ new Map();
16949
17116
  try {
16950
17117
  parseMacroArgs(token.rawArgs, params, spans);
@@ -16955,8 +17122,10 @@ function scanVariableParameters(token, params, at3, emit) {
16955
17122
  for (const param of params) {
16956
17123
  const span = spans.get(param);
16957
17124
  if (param.type !== "variable" || !span) continue;
16958
- const ref2 = termReferenceAt(token.rawArgs.slice(span[0], span[1]), 0, (i2) => at3(token.argsStart + span[0] + i2));
16959
- if (ref2) emit(ref2);
17125
+ const start = token.argsStart + span[0];
17126
+ scanCode(token.rawArgs.slice(span[0], span[1]), (i2) => at3(start + i2), (ref2) => {
17127
+ if (all || ref2.start !== at3(start)) emit(ref2);
17128
+ });
16960
17129
  }
16961
17130
  }
16962
17131
  function scanSelectors(content2, token, at3, emit, options) {
@@ -16992,6 +17161,7 @@ function boundReference(token, at3, all) {
16992
17161
  }
16993
17162
  function collectTokenRefs(content2, tokens2, at3, emit, options) {
16994
17163
  const { storeVarMacros, parametersOf: parametersOf2 } = options;
17164
+ const argumentRole = macroArgumentLookup(tokens2, parametersOf2);
16995
17165
  for (let t = 0; t < tokens2.length; t++) {
16996
17166
  const token = tokens2[t];
16997
17167
  if (options.all && token.type !== "macro" && token.type !== "html" && token.type !== "text") scanSelectors(content2, token, at3, emit, options);
@@ -17017,13 +17187,15 @@ function collectTokenRefs(content2, tokens2, at3, emit, options) {
17017
17187
  else scanInterpolations(value, valueIn, emit, options);
17018
17188
  }
17019
17189
  else if (token.type === "macro" && !token.isClose) {
17020
- const binding = storeVarMacros.has(token.name.toLowerCase()) && !parametersOf2.widget?.(token.name) ? boundReference(token, at3, options.all) : void 0;
17021
- scanArgs(token, parametersOf2, at3, (ref2) => {
17190
+ const role = argumentRole(token);
17191
+ const binding = role === "invocation" && storeVarMacros.has(token.name.toLowerCase()) && !parametersOf2.widget?.(token.name) ? boundReference(token, at3, options.all) : void 0;
17192
+ if (role === "condition") scanCode(token.rawArgs, (i2) => at3(token.argsStart + i2), emit);
17193
+ else if (role === "invocation") scanArgs(token, parametersOf2, at3, (ref2) => {
17022
17194
  if (!binding || ref2.start < binding.start || ref2.start >= binding.end) emit(ref2);
17023
17195
  }, options);
17024
17196
  scanSelectors(content2, token, at3, emit, options);
17025
17197
  if (binding) emit(binding);
17026
- if (executesRawBody(token.name, parametersOf2)) {
17198
+ if (role === "invocation" && executesRawBody(token.name, parametersOf2)) {
17027
17199
  let close = t + 1;
17028
17200
  while (close < tokens2.length) {
17029
17201
  const c = tokens2[close];
@@ -17040,7 +17212,10 @@ function collectTokenRefs(content2, tokens2, at3, emit, options) {
17040
17212
  }
17041
17213
  function collectVariableReferences(content2, options) {
17042
17214
  const found = [];
17043
- withParseCache(() => collectTokenRefs(content2, tokensOf(content2, false, options), (i2) => i2, (ref2) => found.push(ref2), options));
17215
+ withParseCache(() => collectTokenRefs(content2, tokensOf(content2, false, options), (i2) => i2, (ref2) => found.push(ref2), {
17216
+ ...options,
17217
+ expansionBudget: new MarkupExpansionBudget(content2.length)
17218
+ }));
17044
17219
  return found.sort((a, b) => a.start - b.start);
17045
17220
  }
17046
17221
  function checkVariableReferences(passages, declared, options) {
@@ -21377,7 +21552,7 @@ async function formatLf(text3, options) {
21377
21552
  }
21378
21553
  if (region.type === "html") {
21379
21554
  const htmlText2 = region.lines.join("\n");
21380
- if (WHITESPACE_AS_WRITTEN.test(htmlText2) || region.lines.some((line) => literalLines.has(line.trim()))) {
21555
+ if (WHITESPACE_AS_WRITTEN.test(htmlText2) || region.lines.some((line) => literalLines.has(line.trim())) || hasMarkdownBoundaries(htmlText2, isBlock)) {
21381
21556
  for (let i2 = 0; i2 < region.lines.length; i2++) preservedLines.add(lines.length + i2);
21382
21557
  lines.push(...region.lines);
21383
21558
  continue;
@@ -21408,6 +21583,16 @@ async function formatLf(text3, options) {
21408
21583
  output2 = output2.replace(/\n*$/, "\n");
21409
21584
  return replaceAll(output2.split("\n").map((line) => literalLines.get(line.trim()) ?? line).join("\n"), sentinel, "\n");
21410
21585
  }
21586
+ function hasMarkdownBoundaries(source, isBlock) {
21587
+ const { tokens: tokens2 } = analyzeMarkup(source, { hooks: { isBlock } });
21588
+ let possibleBoundary = false;
21589
+ for (const token of tokens2) {
21590
+ if (token.type !== "text") continue;
21591
+ if (/\n[ \t]*\n/.test(token.value)) return true;
21592
+ if (/[\\#*+\d=>[`~|-]|[ \t]\n/.test(token.value)) possibleBoundary = true;
21593
+ }
21594
+ return possibleBoundary && markdownLines(source, isBlock, HTML_MARKDOWN_BOUNDARIES).size > 0;
21595
+ }
21411
21596
  function whitespaceSensitiveLines(source, isBlock) {
21412
21597
  const lines = /* @__PURE__ */ new Set();
21413
21598
  if (!WHITESPACE_AS_WRITTEN.test(source)) return lines;
@@ -21623,16 +21808,20 @@ function wordWrap(text3, maxLen, indent) {
21623
21808
  }
21624
21809
  return lines;
21625
21810
  }
21811
+ function effectiveToolingMacros(macros) {
21812
+ const list3 = [...macros];
21813
+ return effectiveNamedDefinitions([...list3.filter((m) => !m.widget), ...list3.filter((m) => m.widget)]);
21814
+ }
21626
21815
  function tokenizationOptions(options) {
21627
21816
  if (!options.macros) return options;
21628
- const macros = [...options.macros];
21817
+ const macros = effectiveToolingMacros(options.macros);
21629
21818
  const parametersOf2 = parameterLookup(macros);
21630
21819
  return {
21631
21820
  ...options,
21632
21821
  hooks: {
21633
21822
  isRaw: (name) => isRawMacro(name, parametersOf2.widget?.(name) !== void 0),
21634
21823
  isBlock: blockLookup(macros),
21635
- branchParents: (name) => branchParents(name, macros),
21824
+ branchParents: branchParentLookup(macros),
21636
21825
  ...options.hooks
21637
21826
  }
21638
21827
  };
@@ -21642,23 +21831,27 @@ function tokenizeMarkupTolerant(source, options = {}) {
21642
21831
  }
21643
21832
  function validateStoryMarkup(passages, macros, options = {}) {
21644
21833
  const list3 = [...passages];
21645
- const all = [...macros];
21834
+ const all = effectiveToolingMacros(macros);
21646
21835
  const known = /* @__PURE__ */ new Set();
21647
21836
  const widgets = effectiveWidgetDefinitions(widgetDefinitions$1(list3, parameterLookup(all)));
21648
- const blocks = new Set(widgets.filter((def) => def.block).map((def) => def.name.toLowerCase()));
21837
+ const effective = effectiveToolingMacros([...all, ...widgets.map((widget) => ({
21838
+ name: widget.name,
21839
+ block: widget.block,
21840
+ subMacros: [],
21841
+ widget
21842
+ }))]);
21649
21843
  const parametersOf2 = parameterLookup([...all, ...definedWidgetMacros(widgets)]);
21650
21844
  for (const macro of all) {
21651
21845
  const name = macro.name.toLowerCase();
21652
21846
  known.add(name);
21653
21847
  for (const sub of macro.subMacros) known.add(sub.toLowerCase());
21654
- if (macro.block) blocks.add(name);
21655
21848
  }
21656
21849
  return validateMarkup(list3, {
21657
- branchParents: (name) => branchParents(name, all),
21850
+ branchParents: branchParentLookup(effective),
21658
21851
  isKnownMacro: (name) => known.has(name),
21659
21852
  macroNames: known,
21660
21853
  parametersOf: parametersOf2,
21661
- isBlockMacro: (name) => blocks.has(name.toLowerCase()) || isBlockMacro(name),
21854
+ isBlockMacro: blockLookup(effective),
21662
21855
  checkPassageNames: options.checkPassageNames,
21663
21856
  tolerant: options.tolerant
21664
21857
  });
@@ -21669,17 +21862,18 @@ function definedWidgetMacros(widgets) {
21669
21862
  widget
21670
21863
  }));
21671
21864
  }
21672
- function blockLookup(macros) {
21673
- const blocks = new Set(macros.filter((m) => m.block).map((m) => m.name.toLowerCase()));
21674
- return (name) => blocks.has(name.toLowerCase()) || isBlockMacro(name);
21865
+ function blockLookup(macros, includeRuntime = true) {
21866
+ const definitions = new NameMap();
21867
+ for (const macro of effectiveToolingMacros(macros)) definitions.set(macro.name, macro.block);
21868
+ return (name) => includeRuntime && isRegisteredBlockMacro(name) || (definitions.get(name) ?? (includeRuntime ? isBlockMacro(name) : isBuiltinBlockMacro(name)));
21675
21869
  }
21676
21870
  function passagePieces(source, macros = []) {
21677
- const all = [...macros];
21871
+ const all = effectiveToolingMacros(macros);
21678
21872
  const parametersOf2 = parameterLookup(all);
21679
21873
  return passagePiecesOfTokens(source, all, tokenizeMarkupTolerant(source, { hooks: { isRaw: (name) => isRawMacro(name, parametersOf2.widget?.(name) !== void 0) } }).tokens);
21680
21874
  }
21681
21875
  function passagePiecesOfTokens(source, macros, tokens2) {
21682
- const all = [...macros];
21876
+ const all = effectiveToolingMacros(macros);
21683
21877
  return [...passagePiecesOf(source, tokens2, parameterLookup(all), { isBlock: blockLookup(all) })];
21684
21878
  }
21685
21879
  function widgetDefinitions(passages, macros = []) {
@@ -21704,7 +21898,7 @@ function validateVariableReferences(passages, declarations, macros = []) {
21704
21898
  const list3 = [...passages];
21705
21899
  return checkVariableReferences(list3.filter((p) => !p.tags?.some((t) => NOT_MARKUP_TAGS.includes(t))), declarations, referenceOptions(macros, false, list3));
21706
21900
  }
21707
- var __defProp2, __exportAll, NameMap, NameSet, SIGIL_SCOPES, SIGIL_CHARS, grammar_shared_default, LB, RB, SIGIL_SCOPES$1, withSelectors, VOID_TAGS, BRANCH_PARENT$1, LINK_SEPARATORS, NO_SELECTORS, EMAIL_AUTOLINK, peg$SyntaxError, BLOCK_MACROS, BLOCK_WIDGETS, WIDGETS, DEFINED_BLOCK_MACROS, RAW_BODY_MACROS, BRANCHING, quoteArg, PARAMETER_TYPES, STRING_HOLDS, metadataRegistry, companionParents, last$1, BRANCH_PARENT, lowerName, nameOf, closerOf, closes, astralIdentifierCodes, astralIdentifierStartCodes, nonASCIIidentifierChars, nonASCIIidentifierStartChars, reservedWords, ecma5AndLessKeywords, keywords$1, keywordRelationalOperator, nonASCIIidentifierStart, nonASCIIidentifier, TokenType, beforeExpr, startsExpr, keywords, types$1, lineBreak, lineBreakG, nonASCIIwhitespace, skipWhiteSpace, ref, hasOwnProperty$1, toString, hasOwn$1, isArray, regexpCache, loneSurrogate, Position, SourceLocation, defaultOptions, warnedAboutEcmaVersion, SCOPE_TOP, SCOPE_FUNCTION, SCOPE_ASYNC, SCOPE_GENERATOR, SCOPE_ARROW, SCOPE_SIMPLE_CATCH, SCOPE_SUPER, SCOPE_DIRECT_SUPER, SCOPE_CLASS_STATIC_BLOCK, SCOPE_CLASS_FIELD_INIT, SCOPE_SWITCH, SCOPE_VAR, BIND_NONE, BIND_VAR, BIND_LEXICAL, BIND_FUNCTION, BIND_SIMPLE_CATCH, BIND_OUTSIDE, Parser, prototypeAccessors, pp$9, literal, DestructuringErrors, pp$8, loopLabel, switchLabel, empty$1, FUNC_STATEMENT, FUNC_HANGING_STATEMENT, FUNC_NULLABLE_ID, pp$7, TokContext, types, pp$6, pp$5, empty, pp$4, pp$3, Scope, Node, pp$2, scriptValuesAddedInUnicode, ecma9BinaryProperties, ecma10BinaryProperties, ecma11BinaryProperties, ecma12BinaryProperties, ecma13BinaryProperties, unicodeBinaryProperties, unicodeBinaryPropertiesOfStrings, unicodeGeneralCategoryValues, ecma9ScriptValues, ecma10ScriptValues, ecma11ScriptValues, ecma12ScriptValues, ecma13ScriptValues, unicodeScriptValues, data, ecmaVersion, i, list$2, pp$1, BranchID, RegExpValidationState, CharSetNone, CharSetOk, CharSetString, Token, pp, INVALID_TEMPLATE_ESCAPE_ERROR, OPTIONS, AT_NAME_RE, TRANS_NAME_RE, SIGIL_IDENT_RE, LINE_BREAK_RE, NOT_A_BODY_RE, OPERAND_ENDS, SigilParser, OPENERS, CLOSERS$1, CodeSyntaxError, last, CLOSER, isNode, NAMESPACE_NAME, WORD_RE$2, FLAGS_RE, WORDS_BEFORE_STRING, NON_STRING_QUOTE_PREFIX, memoInput, memo, defaultCodeEnd, defaultHooks, MarkupError, OFFSET_FIELD, listeners, RESERVED_NAME, isArrayIndex, hasOwn, createNamespace, TRAILING_OPERATOR_RE, slice, MacroArgumentError, WORD_RE$1, NUMBER_RE$1, WORD_OR_PUNCT_RE, LEADING_OPERATOR_RE, takesRest, SUB_MACRO_PARAMETERS, parses, CONDITION_MACROS, PASSAGE_BODIES, holdsByDefault, JS_ESCAPE_RE, textPiece, LITERAL, registeredParameters, WIDGET_PARAMETERS, NOT_MARKUP, NOT_MARKUP_TAGS$1, BRANCH_MACROS, didYouMean, PASSAGE_ARGUMENT_HINT, BARE_WORD_RE, NUMBER_RE, IDENT_RE, KEY_RE, ARROW_RE, LINE_END_RE, ARRAY_INDEX_RE, UNSUPPORTED_UNDEFINED, Reader, VAR_PATH_RE, VALID_VAR_TYPES, PRIMITIVE_SAMPLES, UNSUPPORTED_TYPE, NO_STORE_VAR_MACROS, FIELD_PATH_RE, CLOSERS, BLOCK_WORDS, NOT_PARAMETERS, DECLARATIONS, WRITE_RE, MUTATORS, MUTATING_CALL_RE, BEFORE_BINDING, isWordChar, Bindings, CALL_RE, IDENTIFIER_ARGUMENT_RE, WORD_RE, MODIFIER_RE, KEY_START_RE, CONFIG_KEYS, ConfigReader, stringOf, booleanOf, textOf, stringsOf, VOID_ELEMENTS, characterEntities, own, hasOwnProperty, asciiAlpha, asciiAlphanumeric, asciiAtext, asciiDigit, asciiHexDigit, asciiPunctuation, unicodePunctuation, unicodeWhitespace, content$1, EditMap$1, document$1, containerConstruct, attention, autolink, blankLine, blockQuote, characterEscape, characterReference, nonLazyContinuation, codeFenced, codeIndented, furtherStart, codeText, SpliceBuffer, content, continuationConstruct, definition, titleBefore, hardBreakEscape, headingAtx, htmlBlockNames, htmlRawNames, htmlFlow, blankLineBefore, nonLazyContinuationStart, htmlText, labelEnd, resourceConstruct, referenceFullConstruct, referenceCollapsedConstruct, labelStartImage, labelStartLink, lineEnding, thematicBreak, list, listItemPrefixWhitespaceConstruct, indentConstruct, setextUnderline, flow$1, resolver, string$1, text$1, constructs_exports, document, contentInitial, flowInitial, flow, string, text, insideSpan, attentionMarkers, disable, search, alignment, EditMap, DEFAULT_PRETTIER_OPTIONS, SVG_PLACEHOLDERS, SPINDLE_PLACEHOLDERS, PLACEHOLDER_PATTERN, DEFAULT_DEDENTING, WHITESPACE_AS_WRITTEN, LIST_ITEM_REGEX, CODE_FENCE_REGEX, NO_WRAP, NOT_MARKUP_TAGS;
21901
+ var __defProp2, __exportAll, NameMap, NameSet, SIGIL_SCOPES, SIGIL_CHARS, grammar_shared_default, LB, RB, SIGIL_SCOPES$1, withSelectors, VOID_TAGS, BRANCH_PARENT$1, LINK_SEPARATORS, NO_SELECTORS, EMAIL_AUTOLINK, peg$SyntaxError, OPTION_CLAUSE, PARAMETER_TYPES, STRING_HOLDS, metadataRegistry, companionParents, BLOCK_MACROS, BLOCK_WIDGETS, WIDGETS, DEFINED_BLOCK_MACROS, DEFINED_MACROS, REGISTERED_BLOCK_MACROS, RAW_BODY_MACROS, BRANCHING, quoteArg, MAX_MARKUP_DEPTH, last$1, BRANCH_PARENT, lowerName, nameOf, closerOf, closes, astralIdentifierCodes, astralIdentifierStartCodes, nonASCIIidentifierChars, nonASCIIidentifierStartChars, reservedWords, ecma5AndLessKeywords, keywords$1, keywordRelationalOperator, nonASCIIidentifierStart, nonASCIIidentifier, TokenType, beforeExpr, startsExpr, keywords, types$1, lineBreak, lineBreakG, nonASCIIwhitespace, skipWhiteSpace, ref, hasOwnProperty$1, toString, hasOwn$1, isArray, regexpCache, loneSurrogate, Position, SourceLocation, defaultOptions, warnedAboutEcmaVersion, SCOPE_TOP, SCOPE_FUNCTION, SCOPE_ASYNC, SCOPE_GENERATOR, SCOPE_ARROW, SCOPE_SIMPLE_CATCH, SCOPE_SUPER, SCOPE_DIRECT_SUPER, SCOPE_CLASS_STATIC_BLOCK, SCOPE_CLASS_FIELD_INIT, SCOPE_SWITCH, SCOPE_VAR, BIND_NONE, BIND_VAR, BIND_LEXICAL, BIND_FUNCTION, BIND_SIMPLE_CATCH, BIND_OUTSIDE, Parser, prototypeAccessors, pp$9, literal, DestructuringErrors, pp$8, loopLabel, switchLabel, empty$1, FUNC_STATEMENT, FUNC_HANGING_STATEMENT, FUNC_NULLABLE_ID, pp$7, TokContext, types, pp$6, pp$5, empty, pp$4, pp$3, Scope, Node, pp$2, scriptValuesAddedInUnicode, ecma9BinaryProperties, ecma10BinaryProperties, ecma11BinaryProperties, ecma12BinaryProperties, ecma13BinaryProperties, unicodeBinaryProperties, unicodeBinaryPropertiesOfStrings, unicodeGeneralCategoryValues, ecma9ScriptValues, ecma10ScriptValues, ecma11ScriptValues, ecma12ScriptValues, ecma13ScriptValues, unicodeScriptValues, data, ecmaVersion, i, list$2, pp$1, BranchID, RegExpValidationState, CharSetNone, CharSetOk, CharSetString, Token, pp, INVALID_TEMPLATE_ESCAPE_ERROR, OPTIONS, AT_NAME_RE, TRANS_NAME_RE, SIGIL_IDENT_RE, LINE_BREAK_RE, NOT_A_BODY_RE, OPERAND_ENDS, SigilParser, OPENERS, CLOSERS$1, CodeSyntaxError, last, CLOSER, isNode, NAMESPACE_NAME, WORD_RE$2, FLAGS_RE, WORDS_BEFORE_STRING, NON_STRING_QUOTE_PREFIX, memoInput, memo, defaultCodeEnd, defaultHooks, MarkupError, OFFSET_FIELD, listeners, RESERVED_NAME, isArrayIndex, hasOwn, createNamespace, TRAILING_OPERATOR_RE, slice, MacroArgumentError, WORD_RE$1, NUMBER_RE$1, WORD_OR_PUNCT_RE, LEADING_OPERATOR_RE, takesRest, SUB_MACRO_PARAMETERS, _MarkupExpansionBudget, MarkupExpansionBudget, parses, CONDITION_MACROS, PASSAGE_BODIES, holdsByDefault, JS_ESCAPE_RE, textPiece, LITERAL, registeredParameters, WIDGET_PARAMETERS, SPECIAL_PASSAGES, NOT_MARKUP, NOT_MARKUP_TAGS$1, BRANCH_MACROS, didYouMean, PASSAGE_ARGUMENT_HINT, BARE_WORD_RE, NUMBER_RE, IDENT_RE, KEY_RE, ARROW_RE, LINE_END_RE, ARRAY_INDEX_RE, UNSUPPORTED_UNDEFINED, Reader, VALID_VAR_TYPES, PRIMITIVE_SAMPLES, UNSUPPORTED_TYPE, NO_STORE_VAR_MACROS, CLOSERS, BLOCK_WORDS, NOT_PARAMETERS, DECLARATIONS, WRITE_RE, MUTATORS, MUTATING_CALL_RE, BEFORE_BINDING, isWordChar, Bindings, CALL_RE, IDENTIFIER_ARGUMENT_RE, WORD_RE, MODIFIER_RE, KEY_START_RE, CONFIG_KEYS, ConfigReader, stringOf, booleanOf, textOf, stringsOf, VOID_ELEMENTS, characterEntities, own, hasOwnProperty, asciiAlpha, asciiAlphanumeric, asciiAtext, asciiDigit, asciiHexDigit, asciiPunctuation, unicodePunctuation, unicodeWhitespace, content$1, EditMap$1, document$1, containerConstruct, attention, autolink, blankLine, blockQuote, characterEscape, characterReference, nonLazyContinuation, codeFenced, codeIndented, furtherStart, codeText, SpliceBuffer, content, continuationConstruct, definition, titleBefore, hardBreakEscape, headingAtx, htmlBlockNames, htmlRawNames, htmlFlow, blankLineBefore, nonLazyContinuationStart, htmlText, labelEnd, resourceConstruct, referenceFullConstruct, referenceCollapsedConstruct, labelStartImage, labelStartLink, lineEnding, thematicBreak, list, listItemPrefixWhitespaceConstruct, indentConstruct, setextUnderline, flow$1, resolver, string$1, text$1, constructs_exports, document, contentInitial, flowInitial, flow, string, text, insideSpan, attentionMarkers, disable, search, alignment, EditMap, DEFAULT_PRETTIER_OPTIONS, SVG_PLACEHOLDERS, SPINDLE_PLACEHOLDERS, PLACEHOLDER_PATTERN, DEFAULT_DEDENTING, WHITESPACE_AS_WRITTEN, HTML_MARKDOWN_BOUNDARIES, LIST_ITEM_REGEX, CODE_FENCE_REGEX, NO_WRAP, NOT_MARKUP_TAGS;
21708
21902
  var init_story_variables = __esm({
21709
21903
  "node_modules/@rohal12/spindle/dist/pkg/story-variables.js"() {
21710
21904
  __defProp2 = Object.defineProperty;
@@ -21883,32 +22077,7 @@ var init_story_variables = __esm({
21883
22077
  return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
21884
22078
  }
21885
22079
  };
21886
- BLOCK_MACROS = new NameSet([
21887
- "if",
21888
- "for",
21889
- "do",
21890
- "button",
21891
- "link",
21892
- "listbox",
21893
- "cycle",
21894
- "switch",
21895
- "timed",
21896
- "repeat",
21897
- "type",
21898
- "widget",
21899
- "span",
21900
- "nobr"
21901
- ]);
21902
- BLOCK_WIDGETS = new NameSet();
21903
- WIDGETS = new NameSet();
21904
- DEFINED_BLOCK_MACROS = new NameSet();
21905
- RAW_BODY_MACROS = /* @__PURE__ */ new Set(["do"]);
21906
- BRANCHING = /* @__PURE__ */ new Set([
21907
- "if",
21908
- "switch",
21909
- "timed"
21910
- ]);
21911
- quoteArg = (value) => `"${value.replace(/[\\"]/g, "\\$&")}"`;
22080
+ OPTION_CLAUSE = "option";
21912
22081
  new NameMap();
21913
22082
  new NameMap();
21914
22083
  new NameSet();
@@ -21936,6 +22105,35 @@ var init_story_variables = __esm({
21936
22105
  metadataRegistry = new NameMap();
21937
22106
  companionParents = new NameMap();
21938
22107
  getMacroParameters.source = (name) => metadataRegistry.get(name)?.source;
22108
+ BLOCK_MACROS = new NameSet([
22109
+ "if",
22110
+ "for",
22111
+ "do",
22112
+ "button",
22113
+ "link",
22114
+ "listbox",
22115
+ "cycle",
22116
+ "switch",
22117
+ "timed",
22118
+ "repeat",
22119
+ "type",
22120
+ "widget",
22121
+ "span",
22122
+ "nobr"
22123
+ ]);
22124
+ BLOCK_WIDGETS = new NameSet();
22125
+ WIDGETS = new NameSet();
22126
+ DEFINED_BLOCK_MACROS = new NameSet();
22127
+ DEFINED_MACROS = new NameSet();
22128
+ REGISTERED_BLOCK_MACROS = new NameSet();
22129
+ RAW_BODY_MACROS = /* @__PURE__ */ new Set(["do"]);
22130
+ BRANCHING = /* @__PURE__ */ new Set([
22131
+ "if",
22132
+ "switch",
22133
+ "timed"
22134
+ ]);
22135
+ quoteArg = (value) => `"${value.replace(/[\\"]/g, "\\$&")}"`;
22136
+ MAX_MARKUP_DEPTH = 256;
21939
22137
  last$1 = (items) => items[items.length - 1];
21940
22138
  BRANCH_PARENT = {
21941
22139
  elseif: "if",
@@ -27168,12 +27366,29 @@ var init_story_variables = __esm({
27168
27366
  WORD_OR_PUNCT_RE = /\S+/y;
27169
27367
  LEADING_OPERATOR_RE = /^(?:[-+*/&|^=<>?:,]|\.(?!\d)|%(?![A-Za-z_]))/;
27170
27368
  takesRest = (param) => param.type === "expression" || param.type === "statements" || param.type === "passage" || param.type === "text";
27171
- SUB_MACRO_PARAMETERS = /* @__PURE__ */ new Map([["option", [{
27369
+ SUB_MACRO_PARAMETERS = /* @__PURE__ */ new Map([[OPTION_CLAUSE, [{
27172
27370
  name: "value",
27173
27371
  type: "string",
27174
27372
  holds: "text",
27175
27373
  required: true
27176
27374
  }]]]);
27375
+ MarkupExpansionBudget = class MarkupExpansionBudget2 {
27376
+ constructor(sourceLength) {
27377
+ this.depth = 0;
27378
+ this.remaining = Math.max(65536, sourceLength * 8);
27379
+ }
27380
+ enter(length) {
27381
+ if (this.depth >= MarkupExpansionBudget2.maxDepth || length > this.remaining) return false;
27382
+ this.remaining -= length;
27383
+ this.depth++;
27384
+ return true;
27385
+ }
27386
+ leave() {
27387
+ this.depth--;
27388
+ }
27389
+ };
27390
+ _MarkupExpansionBudget = MarkupExpansionBudget;
27391
+ _MarkupExpansionBudget.maxDepth = 64;
27177
27392
  parses = null;
27178
27393
  CONDITION_MACROS = /* @__PURE__ */ new Set([
27179
27394
  "if",
@@ -27204,6 +27419,19 @@ var init_story_variables = __esm({
27204
27419
  type: "text",
27205
27420
  holds: "text"
27206
27421
  }];
27422
+ SPECIAL_PASSAGES = /* @__PURE__ */ new Set([
27423
+ "StoryInit",
27424
+ "StoryInterface",
27425
+ "StoryVariables",
27426
+ "StoryTransients",
27427
+ "StoryLoading",
27428
+ "SaveTitle",
27429
+ "PassageReady",
27430
+ "PassageHeader",
27431
+ "PassageFooter",
27432
+ "PassageDone",
27433
+ "StoryContext"
27434
+ ]);
27207
27435
  NOT_MARKUP = /* @__PURE__ */ new Set([
27208
27436
  "StoryVariables",
27209
27437
  "StoryTransients",
@@ -27585,7 +27813,6 @@ var init_story_variables = __esm({
27585
27813
  return this.skipNested("`");
27586
27814
  }
27587
27815
  };
27588
- VAR_PATH_RE = /([$%])(\w+(?:\.\w+)*)/y;
27589
27816
  VALID_VAR_TYPES = /* @__PURE__ */ new Set([
27590
27817
  "number",
27591
27818
  "string",
@@ -27598,7 +27825,6 @@ var init_story_variables = __esm({
27598
27825
  };
27599
27826
  UNSUPPORTED_TYPE = "Expected number, string, boolean, array, or object.";
27600
27827
  NO_STORE_VAR_MACROS = /* @__PURE__ */ new Set();
27601
- FIELD_PATH_RE = /(?:\.\w+)*/y;
27602
27828
  CLOSERS = {
27603
27829
  "}": "{",
27604
27830
  ")": "(",
@@ -30998,6 +31224,19 @@ var init_story_variables = __esm({
30998
31224
  "default"
30999
31225
  ]);
31000
31226
  WHITESPACE_AS_WRITTEN = /<(?:pre|textarea|svg)[\s>]|white-space\s*:/i;
31227
+ HTML_MARKDOWN_BOUNDARIES = /* @__PURE__ */ new Set([
31228
+ "hardBreakTrailing",
31229
+ "hardBreakEscape",
31230
+ "atxHeading",
31231
+ "setextHeading",
31232
+ "listOrdered",
31233
+ "listUnordered",
31234
+ "blockQuote",
31235
+ "thematicBreak",
31236
+ "definition",
31237
+ "table",
31238
+ "codeFenced"
31239
+ ]);
31001
31240
  LIST_ITEM_REGEX = /^(?:[-+*]|\d{1,9}[.)])(?:[ \t]|$)/;
31002
31241
  CODE_FENCE_REGEX = /^(?:`{3,}|~{3,})/;
31003
31242
  NO_WRAP = /* @__PURE__ */ new Set([
@@ -31103,7 +31342,10 @@ var init_macro_registry = __esm({
31103
31342
  {
31104
31343
  "name": "if",
31105
31344
  "block": true,
31106
- "subMacros": [],
31345
+ "subMacros": [
31346
+ "elseif",
31347
+ "else"
31348
+ ],
31107
31349
  "interpolate": true,
31108
31350
  "merged": true,
31109
31351
  "source": "builtin"
@@ -31528,7 +31770,9 @@ var init_macro_registry = __esm({
31528
31770
  {
31529
31771
  "name": "cycle",
31530
31772
  "block": true,
31531
- "subMacros": [],
31773
+ "subMacros": [
31774
+ "option"
31775
+ ],
31532
31776
  "storeVar": true,
31533
31777
  "interpolate": true,
31534
31778
  "parameters": [
@@ -31798,7 +32042,7 @@ function parsePassageHeader(rawLine, lineNumber) {
31798
32042
  };
31799
32043
  }
31800
32044
  function isReservedPassageName(name) {
31801
- return RESERVED_PASSAGE_NAMES.has(name);
32045
+ return isSpecialPassage(name) || TWEE_PASSAGE_NAMES.has(name);
31802
32046
  }
31803
32047
  function isScriptOrStylesheetPassage(passage) {
31804
32048
  return passage.tags?.some((t) => CODE_PASSAGE_TAGS.has(t)) ?? false;
@@ -31809,38 +32053,24 @@ function isRuntimePassage(passage) {
31809
32053
  function isMarkupPassage(passage) {
31810
32054
  return isRuntimePassage(passage) && !(passage.name !== void 0 && NON_MARKUP_PASSAGES.has(passage.name));
31811
32055
  }
31812
- var SPECIAL_PASSAGES, CODE_PASSAGE_TAGS, RESERVED_PASSAGE_NAMES, NON_MARKUP_PASSAGES;
32056
+ var CODE_PASSAGE_TAGS, TWEE_PASSAGE_NAMES, NON_MARKUP_PASSAGES;
31813
32057
  var init_passage_parser = __esm({
31814
32058
  "src/core/parsing/passage-parser.ts"() {
31815
32059
  "use strict";
32060
+ init_tooling();
31816
32061
  init_text();
31817
- SPECIAL_PASSAGES = /* @__PURE__ */ new Set([
31818
- "StoryVariables",
31819
- "StoryInit",
32062
+ CODE_PASSAGE_TAGS = /* @__PURE__ */ new Set(["script", "stylesheet"]);
32063
+ TWEE_PASSAGE_NAMES = /* @__PURE__ */ new Set([
31820
32064
  "StoryData",
31821
32065
  "StoryTitle",
32066
+ "StoryScript",
31822
32067
  "StoryBanner",
31823
32068
  "StoryCaption",
31824
- "StoryContext",
31825
32069
  "StoryMenu",
31826
- "StoryInterface",
31827
- "StoryAuthor"
31828
- ]);
31829
- CODE_PASSAGE_TAGS = /* @__PURE__ */ new Set(["script", "stylesheet"]);
31830
- RESERVED_PASSAGE_NAMES = /* @__PURE__ */ new Set([
31831
- ...SPECIAL_PASSAGES,
32070
+ "StoryAuthor",
31832
32071
  // twee-ts also reads these legacy compiler metadata passages by name.
31833
32072
  "StorySettings",
31834
- "StoryIncludes",
31835
- "StoryTitle",
31836
- "StoryLoading",
31837
- "StoryTransients",
31838
- "StoryScript",
31839
- "SaveTitle",
31840
- "PassageReady",
31841
- "PassageDone",
31842
- "PassageHeader",
31843
- "PassageFooter"
32073
+ "StoryIncludes"
31844
32074
  ]);
31845
32075
  NON_MARKUP_PASSAGES = /* @__PURE__ */ new Set([
31846
32076
  "StoryTitle",
@@ -34301,6 +34531,8 @@ var init_workspace_model = __esm({
34301
34531
  * A same-name block macro retains its syntax when a widget owns the call.
34302
34532
  */
34303
34533
  isContainer(name) {
34534
+ const widget = this.widgets.getEffectiveWidget(name);
34535
+ if (widget) return widget.block;
34304
34536
  return this.macros.isBlock(name) || (this.widgets.getWidget(name)?.block ?? false);
34305
34537
  }
34306
34538
  /** Runtime macro metadata, after effective widgets have claimed their names. */
@@ -34496,7 +34728,7 @@ var init_workspace_model = __esm({
34496
34728
  /** All runtime metadata, with effective widgets taking precedence over macros. */
34497
34729
  toolingMacros() {
34498
34730
  const macros = this.macros.toolingMacros().filter((macro) => !this.widgets.getEffectiveWidget(macro.name));
34499
- return [...macros, ...this.widgets.getAllWidgets().map((widget) => ({ name: widget.name, block: widget.block || this.macros.isBlock(widget.name), subMacros: [], widget: widget.tooling }))];
34731
+ return [...macros, ...this.widgets.getAllWidgets().map((widget) => ({ name: widget.name, block: widget.block, subMacros: [], widget: widget.tooling }))];
34500
34732
  }
34501
34733
  /** Coalesce analysis and diagnostics across a burst of document changes. */
34502
34734
  scheduleModelReady() {
@@ -34552,6 +34784,8 @@ var init_diagnostic_codes = __esm({
34552
34784
  UnquotedPassageName: "SP113",
34553
34785
  ChildMaxExceeded: "SP114",
34554
34786
  ChildMinNotMet: "SP115",
34787
+ /** `nesting-limit`: markup nested deeper than Spindle builds and renders. */
34788
+ NestingLimitExceeded: "SP116",
34555
34789
  /** `undeclared-variable`. */
34556
34790
  UndeclaredVariable: "SP200",
34557
34791
  /** `primitive-field`: a field of a number, string or boolean. */
@@ -34571,6 +34805,8 @@ var init_diagnostic_codes = __esm({
34571
34805
  UnusedWidget: "SP303",
34572
34806
  LinkRuntimeMismatch: "SP304",
34573
34807
  LiteralLinkInterpolation: "SP305",
34808
+ /** `special-passage`: a link or macro that navigates to a special passage (`StoryInit`), which throws. */
34809
+ SpecialPassageNavigation: "SP306",
34574
34810
  DeadEndPassage: "SP400",
34575
34811
  UnreachablePassage: "SP401",
34576
34812
  LineTooLong: "SP500"
@@ -34593,6 +34829,7 @@ var init_diagnostic_codes = __esm({
34593
34829
  [DiagnosticCode.UnquotedPassageName]: "error",
34594
34830
  [DiagnosticCode.ChildMaxExceeded]: "error",
34595
34831
  [DiagnosticCode.ChildMinNotMet]: "error",
34832
+ [DiagnosticCode.NestingLimitExceeded]: "error",
34596
34833
  [DiagnosticCode.UndeclaredVariable]: "error",
34597
34834
  [DiagnosticCode.PrimitiveFieldAccess]: "error",
34598
34835
  [DiagnosticCode.NoStoryVariables]: "info",
@@ -34607,6 +34844,7 @@ var init_diagnostic_codes = __esm({
34607
34844
  [DiagnosticCode.UnusedWidget]: "hint",
34608
34845
  [DiagnosticCode.LinkRuntimeMismatch]: "warning",
34609
34846
  [DiagnosticCode.LiteralLinkInterpolation]: "warning",
34847
+ [DiagnosticCode.SpecialPassageNavigation]: "error",
34610
34848
  [DiagnosticCode.DeadEndPassage]: "hint",
34611
34849
  [DiagnosticCode.UnreachablePassage]: "hint",
34612
34850
  [DiagnosticCode.LineTooLong]: "warning"
@@ -35168,7 +35406,7 @@ function parentDiagnostics(passage, token, ancestors, workspace) {
35168
35406
  const name = token.name.toLowerCase();
35169
35407
  const parents = workspace.getEffectiveMacro(name)?.parents;
35170
35408
  if (!parents || parents.length === 0 || BRANCH_MACROS2.has(name)) return [];
35171
- if (workspace.macros.getAllMacros().some((macro) => macro.source === "user" && macro.name.toLowerCase() !== "listbox" && macro.subMacros.some((sub) => sub.toLowerCase() === name))) return [];
35409
+ if (workspace.macros.getAllMacros().some((macro) => macro.source === "user" && macro.subMacros.some((sub) => sub.toLowerCase() === name))) return [];
35172
35410
  const allowed = new Set(parents.map((parent) => parent.toLowerCase()));
35173
35411
  const parentList = parents.join(", ");
35174
35412
  const range2 = passage.range(token.start, token.end);
@@ -35505,6 +35743,7 @@ var init_diagnostics = __esm({
35505
35743
  "mismatched-closer": DiagnosticCode.MalformedContainer,
35506
35744
  "stray-closer": DiagnosticCode.MalformedContainer,
35507
35745
  "misplaced-branch": DiagnosticCode.InvalidChildren,
35746
+ "nesting-limit": DiagnosticCode.NestingLimitExceeded,
35508
35747
  "invalid-closer": DiagnosticCode.IllegalClosingTag,
35509
35748
  "closer-with-selectors": DiagnosticCode.IllegalClosingTag,
35510
35749
  "closer-with-arguments": DiagnosticCode.IllegalClosingTag,
@@ -35518,6 +35757,7 @@ var init_diagnostics = __esm({
35518
35757
  "argument-error": DiagnosticCode.ParameterTypeError,
35519
35758
  "code-syntax": DiagnosticCode.CodeSyntaxError,
35520
35759
  "unknown-passage": DiagnosticCode.BrokenPassageLink,
35760
+ "special-passage": DiagnosticCode.SpecialPassageNavigation,
35521
35761
  "unquoted-passage-name": DiagnosticCode.UnquotedPassageName
35522
35762
  };
35523
35763
  text2 = (value) => typeof value === "string" ? value : "";
@@ -87893,10 +88133,6 @@ function computeRename(uri, position, newName, workspace) {
87893
88133
  `Cannot rename to ${JSON.stringify(newName)}: a widget name must start with a letter and contain only letters, digits, _ and - to be called as {name}.`
87894
88134
  ).at(uri, symbol2.range);
87895
88135
  }
87896
- const widget = workspace.widgets.getWidget(symbol2.name);
87897
- if (widget && !widget.block && workspace.macros.isBlock(symbol2.name)) {
87898
- throw new RenameError("Cannot rename this inline widget: its current name inherits a macro container body that the new name would lose.").at(uri, symbol2.range);
87899
- }
87900
88136
  if (workspace.macros.getMacro(newName)) {
87901
88137
  throw new RenameError(
87902
88138
  `Cannot rename to ${JSON.stringify(newName)}: a macro of that name already exists.`
@@ -87910,6 +88146,7 @@ function computeRename(uri, position, newName, workspace) {
87910
88146
  for (const ref2 of invocationRefs) {
87911
88147
  addEdit(ref2.uri, ref2.range, newName);
87912
88148
  }
88149
+ const widget = workspace.widgets.getWidget(symbol2.name);
87913
88150
  if (widget) {
87914
88151
  addEdit(widget.uri, widget.nameRange, newName);
87915
88152
  }