@nocturnium/svelte-ide 1.1.1 → 1.2.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 (33) hide show
  1. package/dist/components/editor/CognitiveLoadMeter.svelte +27 -0
  2. package/dist/components/editor/ComplexityHeatLayer.svelte +157 -0
  3. package/dist/components/editor/ComplexityHeatLayer.svelte.d.ts +24 -0
  4. package/dist/components/editor/ComplexityLayer.svelte +325 -109
  5. package/dist/components/editor/ComplexityLayer.svelte.d.ts +13 -0
  6. package/dist/components/editor/ConflictZoneLayer.svelte +22 -15
  7. package/dist/components/editor/CustomEditor.svelte +80 -1
  8. package/dist/components/editor/CustomEditor.svelte.d.ts +3 -1
  9. package/dist/components/editor/EchoCursorLayer.svelte +60 -0
  10. package/dist/components/editor/PluginPreviewSandbox.svelte +43 -9
  11. package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +4 -4
  12. package/dist/components/editor/core/complexity-analyzer.d.ts +31 -0
  13. package/dist/components/editor/core/complexity-analyzer.js +479 -29
  14. package/dist/components/editor/core/conflict-predictor.d.ts +32 -0
  15. package/dist/components/editor/core/conflict-predictor.js +55 -0
  16. package/dist/components/editor/core/crdt-binding.d.ts +4 -0
  17. package/dist/components/editor/core/crdt-binding.js +34 -9
  18. package/dist/components/editor/core/echo-cursor.d.ts +18 -1
  19. package/dist/components/editor/core/echo-cursor.js +117 -6
  20. package/dist/components/editor/core/extract-function.d.ts +27 -0
  21. package/dist/components/editor/core/extract-function.js +865 -0
  22. package/dist/components/editor/core/index.d.ts +1 -0
  23. package/dist/components/editor/core/index.js +1 -0
  24. package/dist/components/editor/core/state.d.ts +38 -5
  25. package/dist/components/editor/core/state.js +175 -98
  26. package/dist/components/editor/core/timeline.js +6 -1
  27. package/dist/components/editor/editor-find.js +15 -3
  28. package/dist/components/editor/theme.d.ts +8 -0
  29. package/dist/components/editor/theme.js +52 -0
  30. package/dist/services/lsp-client.d.ts +3 -0
  31. package/dist/services/lsp-client.js +86 -14
  32. package/dist/styles/theme.css +4 -1
  33. package/package.json +1 -1
@@ -0,0 +1,865 @@
1
+ import { resolveLanguage, tokenize } from '../tokenizer';
2
+ const SUPPORTED_LANGUAGES = new Set(['javascript', 'typescript', 'jsx', 'tsx']);
3
+ const IDENTIFIER_TYPES = new Set(['variable', 'function.call', 'type.class']);
4
+ const DECLARATION_KEYWORDS = new Set(['let', 'const', 'var']);
5
+ const ASSIGNMENT_OPERATORS = new Set([
6
+ '=',
7
+ '+=',
8
+ '-=',
9
+ '*=',
10
+ '/=',
11
+ '%=',
12
+ '**=',
13
+ '&=',
14
+ '|=',
15
+ '^=',
16
+ '&&=',
17
+ '||=',
18
+ '??=',
19
+ '<<=',
20
+ '>>=',
21
+ '>>>='
22
+ ]);
23
+ const LOOP_OR_SWITCH_KEYWORDS = new Set(['for', 'while', 'do', 'switch']);
24
+ const GLOBALS = new Set([
25
+ 'console',
26
+ 'window',
27
+ 'document',
28
+ 'Math',
29
+ 'JSON',
30
+ 'Object',
31
+ 'Array',
32
+ 'Promise',
33
+ 'process',
34
+ 'globalThis',
35
+ 'undefined',
36
+ 'NaN',
37
+ 'Infinity',
38
+ 'true',
39
+ 'false',
40
+ 'null',
41
+ 'String',
42
+ 'Number',
43
+ 'Boolean',
44
+ 'Function',
45
+ 'Symbol',
46
+ 'Map',
47
+ 'Set',
48
+ 'WeakMap',
49
+ 'WeakSet',
50
+ 'Date',
51
+ 'RegExp',
52
+ 'Error',
53
+ 'TypeError',
54
+ 'ReferenceError',
55
+ 'SyntaxError',
56
+ 'Buffer'
57
+ ]);
58
+ export function planExtractFunction(input) {
59
+ try {
60
+ return planExtractFunctionUnsafe(input);
61
+ }
62
+ catch {
63
+ return { ok: false, reason: 'Could not safely determine the block inputs and outputs.' };
64
+ }
65
+ }
66
+ function planExtractFunctionUnsafe(input) {
67
+ const language = resolveLanguage(input.language);
68
+ if (!SUPPORTED_LANGUAGES.has(language)) {
69
+ return { ok: false, reason: 'Extract function supports JavaScript/TypeScript only.' };
70
+ }
71
+ const lines = input.lines;
72
+ const functionStart = input.region.startLine;
73
+ const functionEnd = input.region.endLine;
74
+ if (!isValidLineRange(lines, functionStart, functionEnd)) {
75
+ return { ok: false, reason: 'Nothing meaningful to extract.' };
76
+ }
77
+ const block = resolveBlock(input, functionStart, functionEnd);
78
+ if (!block || !isValidLineRange(lines, block.start, block.end)) {
79
+ return { ok: false, reason: 'Nothing meaningful to extract.' };
80
+ }
81
+ if (block.start <= functionStart || block.end >= functionEnd) {
82
+ return { ok: false, reason: 'Nothing meaningful to extract.' };
83
+ }
84
+ if (lines.slice(block.start, block.end + 1).every((line) => line.text.trim().length === 0)) {
85
+ return { ok: false, reason: 'Nothing meaningful to extract.' };
86
+ }
87
+ if (lines.slice(block.start, block.end + 1).every((line) => /^[\s{};]*$/.test(line.text))) {
88
+ return { ok: false, reason: 'Nothing meaningful to extract.' };
89
+ }
90
+ const tokenized = tokenize(lines.map((line) => line.text).join('\n'), language);
91
+ const tokensByLine = tokenized.map((line) => getCodeTokens(line.tokens));
92
+ const flatTokens = flattenTokens(tokensByLine, functionStart, functionEnd);
93
+ const blockTokens = flatTokens.filter((token) => token.line >= block.start && token.line <= block.end);
94
+ if (hasNameCollision(flatTokens)) {
95
+ return { ok: false, reason: 'A function named extracted already exists.' };
96
+ }
97
+ if (isGeneratorHeader(flatTokens, functionStart)) {
98
+ return { ok: false, reason: 'Selection would escape the new function.' };
99
+ }
100
+ const jumpRefusal = getJumpRefusal(blockTokens);
101
+ if (jumpRefusal)
102
+ return jumpRefusal;
103
+ if (hasLabelledStatement(blockTokens)) {
104
+ return { ok: false, reason: 'Block contains labelled jumps.' };
105
+ }
106
+ if (!hasBalancedDelimiters(blockTokens)) {
107
+ return { ok: false, reason: 'Selection is not a complete set of statements.' };
108
+ }
109
+ if (hasEscapingBreakOrContinue(blockTokens)) {
110
+ return { ok: false, reason: 'Selection would escape the new function.' };
111
+ }
112
+ const functionParams = getFunctionParams(flatTokens, functionStart);
113
+ const declaredBeforeB = new Set([
114
+ ...functionParams,
115
+ ...getDeclarations(flatTokens.filter((token) => token.line < block.start)).map((decl) => decl.name)
116
+ ]);
117
+ const insideDeclarations = getDeclarations(blockTokens);
118
+ const declaredInsideB = new Set(insideDeclarations.filter((decl) => decl.kind !== 'param').map((decl) => decl.name));
119
+ const usedInsideB = getIdentifierUses(blockTokens);
120
+ const usedAfterB = new Set(getIdentifierUses(flatTokens.filter((token) => token.line > block.end)).map((use) => use.name));
121
+ const laterVarDeclaration = getDeclarations(flatTokens.filter((token) => token.line > block.end && token.line <= functionEnd)).find((decl) => decl.kind === 'var' && usedInsideB.some((use) => use.name === decl.name));
122
+ if (laterVarDeclaration) {
123
+ return { ok: false, reason: 'Could not safely determine the block inputs and outputs.' };
124
+ }
125
+ for (const decl of insideDeclarations) {
126
+ if (usedAfterB.has(decl.name) && decl.depth > 0) {
127
+ return {
128
+ ok: false,
129
+ reason: 'A variable used after the block is only conditionally defined inside it.'
130
+ };
131
+ }
132
+ }
133
+ const params = uniqueByFirstPosition(usedInsideB.filter((use) => declaredBeforeB.has(use.name) && !isDeclaredInsideAtUse(use, insideDeclarations)));
134
+ const assignedInsideB = getAssignments(blockTokens, insideDeclarations);
135
+ const unmodelledAssignedOutput = assignedInsideB.find((name) => usedAfterB.has(name.name) &&
136
+ !declaredBeforeB.has(name.name) &&
137
+ !declaredInsideB.has(name.name));
138
+ if (unmodelledAssignedOutput) {
139
+ return { ok: false, reason: 'Could not safely determine the block inputs and outputs.' };
140
+ }
141
+ const returns = uniqueByFirstPosition(assignedInsideB.filter((name) => usedAfterB.has(name.name)));
142
+ const returnNames = returns.map((item) => item.name);
143
+ const declaredReturnNames = new Set(returnNames.filter((name) => declaredInsideB.has(name)));
144
+ const outerReturnNames = returnNames.filter((name) => !declaredInsideB.has(name));
145
+ if (returnNames.length > 1 && outerReturnNames.length > 0) {
146
+ return {
147
+ ok: false,
148
+ reason: 'Block returns multiple values including a reassigned outer variable; not supported.'
149
+ };
150
+ }
151
+ const functionText = buildFunctionText(lines, functionStart, block, params, returnNames);
152
+ const callText = buildCallText(params, returnNames, declaredReturnNames);
153
+ if (!callText) {
154
+ return {
155
+ ok: false,
156
+ reason: 'Block returns multiple values including a reassigned outer variable; not supported.'
157
+ };
158
+ }
159
+ return {
160
+ ok: true,
161
+ functionText,
162
+ callText,
163
+ params: params.map((param) => param.name),
164
+ returns: returnNames,
165
+ insertAfterLine: functionEnd
166
+ };
167
+ }
168
+ function getCodeTokens(tokens) {
169
+ return tokens.filter((token) => {
170
+ if (token.type === 'text')
171
+ return token.text.trim().length > 0;
172
+ if (token.type === 'comment' || token.type.startsWith('comment.'))
173
+ return false;
174
+ if (token.type === 'string' || token.type.startsWith('string.'))
175
+ return false;
176
+ return true;
177
+ });
178
+ }
179
+ function flattenTokens(tokensByLine, startLine, endLine) {
180
+ const flat = [];
181
+ for (let line = startLine; line <= endLine; line++) {
182
+ for (const token of tokensByLine[line] ?? []) {
183
+ flat.push({ ...token, line, index: flat.length });
184
+ }
185
+ }
186
+ return flat;
187
+ }
188
+ function isValidLineRange(lines, start, end) {
189
+ return (Number.isInteger(start) &&
190
+ Number.isInteger(end) &&
191
+ start >= 0 &&
192
+ end >= start &&
193
+ end < lines.length);
194
+ }
195
+ function resolveBlock(input, functionStart, functionEnd) {
196
+ if (input.blockStart !== undefined && input.blockEnd !== undefined) {
197
+ return { start: input.blockStart, end: input.blockEnd };
198
+ }
199
+ let end = functionEnd - 1;
200
+ while (end > functionStart && input.lines[end].text.trim().length === 0)
201
+ end--;
202
+ if (/^\s*return\b/.test(input.lines[end]?.text ?? ''))
203
+ end--;
204
+ return { start: functionStart + 1, end };
205
+ }
206
+ function getJumpRefusal(tokens) {
207
+ for (const token of tokens) {
208
+ if (token.text === 'return' || token.text === 'yield') {
209
+ return { ok: false, reason: 'Selection would escape the new function.' };
210
+ }
211
+ if (token.text === 'await') {
212
+ return {
213
+ ok: false,
214
+ reason: 'Block uses await; extracting async blocks is not supported yet.'
215
+ };
216
+ }
217
+ if (token.text === 'this' || token.text === 'arguments' || token.text === 'super') {
218
+ return {
219
+ ok: false,
220
+ reason: 'Block references this/arguments/super and cannot be safely extracted.'
221
+ };
222
+ }
223
+ if (token.text === 'new' && nextSignificant(tokens, token.index)?.text === '.') {
224
+ return {
225
+ ok: false,
226
+ reason: 'Block references this/arguments/super and cannot be safely extracted.'
227
+ };
228
+ }
229
+ }
230
+ return undefined;
231
+ }
232
+ function hasNameCollision(tokens) {
233
+ for (let i = 0; i < tokens.length; i++) {
234
+ if (tokens[i].text === 'function' && tokens[i + 1]?.text === 'extracted')
235
+ return true;
236
+ }
237
+ return false;
238
+ }
239
+ function isGeneratorHeader(tokens, functionStart) {
240
+ const header = tokens.filter((token) => token.line === functionStart);
241
+ const functionIndex = header.findIndex((token) => token.text === 'function');
242
+ if (functionIndex === -1)
243
+ return false;
244
+ const parenIndex = header.findIndex((token, index) => index > functionIndex && token.text === '(');
245
+ return header
246
+ .slice(functionIndex + 1, parenIndex === -1 ? header.length : parenIndex)
247
+ .some((token) => token.text === '*');
248
+ }
249
+ function hasBalancedDelimiters(tokens) {
250
+ let braces = 0;
251
+ let parens = 0;
252
+ let brackets = 0;
253
+ for (const token of tokens) {
254
+ if (token.text === '{')
255
+ braces++;
256
+ else if (token.text === '}')
257
+ braces--;
258
+ else if (token.text === '(')
259
+ parens++;
260
+ else if (token.text === ')')
261
+ parens--;
262
+ else if (token.text === '[')
263
+ brackets++;
264
+ else if (token.text === ']')
265
+ brackets--;
266
+ if (braces < 0 || parens < 0 || brackets < 0)
267
+ return false;
268
+ }
269
+ return braces === 0 && parens === 0 && brackets === 0;
270
+ }
271
+ function hasEscapingBreakOrContinue(tokens) {
272
+ for (let i = 0; i < tokens.length; i++) {
273
+ if (tokens[i].text !== 'break' && tokens[i].text !== 'continue')
274
+ continue;
275
+ const next = tokens[i + 1];
276
+ if (next && /^[A-Za-z_$][\w$]*$/.test(next.text))
277
+ return true;
278
+ if (!isInsideBlockOpenedInSelection(tokens, i, LOOP_OR_SWITCH_KEYWORDS))
279
+ return true;
280
+ }
281
+ return false;
282
+ }
283
+ function isInsideBlockOpenedInSelection(tokens, index, openers) {
284
+ const stack = [];
285
+ for (let i = 0; i < index; i++) {
286
+ const token = tokens[i];
287
+ if (token.text === '{') {
288
+ let j = i - 1;
289
+ while (j >= 0 && tokens[j].text !== ')' && tokens[j].text !== 'do')
290
+ j--;
291
+ while (j >= 0 && tokens[j].text !== '(' && tokens[j].text !== 'do')
292
+ j--;
293
+ const window = tokens.slice(Math.max(0, j - 3), i).map((item) => item.text);
294
+ const opener = window.find((text) => openers.has(text));
295
+ stack.push(opener ?? '');
296
+ }
297
+ else if (token.text === '}') {
298
+ stack.pop();
299
+ }
300
+ }
301
+ return stack.some((item) => item.length > 0);
302
+ }
303
+ function hasLabelledStatement(tokens) {
304
+ for (let i = 0; i < tokens.length - 1; i++) {
305
+ const token = tokens[i];
306
+ const previous = tokens[i - 1];
307
+ if (IDENTIFIER_TYPES.has(token.type) &&
308
+ tokens[i + 1].text === ':' &&
309
+ !isObjectLiteralKey(tokens, i) &&
310
+ (!previous || previous.text === ';' || previous.text === '{' || previous.text === '}')) {
311
+ return true;
312
+ }
313
+ if ((token.text === 'break' || token.text === 'continue') &&
314
+ /^[A-Za-z_$][\w$]*$/.test(tokens[i + 1].text)) {
315
+ return true;
316
+ }
317
+ }
318
+ return false;
319
+ }
320
+ function getFunctionParams(tokens, functionStart) {
321
+ const startIndex = tokens.findIndex((token) => token.line === functionStart && token.text === '(');
322
+ if (startIndex === -1)
323
+ return [];
324
+ const endIndex = findMatching(tokens, startIndex, '(', ')');
325
+ if (endIndex === -1)
326
+ return [];
327
+ return collectBindingNames(tokens.slice(startIndex + 1, endIndex));
328
+ }
329
+ function getDeclarations(tokens) {
330
+ const declarations = [];
331
+ for (let i = 0; i < tokens.length; i++) {
332
+ const token = tokens[i];
333
+ if (DECLARATION_KEYWORDS.has(token.text)) {
334
+ const end = findStatementEnd(tokens, i + 1);
335
+ const segment = tokens.slice(i + 1, end);
336
+ const names = collectDeclarationNames(segment);
337
+ for (const name of names) {
338
+ declarations.push({
339
+ ...name,
340
+ kind: token.text,
341
+ depth: getBraceDepthAt(tokens, name.line, name.col)
342
+ });
343
+ }
344
+ }
345
+ else if (token.text === 'function') {
346
+ const next = tokens[i + 1];
347
+ if (next && isIdentifierToken(next)) {
348
+ declarations.push({
349
+ name: next.text,
350
+ line: next.line,
351
+ col: next.start,
352
+ index: next.index,
353
+ depth: getBraceDepthAt(tokens, next.line, next.start),
354
+ kind: 'function'
355
+ });
356
+ }
357
+ const paramStart = tokens.findIndex((item, index) => index > i && item.text === '(');
358
+ if (paramStart !== -1) {
359
+ const paramEnd = findMatching(tokens, paramStart, '(', ')');
360
+ if (paramEnd !== -1) {
361
+ const scope = getFunctionParamScope(tokens, paramStart, paramEnd);
362
+ for (const name of collectBindingNamePositions(tokens.slice(paramStart + 1, paramEnd))) {
363
+ declarations.push({
364
+ ...name,
365
+ depth: getBraceDepthAt(tokens, name.line, name.col) + 1,
366
+ kind: 'param',
367
+ scope
368
+ });
369
+ }
370
+ }
371
+ }
372
+ }
373
+ else if (token.text === 'catch' && tokens[i + 1]?.text === '(') {
374
+ const paramEnd = findMatching(tokens, i + 1, '(', ')');
375
+ if (paramEnd !== -1) {
376
+ const scope = getCatchParamScope(tokens, i + 1, paramEnd);
377
+ for (const name of collectBindingNamePositions(tokens.slice(i + 2, paramEnd))) {
378
+ declarations.push({
379
+ ...name,
380
+ depth: getBraceDepthAt(tokens, name.line, name.col) + 1,
381
+ kind: 'param',
382
+ scope
383
+ });
384
+ }
385
+ }
386
+ }
387
+ else if (token.text === '=>') {
388
+ const previous = tokens[i - 1];
389
+ if (previous?.text === ')') {
390
+ const paramStart = findMatchingBackward(tokens, i - 1, '(', ')');
391
+ if (paramStart !== -1) {
392
+ const scope = getArrowParamScope(tokens, paramStart, i);
393
+ for (const name of collectBindingNamePositions(tokens.slice(paramStart + 1, i - 1))) {
394
+ declarations.push({
395
+ ...name,
396
+ depth: getBraceDepthAt(tokens, name.line, name.col) + 1,
397
+ kind: 'param',
398
+ scope
399
+ });
400
+ }
401
+ }
402
+ }
403
+ else if (isIdentifierToken(previous)) {
404
+ const scope = getArrowParamScope(tokens, i - 1, i);
405
+ declarations.push({
406
+ name: previous.text,
407
+ line: previous.line,
408
+ col: previous.start,
409
+ index: previous.index,
410
+ depth: getBraceDepthAt(tokens, previous.line, previous.start) + 1,
411
+ kind: 'param',
412
+ scope
413
+ });
414
+ }
415
+ }
416
+ }
417
+ return declarations;
418
+ }
419
+ function isDeclaredInsideAtUse(use, declarations) {
420
+ return declarations.some((decl) => {
421
+ if (decl.name !== use.name)
422
+ return false;
423
+ if (decl.kind !== 'param')
424
+ return true;
425
+ if (!decl.scope)
426
+ return use.index === decl.index;
427
+ return use.index >= decl.scope.startIndex && use.index <= decl.scope.endIndex;
428
+ });
429
+ }
430
+ function getFunctionParamScope(tokens, paramStart, paramEnd) {
431
+ const bodyStart = tokens.findIndex((item, index) => index > paramEnd && item.text === '{');
432
+ if (bodyStart === -1) {
433
+ return { startIndex: tokens[paramStart].index, endIndex: tokens[paramEnd].index };
434
+ }
435
+ const bodyEnd = findMatching(tokens, bodyStart, '{', '}');
436
+ return {
437
+ startIndex: tokens[paramStart].index,
438
+ endIndex: tokens[bodyEnd === -1 ? paramEnd : bodyEnd].index
439
+ };
440
+ }
441
+ function getCatchParamScope(tokens, paramStart, paramEnd) {
442
+ const bodyStart = tokens.findIndex((item, index) => index > paramEnd && item.text === '{');
443
+ if (bodyStart === -1) {
444
+ return { startIndex: tokens[paramStart].index, endIndex: tokens[paramEnd].index };
445
+ }
446
+ const bodyEnd = findMatching(tokens, bodyStart, '{', '}');
447
+ return {
448
+ startIndex: tokens[paramStart].index,
449
+ endIndex: tokens[bodyEnd === -1 ? paramEnd : bodyEnd].index
450
+ };
451
+ }
452
+ function getArrowParamScope(tokens, paramStart, arrowIndex) {
453
+ const bodyStart = arrowIndex + 1;
454
+ if (!tokens[bodyStart]) {
455
+ return { startIndex: tokens[paramStart].index, endIndex: tokens[arrowIndex].index };
456
+ }
457
+ if (tokens[bodyStart].text === '{') {
458
+ const bodyEnd = findMatching(tokens, bodyStart, '{', '}');
459
+ return {
460
+ startIndex: tokens[paramStart].index,
461
+ endIndex: tokens[bodyEnd === -1 ? bodyStart : bodyEnd].index
462
+ };
463
+ }
464
+ const bodyEnd = findArrowExpressionEnd(tokens, bodyStart);
465
+ return {
466
+ startIndex: tokens[paramStart].index,
467
+ endIndex: tokens[Math.max(bodyStart, bodyEnd)].index
468
+ };
469
+ }
470
+ function findArrowExpressionEnd(tokens, bodyStart) {
471
+ let paren = 0;
472
+ let bracket = 0;
473
+ let brace = 0;
474
+ for (let i = bodyStart; i < tokens.length; i++) {
475
+ const token = tokens[i];
476
+ if (paren === 0 &&
477
+ bracket === 0 &&
478
+ brace === 0 &&
479
+ (token.text === ',' ||
480
+ token.text === ';' ||
481
+ token.text === ')' ||
482
+ token.text === ']' ||
483
+ token.text === '}')) {
484
+ return i - 1;
485
+ }
486
+ if (token.text === '(')
487
+ paren++;
488
+ else if (token.text === ')')
489
+ paren--;
490
+ else if (token.text === '[')
491
+ bracket++;
492
+ else if (token.text === ']')
493
+ bracket--;
494
+ else if (token.text === '{')
495
+ brace++;
496
+ else if (token.text === '}')
497
+ brace--;
498
+ }
499
+ return tokens.length - 1;
500
+ }
501
+ function collectDeclarationNames(tokens) {
502
+ const names = [];
503
+ let paren = 0;
504
+ let bracket = 0;
505
+ let brace = 0;
506
+ let expectingName = true;
507
+ for (let i = 0; i < tokens.length; i++) {
508
+ const token = tokens[i];
509
+ if (token.text === '(')
510
+ paren++;
511
+ else if (token.text === ')')
512
+ paren--;
513
+ else if (token.text === '[')
514
+ bracket++;
515
+ else if (token.text === ']')
516
+ bracket--;
517
+ else if (token.text === '{')
518
+ brace++;
519
+ else if (token.text === '}')
520
+ brace--;
521
+ if ((token.text === '{' || token.text === '[') && expectingName && paren === 0) {
522
+ throw new Error('destructuring declaration');
523
+ }
524
+ if (expectingName && paren === 0 && bracket === 0 && brace === 0 && isIdentifierToken(token)) {
525
+ names.push({
526
+ name: token.text,
527
+ line: token.line,
528
+ col: token.start,
529
+ depth: 0,
530
+ index: token.index
531
+ });
532
+ expectingName = false;
533
+ continue;
534
+ }
535
+ if (paren === 0 && bracket === 0 && brace === 0 && token.text === ',') {
536
+ expectingName = true;
537
+ }
538
+ }
539
+ return names;
540
+ }
541
+ function collectBindingNames(tokens) {
542
+ return collectBindingNamePositions(tokens).map((name) => name.name);
543
+ }
544
+ function collectBindingNamePositions(tokens) {
545
+ const positions = [];
546
+ let paren = 0;
547
+ let bracket = 0;
548
+ let brace = 0;
549
+ let inType = false;
550
+ let expectingName = true;
551
+ for (const token of tokens) {
552
+ if (token.text === '<')
553
+ inType = true;
554
+ if (token.text === '>')
555
+ inType = false;
556
+ if (token.text === ':') {
557
+ inType = true;
558
+ continue;
559
+ }
560
+ if (token.text === ',' && paren === 0 && bracket === 0 && brace === 0) {
561
+ inType = false;
562
+ expectingName = true;
563
+ continue;
564
+ }
565
+ if (inType)
566
+ continue;
567
+ if (token.text === '(')
568
+ paren++;
569
+ else if (token.text === ')')
570
+ paren--;
571
+ else if (token.text === '[')
572
+ bracket++;
573
+ else if (token.text === ']')
574
+ bracket--;
575
+ else if (token.text === '{')
576
+ brace++;
577
+ else if (token.text === '}')
578
+ brace--;
579
+ if (expectingName && paren === 0 && bracket === 0 && brace === 0 && isIdentifierToken(token)) {
580
+ positions.push({
581
+ name: token.text,
582
+ line: token.line,
583
+ col: token.start,
584
+ depth: 0,
585
+ index: token.index
586
+ });
587
+ expectingName = false;
588
+ }
589
+ }
590
+ const seen = new Set();
591
+ return positions.filter((position) => {
592
+ if (seen.has(position.name))
593
+ return false;
594
+ seen.add(position.name);
595
+ return true;
596
+ });
597
+ }
598
+ function getIdentifierUses(tokens) {
599
+ const uses = [];
600
+ for (let i = 0; i < tokens.length; i++) {
601
+ const token = tokens[i];
602
+ if (!isIdentifierUse(tokens, i))
603
+ continue;
604
+ uses.push({
605
+ name: token.text,
606
+ line: token.line,
607
+ col: token.start,
608
+ index: token.index,
609
+ depth: getBraceDepthAt(tokens, token.line, token.start)
610
+ });
611
+ }
612
+ return uses;
613
+ }
614
+ function isIdentifierUse(tokens, index) {
615
+ const token = tokens[index];
616
+ if (!isIdentifierToken(token))
617
+ return false;
618
+ if (GLOBALS.has(token.text))
619
+ return false;
620
+ const previous = tokens[index - 1];
621
+ if (previous?.text === '.')
622
+ return false;
623
+ if (isObjectLiteralKey(tokens, index))
624
+ return false;
625
+ if (previous?.text === 'function')
626
+ return false;
627
+ if (previous && DECLARATION_KEYWORDS.has(previous.text))
628
+ return false;
629
+ if (token.type.startsWith('keyword') ||
630
+ token.type.startsWith('constant') ||
631
+ token.type === 'type.builtin')
632
+ return false;
633
+ return true;
634
+ }
635
+ function getAssignments(tokens, declarations) {
636
+ const assigned = declarations.map((decl) => ({
637
+ name: decl.name,
638
+ line: decl.line,
639
+ col: decl.col,
640
+ index: decl.index,
641
+ depth: decl.depth
642
+ }));
643
+ for (let i = 0; i < tokens.length; i++) {
644
+ if (isAssignmentOperatorToken(tokens, i)) {
645
+ const target = previousIdentifierInAssignment(tokens, i);
646
+ if (target)
647
+ assigned.push(target);
648
+ }
649
+ const incrementTarget = identifierInIncrement(tokens, i);
650
+ if (incrementTarget) {
651
+ assigned.push(incrementTarget);
652
+ }
653
+ }
654
+ return uniqueByFirstPosition(assigned);
655
+ }
656
+ function isAssignmentOperatorToken(tokens, index) {
657
+ const token = tokens[index];
658
+ if (!ASSIGNMENT_OPERATORS.has(token.text))
659
+ return false;
660
+ if (token.text !== '=')
661
+ return true;
662
+ const previous = tokens[index - 1];
663
+ const next = tokens[index + 1];
664
+ if (previous && ['=', '!', '<', '>', '=>'].includes(previous.text))
665
+ return false;
666
+ if (next && (next.text === '=' || next.text === '>'))
667
+ return false;
668
+ return token.type === 'operator';
669
+ }
670
+ function identifierInIncrement(tokens, index) {
671
+ const first = tokens[index];
672
+ const second = tokens[index + 1];
673
+ if (!first || !second)
674
+ return undefined;
675
+ if ((first.text !== '+' && first.text !== '-') || second.text !== first.text)
676
+ return undefined;
677
+ if (!areAdjacent(first, second))
678
+ return undefined;
679
+ const previous = tokens[index - 1];
680
+ const next = tokens[index + 2];
681
+ const target = isIdentifierToken(previous)
682
+ ? previous
683
+ : isIdentifierToken(next)
684
+ ? next
685
+ : undefined;
686
+ if (!target)
687
+ return undefined;
688
+ if (target === previous && tokens[index - 2]?.text === '.')
689
+ return undefined;
690
+ if (target === next && tokens[index + 3]?.text === '.')
691
+ return undefined;
692
+ return {
693
+ name: target.text,
694
+ line: target.line,
695
+ col: target.start,
696
+ index: target.index,
697
+ depth: getBraceDepthAt(tokens, target.line, target.start)
698
+ };
699
+ }
700
+ function previousIdentifierInAssignment(tokens, assignmentIndex) {
701
+ let i = assignmentIndex - 1;
702
+ let crossedMemberOrComputed = false;
703
+ while (i >= 0 && tokens[i].type === 'operator')
704
+ i--;
705
+ while (i >= 0 && (tokens[i].text === ')' || tokens[i].text === ']')) {
706
+ crossedMemberOrComputed = true;
707
+ const opener = tokens[i].text === ')' ? '(' : '[';
708
+ const closer = tokens[i].text;
709
+ let depth = 1;
710
+ i--;
711
+ while (i >= 0 && depth > 0) {
712
+ if (tokens[i].text === closer)
713
+ depth++;
714
+ else if (tokens[i].text === opener)
715
+ depth--;
716
+ i--;
717
+ }
718
+ while (i >= 0 && tokens[i].type === 'operator')
719
+ i--;
720
+ }
721
+ if (i >= 0 &&
722
+ isIdentifierToken(tokens[i]) &&
723
+ tokens[i - 1]?.text !== '.' &&
724
+ !crossedMemberOrComputed) {
725
+ return {
726
+ name: tokens[i].text,
727
+ line: tokens[i].line,
728
+ col: tokens[i].start,
729
+ index: tokens[i].index,
730
+ depth: getBraceDepthAt(tokens, tokens[i].line, tokens[i].start)
731
+ };
732
+ }
733
+ return undefined;
734
+ }
735
+ function isObjectLiteralKey(tokens, index) {
736
+ const previous = tokens[index - 1];
737
+ const next = tokens[index + 1];
738
+ return (isIdentifierToken(tokens[index]) &&
739
+ (previous?.text === '{' || previous?.text === ',') &&
740
+ next?.text === ':');
741
+ }
742
+ function uniqueByFirstPosition(items) {
743
+ const byName = new Map();
744
+ for (const item of [...items].sort((a, b) => a.line - b.line || a.col - b.col)) {
745
+ if (!byName.has(item.name))
746
+ byName.set(item.name, item);
747
+ }
748
+ return [...byName.values()];
749
+ }
750
+ function buildFunctionText(lines, functionStart, block, params, returnNames) {
751
+ const functionIndent = lines[functionStart].text.match(/^[ \t]*/)?.[0] ?? '';
752
+ const bodyIndent = `${functionIndent}\t`;
753
+ const blockLines = lines.slice(block.start, block.end + 1).map((line) => line.text);
754
+ const blockIndent = commonIndent(blockLines.filter((line) => line.trim().length > 0));
755
+ const reindented = blockLines.map((line) => line.trim().length === 0 ? bodyIndent : `${bodyIndent}${line.slice(blockIndent.length)}`);
756
+ const output = [
757
+ `${functionIndent}function extracted(${params.map((param) => param.name).join(', ')}) {`,
758
+ ...reindented
759
+ ];
760
+ if (returnNames.length === 1) {
761
+ output.push(`${bodyIndent}return ${returnNames[0]};`);
762
+ }
763
+ else if (returnNames.length > 1) {
764
+ output.push(`${bodyIndent}return { ${returnNames.join(', ')} };`);
765
+ }
766
+ output.push(`${functionIndent}}`);
767
+ return output.join('\n');
768
+ }
769
+ function buildCallText(params, returns, declaredReturnNames) {
770
+ const args = params.map((param) => param.name).join(', ');
771
+ const call = `extracted(${args})`;
772
+ if (returns.length === 0)
773
+ return `${call};`;
774
+ if (returns.length === 1) {
775
+ const name = returns[0];
776
+ return declaredReturnNames.has(name) ? `const ${name} = ${call};` : `${name} = ${call};`;
777
+ }
778
+ if (returns.every((name) => declaredReturnNames.has(name))) {
779
+ return `const { ${returns.join(', ')} } = ${call};`;
780
+ }
781
+ return undefined;
782
+ }
783
+ function commonIndent(lines) {
784
+ if (lines.length === 0)
785
+ return '';
786
+ let best = lines[0].match(/^[ \t]*/)?.[0] ?? '';
787
+ for (const line of lines.slice(1)) {
788
+ const indent = line.match(/^[ \t]*/)?.[0] ?? '';
789
+ let index = 0;
790
+ while (index < best.length && index < indent.length && best[index] === indent[index])
791
+ index++;
792
+ best = best.slice(0, index);
793
+ }
794
+ return best;
795
+ }
796
+ function getBraceDepthAt(tokens, line, col) {
797
+ let depth = 0;
798
+ for (const token of tokens) {
799
+ if (token.line > line || (token.line === line && token.start >= col))
800
+ break;
801
+ if (token.text === '{')
802
+ depth++;
803
+ else if (token.text === '}')
804
+ depth = Math.max(0, depth - 1);
805
+ }
806
+ return depth;
807
+ }
808
+ function findStatementEnd(tokens, start) {
809
+ let paren = 0;
810
+ let bracket = 0;
811
+ let brace = 0;
812
+ for (let i = start; i < tokens.length; i++) {
813
+ const token = tokens[i];
814
+ if (token.text === '(')
815
+ paren++;
816
+ else if (token.text === ')')
817
+ paren--;
818
+ else if (token.text === '[')
819
+ bracket++;
820
+ else if (token.text === ']')
821
+ bracket--;
822
+ else if (token.text === '{')
823
+ brace++;
824
+ else if (token.text === '}')
825
+ brace--;
826
+ else if (token.text === ';' && paren === 0 && bracket === 0 && brace === 0)
827
+ return i;
828
+ }
829
+ return tokens.length;
830
+ }
831
+ function findMatching(tokens, start, open, close) {
832
+ let depth = 0;
833
+ for (let i = start; i < tokens.length; i++) {
834
+ if (tokens[i].text === open)
835
+ depth++;
836
+ else if (tokens[i].text === close) {
837
+ depth--;
838
+ if (depth === 0)
839
+ return i;
840
+ }
841
+ }
842
+ return -1;
843
+ }
844
+ function findMatchingBackward(tokens, start, open, close) {
845
+ let depth = 0;
846
+ for (let i = start; i >= 0; i--) {
847
+ if (tokens[i].text === close)
848
+ depth++;
849
+ else if (tokens[i].text === open) {
850
+ depth--;
851
+ if (depth === 0)
852
+ return i;
853
+ }
854
+ }
855
+ return -1;
856
+ }
857
+ function nextSignificant(tokens, index) {
858
+ return tokens.find((token) => token.index > index);
859
+ }
860
+ function isIdentifierToken(token) {
861
+ return !!token && IDENTIFIER_TYPES.has(token.type);
862
+ }
863
+ function areAdjacent(left, right) {
864
+ return left.line === right.line && left.end === right.start;
865
+ }