@silverbulletmd/silverbullet 2.9.0 → 2.11.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.
Files changed (123) hide show
  1. package/README.md +38 -25
  2. package/client/asset_bundle/bundle.ts +0 -1
  3. package/client/config.ts +343 -0
  4. package/client/markdown_parser/constants.ts +3 -1
  5. package/client/plugos/hooks/command.ts +0 -5
  6. package/client/plugos/hooks/event.ts +0 -6
  7. package/client/plugos/hooks/mq.ts +0 -3
  8. package/client/plugos/hooks/slash_command.ts +1 -7
  9. package/client/plugos/hooks/syscall.ts +24 -14
  10. package/client/plugos/manifest_cache.ts +0 -32
  11. package/client/plugos/plug.ts +0 -3
  12. package/client/plugos/plug_compile.ts +3 -13
  13. package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
  14. package/client/plugos/syscalls/asset.ts +61 -23
  15. package/client/plugos/syscalls/clientStore.ts +32 -6
  16. package/client/plugos/syscalls/client_code_widget.ts +9 -4
  17. package/client/plugos/syscalls/code_widget.ts +26 -11
  18. package/client/plugos/syscalls/config.ts +146 -30
  19. package/client/plugos/syscalls/datastore.ts +146 -40
  20. package/client/plugos/syscalls/editor.ts +1518 -722
  21. package/client/plugos/syscalls/event.ts +44 -13
  22. package/client/plugos/syscalls/fetch.ts +120 -83
  23. package/client/plugos/syscalls/icon.ts +46 -0
  24. package/client/plugos/syscalls/index.ts +267 -121
  25. package/client/plugos/syscalls/jsonschema.ts +107 -9
  26. package/client/plugos/syscalls/language.ts +38 -12
  27. package/client/plugos/syscalls/markdown.ts +166 -50
  28. package/client/plugos/syscalls/mq.ts +92 -20
  29. package/client/plugos/syscalls/navigator.ts +143 -0
  30. package/client/plugos/syscalls/schema_introspection.ts +33 -0
  31. package/client/plugos/syscalls/search.ts +44 -0
  32. package/client/plugos/syscalls/service_registry.ts +74 -20
  33. package/client/plugos/syscalls/shell.ts +51 -21
  34. package/client/plugos/syscalls/space.ts +412 -113
  35. package/client/plugos/syscalls/sync.ts +103 -20
  36. package/client/plugos/syscalls/system.ts +274 -139
  37. package/client/plugos/system.ts +28 -10
  38. package/client/plugos/worker_runtime.ts +0 -2
  39. package/client/space_lua/aggregates.ts +0 -16
  40. package/client/space_lua/api_documentation.ts +107 -0
  41. package/client/space_lua/ast.ts +13 -5
  42. package/client/space_lua/ast_narrow.ts +0 -5
  43. package/client/space_lua/budget.ts +113 -0
  44. package/client/space_lua/budget_ui.ts +49 -0
  45. package/client/space_lua/eval.ts +125 -92
  46. package/client/space_lua/labels.ts +0 -4
  47. package/client/space_lua/numeric.ts +0 -5
  48. package/client/space_lua/parse.ts +243 -125
  49. package/client/space_lua/pretty_print.ts +128 -54
  50. package/client/space_lua/quarantine.ts +127 -0
  51. package/client/space_lua/query_collection.ts +7 -51
  52. package/client/space_lua/query_env.ts +0 -2
  53. package/client/space_lua/render_lua_markdown.ts +2 -7
  54. package/client/space_lua/render_widget.ts +173 -0
  55. package/client/space_lua/runtime.ts +133 -92
  56. package/client/space_lua/stdlib/crypto.ts +8 -3
  57. package/client/space_lua/stdlib/encoding.ts +27 -9
  58. package/client/space_lua/stdlib/format.ts +0 -12
  59. package/client/space_lua/stdlib/js.ts +136 -23
  60. package/client/space_lua/stdlib/load.ts +24 -16
  61. package/client/space_lua/stdlib/math.ts +331 -133
  62. package/client/space_lua/stdlib/net.ts +57 -13
  63. package/client/space_lua/stdlib/os.ts +111 -54
  64. package/client/space_lua/stdlib/pattern.ts +10 -15
  65. package/client/space_lua/stdlib/space_lua.ts +344 -21
  66. package/client/space_lua/stdlib/string.ts +282 -104
  67. package/client/space_lua/stdlib/string_pack.ts +58 -22
  68. package/client/space_lua/stdlib/table.ts +195 -45
  69. package/client/space_lua/stdlib.ts +354 -125
  70. package/client/space_lua/syscalls.ts +270 -0
  71. package/client/space_lua/tonumber.ts +0 -9
  72. package/dist/plug-compile.js +2 -3
  73. package/package.json +15 -5
  74. package/plug-api/lib/async.ts +1 -6
  75. package/plug-api/lib/collation.ts +22 -0
  76. package/plug-api/lib/crypto.ts +16 -15
  77. package/plug-api/lib/dates.ts +33 -0
  78. package/plug-api/lib/fuzzy.ts +282 -0
  79. package/plug-api/lib/json.ts +0 -6
  80. package/plug-api/lib/limited_map.ts +0 -2
  81. package/plug-api/lib/link_write.ts +27 -0
  82. package/plug-api/{ui → lib}/panel_styles.ts +4 -3
  83. package/plug-api/lib/ref.ts +124 -5
  84. package/plug-api/lib/resolve.ts +0 -1
  85. package/plug-api/lib/resolve_path.ts +290 -0
  86. package/plug-api/lib/shortcut.ts +17 -1
  87. package/plug-api/lib/tags.ts +0 -3
  88. package/plug-api/lib/transclusion.ts +28 -1
  89. package/plug-api/lib/tree.ts +0 -5
  90. package/plug-api/lib/yaml.ts +3 -42
  91. package/plug-api/syscall.ts +1 -5
  92. package/plug-api/syscalls/config.ts +7 -5
  93. package/plug-api/syscalls/editor.ts +70 -208
  94. package/plug-api/syscalls/icon.ts +18 -0
  95. package/plug-api/syscalls/index.ts +39 -5
  96. package/plug-api/syscalls/jsonschema.ts +9 -0
  97. package/plug-api/syscalls/lua.ts +7 -0
  98. package/plug-api/syscalls/search.ts +11 -0
  99. package/plug-api/syscalls/shell.ts +0 -1
  100. package/plug-api/syscalls/space.ts +122 -5
  101. package/plug-api/syscalls/sync.ts +23 -2
  102. package/plug-api/syscalls/system.ts +53 -0
  103. package/plug-api/syscalls.ts +2 -0
  104. package/plug-api/system_mock.ts +15 -2
  105. package/plug-api/types/client.ts +4 -3
  106. package/plug-api/types/datastore.ts +11 -2
  107. package/plug-api/types/index.ts +62 -1
  108. package/plug-api/types/manifest.ts +22 -4
  109. package/plug-api/types/profile.ts +13 -0
  110. package/plug-api/types/revisions.ts +67 -0
  111. package/plug-api/ui/cx.ts +1 -3
  112. package/plug-api/ui/description.ts +64 -0
  113. package/plug-api/ui/hover.ts +69 -0
  114. package/plug-api/ui/index.ts +61 -2
  115. package/plug-api/ui/scroll.ts +30 -0
  116. package/plug-api/ui/slugify.ts +44 -0
  117. package/plug-api/ui/tree_model.ts +263 -0
  118. package/plug-api/ui/tree_types.ts +66 -0
  119. package/plug-api/ui/use_fit_collapse.ts +67 -0
  120. package/plugs/builtin_plugs.ts +0 -1
  121. package/plugs/index/types.ts +9 -0
  122. package/client/plugos/syscalls/lua.ts +0 -81
  123. package/plug-api/lib/memory_cache.ts +0 -21
@@ -35,7 +35,6 @@ class LabelResolveError extends Error {
35
35
  }
36
36
  }
37
37
 
38
- // Cache
39
38
  const functionMetaByRoot = new WeakMap<LuaBlock, FunctionMeta>();
40
39
  const functionMetaByAnyBlock = new WeakMap<LuaBlock, FunctionMeta>();
41
40
 
@@ -107,11 +106,9 @@ function resolveFunction(root: LuaBlock): FunctionMeta {
107
106
  roleByBlock,
108
107
  );
109
108
 
110
- // Validate gotos
111
109
  for (const g of vctx.gotos) {
112
110
  const target = g.node.name;
113
111
 
114
- // Search current block for the label, then ancestors
115
112
  let searchBlock: LuaBlock | undefined = g.block;
116
113
  let labelIndex: number | undefined;
117
114
  let labelDefBlock: LuaBlock | undefined;
@@ -151,7 +148,6 @@ function resolveFunction(root: LuaBlock): FunctionMeta {
151
148
  );
152
149
  }
153
150
 
154
- // Local scope forward jump check
155
151
  let entersLocalScope = false;
156
152
  let entersCloseScope = false;
157
153
 
@@ -7,7 +7,6 @@ export interface LuaTaggedFloat {
7
7
  readonly isFloat: true;
8
8
  }
9
9
 
10
- // Pre-allocated singletons for float zeros
11
10
  const FLOAT_POS_ZERO: LuaTaggedFloat = { value: 0, isFloat: true };
12
11
  const FLOAT_NEG_ZERO: LuaTaggedFloat = { value: -0, isFloat: true };
13
12
 
@@ -159,7 +158,6 @@ export function coerceNumericPair(
159
158
  ): CoerceNumericPairResult {
160
159
  const forceFloat = op === "/" || op === "^";
161
160
 
162
- // Both plain numbers
163
161
  if (typeof a === "number" && typeof b === "number") {
164
162
  const lt = leftType ?? inferNumericType(a);
165
163
  const rt = rightType ?? inferNumericType(b);
@@ -175,7 +173,6 @@ export function coerceNumericPair(
175
173
  };
176
174
  }
177
175
 
178
- // One tagged float, one plain number
179
176
  if (typeof a === "number" && isTaggedFloat(b)) {
180
177
  return {
181
178
  left: a,
@@ -192,7 +189,6 @@ export function coerceNumericPair(
192
189
  };
193
190
  }
194
191
 
195
- // Both tagged floats
196
192
  if (isTaggedFloat(a) && isTaggedFloat(b)) {
197
193
  return {
198
194
  left: a.value,
@@ -201,7 +197,6 @@ export function coerceNumericPair(
201
197
  };
202
198
  }
203
199
 
204
- // General fallback
205
200
  const A = coerceNumeric(a, leftType);
206
201
  const B = coerceNumeric(b, rightType);
207
202
 
@@ -1,8 +1,8 @@
1
1
  import { lezerToParseTree } from "../../client/markdown_parser/parse_tree.ts";
2
2
  import type { SyntaxNode } from "@lezer/common";
3
3
  import {
4
- cleanTree,
5
4
  type ParseTree,
5
+ renderToText,
6
6
  } from "@silverbulletmd/silverbullet/lib/tree";
7
7
  // @ts-expect-error - Local generated JavaScript file without type definitions
8
8
  import { parser } from "./parse-lua.js";
@@ -12,6 +12,7 @@ import type {
12
12
  ASTCtx,
13
13
  LuaAttName,
14
14
  LuaBlock,
15
+ LuaComment,
15
16
  LuaExpression,
16
17
  LuaFunctionBody,
17
18
  LuaFunctionCallExpression,
@@ -25,6 +26,7 @@ import type {
25
26
  LuaStatement,
26
27
  LuaTableField,
27
28
  } from "./ast.ts";
29
+ import type { LuaFunctionDocumentation } from "../../plug-api/types/index.ts";
28
30
  import { LuaAttribute } from "./ast.ts";
29
31
  import { getBlockGotoMeta } from "./labels.ts";
30
32
  import { LuaRuntimeError, LuaStackFrame } from "./runtime.ts";
@@ -48,7 +50,6 @@ const customIndent = indentNodeProp.add({
48
50
  },
49
51
  });
50
52
 
51
- // Use the customIndent in your language support
52
53
  export const luaLanguage = LRLanguage.define({
53
54
  name: "space-lua",
54
55
  parser: parser.configure({
@@ -67,7 +68,217 @@ function parseChunk(t: ParseTree, ctx: ASTCtx): LuaBlock {
67
68
  if (t.type !== "Chunk") {
68
69
  throw new Error(`Expected Chunk, got ${t.type}`);
69
70
  }
70
- return parseBlockNode(t.children![0], ctx);
71
+ const block = t.children?.find((child) => child.type === "Block");
72
+ if (!block) {
73
+ return { type: "Block", statements: [], ctx: context(t, ctx) };
74
+ }
75
+ return parseBlockNode(block, ctx);
76
+ }
77
+
78
+ function collectLuaComments(t: ParseTree, ctx: ASTCtx): LuaComment[] {
79
+ const comments: LuaComment[] = [];
80
+ const visit = (node: ParseTree) => {
81
+ if (node.type === "Comment") {
82
+ const text = renderToText(node);
83
+ comments.push({
84
+ type: "Comment",
85
+ text,
86
+ kind: /^--\[(=*)\[/.test(text) ? "long" : "line",
87
+ ctx: context(node, ctx),
88
+ });
89
+ return;
90
+ }
91
+ for (const child of node.children ?? []) visit(child);
92
+ };
93
+ visit(t);
94
+ return comments.sort((a, b) => (a.ctx.from ?? 0) - (b.ctx.from ?? 0));
95
+ }
96
+
97
+ function withoutComments(t: ParseTree): ParseTree {
98
+ if (!t.children) return t;
99
+ return {
100
+ ...t,
101
+ children: t.children
102
+ .filter((child) => child.type !== "Comment")
103
+ .map(withoutComments),
104
+ };
105
+ }
106
+
107
+ function assignCommentsToBlocks(
108
+ root: LuaBlock,
109
+ comments: LuaComment[],
110
+ source: string,
111
+ ): void {
112
+ const blocks: LuaBlock[] = [];
113
+ const seen = new WeakSet<object>();
114
+ const visit = (value: unknown, key?: string) => {
115
+ if (!value || typeof value !== "object" || key === "ctx") return;
116
+ if (seen.has(value)) return;
117
+ seen.add(value);
118
+ if ((value as { type?: string }).type === "Block") {
119
+ blocks.push(value as LuaBlock);
120
+ }
121
+ for (const [childKey, child] of Object.entries(value)) {
122
+ if (
123
+ childKey !== "ctx" &&
124
+ childKey !== "comments" &&
125
+ childKey !== "documentation"
126
+ ) {
127
+ visit(child, childKey);
128
+ }
129
+ }
130
+ };
131
+ visit(root);
132
+
133
+ for (const comment of comments) {
134
+ const from = comment.ctx.from ?? 0;
135
+ const to = comment.ctx.to ?? from;
136
+ let owner = root;
137
+ let ownerSpan = Number.POSITIVE_INFINITY;
138
+ for (const block of blocks) {
139
+ if (block === root) continue;
140
+ const blockFrom = block.ctx.from;
141
+ const blockTo = block.ctx.to;
142
+ if (blockFrom === undefined || blockTo === undefined) continue;
143
+ const contains = blockFrom <= from && blockTo >= to;
144
+ const immediatelyBefore =
145
+ to <= blockFrom && !source.slice(to, blockFrom).trim();
146
+ const immediatelyAfter =
147
+ blockTo <= from && !source.slice(blockTo, from).trim();
148
+ if (
149
+ (contains || immediatelyBefore || immediatelyAfter) &&
150
+ blockTo - blockFrom < ownerSpan
151
+ ) {
152
+ owner = block;
153
+ ownerSpan = blockTo - blockFrom;
154
+ }
155
+ }
156
+ (owner.comments ??= []).push(comment);
157
+ }
158
+ }
159
+
160
+ function parseFunctionDocumentation(
161
+ comments: LuaComment[],
162
+ ): LuaFunctionDocumentation | undefined {
163
+ const description: string[] = [];
164
+ const parameters: NonNullable<LuaFunctionDocumentation["parameters"]> = [];
165
+ const returns: NonNullable<LuaFunctionDocumentation["returns"]> = [];
166
+ let deprecated: string | boolean | undefined;
167
+ let see: string | undefined;
168
+
169
+ for (const comment of comments) {
170
+ const line = comment.text.slice(3).replace(/^\s?/, "");
171
+ const param = /^@param\s+(\S+)\s+(\S+)(?:\s+(.*))?$/.exec(line);
172
+ if (param) {
173
+ const optional = param[1].endsWith("?");
174
+ parameters.push({
175
+ name: optional ? param[1].slice(0, -1) : param[1],
176
+ type: param[2],
177
+ description: param[3] || undefined,
178
+ optional,
179
+ });
180
+ continue;
181
+ }
182
+ const returnDoc = /^@return\s+(\S+)(?:\s+(.*))?$/.exec(line);
183
+ if (returnDoc) {
184
+ returns.push({
185
+ type: returnDoc[1],
186
+ description: returnDoc[2] || undefined,
187
+ });
188
+ continue;
189
+ }
190
+ const deprecatedDoc = /^@deprecated(?:\s+(.*))?$/.exec(line);
191
+ if (deprecatedDoc) {
192
+ deprecated = deprecatedDoc[1] || true;
193
+ continue;
194
+ }
195
+ const seeDoc = /^@see\s+(.+)$/.exec(line);
196
+ if (seeDoc) {
197
+ see = seeDoc[1];
198
+ continue;
199
+ }
200
+ if (!line.startsWith("@")) description.push(line);
201
+ }
202
+
203
+ const docs: LuaFunctionDocumentation = {};
204
+ const descriptionText = description.join("\n").trim();
205
+ if (descriptionText) docs.description = descriptionText;
206
+ if (parameters.length) docs.parameters = parameters;
207
+ if (returns.length) docs.returns = returns;
208
+ if (deprecated !== undefined) docs.deprecated = deprecated;
209
+ if (see) docs.see = see;
210
+ return Object.keys(docs).length ? docs : undefined;
211
+ }
212
+
213
+ function attachFunctionDocumentation(
214
+ root: LuaBlock,
215
+ comments: LuaComment[],
216
+ source: string,
217
+ ): void {
218
+ const candidates: { from: number; body: LuaFunctionBody }[] = [];
219
+ const seen = new WeakSet<object>();
220
+ const claimedBodies = new WeakSet<LuaFunctionBody>();
221
+
222
+ const addCandidate = (from: number | undefined, body: LuaFunctionBody) => {
223
+ if (from === undefined || claimedBodies.has(body)) return;
224
+ claimedBodies.add(body);
225
+ candidates.push({ from, body });
226
+ };
227
+
228
+ const visit = (value: unknown, key?: string) => {
229
+ if (!value || typeof value !== "object" || key === "ctx") return;
230
+ if (seen.has(value)) return;
231
+ seen.add(value);
232
+ const node = value as Record<string, any>;
233
+ if (node.type === "Function" || node.type === "LocalFunction") {
234
+ addCandidate(node.ctx?.from, node.body);
235
+ } else if (
236
+ (node.type === "Local" || node.type === "Assignment") &&
237
+ node.expressions?.length === 1 &&
238
+ node.expressions[0]?.type === "FunctionDefinition"
239
+ ) {
240
+ addCandidate(node.ctx?.from, node.expressions[0].body);
241
+ } else if (node.type === "FunctionDefinition") {
242
+ addCandidate(node.ctx?.from, node.body);
243
+ }
244
+ for (const [childKey, child] of Object.entries(node)) {
245
+ if (
246
+ childKey !== "ctx" &&
247
+ childKey !== "comments" &&
248
+ childKey !== "documentation"
249
+ ) {
250
+ visit(child, childKey);
251
+ }
252
+ }
253
+ };
254
+ visit(root);
255
+
256
+ const docComments = comments.filter(
257
+ (comment) => comment.kind === "line" && comment.text.startsWith("---"),
258
+ );
259
+ for (const candidate of candidates) {
260
+ const group: LuaComment[] = [];
261
+ let boundary = candidate.from;
262
+ for (let i = docComments.length - 1; i >= 0; i--) {
263
+ const comment = docComments[i];
264
+ const from = comment.ctx.from ?? 0;
265
+ const to = comment.ctx.to ?? from;
266
+ if (to > boundary) continue;
267
+ const gap = source.slice(to, boundary);
268
+ const lineStart = source.lastIndexOf("\n", from - 1) + 1;
269
+ if (
270
+ gap.trim() ||
271
+ /\r?\n[ \t]*\r?\n/.test(gap) ||
272
+ source.slice(lineStart, from).trim()
273
+ ) {
274
+ break;
275
+ }
276
+ group.unshift(comment);
277
+ boundary = from;
278
+ }
279
+ const documentation = parseFunctionDocumentation(group);
280
+ if (documentation) candidate.body.documentation = documentation;
281
+ }
71
282
  }
72
283
 
73
284
  function hasCloseLocal(names: LuaAttName[] | undefined): boolean {
@@ -335,7 +546,6 @@ function functionBodyCapturesNames(
335
546
  return blockReferencesNames(body.block, check);
336
547
  }
337
548
 
338
- // Walk block using `exprReferencesNames` (inside a function body).
339
549
  function blockReferencesNames(block: LuaBlock, names: Set<string>): boolean {
340
550
  for (let i = 0; i < block.statements.length; i++) {
341
551
  if (statementReferencesNames(block.statements[i], names)) return true;
@@ -438,7 +648,6 @@ function statementReferencesNames(
438
648
  }
439
649
  }
440
650
 
441
- // Walk block looking for `FunctionDefinition` nodes that capture `names`.
442
651
  function blockCapturesNames(block: LuaBlock, names: Set<string>): boolean {
443
652
  for (let i = 0; i < block.statements.length; i++) {
444
653
  if (statementCapturesNames(block.statements[i], names)) return true;
@@ -531,8 +740,6 @@ function statementCapturesNames(s: LuaStatement, names: Set<string>): boolean {
531
740
  }
532
741
  }
533
742
 
534
- // At loop block level find `FunctionDefinition` and check if it
535
- // captures `names`.
536
743
  function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
537
744
  if (!e) return false;
538
745
  switch (e.type) {
@@ -659,7 +866,6 @@ function parseBlockNode(t: ParseTree, ctx: ASTCtx): LuaBlock {
659
866
  case "Label": {
660
867
  hasLabel = true;
661
868
  hasLabelHere = true;
662
- // Duplicate labels in the same block are illegal
663
869
  const name = (s as any).name as string;
664
870
  if (!dup) {
665
871
  if (seen.has(name)) {
@@ -995,7 +1201,6 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
995
1201
  case "break":
996
1202
  return { type: "Break", ctx: context(t, ctx) };
997
1203
  default:
998
- // Gracefully ignore unknown empty nodes
999
1204
  if (!t.children || t.children.length === 0) {
1000
1205
  return {
1001
1206
  type: "Semicolon",
@@ -1151,10 +1356,7 @@ function parseExpList(t: ParseTree, ctx: ASTCtx): LuaExpression[] {
1151
1356
 
1152
1357
  const delimiterRegex = /^(\[=*\[)([\s\S]*)(\]=*\])$/;
1153
1358
 
1154
- // In case of quoted strings, remove the quotes and unescape the string
1155
- // In case of a [[ type ]] literal string, remove the brackets
1156
1359
  function parseString(s: string): string {
1157
- // Handle long strings with delimiters
1158
1360
  const delimiterMatch = s.match(delimiterRegex);
1159
1361
  if (delimiterMatch) {
1160
1362
  let text = delimiterMatch[2];
@@ -1191,11 +1393,9 @@ function parseString(s: string): string {
1191
1393
  case "'":
1192
1394
  return "'"; // Single quote
1193
1395
  default:
1194
- // Handle hexadecimal \x00
1195
1396
  if (capture.startsWith("x")) {
1196
1397
  return String.fromCharCode(parseInt(capture.slice(1), 16));
1197
1398
  }
1198
- // Handle unicode \u{XXXX}
1199
1399
  if (capture.startsWith("u{")) {
1200
1400
  const codePoint = parseInt(capture.slice(2, -1), 16);
1201
1401
  return String.fromCodePoint(codePoint);
@@ -1223,7 +1423,6 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
1223
1423
  const text = t.children![0].text!.toLowerCase();
1224
1424
  return {
1225
1425
  type: "Number",
1226
- // Use the integer parser fox 0x literals
1227
1426
  // biome-ignore lint/correctness/useParseIntRadix: hex strings need auto-detect radix
1228
1427
  value: text.includes("x") ? parseInt(text) : parseFloat(text),
1229
1428
  numericType: /[.eEpP]/.test(text) ? "float" : "int",
@@ -1269,7 +1468,11 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
1269
1468
  };
1270
1469
 
1271
1470
  case "Parens":
1272
- return parseExpression(t.children![1], ctx);
1471
+ return {
1472
+ type: "Parenthesized",
1473
+ expression: parseExpression(t.children![1], ctx),
1474
+ ctx: context(t, ctx),
1475
+ };
1273
1476
  case "FunctionCall": {
1274
1477
  return parseFunctionCall(t, ctx);
1275
1478
  }
@@ -1435,7 +1638,6 @@ function parseQueryClause(t: ParseTree, ctx: ASTCtx): LuaQueryClause {
1435
1638
  }
1436
1639
  }
1437
1640
 
1438
- // Parse a single OrderBy node (shared by query OrderByClause and AggOrderBy)
1439
1641
  function parseOrderByNode(child: ParseTree, ctx: ASTCtx): LuaOrderBy {
1440
1642
  const kids = child.children!;
1441
1643
  let direction: "asc" | "desc" = "asc";
@@ -1469,7 +1671,6 @@ function parseOrderByNode(child: ParseTree, ctx: ASTCtx): LuaOrderBy {
1469
1671
  return ob;
1470
1672
  }
1471
1673
 
1472
- // Parse an AggOrderBy node into LuaOrderBy[]
1473
1674
  function parseAggOrderBy(t: ParseTree, ctx: ASTCtx): LuaOrderBy[] {
1474
1675
  if (t.type !== "AggOrderBy") {
1475
1676
  throw new Error(`Expected AggOrderBy, got ${t.type}`);
@@ -1483,7 +1684,6 @@ function parseAggOrderBy(t: ParseTree, ctx: ASTCtx): LuaOrderBy[] {
1483
1684
  return orderBy;
1484
1685
  }
1485
1686
 
1486
- // Parse function args, extracting AggOrderBy if present inside funcParams
1487
1687
  function parseFunctionArgsWithOrderBy(
1488
1688
  ts: ParseTree[],
1489
1689
  ctx: ASTCtx,
@@ -1585,113 +1785,13 @@ function parseTableField(t: ParseTree, ctx: ASTCtx): LuaTableField {
1585
1785
  }
1586
1786
  }
1587
1787
 
1588
- export function stripLuaComments(s: string): string {
1589
- let result = "";
1590
- let i = 0;
1591
-
1592
- while (i < s.length) {
1593
- // Check for long string
1594
- if (s[i] === "[") {
1595
- let j = i + 1;
1596
- let equalsCount = 0;
1597
- while (s[j] === "=") {
1598
- equalsCount++;
1599
- j++;
1600
- }
1601
- if (s[j] === "[") {
1602
- // Found long string start
1603
- const openBracket = s.substring(i, j + 1);
1604
- const closeBracket = `]${"=".repeat(equalsCount)}]`;
1605
- result += openBracket;
1606
- i = j + 1;
1607
-
1608
- // Find matching closing bracket
1609
- const content = s.substring(i);
1610
- const closeIndex = content.indexOf(closeBracket);
1611
- if (closeIndex !== -1) {
1612
- // Copy string content verbatim, including any comment-like sequences
1613
- result += content.substring(0, closeIndex) + closeBracket;
1614
- i += closeIndex + closeBracket.length;
1615
- continue;
1616
- }
1617
- }
1618
- }
1619
-
1620
- // Check for single quoted string
1621
- if (s[i] === '"' || s[i] === "'") {
1622
- const quote = s[i];
1623
- result += quote;
1624
- i++;
1625
- while (i < s.length && s[i] !== quote) {
1626
- if (s[i] === "\\") {
1627
- result += s[i] + s[i + 1];
1628
- i += 2;
1629
- } else {
1630
- result += s[i];
1631
- i++;
1632
- }
1633
- }
1634
- if (i < s.length) {
1635
- result += s[i]; // closing quote
1636
- i++;
1637
- }
1638
- continue;
1639
- }
1640
-
1641
- // Check for comments
1642
- if (s[i] === "-" && s[i + 1] === "-") {
1643
- // Replace the -- with spaces
1644
- result += " ";
1645
- i += 2;
1646
-
1647
- // Check for long comment
1648
- if (s[i] === "[") {
1649
- let j = i + 1;
1650
- let equalsCount = 0;
1651
- while (s[j] === "=") {
1652
- equalsCount++;
1653
- j++;
1654
- }
1655
- if (s[j] === "[") {
1656
- // Found long comment start
1657
- const closeBracket = `]${"=".repeat(equalsCount)}]`;
1658
- // Replace opening bracket with spaces
1659
- result += " ".repeat(j - i + 1);
1660
- i = j + 1;
1661
-
1662
- // Find matching closing bracket
1663
- const content = s.substring(i);
1664
- const closeIndex = content.indexOf(closeBracket);
1665
- if (closeIndex !== -1) {
1666
- // Replace comment content and closing bracket with spaces
1667
- result += " ".repeat(closeIndex) + " ".repeat(closeBracket.length);
1668
- i += closeIndex + closeBracket.length;
1669
- continue;
1670
- }
1671
- }
1672
- }
1673
-
1674
- // Single line comment - replace rest of line with spaces
1675
- while (i < s.length && s[i] !== "\n") {
1676
- result += " ";
1677
- i++;
1678
- }
1679
- continue;
1680
- }
1681
-
1682
- result += s[i];
1683
- i++;
1684
- }
1685
-
1686
- return result;
1687
- }
1688
-
1689
1788
  export function parseBlock(s: string, ctx: ASTCtx = {}): LuaBlock {
1690
1789
  try {
1691
- const t = parseToAST(stripLuaComments(s));
1692
- // console.log("Clean tree", JSON.stringify(t, null, 2));
1693
- const result = parseChunk(t, ctx);
1694
- // console.log("Parsed AST", JSON.stringify(result, null, 2));
1790
+ const concreteTree = parseToAST(s);
1791
+ const comments = collectLuaComments(concreteTree, ctx);
1792
+ const result = parseChunk(withoutComments(concreteTree), ctx);
1793
+ assignCommentsToBlocks(result, comments, s);
1794
+ attachFunctionDocumentation(result, comments, s);
1695
1795
  getBlockGotoMeta(result);
1696
1796
  return result;
1697
1797
  } catch (e: any) {
@@ -1716,7 +1816,25 @@ export function parseToAST(t: string): ParseTree {
1716
1816
  }
1717
1817
 
1718
1818
  const n = lezerToParseTree(t, tree.topNode);
1719
- return cleanTree(n, true);
1819
+ return cleanLuaTree(n);
1820
+ }
1821
+
1822
+ function cleanLuaTree(tree: ParseTree): ParseTree {
1823
+ if (tree.type === "⚠") {
1824
+ throw new Error(`Parse error at pos ${tree.from}`);
1825
+ }
1826
+ if (tree.text !== undefined) return tree;
1827
+ const result: ParseTree = {
1828
+ type: tree.type,
1829
+ children: [],
1830
+ from: tree.from,
1831
+ to: tree.to,
1832
+ };
1833
+ for (const node of tree.children ?? []) {
1834
+ if (node.type) result.children!.push(cleanLuaTree(node));
1835
+ if (node.text?.trim()) result.children!.push(node);
1836
+ }
1837
+ return result;
1720
1838
  }
1721
1839
 
1722
1840
  function findFirstParseError(node: SyntaxNode): SyntaxNode | null {