@rohal12/spindle-lsp 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +153 -25
- package/dist/bin.js.map +3 -3
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -448,8 +448,10 @@ var init_variable_tracker = __esm({
|
|
|
448
448
|
VariableTracker = class {
|
|
449
449
|
declared = /* @__PURE__ */ new Map();
|
|
450
450
|
_hasStoryVariables = false;
|
|
451
|
+
_nullDeclarations = [];
|
|
451
452
|
declaredTransient = /* @__PURE__ */ new Map();
|
|
452
453
|
_hasStoryTransients = false;
|
|
454
|
+
_nullTransientDeclarations = [];
|
|
453
455
|
/** Per-URI list of variable usages. */
|
|
454
456
|
usagesByUri = /* @__PURE__ */ new Map();
|
|
455
457
|
/** Per-URI list of transient variable usages. */
|
|
@@ -458,17 +460,32 @@ var init_variable_tracker = __esm({
|
|
|
458
460
|
* Parse the StoryVariables passage content for declarations.
|
|
459
461
|
* Each line like `$name = value` becomes a declaration.
|
|
460
462
|
*/
|
|
461
|
-
parseStoryVariables(content) {
|
|
463
|
+
parseStoryVariables(content, contentStartLine = 0) {
|
|
462
464
|
this.declared.clear();
|
|
463
465
|
this._hasStoryVariables = true;
|
|
466
|
+
this._nullDeclarations = [];
|
|
464
467
|
const lines = content.split("\n");
|
|
465
|
-
for (
|
|
466
|
-
const trimmed =
|
|
468
|
+
for (let i = 0; i < lines.length; i++) {
|
|
469
|
+
const trimmed = lines[i].trim();
|
|
467
470
|
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("<!--")) continue;
|
|
468
471
|
const match = /^\$([A-Za-z_$][\w$]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
469
472
|
if (!match) continue;
|
|
470
473
|
const name = match[1];
|
|
471
474
|
const expr = match[2].trim();
|
|
475
|
+
if (expr === "null") {
|
|
476
|
+
const charIdx = lines[i].indexOf("null", lines[i].indexOf("="));
|
|
477
|
+
const absLine = contentStartLine + i;
|
|
478
|
+
this._nullDeclarations.push({
|
|
479
|
+
name,
|
|
480
|
+
sigil: "$",
|
|
481
|
+
range: {
|
|
482
|
+
start: { line: absLine, character: charIdx },
|
|
483
|
+
end: { line: absLine, character: charIdx + 4 }
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
this.declared.set(name, { name, sigil: "$" });
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
472
489
|
const decl = { name, sigil: "$" };
|
|
473
490
|
if (expr.startsWith("{")) {
|
|
474
491
|
const fieldRegex = /(\w+)\s*:/g;
|
|
@@ -488,17 +505,32 @@ var init_variable_tracker = __esm({
|
|
|
488
505
|
* Parse the StoryTransients passage content for declarations.
|
|
489
506
|
* Each line like `%name = value` becomes a declaration.
|
|
490
507
|
*/
|
|
491
|
-
parseStoryTransients(content) {
|
|
508
|
+
parseStoryTransients(content, contentStartLine = 0) {
|
|
492
509
|
this.declaredTransient.clear();
|
|
493
510
|
this._hasStoryTransients = true;
|
|
511
|
+
this._nullTransientDeclarations = [];
|
|
494
512
|
const lines = content.split("\n");
|
|
495
|
-
for (
|
|
496
|
-
const trimmed =
|
|
513
|
+
for (let i = 0; i < lines.length; i++) {
|
|
514
|
+
const trimmed = lines[i].trim();
|
|
497
515
|
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("<!--")) continue;
|
|
498
516
|
const match = /^%([A-Za-z_$][\w$]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
499
517
|
if (!match) continue;
|
|
500
518
|
const name = match[1];
|
|
501
519
|
const expr = match[2].trim();
|
|
520
|
+
if (expr === "null") {
|
|
521
|
+
const charIdx = lines[i].indexOf("null", lines[i].indexOf("="));
|
|
522
|
+
const absLine = contentStartLine + i;
|
|
523
|
+
this._nullTransientDeclarations.push({
|
|
524
|
+
name,
|
|
525
|
+
sigil: "%",
|
|
526
|
+
range: {
|
|
527
|
+
start: { line: absLine, character: charIdx },
|
|
528
|
+
end: { line: absLine, character: charIdx + 4 }
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
this.declaredTransient.set(name, { name, sigil: "%" });
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
502
534
|
const decl = { name, sigil: "%" };
|
|
503
535
|
if (expr.startsWith("{")) {
|
|
504
536
|
const fieldRegex = /(\w+)\s*:/g;
|
|
@@ -660,6 +692,14 @@ var init_variable_tracker = __esm({
|
|
|
660
692
|
hasStoryTransients() {
|
|
661
693
|
return this._hasStoryTransients;
|
|
662
694
|
}
|
|
695
|
+
/** Get variables declared with null values in StoryVariables. */
|
|
696
|
+
getNullDeclarations() {
|
|
697
|
+
return this._nullDeclarations;
|
|
698
|
+
}
|
|
699
|
+
/** Get transient variables declared with null values in StoryTransients. */
|
|
700
|
+
getNullTransientDeclarations() {
|
|
701
|
+
return this._nullTransientDeclarations;
|
|
702
|
+
}
|
|
663
703
|
};
|
|
664
704
|
}
|
|
665
705
|
});
|
|
@@ -1238,7 +1278,7 @@ var init_workspace_model = __esm({
|
|
|
1238
1278
|
}
|
|
1239
1279
|
}
|
|
1240
1280
|
const content = lines.slice(contentStart, contentEnd).join("\n");
|
|
1241
|
-
this.variables.parseStoryVariables(content);
|
|
1281
|
+
this.variables.parseStoryVariables(content, contentStart);
|
|
1242
1282
|
}
|
|
1243
1283
|
}
|
|
1244
1284
|
const storyTransients = this.passages.getStoryTransients();
|
|
@@ -1255,7 +1295,7 @@ var init_workspace_model = __esm({
|
|
|
1255
1295
|
}
|
|
1256
1296
|
}
|
|
1257
1297
|
const content = lines.slice(contentStart, contentEnd).join("\n");
|
|
1258
|
-
this.variables.parseStoryTransients(content);
|
|
1298
|
+
this.variables.parseStoryTransients(content, contentStart);
|
|
1259
1299
|
}
|
|
1260
1300
|
}
|
|
1261
1301
|
for (const uri of this.documents.getUris()) {
|
|
@@ -1306,6 +1346,7 @@ var init_diagnostic_codes = __esm({
|
|
|
1306
1346
|
UndeclaredField: "SP201",
|
|
1307
1347
|
NoStoryVariables: "SP202",
|
|
1308
1348
|
UndeclaredTransient: "SP203",
|
|
1349
|
+
NullVariableValue: "SP204",
|
|
1309
1350
|
BrokenPassageLink: "SP300",
|
|
1310
1351
|
WidgetArgCountMismatch: "SP301",
|
|
1311
1352
|
DeadEndPassage: "SP400",
|
|
@@ -1324,10 +1365,11 @@ var init_diagnostic_codes = __esm({
|
|
|
1324
1365
|
SP112: "warning",
|
|
1325
1366
|
SP114: "error",
|
|
1326
1367
|
SP115: "error",
|
|
1327
|
-
SP200: "
|
|
1368
|
+
SP200: "error",
|
|
1328
1369
|
SP201: "warning",
|
|
1329
1370
|
SP202: "info",
|
|
1330
1371
|
SP203: "warning",
|
|
1372
|
+
SP204: "error",
|
|
1331
1373
|
SP300: "warning",
|
|
1332
1374
|
SP301: "warning",
|
|
1333
1375
|
SP400: "hint",
|
|
@@ -2434,6 +2476,16 @@ function validateVariables(uri, workspace, diagnostics) {
|
|
|
2434
2476
|
`Variable '$${u.name}' is not declared in StoryVariables`
|
|
2435
2477
|
));
|
|
2436
2478
|
}
|
|
2479
|
+
const storyVars = workspace.passages.getStoryVariables();
|
|
2480
|
+
if (storyVars && storyVars.uri === uri) {
|
|
2481
|
+
for (const nd of workspace.variables.getNullDeclarations()) {
|
|
2482
|
+
diagnostics.push(makeDiag(
|
|
2483
|
+
nd.range,
|
|
2484
|
+
DiagnosticCode.NullVariableValue,
|
|
2485
|
+
`Variable '$${nd.name}' is set to null. Spindle does not support null \u2014 use a valid default (number, string, boolean, array, or object).`
|
|
2486
|
+
));
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2437
2489
|
}
|
|
2438
2490
|
if (workspace.variables.hasStoryTransients()) {
|
|
2439
2491
|
const undeclaredTransient = workspace.variables.getUndeclaredTransient(uri);
|
|
@@ -2444,6 +2496,16 @@ function validateVariables(uri, workspace, diagnostics) {
|
|
|
2444
2496
|
`Transient variable '%${u.name}' is not declared in StoryTransients`
|
|
2445
2497
|
));
|
|
2446
2498
|
}
|
|
2499
|
+
const storyTransients = workspace.passages.getStoryTransients();
|
|
2500
|
+
if (storyTransients && storyTransients.uri === uri) {
|
|
2501
|
+
for (const nd of workspace.variables.getNullTransientDeclarations()) {
|
|
2502
|
+
diagnostics.push(makeDiag(
|
|
2503
|
+
nd.range,
|
|
2504
|
+
DiagnosticCode.NullVariableValue,
|
|
2505
|
+
`Transient variable '%${nd.name}' is set to null. Spindle does not support null \u2014 use a valid default (number, string, boolean, array, or object).`
|
|
2506
|
+
));
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2447
2509
|
}
|
|
2448
2510
|
}
|
|
2449
2511
|
function validateLinks(text, passages, passageNames, diagnostics) {
|
|
@@ -3172,6 +3234,50 @@ var init_prettier_bridge = __esm({
|
|
|
3172
3234
|
});
|
|
3173
3235
|
|
|
3174
3236
|
// src/plugins/format/placeholders.ts
|
|
3237
|
+
function scanSpindleTokens(text) {
|
|
3238
|
+
const matches = [];
|
|
3239
|
+
let i = 0;
|
|
3240
|
+
while (i < text.length) {
|
|
3241
|
+
if (text[i] === "[" && text[i + 1] === "[") {
|
|
3242
|
+
const closeIdx = text.indexOf("]]", i + 2);
|
|
3243
|
+
if (closeIdx !== -1) {
|
|
3244
|
+
const end = closeIdx + 2;
|
|
3245
|
+
matches.push({ start: i, end, token: text.slice(i, end) });
|
|
3246
|
+
i = end;
|
|
3247
|
+
continue;
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
if (text[i] === "{" && !(i > 0 && text[i - 1] === "\\")) {
|
|
3251
|
+
const next = i + 1 < text.length ? text[i + 1] : "";
|
|
3252
|
+
let isToken = false;
|
|
3253
|
+
if (next === "/") {
|
|
3254
|
+
isToken = i + 2 < text.length && /[A-Za-z]/.test(text[i + 2]);
|
|
3255
|
+
} else if (next === "#" || next === ".") {
|
|
3256
|
+
isToken = i + 2 < text.length && /[a-zA-Z]/.test(text[i + 2]);
|
|
3257
|
+
} else if (/[A-Za-z]/.test(next)) {
|
|
3258
|
+
isToken = true;
|
|
3259
|
+
} else if (/[$_@%]/.test(next)) {
|
|
3260
|
+
isToken = i + 2 < text.length && /[a-zA-Z]/.test(text[i + 2]);
|
|
3261
|
+
}
|
|
3262
|
+
if (isToken) {
|
|
3263
|
+
let depth = 1;
|
|
3264
|
+
let j = i + 1;
|
|
3265
|
+
while (j < text.length && depth > 0) {
|
|
3266
|
+
if (text[j] === "{") depth++;
|
|
3267
|
+
else if (text[j] === "}") depth--;
|
|
3268
|
+
j++;
|
|
3269
|
+
}
|
|
3270
|
+
if (depth === 0) {
|
|
3271
|
+
matches.push({ start: i, end: j, token: text.slice(i, j) });
|
|
3272
|
+
i = j;
|
|
3273
|
+
continue;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
i++;
|
|
3278
|
+
}
|
|
3279
|
+
return matches;
|
|
3280
|
+
}
|
|
3175
3281
|
function isInsideAttribute(text, pos) {
|
|
3176
3282
|
let inQuote = false;
|
|
3177
3283
|
let quoteChar = "";
|
|
@@ -3208,13 +3314,15 @@ function replaceSpindleTokens(html) {
|
|
|
3208
3314
|
const resultLines = [];
|
|
3209
3315
|
for (const line of lines) {
|
|
3210
3316
|
const trimmed = line.trim();
|
|
3211
|
-
|
|
3212
|
-
if (
|
|
3317
|
+
const found = scanSpindleTokens(trimmed);
|
|
3318
|
+
if (found.length === 0) {
|
|
3213
3319
|
resultLines.push(line);
|
|
3214
3320
|
continue;
|
|
3215
3321
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3322
|
+
let withoutSpindle = trimmed;
|
|
3323
|
+
for (let fi = found.length - 1; fi >= 0; fi--) {
|
|
3324
|
+
withoutSpindle = withoutSpindle.slice(0, found[fi].start) + withoutSpindle.slice(found[fi].end);
|
|
3325
|
+
}
|
|
3218
3326
|
const hasHtml = HTML_TAG_REGEX.test(withoutSpindle);
|
|
3219
3327
|
if (!hasHtml) {
|
|
3220
3328
|
const idx = tokens.length;
|
|
@@ -3222,16 +3330,20 @@ function replaceSpindleTokens(html) {
|
|
|
3222
3330
|
const indent = line.match(/^(\s*)/)?.[1] ?? "";
|
|
3223
3331
|
resultLines.push(`${indent}<!--SP:${idx}-->`);
|
|
3224
3332
|
} else {
|
|
3225
|
-
|
|
3226
|
-
const
|
|
3227
|
-
|
|
3333
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? "";
|
|
3334
|
+
const indentLen = indent.length;
|
|
3335
|
+
const assignments = found.map((t) => {
|
|
3336
|
+
const lineStart = t.start + indentLen;
|
|
3228
3337
|
const idx = tokens.length;
|
|
3229
|
-
tokens.push(
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
}
|
|
3233
|
-
return `<!--SP:${idx}-->`;
|
|
3338
|
+
tokens.push(t.token);
|
|
3339
|
+
const placeholder = isInsideAttribute(line, lineStart) ? `__SP${idx}__` : `<!--SP:${idx}-->`;
|
|
3340
|
+
return { start: lineStart, end: t.end + indentLen, placeholder };
|
|
3234
3341
|
});
|
|
3342
|
+
let replaced = line;
|
|
3343
|
+
for (let ai = assignments.length - 1; ai >= 0; ai--) {
|
|
3344
|
+
const a = assignments[ai];
|
|
3345
|
+
replaced = replaced.slice(0, a.start) + a.placeholder + replaced.slice(a.end);
|
|
3346
|
+
}
|
|
3235
3347
|
resultLines.push(replaced);
|
|
3236
3348
|
}
|
|
3237
3349
|
}
|
|
@@ -3245,11 +3357,10 @@ function restoreSpindleTokens(text, tokens) {
|
|
|
3245
3357
|
}
|
|
3246
3358
|
return result;
|
|
3247
3359
|
}
|
|
3248
|
-
var
|
|
3360
|
+
var HTML_TAG_REGEX;
|
|
3249
3361
|
var init_placeholders = __esm({
|
|
3250
3362
|
"src/plugins/format/placeholders.ts"() {
|
|
3251
3363
|
"use strict";
|
|
3252
|
-
SPINDLE_TOKEN_REGEX = /(?:\{\/[A-Za-z][\w-]*\s*\}|\{(?:[#.][a-zA-Z][\w-]*\s*)*[A-Za-z][\w-]*(?:\s+(?:[^{}]|\{[^{}]*\})*)?\}|\{[$_@][a-zA-Z][\w.]*\}|\[\[(?:[^\]]*)\]\])/g;
|
|
3253
3364
|
HTML_TAG_REGEX = /<\/?[a-zA-Z][\w-]*[\s>/]/;
|
|
3254
3365
|
}
|
|
3255
3366
|
});
|
|
@@ -5338,6 +5449,11 @@ function computeCodeActions(uri, diagnostics, workspace) {
|
|
|
5338
5449
|
if (action) actions.push(action);
|
|
5339
5450
|
break;
|
|
5340
5451
|
}
|
|
5452
|
+
case DiagnosticCode.NullVariableValue: {
|
|
5453
|
+
const action = fixNullVariableValue(uri, diag);
|
|
5454
|
+
if (action) actions.push(action);
|
|
5455
|
+
break;
|
|
5456
|
+
}
|
|
5341
5457
|
}
|
|
5342
5458
|
}
|
|
5343
5459
|
return actions;
|
|
@@ -5394,7 +5510,7 @@ function fixUndeclaredVariable(diag, workspace) {
|
|
|
5394
5510
|
start: { line: insertLine, character: 0 },
|
|
5395
5511
|
end: { line: insertLine, character: 0 }
|
|
5396
5512
|
},
|
|
5397
|
-
newText: `$${varName} =
|
|
5513
|
+
newText: `$${varName} = 0
|
|
5398
5514
|
`
|
|
5399
5515
|
}]
|
|
5400
5516
|
};
|
|
@@ -5449,11 +5565,23 @@ function fixUndeclaredTransient(diag, workspace) {
|
|
|
5449
5565
|
start: { line: insertLine, character: 0 },
|
|
5450
5566
|
end: { line: insertLine, character: 0 }
|
|
5451
5567
|
},
|
|
5452
|
-
newText: `%${varName} =
|
|
5568
|
+
newText: `%${varName} = 0
|
|
5453
5569
|
`
|
|
5454
5570
|
}]
|
|
5455
5571
|
};
|
|
5456
5572
|
}
|
|
5573
|
+
function fixNullVariableValue(uri, diag) {
|
|
5574
|
+
return {
|
|
5575
|
+
title: "Replace null with 0",
|
|
5576
|
+
kind: "quickfix",
|
|
5577
|
+
diagnosticCodes: [DiagnosticCode.NullVariableValue],
|
|
5578
|
+
edits: [{
|
|
5579
|
+
uri,
|
|
5580
|
+
range: diag.range,
|
|
5581
|
+
newText: "0"
|
|
5582
|
+
}]
|
|
5583
|
+
};
|
|
5584
|
+
}
|
|
5457
5585
|
function toLspRange6(r) {
|
|
5458
5586
|
return {
|
|
5459
5587
|
start: { line: r.start.line, character: r.start.character },
|