@rohal12/spindle-lsp 0.15.16 → 0.15.18

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.16";
58
+ VERSION = false ? pkg.version : "0.15.18";
59
59
  }
60
60
  });
61
61
 
@@ -31737,6 +31737,13 @@ var init_text = __esm({
31737
31737
 
31738
31738
  // src/core/parsing/passage-parser.ts
31739
31739
  import { createRequire as createCompilerRequire } from "node:module";
31740
+ function encodePassageName(name) {
31741
+ if (!name || /[\r\n]/.test(name)) return void 0;
31742
+ const encoded = name.replace(/[[\]{}\\]/g, "\\$&");
31743
+ const { parseTwee } = createCompilerRequire(import.meta.url)("@rohal12/twee-ts");
31744
+ const parsed = parseTwee(`:: ${encoded}`);
31745
+ return parsed.passages.length === 1 && parsed.passages[0].name === name && !parsed.diagnostics.some((d) => d.level === "error") ? encoded : void 0;
31746
+ }
31740
31747
  function parsePassageHeader(rawLine, lineNumber) {
31741
31748
  const line = withoutCr(rawLine);
31742
31749
  const bomLength = /^\uFEFF*/.exec(line)[0].length;
@@ -33704,7 +33711,6 @@ function indexStoryDataTexts(texts) {
33704
33711
  const index = new PassageIndex();
33705
33712
  const byUri = /* @__PURE__ */ new Map();
33706
33713
  for (const text3 of texts) {
33707
- if (!text3.includes("StoryData")) continue;
33708
33714
  const uri = `story-format:${String(byUri.size).padStart(16, "0")}`;
33709
33715
  byUri.set(uri, text3);
33710
33716
  index.rebuild(uri, text3);
@@ -34502,6 +34508,10 @@ var init_diagnostic_codes = __esm({
34502
34508
  DiagnosticCode = {
34503
34509
  /** The Spindle the project targets is older than the oldest one supported. */
34504
34510
  UnsupportedSpindleVersion: "SP001",
34511
+ /** A fatal Twee source error reported by the public compiler parser. */
34512
+ TweeSyntaxError: "SP002",
34513
+ /** StoryData explicitly selects an entry passage absent from the story. */
34514
+ MissingEntryPassage: "SP004",
34505
34515
  /** `unknown-macro`: a macro that is none of the built-in, configured or defined ones. */
34506
34516
  UndefinedMacro: "SP100",
34507
34517
  /** `unclosed-block`, `mismatched-closer`, `stray-closer` of a macro. */
@@ -34550,38 +34560,40 @@ var init_diagnostic_codes = __esm({
34550
34560
  LineTooLong: "SP500"
34551
34561
  };
34552
34562
  severityMap = {
34553
- SP001: "warning",
34554
- SP100: "warning",
34555
- SP101: "error",
34556
- SP102: "error",
34557
- SP104: "error",
34558
- SP105: "error",
34559
- SP106: "error",
34560
- SP107: "error",
34561
- SP108: "error",
34562
- SP109: "error",
34563
- SP110: "warning",
34564
- SP111: "error",
34565
- SP113: "error",
34566
- SP114: "error",
34567
- SP115: "error",
34568
- SP200: "error",
34569
- SP201: "error",
34570
- SP202: "info",
34571
- SP203: "warning",
34572
- SP205: "warning",
34573
- SP206: "warning",
34574
- SP207: "error",
34575
- SP208: "error",
34576
- SP300: "warning",
34577
- SP301: "warning",
34578
- SP302: "warning",
34579
- SP303: "hint",
34580
- SP304: "warning",
34581
- SP305: "warning",
34582
- SP400: "hint",
34583
- SP401: "hint",
34584
- SP500: "warning"
34563
+ [DiagnosticCode.UnsupportedSpindleVersion]: "warning",
34564
+ [DiagnosticCode.TweeSyntaxError]: "error",
34565
+ [DiagnosticCode.MissingEntryPassage]: "error",
34566
+ [DiagnosticCode.UndefinedMacro]: "warning",
34567
+ [DiagnosticCode.MalformedContainer]: "error",
34568
+ [DiagnosticCode.MalformedElement]: "error",
34569
+ [DiagnosticCode.IllegalClosingTag]: "error",
34570
+ [DiagnosticCode.UnclosedMarkup]: "error",
34571
+ [DiagnosticCode.CodeSyntaxError]: "error",
34572
+ [DiagnosticCode.InvalidChildren]: "error",
34573
+ [DiagnosticCode.ExpectedNoArguments]: "error",
34574
+ [DiagnosticCode.ParameterTypeError]: "error",
34575
+ [DiagnosticCode.ParameterWarning]: "warning",
34576
+ [DiagnosticCode.TooManyArguments]: "error",
34577
+ [DiagnosticCode.UnquotedPassageName]: "error",
34578
+ [DiagnosticCode.ChildMaxExceeded]: "error",
34579
+ [DiagnosticCode.ChildMinNotMet]: "error",
34580
+ [DiagnosticCode.UndeclaredVariable]: "error",
34581
+ [DiagnosticCode.PrimitiveFieldAccess]: "error",
34582
+ [DiagnosticCode.NoStoryVariables]: "info",
34583
+ [DiagnosticCode.UndeclaredTransient]: "warning",
34584
+ [DiagnosticCode.TemporaryAssignedInLoop]: "warning",
34585
+ [DiagnosticCode.ArrayMemberAccess]: "warning",
34586
+ [DiagnosticCode.InvalidDeclaration]: "error",
34587
+ [DiagnosticCode.ReservedVariableName]: "error",
34588
+ [DiagnosticCode.BrokenPassageLink]: "warning",
34589
+ [DiagnosticCode.WidgetArgCountMismatch]: "warning",
34590
+ [DiagnosticCode.IncludeWidgetPassage]: "warning",
34591
+ [DiagnosticCode.UnusedWidget]: "hint",
34592
+ [DiagnosticCode.LinkRuntimeMismatch]: "warning",
34593
+ [DiagnosticCode.LiteralLinkInterpolation]: "warning",
34594
+ [DiagnosticCode.DeadEndPassage]: "hint",
34595
+ [DiagnosticCode.UnreachablePassage]: "hint",
34596
+ [DiagnosticCode.LineTooLong]: "warning"
34585
34597
  };
34586
34598
  }
34587
34599
  });
@@ -34833,12 +34845,14 @@ var init_story_variables_owner = __esm({
34833
34845
  });
34834
34846
 
34835
34847
  // src/plugins/diagnostics.ts
34848
+ import { createRequire as createCompilerRequire3 } from "node:module";
34836
34849
  function computeDiagnostics(uri, workspace, options) {
34837
34850
  if (!workspace.initialized) return [];
34838
34851
  if (!workspace.isSpindleProject()) return [];
34839
34852
  const doc = workspace.markup.get(uri);
34840
- if (!doc || doc.passages.length === 0) return [];
34841
- const diagnostics = [];
34853
+ if (!doc || isMacroSource(uri)) return [];
34854
+ const diagnostics = [...tweeDiagnostics(doc)];
34855
+ if (doc.passages.length === 0) return diagnostics;
34842
34856
  const attempt = (check2) => {
34843
34857
  try {
34844
34858
  check2();
@@ -34846,6 +34860,7 @@ function computeDiagnostics(uri, workspace, options) {
34846
34860
  }
34847
34861
  };
34848
34862
  const story = storyReport(workspace);
34863
+ diagnostics.push(...story.metadata.get(doc) ?? []);
34849
34864
  const effective = new Set(workspace.passages.getEffectivePassages());
34850
34865
  for (const passage of doc.passages) {
34851
34866
  if (!effective.has(passage.passage)) continue;
@@ -34869,6 +34884,27 @@ function computeDiagnostics(uri, workspace, options) {
34869
34884
  }
34870
34885
  return diagnostics.sort((a, b) => a.range.start.line - b.range.start.line || a.range.start.character - b.range.start.character);
34871
34886
  }
34887
+ function tweeDiagnostics(doc) {
34888
+ const kept3 = tweeReports.get(doc);
34889
+ if (kept3) return kept3;
34890
+ const lines = splitLines(doc.text);
34891
+ const indexed = new Set(doc.passages.map((p) => p.passage.headerEnd.start.line));
34892
+ if (!lines.some((line, i2) => /^\uFEFF*::/.test(line) && (!indexed.has(i2) || /[[\]{}]/.test(line)))) {
34893
+ tweeReports.set(doc, []);
34894
+ return [];
34895
+ }
34896
+ const { parseTwee } = createCompilerRequire3(import.meta.url)("@rohal12/twee-ts");
34897
+ const diagnostics = parseTwee(doc.text).diagnostics.filter((d) => d.level === "error").map((d) => {
34898
+ const line = Math.min(Math.max((d.line ?? 1) - 1, 0), lines.length - 1);
34899
+ return makeDiag(
34900
+ { start: { line, character: 0 }, end: { line, character: lines[line].length } },
34901
+ DiagnosticCode.TweeSyntaxError,
34902
+ d.message
34903
+ );
34904
+ });
34905
+ tweeReports.set(doc, diagnostics);
34906
+ return diagnostics;
34907
+ }
34872
34908
  function storyReport(workspace) {
34873
34909
  const documents = [];
34874
34910
  for (const uri of workspace.passages.getDocumentUris()) {
@@ -34930,6 +34966,7 @@ function buildReport(documents, workspace) {
34930
34966
  }
34931
34967
  return {
34932
34968
  documents,
34969
+ metadata: storyDataDiagnostics(documents, workspace),
34933
34970
  passages,
34934
34971
  macros,
34935
34972
  markup,
@@ -34941,6 +34978,22 @@ function buildReport(documents, workspace) {
34941
34978
  variableDiagnostics: /* @__PURE__ */ new Map()
34942
34979
  };
34943
34980
  }
34981
+ function storyDataDiagnostics(documents, workspace) {
34982
+ const found = /* @__PURE__ */ new Map();
34983
+ const effective = workspace.passages.getEffectivePassage("StoryData");
34984
+ for (const doc of documents) {
34985
+ const diagnostics = [];
34986
+ for (const passage of doc.passages) {
34987
+ if (passage.passage !== effective) continue;
34988
+ const data2 = readStoryData(passage.content);
34989
+ if (data2.start && !workspace.passages.getEffectivePassage(data2.start)) {
34990
+ diagnostics.push(makeDiag(passage.passage.nameRange, DiagnosticCode.MissingEntryPassage, `Starting passage "${data2.start}" not found.`));
34991
+ }
34992
+ }
34993
+ found.set(doc, diagnostics);
34994
+ }
34995
+ return found;
34996
+ }
34944
34997
  function declarationsOf(workspace, name) {
34945
34998
  const passage = name === "StoryVariables" ? workspace.passages.getStoryVariables() : workspace.passages.getStoryTransients();
34946
34999
  if (!passage || !isRuntimePassage(passage)) return void 0;
@@ -35148,7 +35201,7 @@ function temporaryRefRegex(name) {
35148
35201
  return new RegExp(`(?<![.\\w$@%])_(${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(?![\\w$])`, "g");
35149
35202
  }
35150
35203
  function maskLiterals(code) {
35151
- const chars = Array.from(code, (ch) => ch === "\n" ? "\n" : " ");
35204
+ const chars = code.split("").map((ch) => ch === "\n" ? "\n" : " ");
35152
35205
  const copy = (text3, index) => {
35153
35206
  for (let i2 = 0; i2 < text3.length; i2++) chars[index + i2] = text3[i2];
35154
35207
  };
@@ -35218,13 +35271,15 @@ function loopDiagnostics(passage, token, ancestors, workspace) {
35218
35271
  const close = loop?.node.body?.close;
35219
35272
  if (!loop || !close) return [];
35220
35273
  const targets = macro === "computed" ? computedTemporaryTarget(token.rawArgs) : setTemporaryTargets(token.rawArgs);
35274
+ const view = sourceView(passage, token.argsStart, token.argsEnd);
35275
+ const argsIndex = viewIndex(view, token.argsStart);
35221
35276
  const bodyStart = loop.node.token.end;
35222
35277
  const diagnostics = [];
35223
35278
  for (const target of targets) {
35224
35279
  if (!isReadInBody(passage, bodyStart, close.start, target.name, token.start, token.end)) continue;
35225
- const start = token.argsStart + target.offset;
35280
+ const start = argsIndex + target.offset;
35226
35281
  diagnostics.push(makeDiag(
35227
- passage.range(start, start + 1 + target.name.length),
35282
+ passage.range(pieceOffset(view, start), pieceOffset(view, start + 1 + target.name.length)),
35228
35283
  DiagnosticCode.TemporaryAssignedInLoop,
35229
35284
  `Temporary '_${target.name}' is assigned inside {${loop.name}} and read in the loop body. Temporaries are shared by every iteration, so all iterations end up seeing the value assigned by the last one. Use the iteration-local '@${target.name}' instead, or assign it once before the loop if the value does not depend on the iteration.`
35230
35285
  ));
@@ -35405,7 +35460,7 @@ function toLspDiagnostic(d) {
35405
35460
  data: d.data
35406
35461
  };
35407
35462
  }
35408
- var reports, COMPILER_PASSAGES, MARKUP_CODES, text2, list2, VARIABLE_CODES, WILL_NOT_START, BRANCH_MACROS2, DIRECT_CHILD_MACROS, DEFERRED_CONTAINERS, SPINDLE_MACROS, diagnosticsPlugin;
35463
+ var tweeReports, reports, COMPILER_PASSAGES, MARKUP_CODES, text2, list2, VARIABLE_CODES, WILL_NOT_START, BRANCH_MACROS2, DIRECT_CHILD_MACROS, DEFERRED_CONTAINERS, SPINDLE_MACROS, diagnosticsPlugin;
35409
35464
  var init_diagnostics = __esm({
35410
35465
  "src/plugins/diagnostics.ts"() {
35411
35466
  "use strict";
@@ -35420,8 +35475,10 @@ var init_diagnostics = __esm({
35420
35475
  init_link_parser();
35421
35476
  init_passage_parser();
35422
35477
  init_story_variables_owner();
35478
+ init_story_data();
35423
35479
  init_macro_sources();
35424
35480
  init_spindle_version();
35481
+ tweeReports = /* @__PURE__ */ new WeakMap();
35425
35482
  reports = /* @__PURE__ */ new WeakMap();
35426
35483
  COMPILER_PASSAGES = /* @__PURE__ */ new Set(["StoryTitle", "StoryData"]);
35427
35484
  MARKUP_CODES = {
@@ -80094,7 +80151,11 @@ function gateRegistrations(target, methods, enabled, nested = {}) {
80094
80151
  }
80095
80152
  if (methods.includes(prop) && typeof value === "function") {
80096
80153
  return (handler, ...rest) => Reflect.apply(value, obj, [
80097
- (...args2) => enabled() ? handler(...args2) : null,
80154
+ (...args2) => {
80155
+ const allowed = enabled();
80156
+ const answer = (yes) => yes ? handler(...args2) : null;
80157
+ return typeof allowed === "boolean" ? answer(allowed) : allowed.then(answer);
80158
+ },
80098
80159
  ...rest
80099
80160
  ]);
80100
80161
  }
@@ -81143,10 +81204,13 @@ function computeRename(uri, position, newName, workspace) {
81143
81204
  if (isReservedPassageName(newName)) {
81144
81205
  throw new RenameError(`Cannot rename to ${JSON.stringify(newName)}: it is a reserved passage name with a fixed meaning.`).at(uri, symbol2.range);
81145
81206
  }
81207
+ const encoded = encodePassageName(newName);
81146
81208
  for (const declaration of workspace.passages.getPassages(symbol2.name)) {
81147
- const problem = passageHeaderProblem(newName);
81148
- if (problem) throw new RenameError(`Cannot rename to ${JSON.stringify(newName)}: ${problem}.`).at(declaration.uri, declaration.nameRange);
81149
- addEdit(declaration.uri, declaration.nameRange, escapePassageName(newName));
81209
+ if (encoded === void 0) {
81210
+ const reason = /[\r\n]/.test(newName) ? "a passage header is a single line" : "a passage header cannot represent that name exactly";
81211
+ throw new RenameError(`Cannot rename to ${JSON.stringify(newName)}: ${reason}.`).at(declaration.uri, declaration.nameRange);
81212
+ }
81213
+ addEdit(declaration.uri, declaration.nameRange, encoded);
81150
81214
  }
81151
81215
  for (const { uri: refUri, ref: ref2, text: text3 } of refEdits) addEdit(refUri, ref2.range, text3);
81152
81216
  preserveEntryPassage(symbol2.name, newName, workspace, addEdit);
@@ -81295,12 +81359,6 @@ function preserveEntryPassage(oldName, newName, workspace, addEdit) {
81295
81359
  function isEntryField(key) {
81296
81360
  return readStoryData(JSON.stringify({ [key]: "Start" })).start === "Start";
81297
81361
  }
81298
- function passageHeaderProblem(name) {
81299
- if (name.trim() === "") return "a passage name cannot be empty";
81300
- if (/[\r\n]/.test(name)) return "a passage header is a single line, so the name cannot contain a line break";
81301
- if (name !== name.trim()) return "a passage name cannot start or end with whitespace";
81302
- return null;
81303
- }
81304
81362
  function unrepresentable(newName, where2) {
81305
81363
  return new RenameError(`Cannot rename to ${JSON.stringify(newName)}: it cannot be written inside ${where2}.`);
81306
81364
  }
@@ -81364,9 +81422,6 @@ function misreadRewrite(passage, edits, oldName, newName) {
81364
81422
  const owner = edits.filter((edit) => edit.ref.piece <= index).at(-1) ?? edits[0];
81365
81423
  return owner.ref;
81366
81424
  }
81367
- function escapePassageName(name) {
81368
- return name.replace(/[[\]{}\\]/g, "\\$&");
81369
- }
81370
81425
  function resolveSymbolAtCursor(uri, position, workspace) {
81371
81426
  const text3 = workspace.documents.getText(uri);
81372
81427
  if (text3 === void 0 || !workspace.hasPassages(uri)) return null;
@@ -82055,10 +82110,10 @@ function fixBrokenLink(uri, diag, workspace) {
82055
82110
  edits: [{ uri, ...edit }]
82056
82111
  });
82057
82112
  }
82058
- const header = `:: ${name.replace(/[\\[\]{}]/g, "\\$&")}`;
82059
- if (!workspace.passages.getPassage(name) && name.trim() === name && name !== "" && !/[\r\n]/.test(name)) {
82113
+ const encoded = encodePassageName(name);
82114
+ if (!workspace.passages.getPassage(name) && encoded !== void 0) {
82060
82115
  const edit = insertLinesAt(text3, splitLines(text3).length, `
82061
- ${header}
82116
+ :: ${encoded}
82062
82117
  `, lineEndingFor(workspace, uri));
82063
82118
  actions.push({
82064
82119
  title: `Create passage '${name}'`,
@@ -82517,6 +82572,10 @@ function startServer(args2) {
82517
82572
  let activePlugins = [];
82518
82573
  let workspaceRoot;
82519
82574
  let startupWarning;
82575
+ let completeInitialScan;
82576
+ const initialScan = new Promise((resolve8) => {
82577
+ completeInitialScan = resolve8;
82578
+ });
82520
82579
  connection.onInitialize((params) => {
82521
82580
  console.error("[spindle-lsp] onInitialize called");
82522
82581
  const initOptions = params.initializationOptions ?? {};
@@ -82544,7 +82603,10 @@ function startServer(args2) {
82544
82603
  activePlugins = loadPlugins(allPlugins, config2);
82545
82604
  console.error("[spindle-lsp] plugins loaded:", activePlugins.map((p) => p.id).join(", "));
82546
82605
  const model = workspace;
82547
- const pluginConnection = gateConnection(connection, () => model.isSpindleProject());
82606
+ const pluginConnection = gateConnection(connection, async () => {
82607
+ await initialScan;
82608
+ return model.initialized && model.isSpindleProject();
82609
+ });
82548
82610
  for (const plugin of activePlugins) {
82549
82611
  plugin.initialize({ connection: pluginConnection, workspace, config: config2 });
82550
82612
  }
@@ -82596,6 +82658,8 @@ function startServer(args2) {
82596
82658
  console.error("[spindle-lsp] workspace initialized, macros:", workspace.macros.getAllMacros().length);
82597
82659
  } catch (err) {
82598
82660
  console.error("[spindle-lsp] workspace scan failed:", err);
82661
+ } finally {
82662
+ completeInitialScan();
82599
82663
  }
82600
82664
  });
82601
82665
  }