@adecore/editor-react 0.39.0 → 0.40.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 (154) hide show
  1. package/dist/CallHierarchyPanel.js +30 -55
  2. package/dist/CodeAuthorsCard.js +0 -1
  3. package/dist/CompletionPopup.js +150 -181
  4. package/dist/DiffView.d.ts +1 -1
  5. package/dist/DiffView.js +2 -29
  6. package/dist/PickPopup.js +54 -80
  7. package/dist/SplitDiffView.js +27 -57
  8. package/dist/SymbolPicker.js +2 -8
  9. package/dist/code-actions-model.js +2 -9
  10. package/dist/code-actions.js +10 -11
  11. package/dist/code-vision.js +2 -1
  12. package/dist/completion-model.js +2 -2
  13. package/dist/completion.js +13 -34
  14. package/dist/definition-link.d.ts +1 -0
  15. package/dist/definition-link.js +22 -18
  16. package/dist/diagnostics-model.d.ts +1 -0
  17. package/dist/diagnostics-model.js +3 -1
  18. package/dist/diagnostics.js +1 -2
  19. package/dist/diff-syntax.d.ts +1 -0
  20. package/dist/diff-syntax.js +31 -0
  21. package/dist/doc-comment-model.d.ts +1 -0
  22. package/dist/doc-comment-model.js +1 -1
  23. package/dist/folding.js +2 -3
  24. package/dist/format-on-type.js +6 -10
  25. package/dist/hover-content.d.ts +0 -2
  26. package/dist/hover-content.js +1 -9
  27. package/dist/inlay-hints.d.ts +0 -1
  28. package/dist/inlay-hints.js +11 -41
  29. package/dist/navigation.js +4 -14
  30. package/dist/peek.js +2 -12
  31. package/dist/popup-focus.d.ts +3 -1
  32. package/dist/popup-focus.js +33 -0
  33. package/dist/recent-choices.d.ts +0 -1
  34. package/dist/recent-choices.js +0 -4
  35. package/dist/rendered-docs.d.ts +1 -0
  36. package/dist/rendered-docs.js +15 -28
  37. package/dist/snippet-field-transforms.d.ts +5 -0
  38. package/dist/snippet-field-transforms.js +27 -16
  39. package/dist/snippet-session.js +8 -20
  40. package/dist/symbol-links.js +3 -10
  41. package/dist/symbol-picker-model.d.ts +1 -0
  42. package/dist/symbol-picker-model.js +7 -0
  43. package/dist/unique.d.ts +10 -0
  44. package/dist/unique.js +15 -0
  45. package/package.json +9 -9
  46. package/src/AnchoredPopup.tsx +2 -0
  47. package/src/BaselineStatus.tsx +1 -0
  48. package/src/CallHierarchyPanel.tsx +16 -6
  49. package/src/ChangeReview.tsx +7 -0
  50. package/src/CodeAuthorsCard.tsx +2 -5
  51. package/src/CompletionPopup.tsx +21 -15
  52. package/src/DiffView.tsx +53 -30
  53. package/src/EditorContextMenu.tsx +7 -0
  54. package/src/EditorView.tsx +28 -0
  55. package/src/FindReplace.tsx +8 -0
  56. package/src/HoverCard.tsx +8 -0
  57. package/src/HoverSections.tsx +11 -0
  58. package/src/LanguagePopups.tsx +1 -0
  59. package/src/PanelResizeHandle.tsx +8 -0
  60. package/src/PathText.tsx +1 -0
  61. package/src/PeekPanel.tsx +13 -0
  62. package/src/PickPopup.tsx +7 -6
  63. package/src/RenameCard.tsx +13 -0
  64. package/src/RenderedDocs.tsx +11 -0
  65. package/src/SplitDiffView.tsx +91 -56
  66. package/src/SymbolPicker.tsx +10 -18
  67. package/src/call-hierarchy-model.ts +10 -0
  68. package/src/call-hierarchy.ts +82 -0
  69. package/src/code-actions-model.ts +21 -9
  70. package/src/code-actions.ts +83 -7
  71. package/src/code-authors.ts +20 -0
  72. package/src/code-vision-model.ts +37 -0
  73. package/src/code-vision.ts +47 -3
  74. package/src/completion-call.ts +19 -0
  75. package/src/completion-model.ts +58 -2
  76. package/src/completion.ts +156 -30
  77. package/src/conflict-model.ts +4 -0
  78. package/src/context-menu-rows.ts +1 -0
  79. package/src/context-menu.ts +7 -0
  80. package/src/definition-link.ts +42 -18
  81. package/src/diagnostics-model.ts +19 -1
  82. package/src/diagnostics.ts +31 -2
  83. package/src/diff-expansion.ts +9 -0
  84. package/src/diff-overlays.tsx +8 -0
  85. package/src/diff-syntax.ts +54 -0
  86. package/src/diff-wordmarks.ts +8 -0
  87. package/src/doc-comment-model.ts +51 -1
  88. package/src/editor-language.ts +11 -0
  89. package/src/find-query.ts +7 -0
  90. package/src/folding.ts +10 -3
  91. package/src/format-on-type.ts +23 -10
  92. package/src/highlight-layers.ts +3 -0
  93. package/src/highlights.ts +18 -0
  94. package/src/history.ts +11 -0
  95. package/src/holder.ts +3 -0
  96. package/src/hover-content.ts +34 -17
  97. package/src/hover.ts +57 -0
  98. package/src/index.ts +4 -0
  99. package/src/inlay-hints.ts +86 -47
  100. package/src/key-priority.ts +2 -0
  101. package/src/line-edits.ts +8 -0
  102. package/src/models.ts +6 -0
  103. package/src/navigation-history.ts +5 -0
  104. package/src/navigation.ts +28 -13
  105. package/src/panel-size.ts +7 -0
  106. package/src/patch-decorations.ts +39 -0
  107. package/src/patch-layout.ts +47 -0
  108. package/src/peek-model.ts +21 -0
  109. package/src/peek.ts +49 -13
  110. package/src/pick.ts +35 -0
  111. package/src/popup-focus.ts +11 -1
  112. package/src/popup-placement.ts +5 -0
  113. package/src/popups.ts +2 -6
  114. package/src/project-language.ts +33 -0
  115. package/src/project-problems.ts +11 -0
  116. package/src/proposal-model.ts +40 -0
  117. package/src/recent-choices.ts +3 -5
  118. package/src/refresher.ts +3 -0
  119. package/src/rename-model.ts +28 -0
  120. package/src/rename.ts +56 -0
  121. package/src/rendered-docs.ts +89 -19
  122. package/src/review-group.ts +4 -0
  123. package/src/row-host.ts +12 -0
  124. package/src/selection-ranges.ts +6 -0
  125. package/src/semantic-delta.ts +7 -0
  126. package/src/semantic-model.ts +11 -0
  127. package/src/semantic-tokens.ts +37 -0
  128. package/src/shortcut-keys.ts +3 -0
  129. package/src/signature-model.ts +7 -0
  130. package/src/signature.ts +27 -0
  131. package/src/snippet-field-transforms.ts +48 -13
  132. package/src/snippet-indent.ts +10 -0
  133. package/src/snippet-range-order.ts +5 -0
  134. package/src/snippet-session.ts +173 -31
  135. package/src/snippet-transform-client.ts +14 -0
  136. package/src/snippet-transform-format.ts +98 -0
  137. package/src/snippet-transform-protocol.ts +5 -0
  138. package/src/snippet-transform-queue.ts +53 -0
  139. package/src/snippet-transform-worker.ts +14 -0
  140. package/src/snippet-variables.ts +9 -0
  141. package/src/snippet.ts +107 -0
  142. package/src/split-alignment.ts +10 -0
  143. package/src/split-scroll.ts +8 -0
  144. package/src/symbol-links.ts +9 -12
  145. package/src/symbol-picker-model.ts +25 -0
  146. package/src/symbol-picker.ts +9 -0
  147. package/src/symbols.ts +21 -0
  148. package/src/testing.ts +7 -0
  149. package/src/timers.ts +5 -0
  150. package/src/unique.ts +23 -0
  151. package/src/use-problem-counts.ts +4 -0
  152. package/src/view-state.ts +9 -0
  153. package/src/word-diff.ts +5 -0
  154. package/src/workspace-edit.ts +84 -0
@@ -1,33 +1,41 @@
1
1
  import { createWorkerRequestClient, WorkerRequestError, type WorkerRequestPort } from '@adecore/editor/worker-client';
2
2
  import { validateSnippetTransformJobs } from './snippet-transform-format.ts';
3
3
  import { MAX_TRANSFORM_CHARACTERS, type SnippetTransformInput, type SnippetTransformOutput, type SnippetTransformJob } from './snippet-transform-protocol.ts';
4
+
4
5
  export type SnippetTransformErrorKind = 'syntax' | 'range' | 'error' | 'timeout' | 'superseded' | 'unavailable' | 'disposed';
6
+
5
7
  export class SnippetTransformError extends Error {
6
8
  readonly kind: SnippetTransformErrorKind;
9
+
7
10
  constructor(kind: SnippetTransformErrorKind, message: string) {
8
11
  super(message);
9
12
  this.kind = kind;
10
13
  this.name = 'SnippetTransformError';
11
14
  }
12
15
  }
16
+
13
17
  export interface SnippetTransformClientOptions {
14
18
  readonly workerFactory: () => WorkerRequestPort;
15
19
  readonly deadline?: number;
16
20
  readonly schedule?: (callback: () => void, delay: number) => () => void;
17
21
  }
22
+
18
23
  export interface SnippetTransformClient {
19
24
  transform(jobs: readonly SnippetTransformJob[]): Promise<readonly string[]>;
20
25
  cancel(): void;
21
26
  release(): void;
22
27
  dispose(): void;
23
28
  }
29
+
24
30
  export function createSnippetTransformClient(options: SnippetTransformClientOptions): SnippetTransformClient {
25
31
  const transport = createWorkerRequestClient<SnippetTransformInput, Exclude<SnippetTransformOutput, { type: 'ready' }>>({
26
32
  ...options,
27
33
  failure: (reply) =>
28
34
  reply.type === 'error' ? { error: new SnippetTransformError(reply.kind, reply.message), restart: reply.kind === 'error' } : undefined
29
35
  });
36
+
30
37
  let disposed = false;
38
+
31
39
  const failure = (error: unknown): SnippetTransformError =>
32
40
  error instanceof SnippetTransformError
33
41
  ? error
@@ -37,21 +45,26 @@ export function createSnippetTransformClient(options: SnippetTransformClientOpti
37
45
  error instanceof SyntaxError ? 'syntax' : error instanceof RangeError ? 'range' : 'error',
38
46
  error instanceof Error ? error.message : String(error)
39
47
  );
48
+
40
49
  return {
41
50
  transform(jobs) {
42
51
  if (disposed) {
43
52
  return Promise.reject(new SnippetTransformError('disposed', 'The snippet transform client was disposed.'));
44
53
  }
54
+
45
55
  if (Array.isArray(jobs) && jobs.length === 0) {
46
56
  return Promise.resolve([]);
47
57
  }
58
+
48
59
  let copied: SnippetTransformJob[];
60
+
49
61
  try {
50
62
  validateSnippetTransformJobs(jobs);
51
63
  copied = jobs.map((job) => ({ ...job, format: job.format.map((node) => ({ ...node })) }));
52
64
  } catch (error) {
53
65
  return Promise.reject(failure(error));
54
66
  }
67
+
55
68
  return transport
56
69
  .request((id) => ({ type: 'transform', id, jobs: copied }))
57
70
  .then((reply) => {
@@ -64,6 +77,7 @@ export function createSnippetTransformClient(options: SnippetTransformClientOpti
64
77
  ) {
65
78
  throw new SnippetTransformError('error', 'Invalid snippet transform response.');
66
79
  }
80
+
67
81
  return reply.values;
68
82
  })
69
83
  .catch((error) => {
@@ -11,85 +11,116 @@ import {
11
11
  type SnippetTransformFormatNode,
12
12
  type SnippetTransformJob
13
13
  } from './snippet-transform-protocol.ts';
14
+
14
15
  const CASES: ReadonlySet<string> = new Set(['upcase', 'downcase', 'capitalize', 'camelcase', 'pascalcase']);
16
+
15
17
  function readFormat(source: string, start: number, stopAtSlash: boolean): { nodes: readonly SnippetTransformFormatNode[]; end: number } {
16
18
  const nodes: SnippetTransformFormatNode[] = [];
19
+
17
20
  let at = start,
18
21
  text = '';
22
+
19
23
  const bound = (): void => {
20
24
  if (at - start > MAX_TRANSFORM_FORMAT) {
21
25
  throw new RangeError('The transform format is too long.');
22
26
  }
23
27
  };
28
+
24
29
  const push = (node: SnippetTransformFormatNode): void => {
25
30
  if (nodes.length >= MAX_TRANSFORM_NODES) {
26
31
  throw new RangeError('The transform format has too many nodes.');
27
32
  }
33
+
28
34
  nodes.push(node);
29
35
  };
36
+
30
37
  const flush = (): void => {
31
38
  if (text !== '') {
32
39
  push({ kind: 'text', text });
33
40
  text = '';
34
41
  }
35
42
  };
43
+
36
44
  const string = (colon = false): string => {
37
45
  let value = '';
46
+
38
47
  while (at < source.length && source[at] !== '}' && !(colon && source[at] === ':')) {
39
48
  if (source[at] === '\\') {
40
49
  if (!['$', '}', '\\'].includes(source[at + 1] ?? '')) {
41
50
  throw new SyntaxError('Invalid conditional transform escape.');
42
51
  }
52
+
43
53
  at++;
44
54
  }
55
+
45
56
  value += source[at++]!;
46
57
  bound();
47
58
  }
59
+
48
60
  return value;
49
61
  };
62
+
50
63
  while (at < source.length && !(stopAtSlash && source[at] === '/')) {
51
64
  bound();
65
+
52
66
  if (source[at] === '\\' && (source[at + 1] === '\\' || source[at + 1] === '/')) {
53
67
  at++;
54
68
  text += source[at++]!;
69
+
55
70
  continue;
56
71
  }
72
+
57
73
  if (source[at] !== '$') {
58
74
  text += source[at++]!;
75
+
59
76
  continue;
60
77
  }
78
+
61
79
  at++;
62
80
  const brace = source[at] === '{';
81
+
63
82
  if (brace) {
64
83
  at++;
65
84
  }
85
+
66
86
  const start = at;
87
+
67
88
  while (at < source.length && source.charCodeAt(at) >= 48 && source.charCodeAt(at) <= 57) {
68
89
  at++;
69
90
  }
91
+
70
92
  if (at === start) {
71
93
  if (brace) {
72
94
  throw new SyntaxError('A transform format needs a capture index.');
73
95
  }
96
+
74
97
  text += '$';
98
+
75
99
  continue;
76
100
  }
101
+
77
102
  const index = Number(source.slice(start, at));
103
+
78
104
  if (index > 99) {
79
105
  throw new RangeError('Transform captures are limited to 99.');
80
106
  }
107
+
81
108
  flush();
82
109
  let node: SnippetTransformFormatNode = { kind: 'capture', index };
110
+
83
111
  if (brace) {
84
112
  if (source[at] === ':') {
85
113
  at++;
86
114
  const operator = source[at];
115
+
87
116
  if (operator === '/') {
88
117
  at++;
89
118
  const operation = string();
119
+
90
120
  if (!CASES.has(operation)) {
91
121
  throw new SyntaxError('Unknown transform case operation.');
92
122
  }
123
+
93
124
  node = { kind: 'capture', index, case: operation as SnippetTransformCase };
94
125
  } else if (operator === '+') {
95
126
  at++;
@@ -97,33 +128,43 @@ function readFormat(source: string, start: number, stopAtSlash: boolean): { node
97
128
  } else if (operator === '?') {
98
129
  at++;
99
130
  const when = string(true);
131
+
100
132
  if (source[at++] !== ':') {
101
133
  throw new SyntaxError('A conditional transform needs both branches.');
102
134
  }
135
+
103
136
  node = { kind: 'conditional', index, if: when, else: string() };
104
137
  } else {
105
138
  if (operator === '-') {
106
139
  at++;
107
140
  }
141
+
108
142
  node = { kind: 'conditional', index, else: string() };
109
143
  }
110
144
  }
145
+
111
146
  if (source[at++] !== '}') {
112
147
  throw new SyntaxError('An unclosed transform capture cannot be applied.');
113
148
  }
114
149
  }
150
+
115
151
  push(node);
116
152
  }
153
+
117
154
  flush();
118
155
  bound();
156
+
119
157
  if (stopAtSlash && source[at] !== '/') {
120
158
  throw new SyntaxError('Unclosed snippet transform format.');
121
159
  }
160
+
122
161
  return { nodes, end: at };
123
162
  }
163
+
124
164
  export function compileSnippetTransformFormat(source: string): readonly SnippetTransformFormatNode[] {
125
165
  return readFormat(source, 0, false).nodes;
126
166
  }
167
+
127
168
  export function scanSnippetTransform(
128
169
  source: string,
129
170
  at: number
@@ -131,67 +172,90 @@ export function scanSnippetTransform(
131
172
  if (source[at++] !== '/') {
132
173
  throw new SyntaxError('A snippet transform needs an opening slash.');
133
174
  }
175
+
134
176
  let pattern = '';
177
+
135
178
  while (at < source.length && source[at] !== '/') {
136
179
  const character = source[at++]!;
180
+
137
181
  if (character === '\\' && source[at] === '/') {
138
182
  pattern += source[at++]!;
139
183
  } else {
140
184
  pattern += character;
141
185
  }
186
+
142
187
  if (pattern.length > MAX_TRANSFORM_PATTERN) {
143
188
  throw new RangeError('The snippet transform pattern is too long.');
144
189
  }
145
190
  }
191
+
146
192
  if (source[at++] !== '/') {
147
193
  throw new SyntaxError('Unclosed snippet transform pattern.');
148
194
  }
195
+
149
196
  const parsed = readFormat(source, at, true);
150
197
  at = parsed.end + 1;
151
198
  let flags = '';
199
+
152
200
  while (at < source.length && source[at] !== '}') {
153
201
  const flag = source[at++]!;
202
+
154
203
  if (!'gimsu'.includes(flag) || flags.includes(flag)) {
155
204
  throw new SyntaxError('Invalid snippet transform flags.');
156
205
  }
206
+
157
207
  flags += flag;
158
208
  }
209
+
159
210
  if (source[at++] !== '}') {
160
211
  throw new SyntaxError('Unclosed snippet transform flags.');
161
212
  }
213
+
162
214
  return { pattern, flags, format: parsed.nodes, end: at };
163
215
  }
216
+
164
217
  function capitalize(value: string): string {
165
218
  const first = value.slice(0, (value.codePointAt(0) ?? 0) > 0xffff ? 2 : 1);
219
+
166
220
  return first.toUpperCase() + value.slice(first.length);
167
221
  }
222
+
168
223
  function changeCase(value: string, operation?: SnippetTransformCase): string {
169
224
  if (operation === 'upcase') {
170
225
  return value.toUpperCase();
171
226
  }
227
+
172
228
  if (operation === 'downcase') {
173
229
  return value.toLowerCase();
174
230
  }
231
+
175
232
  if (operation === 'capitalize') {
176
233
  return capitalize(value);
177
234
  }
235
+
178
236
  if (operation === 'camelcase' || operation === 'pascalcase') {
179
237
  const words = value.match(/[\p{L}0-9]+/gu);
238
+
180
239
  if (words === null) {
181
240
  return value;
182
241
  }
242
+
183
243
  return words
184
244
  .map((word, index) => (operation === 'camelcase' && index === 0 ? word.slice(0, 1).toLowerCase() + word.slice(1) : capitalize(word)))
185
245
  .join('');
186
246
  }
247
+
187
248
  return value;
188
249
  }
250
+
189
251
  export function validateSnippetTransformJobs(jobs: readonly SnippetTransformJob[]): void {
190
252
  if (!Array.isArray(jobs) || jobs.length > MAX_TRANSFORM_JOBS) {
191
253
  throw new RangeError('Too many snippet transform jobs.');
192
254
  }
255
+
193
256
  let characters = 0;
194
257
  const patterns = new Set<string>();
258
+
195
259
  for (const job of jobs) {
196
260
  if (
197
261
  typeof job.value !== 'string' ||
@@ -201,10 +265,13 @@ export function validateSnippetTransformJobs(jobs: readonly SnippetTransformJob[
201
265
  ) {
202
266
  throw new RangeError('A snippet transform value or pattern is too large.');
203
267
  }
268
+
204
269
  characters += job.value.length + job.pattern.length;
270
+
205
271
  if (characters > MAX_TRANSFORM_CHARACTERS || !Array.isArray(job.format) || job.format.length > MAX_TRANSFORM_NODES) {
206
272
  throw new RangeError('The snippet transform batch is too large.');
207
273
  }
274
+
208
275
  if (
209
276
  typeof job.flags !== 'string' ||
210
277
  Array.from(job.flags as string).some((flag) => !'gimsu'.includes(flag)) ||
@@ -212,38 +279,49 @@ export function validateSnippetTransformJobs(jobs: readonly SnippetTransformJob[
212
279
  ) {
213
280
  throw new SyntaxError('Snippet transforms support the flags g, i, m, s and u once each.');
214
281
  }
282
+
215
283
  let formatLength = 0;
284
+
216
285
  for (const node of job.format) {
217
286
  if (node.kind === 'text') {
218
287
  if (typeof node.text !== 'string') {
219
288
  throw new SyntaxError('Invalid transform text.');
220
289
  }
290
+
221
291
  formatLength += node.text.length;
222
292
  } else if (node.kind === 'capture' || node.kind === 'conditional') {
223
293
  if (!Number.isInteger(node.index) || node.index < 0 || node.index > 99) {
224
294
  throw new RangeError('Invalid transform capture index.');
225
295
  }
296
+
226
297
  if (node.kind === 'capture' && node.case !== undefined && !CASES.has(node.case)) {
227
298
  throw new SyntaxError('Invalid transform case operation.');
228
299
  }
300
+
229
301
  if (node.kind === 'conditional') {
230
302
  if ((node.if !== undefined && typeof node.if !== 'string') || (node.else !== undefined && typeof node.else !== 'string')) {
231
303
  throw new SyntaxError('Invalid transform branches.');
232
304
  }
305
+
233
306
  formatLength += (node.if?.length ?? 0) + (node.else?.length ?? 0);
234
307
  }
235
308
  } else {
236
309
  throw new SyntaxError('Invalid transform format node.');
237
310
  }
238
311
  }
312
+
239
313
  if (formatLength > MAX_TRANSFORM_FORMAT) {
240
314
  throw new RangeError('The transform format is too long.');
241
315
  }
316
+
242
317
  characters += formatLength;
318
+
243
319
  if (characters > MAX_TRANSFORM_CHARACTERS) {
244
320
  throw new RangeError('The snippet transform batch is too large.');
245
321
  }
322
+
246
323
  const key = job.flags + '\0' + job.pattern;
324
+
247
325
  if (!patterns.has(key)) {
248
326
  // Compilation validates syntax; only the worker executes patterns supplied by a server.
249
327
  new RegExp(job.pattern, job.flags);
@@ -251,19 +329,24 @@ export function validateSnippetTransformJobs(jobs: readonly SnippetTransformJob[
251
329
  }
252
330
  }
253
331
  }
332
+
254
333
  export interface SnippetTransformBudget {
255
334
  remaining: number;
256
335
  matches: number;
257
336
  }
337
+
258
338
  export function createSnippetTransformBudget(): SnippetTransformBudget {
259
339
  return { remaining: MAX_TRANSFORM_WORK, matches: 0 };
260
340
  }
341
+
261
342
  function spend(budget: SnippetTransformBudget, amount: number): void {
262
343
  budget.remaining -= amount;
344
+
263
345
  if (budget.remaining < 0) {
264
346
  throw new RangeError('The snippet transform work budget was exceeded.');
265
347
  }
266
348
  }
349
+
267
350
  export function applySnippetTransform(
268
351
  job: SnippetTransformJob,
269
352
  maximum = MAX_TRANSFORM_CHARACTERS,
@@ -273,53 +356,68 @@ export function applySnippetTransform(
273
356
  expression.lastIndex = 0;
274
357
  spend(budget, job.value.length);
275
358
  const pieces: string[] = [];
359
+
276
360
  let at = 0,
277
361
  length = 0,
278
362
  matches = 0;
363
+
279
364
  const append = (text: string): void => {
280
365
  if (text === '') {
281
366
  return;
282
367
  }
368
+
283
369
  length += text.length;
370
+
284
371
  if (length > maximum) {
285
372
  throw new RangeError('The snippet transform output is too large.');
286
373
  }
374
+
287
375
  pieces.push(text);
288
376
  };
377
+
289
378
  const format = (captures: readonly (string | undefined)[]): void => {
290
379
  for (const node of job.format) {
291
380
  spend(budget, 1);
381
+
292
382
  if (node.kind === 'text') {
293
383
  append(node.text);
294
384
  } else {
295
385
  const capture = captures[node.index] ?? '';
386
+
296
387
  if (node.kind === 'capture' && node.case !== undefined) {
297
388
  spend(budget, capture.length);
298
389
  }
390
+
299
391
  append(node.kind === 'capture' ? changeCase(capture, node.case) : capture !== '' ? (node.if ?? capture) : (node.else ?? ''));
300
392
  }
301
393
  }
302
394
  };
395
+
303
396
  for (let match = expression.exec(job.value); match !== null; match = expression.exec(job.value)) {
304
397
  if (++budget.matches > MAX_TRANSFORM_MATCHES) {
305
398
  throw new RangeError('The snippet transform batch has too many matches.');
306
399
  }
400
+
307
401
  matches++;
308
402
  spend(budget, 1);
309
403
  append(job.value.slice(at, match.index));
310
404
  format(match);
311
405
  at = match.index + match[0].length;
406
+
312
407
  if (!expression.global) {
313
408
  break;
314
409
  }
410
+
315
411
  if (match[0].length === 0) {
316
412
  expression.lastIndex += expression.unicode && (job.value.codePointAt(expression.lastIndex) ?? 0) > 0xffff ? 2 : 1;
317
413
  }
318
414
  }
415
+
319
416
  if (matches === 0 && job.format.some((node) => node.kind === 'conditional' && Boolean(node.else))) {
320
417
  format([]);
321
418
  } else {
322
419
  append(job.value.slice(at));
323
420
  }
421
+
324
422
  return pieces.join('');
325
423
  }
@@ -6,18 +6,23 @@ export const MAX_TRANSFORM_WORK = 1_000_000;
6
6
  export const MAX_TRANSFORM_PATTERN = 1024;
7
7
  export const MAX_TRANSFORM_FORMAT = 10_000;
8
8
  export const MAX_TRANSFORM_NODES = 1000;
9
+
9
10
  export type SnippetTransformCase = 'upcase' | 'downcase' | 'capitalize' | 'camelcase' | 'pascalcase';
11
+
10
12
  export type SnippetTransformFormatNode =
11
13
  | { readonly kind: 'text'; readonly text: string }
12
14
  | { readonly kind: 'capture'; readonly index: number; readonly case?: SnippetTransformCase }
13
15
  | { readonly kind: 'conditional'; readonly index: number; readonly if?: string; readonly else?: string };
16
+
14
17
  export interface SnippetTransformJob {
15
18
  readonly value: string;
16
19
  readonly pattern: string;
17
20
  readonly flags: string;
18
21
  readonly format: readonly SnippetTransformFormatNode[];
19
22
  }
23
+
20
24
  export type SnippetTransformInput = { readonly type: 'transform'; readonly id: number; readonly jobs: readonly SnippetTransformJob[] };
25
+
21
26
  export type SnippetTransformOutput =
22
27
  | { readonly type: 'ready' }
23
28
  | { readonly type: 'transformed'; readonly id: number; readonly values: readonly string[] }