@deckbuilderkit/editor 0.0.0-stage → 0.1.1

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 (56) hide show
  1. package/package.json +53 -4
  2. package/src/ArchiveView.tsx +111 -0
  3. package/src/CodeEditor.tsx +265 -0
  4. package/src/CodeView.tsx +67 -0
  5. package/src/DiffView.tsx +139 -0
  6. package/src/EnvView.tsx +42 -0
  7. package/src/FilePreview.tsx +108 -0
  8. package/src/FontView.tsx +48 -0
  9. package/src/HarView.tsx +70 -0
  10. package/src/HexView.tsx +28 -0
  11. package/src/HtmlPreview.tsx +23 -0
  12. package/src/MarkdownEditor.tsx +90 -0
  13. package/src/MarkdownToc.tsx +135 -0
  14. package/src/MarkdownView.tsx +84 -0
  15. package/src/MediaView.tsx +17 -0
  16. package/src/NotebookView.tsx +74 -0
  17. package/src/PdfView.tsx +289 -0
  18. package/src/RequestEditor.tsx +419 -0
  19. package/src/ResponseView.tsx +89 -0
  20. package/src/SqlGraph.tsx +322 -0
  21. package/src/SqlSteps.tsx +487 -0
  22. package/src/SqliteView.tsx +68 -0
  23. package/src/SvgView.tsx +231 -0
  24. package/src/TableView.tsx +85 -0
  25. package/src/VarSuggest.tsx +125 -0
  26. package/src/curl-file.ts +70 -0
  27. package/src/editorOptions.ts +83 -0
  28. package/src/editorOptionsContext.ts +4 -0
  29. package/src/fileKinds.ts +69 -0
  30. package/src/http-client.ts +9 -0
  31. package/src/http-import.ts +594 -0
  32. package/src/http.ts +251 -0
  33. package/src/import-warning.ts +8 -0
  34. package/src/index.ts +56 -0
  35. package/src/languages.ts +107 -0
  36. package/src/markdown.ts +123 -0
  37. package/src/markdownLive.ts +289 -0
  38. package/src/options.ts +3 -0
  39. package/src/pdf-url.d.ts +4 -0
  40. package/src/sql/format.ts +637 -0
  41. package/src/sql/formatOptions.ts +71 -0
  42. package/src/sql/lineage.ts +365 -0
  43. package/src/sql/lint.ts +951 -0
  44. package/src/sql/markerHighlight.ts +50 -0
  45. package/src/sql/markers.ts +138 -0
  46. package/src/sql/outline.ts +662 -0
  47. package/src/sql/statements.ts +83 -0
  48. package/src/sql/tokens.ts +156 -0
  49. package/src/sql/xmlQuery.ts +249 -0
  50. package/src/sqlProblems.ts +40 -0
  51. package/src/table.ts +89 -0
  52. package/src/text-save.ts +28 -0
  53. package/src/theme.ts +86 -0
  54. package/src/toc.ts +11 -0
  55. package/src/variables.ts +123 -0
  56. package/README.md +0 -3
@@ -0,0 +1,951 @@
1
+ import {
2
+ isNameTok,
3
+ isPunct,
4
+ matchParens,
5
+ nameKey,
6
+ parseAlias,
7
+ parseName,
8
+ sqlOutline,
9
+ word,
10
+ type Ctx,
11
+ type SqlJoinType,
12
+ type SqlOutline,
13
+ type SqlStatementOutline,
14
+ } from './outline.ts';
15
+ import { sqlMarkers, sqlParams } from './markers.ts';
16
+ import { isPlsqlStart } from './statements.ts';
17
+ import { tokenize, type Token } from './tokens.ts';
18
+
19
+ export type SqlLintCode =
20
+ | 'nullCompare'
21
+ | 'notInNullable'
22
+ | 'leftJoinUndone'
23
+ | 'unusedCte'
24
+ | 'duplicateCte'
25
+ | 'duplicateAlias'
26
+ | 'unbalancedParens'
27
+ | 'unionDistinct'
28
+ | 'selectStar'
29
+ | 'cartesianJoin'
30
+ | 'paramOnlyInCondition'
31
+ | 'markerFragmentNoParam'
32
+ | 'paramGap'
33
+ | 'unguardedIn'
34
+ | 'markerUnterminated'
35
+ | 'markerNoFragment'
36
+ | 'setColumnCount'
37
+ | 'rownumOrder'
38
+ | 'fetchNoOrder'
39
+ | 'constantCondition'
40
+ | 'unusedOuterJoin'
41
+ | 'unknownAlias'
42
+ | 'duplicateColumn'
43
+ | 'distinctGroupBy'
44
+ | 'distinctParens'
45
+ | 'emptyStatement'
46
+ | 'emptyStringCompare'
47
+ | 'rownumNever'
48
+ | 'mixedJoinSyntax';
49
+
50
+ export type SqlLintSeverity = 'error' | 'warning' | 'info';
51
+
52
+ export interface SqlProblem {
53
+ code: SqlLintCode;
54
+ severity: SqlLintSeverity;
55
+ from: number;
56
+ to: number;
57
+ /** Values for the i18n message, e.g. {name} or {alias}. */
58
+ params?: Record<string, string>;
59
+ /** CTE name, or '' for the main query, when the problem sits inside a step. */
60
+ step?: string;
61
+ /** Index of the statement in the outline. */
62
+ statement: number;
63
+ }
64
+
65
+ export const SQL_LINT_SEVERITY: Readonly<Record<SqlLintCode, SqlLintSeverity>> = {
66
+ nullCompare: 'error',
67
+ notInNullable: 'warning',
68
+ leftJoinUndone: 'warning',
69
+ unusedCte: 'warning',
70
+ duplicateCte: 'error',
71
+ duplicateAlias: 'error',
72
+ unbalancedParens: 'error',
73
+ unionDistinct: 'info',
74
+ selectStar: 'info',
75
+ cartesianJoin: 'warning',
76
+ paramOnlyInCondition: 'warning',
77
+ markerFragmentNoParam: 'warning',
78
+ paramGap: 'info',
79
+ unguardedIn: 'warning',
80
+ markerUnterminated: 'warning',
81
+ markerNoFragment: 'warning',
82
+ setColumnCount: 'error',
83
+ rownumOrder: 'warning',
84
+ fetchNoOrder: 'warning',
85
+ constantCondition: 'warning',
86
+ unusedOuterJoin: 'warning',
87
+ unknownAlias: 'error',
88
+ duplicateColumn: 'warning',
89
+ distinctGroupBy: 'info',
90
+ distinctParens: 'info',
91
+ emptyStatement: 'info',
92
+ emptyStringCompare: 'error',
93
+ rownumNever: 'error',
94
+ mixedJoinSyntax: 'error',
95
+ };
96
+
97
+ const TRIVIA = new Set(['whitespace', 'newline', 'lineComment', 'blockComment', 'slash']);
98
+ const QUERY_START = new Set(['select', 'with']);
99
+ const COMPARE = new Set(['=', '<>', '!=', '^=', '~=']);
100
+ const NULL_SAFE = new Set(['nvl', 'nvl2', 'coalesce', 'decode', 'isnull', 'ifnull']);
101
+ const JOIN_WORDS = new Set(['inner', 'left', 'right', 'full', 'cross', 'natural', 'outer', 'join', 'apply']);
102
+ const STAR_BEFORE = new Set(['select', 'distinct', 'unique', 'all']);
103
+ const NOT_COLUMN = new Set([
104
+ 'sysdate', 'systimestamp', 'current_date', 'current_timestamp', 'localtimestamp', 'rownum', 'rowid', 'level',
105
+ 'user', 'true', 'false', 'date', 'timestamp', 'interval', 'like', 'escape', 'exists', 'prior',
106
+ ]);
107
+ const NOT_BARE = new Set([
108
+ 'else', 'asc', 'desc', 'nulls', 'first', 'last', 'between', 'rows', 'row', 'only', 'next', 'siblings', 'any', 'some',
109
+ 'ties', 'percent', 'within', 'keep', 'nocycle', 'day', 'month', 'year', 'hour', 'minute', 'second', 'to',
110
+ ]);
111
+ const NOT_ALIAS_AFTER = new Set(['prior', 'connect_by_root', 'date', 'timestamp', 'interval', 'distinct', 'unique', 'all']);
112
+ const SET_OPS = new Set(['union', 'intersect', 'minus', 'except']);
113
+ const CMP_ALL = new Set(['=', '<>', '!=', '^=', '~=', '>=', '<=', '>', '<']);
114
+ const FLIP: Record<string, string> = { '<': '>', '>': '<', '<=': '>=', '>=': '<=', '=': '=' };
115
+ const ANSI_JOIN = new Set<ItemJoin>(['inner', 'left', 'right', 'full', 'cross']);
116
+ const PSEUDO_PREFIX = /^(sys|sys_\w*|dbms_\w*|utl_\w*|apex_\w*|owa_\w*|htp|htf|ctx_\w*|mdsys|xdb|public|new|old|standard)$|pkg|pck|pack/;
117
+
118
+ type ItemJoin = SqlJoinType | 'first';
119
+ interface Item {
120
+ join: ItemJoin;
121
+ key: string;
122
+ table: string;
123
+ label: string;
124
+ from: number;
125
+ to: number;
126
+ keyFrom: number;
127
+ keyTo: number;
128
+ complex: boolean;
129
+ }
130
+ interface Block {
131
+ items: Item[];
132
+ where: Array<[number, number]>;
133
+ ons: Array<[number, number, number]>;
134
+ unsure: boolean;
135
+ start: number;
136
+ end: number;
137
+ selFrom: number;
138
+ selTo: number;
139
+ distinct: number;
140
+ group: boolean;
141
+ order: boolean;
142
+ fetch: number[];
143
+ parent: Block | null;
144
+ named: boolean;
145
+ first: boolean;
146
+ }
147
+ interface Frame {
148
+ own: boolean;
149
+ block: Block | null;
150
+ parent: Block | null;
151
+ named: boolean;
152
+ setOp: number;
153
+ clause: string;
154
+ expect: boolean;
155
+ pending: ItemJoin | null;
156
+ joinWords: string[];
157
+ sec: 'where' | 'on' | null;
158
+ secStart: number;
159
+ set: boolean;
160
+ exists: boolean;
161
+ derived: boolean;
162
+ }
163
+ interface Star {
164
+ from: number;
165
+ to: number;
166
+ exists: boolean;
167
+ }
168
+ type Emit = (code: SqlLintCode, from: number, to: number, params?: Record<string, string>) => void;
169
+
170
+ interface Ref {
171
+ key: string;
172
+ text: string;
173
+ wrapped: boolean;
174
+ branch: number;
175
+ }
176
+ interface Conj {
177
+ from: number;
178
+ to: number;
179
+ refs: Ref[];
180
+ bare: number;
181
+ branches: number;
182
+ isNull: boolean;
183
+ hasCase: boolean;
184
+ plus: boolean;
185
+ hasSub: boolean;
186
+ }
187
+
188
+ const isNameLike = (t: Token | undefined): boolean =>
189
+ t !== undefined && (t.kind === 'identifier' || t.kind === 'keyword' || t.kind === 'quotedIdentifier');
190
+
191
+ function closeAt(ctx: Ctx, i: number): number {
192
+ const m = ctx.match[i] ?? -1;
193
+ return m > i ? m : ctx.ts.length - 1;
194
+ }
195
+
196
+ function operandStart(ctx: Ctx, k: number): number {
197
+ const { ts, match } = ctx;
198
+ if (k < 0) return ts[0]!.from;
199
+ if (isPunct(ctx, k, ')') && (match[k] ?? -1) >= 0) {
200
+ k = match[k]!;
201
+ if (isNameLike(ts[k - 1])) k--;
202
+ }
203
+ while (k >= 2 && isPunct(ctx, k - 1, '.') && isNameLike(ts[k - 2])) k -= 2;
204
+ return ts[k]!.from;
205
+ }
206
+
207
+ function operandEnd(ctx: Ctx, k: number): number {
208
+ const { ts } = ctx;
209
+ if (k >= ts.length) return ts[ts.length - 1]!.to;
210
+ if (isPunct(ctx, k, '(')) return ts[closeAt(ctx, k)]!.to;
211
+ while (isPunct(ctx, k + 1, '.') && isNameLike(ts[k + 2])) k += 2;
212
+ if (isPunct(ctx, k + 1, '(')) k = closeAt(ctx, k + 1);
213
+ return ts[k]!.to;
214
+ }
215
+
216
+ interface Scan {
217
+ blocks: Block[];
218
+ stars: Star[];
219
+ owner: Array<Block | null>;
220
+ setOps: Array<[Block, number, Block]>;
221
+ }
222
+
223
+ function scan(ctx: Ctx, emit: Emit): Scan {
224
+ const { ts, match } = ctx;
225
+ const n = ts.length;
226
+ const blocks: Block[] = [];
227
+ const stars: Star[] = [];
228
+ const owner: Array<Block | null> = new Array<Block | null>(n).fill(null);
229
+ const setOps: Array<[Block, number, Block]> = [];
230
+ const frame = (own: boolean, block: Block | null, exists = false, derived = false, parent: Block | null = null, named = false): Frame => ({
231
+ own, block, parent, named, setOp: -1, clause: 'other', expect: false, pending: null, joinWords: [], sec: null, secStart: 0,
232
+ set: false, exists, derived,
233
+ });
234
+ const stack: Frame[] = [frame(true, null)];
235
+
236
+ const closeSec = (f: Frame, at: number): void => {
237
+ if (f.sec && f.block && at > f.secStart) {
238
+ if (f.sec === 'where') f.block.where.push([f.secStart, at]);
239
+ else f.block.ons.push([f.block.items.length - 1, f.secStart, at]);
240
+ }
241
+ f.sec = null;
242
+ };
243
+ const closeSel = (f: Frame, at: number): void => {
244
+ if (f.block && f.clause === 'select' && f.block.selTo < 0) f.block.selTo = at;
245
+ };
246
+ const addItem = (
247
+ f: Frame, keyText: string, from: number, to: number, keyFrom: number, keyTo: number, complex: boolean, table = '',
248
+ ): void => {
249
+ f.block!.items.push({
250
+ join: f.pending ?? 'first', key: keyText ? nameKey(keyText) : '', table: table ? nameKey(table) : '', label: keyText,
251
+ from, to, keyFrom, keyTo, complex,
252
+ });
253
+ f.pending = null;
254
+ f.expect = false;
255
+ f.joinWords = [];
256
+ };
257
+
258
+ for (let i = 0; i < n; i++) {
259
+ const t = ts[i]!;
260
+ const f = stack[stack.length - 1]!;
261
+ owner[i] = f.block;
262
+ if (t.kind === 'punctuation' && t.text === '(') {
263
+ if (QUERY_START.has(word(ctx, i + 1))) {
264
+ const derived = f.own && f.expect && f.block !== null;
265
+ const named = derived || (word(ctx, i - 1) === 'as' && !isPunct(ctx, i - 2, ')'));
266
+ stack.push(frame(true, null, word(ctx, i - 1) === 'exists', derived, f.block, named));
267
+ } else {
268
+ if (f.own && f.expect && f.block) {
269
+ f.block.unsure = true;
270
+ f.expect = false;
271
+ }
272
+ stack.push(frame(false, f.block, false, false, f.parent));
273
+ }
274
+ continue;
275
+ }
276
+ if (t.kind === 'punctuation' && t.text === ')') {
277
+ if (stack.length === 1) continue;
278
+ closeSec(f, i);
279
+ closeSel(f, i);
280
+ stack.pop();
281
+ const p = stack[stack.length - 1]!;
282
+ if (f.derived && p.block) {
283
+ const al = parseAlias(ctx, i + 1, n);
284
+ const open = ts[match[i]! >= 0 ? match[i]! : i]!;
285
+ const aliasTok = al.alias ? ts[al.next - 1]! : t;
286
+ addItem(p, al.alias ?? '', open.from, aliasTok.to, aliasTok.from, aliasTok.to, true);
287
+ i = al.next - 1;
288
+ }
289
+ continue;
290
+ }
291
+ if (t.kind === 'operator') {
292
+ if (COMPARE.has(t.text) && !f.set) {
293
+ if (word(ctx, i + 1) === 'null' && i > 0) emit('nullCompare', operandStart(ctx, i - 1), ts[i + 1]!.to);
294
+ else if (word(ctx, i - 1) === 'null') emit('nullCompare', ts[i - 1]!.from, operandEnd(ctx, i + 1));
295
+ else if (ts[i + 1]?.kind === 'string' && ts[i + 1]!.text === "''" && i > 0) {
296
+ emit('emptyStringCompare', operandStart(ctx, i - 1), ts[i + 1]!.to);
297
+ } else if (ts[i - 1]?.kind === 'string' && ts[i - 1]!.text === "''") {
298
+ emit('emptyStringCompare', ts[i - 1]!.from, operandEnd(ctx, i + 1));
299
+ }
300
+ } else if (t.text === '*' && f.own && f.block && f.clause === 'select') {
301
+ const pv = ts[i - 1];
302
+ if (pv && ((pv.kind === 'punctuation' && (pv.text === ',' || pv.text === '.')) || STAR_BEFORE.has(word(ctx, i - 1)))) {
303
+ stars.push({ from: t.from, to: t.to, exists: f.exists });
304
+ }
305
+ }
306
+ continue;
307
+ }
308
+ const lw = word(ctx, i);
309
+ if (lw === 'not' && word(ctx, i + 1) === 'in' && isPunct(ctx, i + 2, '(') && QUERY_START.has(word(ctx, i + 3))) {
310
+ emit('notInNullable', t.from, ts[i + 1]!.to);
311
+ }
312
+ if (lw === 'union' && word(ctx, i + 1) !== 'all') emit('unionDistinct', t.from, t.to);
313
+ if (!f.own) continue;
314
+ if (f.expect && f.block) {
315
+ if (lw === 'lateral' || lw === 'only') continue;
316
+ const nm = parseName(ctx, i, n);
317
+ if (nm) {
318
+ if (isPunct(ctx, nm.next, '(')) {
319
+ const c = closeAt(ctx, nm.next);
320
+ const al = parseAlias(ctx, c + 1, n);
321
+ const end = al.alias ? ts[al.next - 1]! : ts[c]!;
322
+ addItem(f, al.alias ?? '', nm.from, end.to, end.from, end.to, true);
323
+ i = al.next - 1;
324
+ continue;
325
+ }
326
+ const al = parseAlias(ctx, nm.next, n);
327
+ const last = nm.name.replace(/@.*$/, '').split('.').pop() ?? nm.name;
328
+ if (al.alias) {
329
+ const at = ts[al.next - 1]!;
330
+ addItem(f, al.alias, nm.from, at.to, at.from, at.to, false, last);
331
+ } else {
332
+ addItem(f, last, nm.from, nm.to, nm.from, nm.to, false, last);
333
+ }
334
+ i = al.next - 1;
335
+ continue;
336
+ }
337
+ f.expect = false;
338
+ }
339
+ if (t.kind === 'punctuation' && t.text === ',') {
340
+ if (f.block && f.clause === 'from') {
341
+ closeSec(f, i);
342
+ f.pending = 'comma';
343
+ f.expect = true;
344
+ }
345
+ continue;
346
+ }
347
+ if (!lw) continue;
348
+ if (lw === 'set') {
349
+ f.set = true;
350
+ continue;
351
+ }
352
+ if (lw === 'where' || lw === 'when' || lw === 'returning') f.set = false;
353
+ if (lw === 'select') {
354
+ closeSec(f, i);
355
+ closeSel(f, i);
356
+ const w1 = word(ctx, i + 1);
357
+ const dist = w1 === 'distinct' || w1 === 'unique';
358
+ const blk: Block = {
359
+ items: [], where: [], ons: [], unsure: false, start: i, end: i, selFrom: dist || w1 === 'all' ? i + 2 : i + 1, selTo: -1,
360
+ distinct: dist ? i + 1 : -1, group: false, order: false, fetch: [], parent: f.parent, named: f.named, first: !f.block,
361
+ };
362
+ if (f.block && f.setOp >= 0) setOps.push([f.block, f.setOp, blk]);
363
+ f.setOp = -1;
364
+ blocks.push(blk);
365
+ f.block = blk;
366
+ f.clause = 'select';
367
+ f.expect = false;
368
+ continue;
369
+ }
370
+ if (!f.block) continue;
371
+ if (lw === 'from') {
372
+ if (f.clause === 'select') {
373
+ closeSec(f, i);
374
+ closeSel(f, i);
375
+ f.clause = 'from';
376
+ f.expect = true;
377
+ f.pending = null;
378
+ }
379
+ continue;
380
+ }
381
+ if (lw === 'where') {
382
+ closeSec(f, i);
383
+ f.sec = 'where';
384
+ f.secStart = i + 1;
385
+ f.clause = 'where';
386
+ continue;
387
+ }
388
+ if (f.clause === 'from' && JOIN_WORDS.has(lw)) {
389
+ closeSec(f, i);
390
+ if (lw === 'join' || lw === 'apply') {
391
+ const w = f.joinWords;
392
+ f.pending =
393
+ lw === 'apply' ? 'apply'
394
+ : w.includes('left') ? 'left'
395
+ : w.includes('right') ? 'right'
396
+ : w.includes('full') ? 'full'
397
+ : w.includes('cross') ? 'cross'
398
+ : 'inner';
399
+ f.joinWords = [];
400
+ f.expect = true;
401
+ } else f.joinWords.push(lw);
402
+ continue;
403
+ }
404
+ if (f.clause === 'from' && lw === 'on') {
405
+ closeSec(f, i);
406
+ f.sec = 'on';
407
+ f.secStart = i + 1;
408
+ continue;
409
+ }
410
+ const nx = word(ctx, i + 1);
411
+ if (
412
+ ((lw === 'group' || lw === 'order') && (nx === 'by' || nx === 'siblings')) ||
413
+ (lw === 'connect' && nx === 'by') || (lw === 'start' && nx === 'with') || (lw === 'for' && nx === 'update') ||
414
+ lw === 'having' || lw === 'union' || lw === 'intersect' || lw === 'minus' || lw === 'except' ||
415
+ lw === 'fetch' || lw === 'offset' || lw === 'limit' || lw === 'model' || lw === 'window' || lw === 'returning' ||
416
+ lw === 'using' || lw === 'pivot' || lw === 'unpivot'
417
+ ) {
418
+ closeSec(f, i);
419
+ if (lw === 'using' && f.clause === 'from') continue;
420
+ closeSel(f, i);
421
+ if (lw === 'group') f.block.group = true;
422
+ else if (lw === 'order') f.block.order = true;
423
+ else if (SET_OPS.has(lw)) f.setOp = i;
424
+ else if ((lw === 'fetch' && (nx === 'first' || nx === 'next')) || (lw === 'offset' && /^rows?$/.test(word(ctx, i + 2)))) {
425
+ f.block.fetch.push(i);
426
+ }
427
+ f.clause = 'other';
428
+ f.expect = false;
429
+ }
430
+ }
431
+ for (const f of stack) {
432
+ closeSec(f, n);
433
+ closeSel(f, n);
434
+ }
435
+ owner.forEach((b, k) => {
436
+ if (b) b.end = k;
437
+ });
438
+ return { blocks, stars, owner, setOps };
439
+ }
440
+
441
+ function splitTop(ctx: Ctx, a: number, b: number): Array<[number, number]> {
442
+ const out: Array<[number, number]> = [];
443
+ let start = a;
444
+ let d = 0;
445
+ for (let k = a; k < b; k++) {
446
+ if (isPunct(ctx, k, '(')) d++;
447
+ else if (isPunct(ctx, k, ')')) d--;
448
+ else if (d === 0 && isPunct(ctx, k, ',')) {
449
+ out.push([start, k]);
450
+ start = k + 1;
451
+ }
452
+ }
453
+ if (b > start) out.push([start, b]);
454
+ return out;
455
+ }
456
+
457
+ const selectItems = (ctx: Ctx, blk: Block): Array<[number, number]> =>
458
+ blk.selTo > blk.selFrom ? splitTop(ctx, blk.selFrom, blk.selTo) : [];
459
+
460
+ const isStarItem = (ctx: Ctx, b: number): boolean => ctx.ts[b - 1]?.kind === 'operator' && ctx.ts[b - 1]!.text === '*';
461
+
462
+ function chainEnd(ctx: Ctx, k: number): number {
463
+ if (!isNameLike(ctx.ts[k])) return -1;
464
+ while (isPunct(ctx, k + 1, '.') && isNameLike(ctx.ts[k + 2])) k += 2;
465
+ return k;
466
+ }
467
+
468
+ /** Output name token of a select item, and whether it is an alias. */
469
+ function outName(ctx: Ctx, a: number, b: number): { k: number; alias: boolean } | null {
470
+ const { ts } = ctx;
471
+ if (b - a >= 3 && word(ctx, b - 2) === 'as' && isNameLike(ts[b - 1])) return { k: b - 1, alias: true };
472
+ if (b - a >= 2 && isNameTok(ctx, b - 1, b) && !NOT_BARE.has(word(ctx, b - 1))) {
473
+ const p = ts[b - 2]!;
474
+ const ok =
475
+ (p.kind === 'punctuation' && p.text === ')') || p.kind === 'number' || p.kind === 'string' ||
476
+ (isNameLike(p) && !NOT_ALIAS_AFTER.has(word(ctx, b - 2)) && (p.kind === 'quotedIdentifier' || isNameTok(ctx, b - 2, b)));
477
+ if (ok) return { k: b - 1, alias: true };
478
+ }
479
+ return chainEnd(ctx, a) === b - 1 ? { k: b - 1, alias: false } : null;
480
+ }
481
+
482
+ function strip(ctx: Ctx, a: number, b: number): [number, number] {
483
+ while (b - a > 1 && isPunct(ctx, a, '(') && ctx.match[a] === b - 1) {
484
+ a++;
485
+ b--;
486
+ }
487
+ return [a, b];
488
+ }
489
+
490
+ function findPlus(ctx: Ctx, a: number, b: number): number {
491
+ for (let k = a; k < b; k++) {
492
+ if (!isPunct(ctx, k, '(')) continue;
493
+ if (QUERY_START.has(word(ctx, k + 1))) k = closeAt(ctx, k);
494
+ else if (ctx.ts[k + 1]?.text === '+' && isPunct(ctx, k + 2, ')')) return k;
495
+ }
496
+ return -1;
497
+ }
498
+
499
+ interface Shared {
500
+ inSpan: (pos: number) => boolean;
501
+ aliasIdx: Set<number>;
502
+ }
503
+
504
+ function checkMore(ctx: Ctx, blk: Block, sh: Shared, emit: Emit): void {
505
+ const { ts } = ctx;
506
+ const n = ts.length;
507
+ const { items } = blk;
508
+ const sel = selectItems(ctx, blk);
509
+ if (blk.distinct >= 0) {
510
+ if (blk.group) emit('distinctGroupBy', ts[blk.distinct]!.from, ts[blk.distinct]!.to);
511
+ if (isPunct(ctx, blk.distinct + 1, '(') && sel.length > 1) {
512
+ emit('distinctParens', ts[blk.distinct]!.from, ts[closeAt(ctx, blk.distinct + 1)]!.to);
513
+ }
514
+ }
515
+ if (!blk.order) for (const k of blk.fetch) emit('fetchNoOrder', ts[k]!.from, ts[k]!.to);
516
+ if (blk.named && blk.first) {
517
+ const seen = new Set<string>();
518
+ for (const [a, b] of sel) {
519
+ const o = outName(ctx, a, b);
520
+ if (!o) continue;
521
+ const key = nameKey(ts[o.k]!.text);
522
+ if (seen.has(key)) emit('duplicateColumn', ts[o.k]!.from, ts[o.k]!.to, { name: ts[o.k]!.text });
523
+ seen.add(key);
524
+ }
525
+ }
526
+
527
+ const where = blk.where.flatMap(([a, b]) => conjuncts(ctx, a, b));
528
+ for (const [a0, b0] of where) {
529
+ const [a, b] = strip(ctx, a0, b0);
530
+ const span = (): [number, number] => [ts[a]!.from, ts[b - 1]!.to];
531
+ if (b - a < 3) continue;
532
+ const head = word(ctx, a) === 'rownum';
533
+ const tail = word(ctx, b - 1) === 'rownum';
534
+ if (!head && !tail) continue;
535
+ const opTok = ts[head ? a + 1 : b - 2]!;
536
+ if (opTok.kind !== 'operator' || !FLIP[opTok.text]) continue;
537
+ const op = head ? opTok.text : FLIP[opTok.text]!;
538
+ if (blk.order && (op === '<' || op === '<=' || op === '=')) emit('rownumOrder', ...span());
539
+ if (b - a !== 3) continue;
540
+ const num = ts[head ? a + 2 : a]!;
541
+ if (num.kind !== 'number') continue;
542
+ const v = Number(num.text);
543
+ if ((op === '>' && v >= 1) || ((op === '>=' || op === '=') && v > 1)) emit('rownumNever', ...span());
544
+ }
545
+
546
+ const conds = [...where, ...blk.ons.flatMap(([, a, b]) => conjuncts(ctx, a, b))];
547
+ for (const [a0, b0] of conds) {
548
+ const [a, b] = strip(ctx, a0, b0);
549
+ if (!isNameTok(ctx, a, b) || NOT_COLUMN.has(word(ctx, a))) continue;
550
+ const j = chainEnd(ctx, a);
551
+ const op = ts[j + 1];
552
+ if (!op || op.kind !== 'operator' || !CMP_ALL.has(op.text)) continue;
553
+ if (!isNameTok(ctx, j + 2, b) || chainEnd(ctx, j + 2) !== b - 1 || b - 1 - (j + 2) !== j - a) continue;
554
+ let same = true;
555
+ for (let k = 0; k <= j - a && same; k++) same = nameKey(ts[a + k]!.text) === nameKey(ts[j + 2 + k]!.text);
556
+ if (same) emit('constantCondition', ts[a]!.from, ts[b - 1]!.to);
557
+ }
558
+
559
+ if (items.some((it) => ANSI_JOIN.has(it.join))) {
560
+ const ranges = [...blk.where, ...blk.ons.map(([, a, b]): [number, number] => [a, b])].sort((x, y) => x[0] - y[0]);
561
+ for (const [a, b] of ranges) {
562
+ const k = findPlus(ctx, a, b);
563
+ if (k >= 0) {
564
+ emit('mixedJoinSyntax', ts[k]!.from, ts[k + 2]!.to);
565
+ break;
566
+ }
567
+ }
568
+ }
569
+
570
+ const outer = items.filter((it) => it.key && (it.join === 'left' || it.join === 'right' || it.join === 'full'));
571
+ if (blk.unsure || outer.length === 0 || sel.some(([a, b]) => b - a === 1 && isStarItem(ctx, b))) return;
572
+ for (let k = blk.start + 1; k <= blk.end; k++) {
573
+ const w = word(ctx, k);
574
+ if (
575
+ (ts[k]!.kind === 'quotedIdentifier' || (isNameTok(ctx, k, n) && !NOT_COLUMN.has(w) && !NOT_BARE.has(w))) &&
576
+ !isPunct(ctx, k - 1, '.') && !isPunct(ctx, k + 1, '.') && !isPunct(ctx, k + 1, '(') && word(ctx, k - 1) !== 'as' &&
577
+ !sh.aliasIdx.has(k) && !sh.inSpan(ts[k]!.from)
578
+ ) {
579
+ return;
580
+ }
581
+ }
582
+ for (const it of outer) {
583
+ const k = items.indexOf(it);
584
+ const own = blk.ons.filter((o) => o[0] === k);
585
+ let used = false;
586
+ for (let x = 0; x < n && !used; x++) {
587
+ const t = ts[x]!;
588
+ used =
589
+ isNameLike(t) && isPunct(ctx, x + 1, '.') && nameKey(t.text) === it.key && !(t.from >= it.from && t.to <= it.to) &&
590
+ !own.some(([, a, b]) => x >= a && x < b);
591
+ }
592
+ if (!used) emit('unusedOuterJoin', it.from, it.to, { alias: it.label });
593
+ }
594
+ }
595
+
596
+ function checkAliases(ctx: Ctx, st: SqlStatementOutline, sc: Scan, sh: Shared, emit: Emit): void {
597
+ if (st.kind !== 'select' && st.kind !== 'insert' && st.kind !== 'create') return;
598
+ const { ts } = ctx;
599
+ const ctes = new Set(st.steps.filter((s) => s.kind === 'cte').map((s) => nameKey(s.name)));
600
+ ts.forEach((t, k) => {
601
+ if (isNameLike(t) && word(ctx, k + 1) === 'as' && isPunct(ctx, k + 2, '(')) ctes.add(nameKey(t.text));
602
+ });
603
+ const scopes = new Map<Block, Set<string> | null>();
604
+ const scope = (b: Block): Set<string> | null => {
605
+ if (scopes.has(b)) return scopes.get(b)!;
606
+ let s: Set<string> | null = null;
607
+ if (!b.unsure) {
608
+ const up = b.parent ? scope(b.parent) : new Set<string>();
609
+ if (up) {
610
+ s = new Set(up);
611
+ for (const it of b.items) {
612
+ if (it.key) s.add(it.key);
613
+ if (it.table) s.add(it.table);
614
+ }
615
+ }
616
+ }
617
+ scopes.set(b, s);
618
+ return s;
619
+ };
620
+ ts.forEach((t, k) => {
621
+ const b = sc.owner[k];
622
+ if (!b || !isNameLike(t) || !isPunct(ctx, k + 1, '.') || !isNameLike(ts[k + 2])) return;
623
+ if (isPunct(ctx, k - 1, '.') || isPunct(ctx, k - 1, '@') || isPunct(ctx, k + 3, '.') || isPunct(ctx, k + 3, '(') || isPunct(ctx, k + 3, '@')) return;
624
+ const col = word(ctx, k + 2);
625
+ const q = nameKey(t.text);
626
+ if (col === 'nextval' || col === 'currval' || PSEUDO_PREFIX.test(q) || ctes.has(q) || sh.inSpan(t.from)) return;
627
+ const s = scope(b);
628
+ if (s && !s.has(q)) emit('unknownAlias', t.from, t.to, { alias: t.text });
629
+ });
630
+ }
631
+
632
+ function checkSetOps(ctx: Ctx, sc: Scan, emit: Emit): void {
633
+ for (const [x, op, y] of sc.setOps) {
634
+ const a = selectItems(ctx, x);
635
+ const b = selectItems(ctx, y);
636
+ if (a.length === 0 || b.length === 0 || [...a, ...b].some(([, q]) => isStarItem(ctx, q))) continue;
637
+ if (a.length !== b.length) {
638
+ emit('setColumnCount', ctx.ts[op]!.from, ctx.ts[op]!.to, { left: String(a.length), right: String(b.length) });
639
+ }
640
+ }
641
+ }
642
+
643
+ function lintTerminators(all: Token[], ol: SqlOutline, out: SqlProblem[]): void {
644
+ let started = false;
645
+ let plsql = false;
646
+ let term = false;
647
+ const hit = (t: Token): void => {
648
+ if (!started && term) {
649
+ const statement = Math.max(0, ol.statements.filter((s) => s.to <= t.from).length - 1);
650
+ out.push({ code: 'emptyStatement', severity: SQL_LINT_SEVERITY.emptyStatement, from: t.from, to: t.to, statement });
651
+ }
652
+ started = false;
653
+ plsql = false;
654
+ term = true;
655
+ };
656
+ for (let i = 0; i < all.length; i++) {
657
+ const t = all[i]!;
658
+ if (t.kind === 'slash') hit(t);
659
+ else if (TRIVIA.has(t.kind)) continue;
660
+ else if (t.kind === 'punctuation' && t.text === ';' && !plsql) hit(t);
661
+ else if (!started) {
662
+ started = true;
663
+ plsql = isPlsqlStart(all, i);
664
+ }
665
+ }
666
+ }
667
+
668
+ function conjuncts(ctx: Ctx, a: number, b: number): Array<[number, number]> {
669
+ const out: Array<[number, number]> = [];
670
+ let start = a;
671
+ let d = 0;
672
+ let caseD = 0;
673
+ let between = false;
674
+ for (let k = a; k < b; k++) {
675
+ if (isPunct(ctx, k, '(')) d++;
676
+ else if (isPunct(ctx, k, ')')) d--;
677
+ else if (d === 0) {
678
+ const lw = word(ctx, k);
679
+ if (lw === 'case') caseD++;
680
+ else if (lw === 'end' && caseD > 0) caseD--;
681
+ else if (caseD === 0 && lw === 'between') between = true;
682
+ else if (caseD === 0 && lw === 'and') {
683
+ if (between) between = false;
684
+ else {
685
+ if (k > start) out.push([start, k]);
686
+ start = k + 1;
687
+ }
688
+ }
689
+ }
690
+ }
691
+ if (b > start) out.push([start, b]);
692
+ return out;
693
+ }
694
+
695
+ function analyzeConj(ctx: Ctx, a0: number, b0: number): Conj {
696
+ const { ts, match } = ctx;
697
+ const c: Conj = {
698
+ from: ts[a0]!.from, to: ts[b0 - 1]!.to, refs: [], bare: 0, branches: 1, isNull: false, hasCase: false, plus: false,
699
+ hasSub: false,
700
+ };
701
+ let a = a0;
702
+ let b = b0;
703
+ while (b - a > 1 && isPunct(ctx, a, '(') && match[a] === b - 1) {
704
+ a++;
705
+ b--;
706
+ }
707
+ const fns: string[] = [];
708
+ for (let k = a; k < b; k++) {
709
+ const t = ts[k]!;
710
+ if (t.kind === 'punctuation' && t.text === '(') {
711
+ if (QUERY_START.has(word(ctx, k + 1))) {
712
+ c.hasSub = true;
713
+ const m = match[k] ?? -1;
714
+ k = m > k && m < b ? m : b;
715
+ continue;
716
+ }
717
+ if (t.text === '(' && ts[k + 1]?.text === '+' && isPunct(ctx, k + 2, ')')) {
718
+ c.plus = true;
719
+ k += 2;
720
+ continue;
721
+ }
722
+ fns.push(word(ctx, k - 1));
723
+ continue;
724
+ }
725
+ if (t.kind === 'punctuation' && t.text === ')') {
726
+ fns.pop();
727
+ continue;
728
+ }
729
+ const lw = word(ctx, k);
730
+ if (lw === 'case') c.hasCase = true;
731
+ else if (lw === 'is' && word(ctx, k + 1) === 'null') c.isNull = true;
732
+ else if (lw === 'or' && fns.length === 0) {
733
+ c.branches++;
734
+ continue;
735
+ }
736
+ if (!isNameLike(t) || isPunct(ctx, k - 1, '.')) continue;
737
+ let j = k;
738
+ while (isPunct(ctx, j + 1, '.') && isNameLike(ts[j + 2])) j += 2;
739
+ const call = isPunct(ctx, j + 1, '(') && !(ts[j + 2]?.text === '+' && isPunct(ctx, j + 3, ')'));
740
+ if (!call) {
741
+ if (j > k) {
742
+ const q = ts[j - 2]!;
743
+ c.refs.push({ key: nameKey(q.text), text: q.text, wrapped: fns.some((f) => NULL_SAFE.has(f)), branch: c.branches - 1 });
744
+ } else if (isNameTok(ctx, k, b) && !NOT_COLUMN.has(lw)) c.bare++;
745
+ }
746
+ k = j;
747
+ }
748
+ return c;
749
+ }
750
+
751
+ function checkBlock(ctx: Ctx, blk: Block, emit: Emit): void {
752
+ const { items } = blk;
753
+ const seen = new Set<string>();
754
+ for (const it of items) {
755
+ if (!it.key) continue;
756
+ if (seen.has(it.key)) emit('duplicateAlias', it.keyFrom, it.keyTo, { alias: it.label });
757
+ seen.add(it.key);
758
+ }
759
+ if (items.length < 2) return;
760
+ const where = blk.where.flatMap(([a, b]) => conjuncts(ctx, a, b)).map(([a, b]) => analyzeConj(ctx, a, b));
761
+
762
+ const nullable = new Map<string, string>();
763
+ items.forEach((it, k) => {
764
+ if (it.key && (it.join === 'left' || it.join === 'full')) nullable.set(it.key, it.label);
765
+ if (it.join === 'right' || it.join === 'full') {
766
+ for (let j = 0; j < k; j++) if (items[j]!.key) nullable.set(items[j]!.key, items[j]!.label);
767
+ }
768
+ });
769
+ if (nullable.size > 0 && !blk.unsure) {
770
+ for (const c of where) {
771
+ if (c.isNull || c.hasCase || c.plus) continue;
772
+ let alias = '';
773
+ let all = true;
774
+ for (let br = 0; br < c.branches && all; br++) {
775
+ const r = c.refs.find((x) => x.branch === br && !x.wrapped && nullable.has(x.key));
776
+ if (!r) all = false;
777
+ else if (!alias) alias = nullable.get(r.key)!;
778
+ }
779
+ if (all && alias) emit('leftJoinUndone', c.from, c.to, { alias });
780
+ }
781
+ }
782
+
783
+ if (blk.unsure || !items.some((it) => it.join === 'comma')) return;
784
+ const parent = items.map((_, k) => k);
785
+ const find = (x: number): number => {
786
+ while (parent[x] !== x) x = parent[x] = parent[parent[x]!]!;
787
+ return x;
788
+ };
789
+ const union = (x: number, y: number): void => {
790
+ parent[find(x)] = find(y);
791
+ };
792
+ const byKey = new Map<string, number[]>();
793
+ items.forEach((it, k) => {
794
+ if (it.key) byKey.set(it.key, [...(byKey.get(it.key) ?? []), k]);
795
+ if (k > 0 && (it.complex || (it.join !== 'comma' && it.join !== 'cross'))) union(k, k - 1);
796
+ });
797
+ const conds: Array<{ c: Conj; item: number }> = where.map((c) => ({ c, item: -1 }));
798
+ for (const [item, a, b] of blk.ons) for (const [x, y] of conjuncts(ctx, a, b)) conds.push({ c: analyzeConj(ctx, x, y), item });
799
+ for (const { c, item } of conds) {
800
+ const idx = new Set<number>();
801
+ for (const r of c.refs) for (const k of byKey.get(r.key) ?? []) idx.add(k);
802
+ if (item >= 0) idx.add(item);
803
+ if (c.hasSub || (c.bare > 0 && idx.size + c.bare >= 2)) return;
804
+ const list = [...idx];
805
+ for (let k = 1; k < list.length; k++) union(list[k]!, list[0]!);
806
+ }
807
+ const min = new Map<number, number>();
808
+ items.forEach((_, k) => {
809
+ const r = find(k);
810
+ if (!min.has(r)) min.set(r, k);
811
+ });
812
+ items.forEach((it, k) => {
813
+ if (k > 0 && it.join === 'comma' && !it.complex && it.key && min.get(find(k)) === k) {
814
+ emit('cartesianJoin', it.from, it.to, { alias: it.label });
815
+ }
816
+ });
817
+ }
818
+
819
+ function stepAt(st: SqlStatementOutline, pos: number): string | undefined {
820
+ for (const s of st.steps) if (s.kind === 'cte' && pos >= s.from && pos <= s.to) return s.name;
821
+ for (const s of st.steps) if (s.kind === 'main' && pos >= s.from && pos <= s.to) return '';
822
+ return undefined;
823
+ }
824
+
825
+ function lintStatement(ts: Token[], st: SqlStatementOutline, index: number, out: SqlProblem[]): void {
826
+ const emit: Emit = (code, from, to, params) => {
827
+ const p: SqlProblem = { code, severity: SQL_LINT_SEVERITY[code], from, to, statement: index };
828
+ if (params) p.params = params;
829
+ const step = stepAt(st, from);
830
+ if (step !== undefined) p.step = step;
831
+ out.push(p);
832
+ };
833
+ const open: Token[] = [];
834
+ for (const t of ts) {
835
+ if (t.kind !== 'punctuation') continue;
836
+ if (t.text === '(') open.push(t);
837
+ else if (t.text === ')' && !open.pop()) emit('unbalancedParens', t.from, t.to);
838
+ }
839
+ for (const t of open) emit('unbalancedParens', t.from, t.to);
840
+ if (st.kind === 'plsql' || ts.length === 0) return;
841
+
842
+ const ctx: Ctx = { ts, match: matchParens(ts), ctes: new Set() };
843
+ const sc = scan(ctx, emit);
844
+ const { blocks, stars } = sc;
845
+ for (const blk of blocks) checkBlock(ctx, blk, emit);
846
+ const spans = blocks.flatMap((b) => b.items.map((it) => [it.from, it.to] as const));
847
+ const aliasIdx = new Set<number>();
848
+ for (const blk of blocks) {
849
+ for (const [a, b] of selectItems(ctx, blk)) {
850
+ const o = outName(ctx, a, b);
851
+ if (o?.alias) aliasIdx.add(o.k);
852
+ }
853
+ }
854
+ const sh: Shared = { inSpan: (pos) => spans.some(([a, b]) => pos >= a && pos < b), aliasIdx };
855
+ for (const blk of blocks) checkMore(ctx, blk, sh, emit);
856
+ checkSetOps(ctx, sc, emit);
857
+ checkAliases(ctx, st, sc, sh, emit);
858
+
859
+ const feedsWrite = st.kind === 'insert' || st.kind === 'create';
860
+ for (const s of stars) {
861
+ if (s.exists) continue;
862
+ if (feedsWrite || st.steps.some((x) => x.kind === 'cte' && s.from >= x.from && s.from <= x.to)) emit('selectStar', s.from, s.to);
863
+ }
864
+
865
+ const names = new Set<string>();
866
+ for (const s of st.steps) {
867
+ if (s.kind !== 'cte' || s.nameFrom === undefined || s.nameTo === undefined) continue;
868
+ const key = nameKey(s.name);
869
+ if (names.has(key)) emit('duplicateCte', s.nameFrom, s.nameTo, { name: s.name });
870
+ else if (s.feeds.length === 0) emit('unusedCte', s.nameFrom, s.nameTo, { name: s.name });
871
+ names.add(key);
872
+ }
873
+ }
874
+
875
+ function lintMarkers(text: string, ol: SqlOutline, out: SqlProblem[]): void {
876
+ const markers = sqlMarkers(text);
877
+ const params = sqlParams(text);
878
+ const emit = (code: SqlLintCode, from: number, to: number, p?: Record<string, string>): void => {
879
+ let statement = ol.statements.findIndex((s) => from >= s.from && from <= s.to);
880
+ if (statement < 0) statement = 0;
881
+ const problem: SqlProblem = { code, severity: SQL_LINT_SEVERITY[code], from, to, statement };
882
+ if (p) problem.params = p;
883
+ out.push(problem);
884
+ };
885
+
886
+ for (let i = text.indexOf('/*%JS='); i >= 0; i = text.indexOf('/*%JS=', i + 1)) {
887
+ if (!markers.some((m) => m.from === i)) emit('markerUnterminated', i, i + 6);
888
+ }
889
+ for (const m of markers) {
890
+ if (!text.slice(m.fragmentFrom, m.fragmentTo).trim()) emit('markerNoFragment', m.from, m.to);
891
+ else if (m.params.length > 0 && m.fragmentParams.length === 0) emit('markerFragmentNoParam', m.from, m.to);
892
+ }
893
+ for (const p of params) {
894
+ if (p.uses.some((u) => u.kind === 'condition') && p.markers === 0 && p.unguarded === 0) {
895
+ const u = p.uses.find((x) => x.kind === 'condition')!;
896
+ emit('paramOnlyInCondition', u.from, u.to, { n: String(p.n) });
897
+ }
898
+ }
899
+ for (let k = 1; k < params.length; k++) {
900
+ const prev = params[k - 1]!.n;
901
+ const cur = params[k]!;
902
+ for (let n = prev + 1; n < cur.n; n++) emit('paramGap', cur.firstFrom, cur.firstFrom + String(cur.n).length + 2, { n: String(n) });
903
+ }
904
+
905
+ const IN = /\bin\s*\(([^()]*)\)/gi;
906
+ for (const m of text.matchAll(IN)) {
907
+ const inner = m[1]!;
908
+ const base = m.index! + m[0].length - 1 - inner.length;
909
+ for (const u of inner.matchAll(/\{(\d+)\}/g)) {
910
+ const at = base + u.index!;
911
+ if (markers.some((k) => at >= k.from && at < k.fragmentTo)) continue;
912
+ emit('unguardedIn', at, at + u[0].length, { n: u[1]! });
913
+ }
914
+ }
915
+ }
916
+
917
+ /** Finds silent logic errors in SQL text (Oracle-first, standard SQL tolerated). Pure; never throws. */
918
+ export function sqlLint(text: string, outline?: SqlOutline): SqlProblem[] {
919
+ const out: SqlProblem[] = [];
920
+ try {
921
+ const ol = outline ?? sqlOutline(text);
922
+ try {
923
+ lintMarkers(text, ol, out);
924
+ } catch {
925
+ // best effort
926
+ }
927
+ const all = tokenize(text);
928
+ try {
929
+ lintTerminators(all, ol, out);
930
+ } catch {
931
+ // best effort
932
+ }
933
+ let k = 0;
934
+ ol.statements.forEach((st, index) => {
935
+ while (k < all.length && all[k]!.from < st.from) k++;
936
+ const ts: Token[] = [];
937
+ while (k < all.length && all[k]!.to <= st.to) {
938
+ if (!TRIVIA.has(all[k]!.kind)) ts.push(all[k]!);
939
+ k++;
940
+ }
941
+ try {
942
+ lintStatement(ts, st, index, out);
943
+ } catch {
944
+ // best effort per statement
945
+ }
946
+ });
947
+ } catch {
948
+ return out;
949
+ }
950
+ return out.sort((x, y) => x.from - y.from || x.to - y.to);
951
+ }