@nocturnium/svelte-ide 1.0.1 → 1.0.2

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.
@@ -45,8 +45,19 @@ export declare class JavaScriptTokenizer {
45
45
  */
46
46
  private tryMatchRegex;
47
47
  private tokenizeString;
48
- private tokenizeTemplateLiteral;
49
- private tokenizeTemplateLiteralContinuation;
48
+ /**
49
+ * Scan the string portion of a template literal, starting at `startPos`. Emits
50
+ * one `string.template` token for the run of literal characters and stops at one
51
+ * of three boundaries:
52
+ * - a closing backtick → ends the literal (clears template state);
53
+ * - a `${` → emits the `${` delimiter and enters interpolation
54
+ * (templateDepth = 1) so the expression is tokenized as code by the caller;
55
+ * - end of line → the literal spans lines and continues next line.
56
+ *
57
+ * `isStart` is true when this is the opening backtick (vs. a continuation of a
58
+ * multi-line literal or the resumption after a `${…}` interpolation).
59
+ */
60
+ private scanTemplateString;
50
61
  private tokenizeJSXTag;
51
62
  }
52
63
  export declare function createJavaScriptTokenizer(): JavaScriptTokenizer;
@@ -263,22 +263,39 @@ export class JavaScriptTokenizer {
263
263
  return { lineNumber, tokens, text: line, state };
264
264
  }
265
265
  }
266
- // Handle template literal continuation
267
- if (state.inTemplateLiteral && (state.templateDepth ?? 0) > 0) {
268
- const result = this.tokenizeTemplateLiteralContinuation(line, pos, state);
269
- tokens.push(...result.tokens);
270
- pos = result.pos;
271
- if (pos >= line.length) {
272
- return { lineNumber, tokens, text: line, state };
273
- }
274
- }
275
266
  while (pos < line.length) {
267
+ // Template literals: scan the string portion (between the backticks and
268
+ // `${` / `}`) as a single string.template token. The interpolation
269
+ // expression is tokenized as ordinary code below, tracking brace depth in
270
+ // state.templateDepth so the closing backtick is recognised. This fixes the
271
+ // leak where an unclosed template state bled onto every following line.
272
+ const inStringPortion = state.inTemplateLiteral && (state.templateDepth ?? 0) === 0;
273
+ const startsTemplate = !state.inTemplateLiteral && line[pos] === '`';
274
+ if (inStringPortion || startsTemplate) {
275
+ const result = this.scanTemplateString(line, pos, state, startsTemplate);
276
+ tokens.push(...result.tokens);
277
+ pos = result.pos;
278
+ continue;
279
+ }
276
280
  const remaining = line.slice(pos);
277
281
  const token = this.getNextToken(remaining, pos, state);
278
282
  if (token) {
279
283
  tokens.push(token);
280
284
  this.updateLastToken(token, state);
281
285
  pos = token.end;
286
+ // Track brace nesting inside a ${...} interpolation so we know when it
287
+ // closes and we return to the template's string portion. Braces inside
288
+ // strings/comments are separate token types, so they aren't miscounted.
289
+ if (state.inTemplateLiteral &&
290
+ (state.templateDepth ?? 0) > 0 &&
291
+ token.type === 'punctuation.brace') {
292
+ if (token.text === '{') {
293
+ state.templateDepth = (state.templateDepth ?? 0) + 1;
294
+ }
295
+ else if (token.text === '}') {
296
+ state.templateDepth = Math.max(0, (state.templateDepth ?? 1) - 1);
297
+ }
298
+ }
282
299
  }
283
300
  else {
284
301
  // No match - shouldn't happen but handle gracefully
@@ -314,10 +331,10 @@ export class JavaScriptTokenizer {
314
331
  return createToken('comment.block', text, pos);
315
332
  }
316
333
  }
317
- // Template literals
318
- if (text.startsWith('`')) {
319
- return this.tokenizeTemplateLiteral(text, pos, state);
320
- }
334
+ // Template literals are handled in tokenizeLine's main loop
335
+ // (scanTemplateString) so the string portions and the ${...} interpolation
336
+ // expression are tokenized separately. A backtick only reaches here while
337
+ // inside an interpolation (a nested template); fall through to consume it.
321
338
  // Regular strings
322
339
  if (text.startsWith('"') || text.startsWith("'")) {
323
340
  return this.tokenizeString(text, pos, text[0]);
@@ -552,59 +569,55 @@ export class JavaScriptTokenizer {
552
569
  // Unterminated string at end of line
553
570
  return createToken('string', text, pos);
554
571
  }
555
- tokenizeTemplateLiteral(text, pos, state) {
556
- let i = 1;
557
- let result = '`';
558
- while (i < text.length) {
559
- if (text[i] === '\\' && i + 1 < text.length) {
560
- result += text.slice(i, i + 2);
561
- i += 2;
562
- continue;
563
- }
564
- if (text[i] === '`') {
565
- result += '`';
566
- return createToken('string.template', result, pos);
567
- }
568
- if (text[i] === '$' && text[i + 1] === '{') {
569
- // Template expression - for simplicity, tokenize up to this point
570
- if (result.length > 1) {
571
- // Return string part first
572
- state.inTemplateLiteral = true;
573
- state.templateDepth = (state.templateDepth ?? 0) + 1;
574
- return createToken('string.template', result, pos);
575
- }
576
- }
577
- result += text[i];
578
- i++;
579
- }
580
- // Multi-line template literal
581
- state.inTemplateLiteral = true;
582
- return createToken('string.template', result, pos);
583
- }
584
- tokenizeTemplateLiteralContinuation(line, startPos, state) {
585
- const tokens = [];
572
+ /**
573
+ * Scan the string portion of a template literal, starting at `startPos`. Emits
574
+ * one `string.template` token for the run of literal characters and stops at one
575
+ * of three boundaries:
576
+ * - a closing backtick ends the literal (clears template state);
577
+ * - a `${` emits the `${` delimiter and enters interpolation
578
+ * (templateDepth = 1) so the expression is tokenized as code by the caller;
579
+ * - end of line → the literal spans lines and continues next line.
580
+ *
581
+ * `isStart` is true when this is the opening backtick (vs. a continuation of a
582
+ * multi-line literal or the resumption after a `${…}` interpolation).
583
+ */
584
+ scanTemplateString(line, startPos, state, isStart) {
586
585
  let pos = startPos;
587
586
  let result = '';
587
+ if (isStart) {
588
+ state.inTemplateLiteral = true;
589
+ state.templateDepth = 0;
590
+ result = '`';
591
+ pos += 1;
592
+ }
588
593
  while (pos < line.length) {
589
- if (line[pos] === '\\' && pos + 1 < line.length) {
594
+ const ch = line[pos];
595
+ if (ch === '\\' && pos + 1 < line.length) {
590
596
  result += line.slice(pos, pos + 2);
591
597
  pos += 2;
592
598
  continue;
593
599
  }
594
- if (line[pos] === '`') {
600
+ if (ch === '`') {
595
601
  result += '`';
596
- tokens.push(createToken('string.template', result, startPos));
597
602
  state.inTemplateLiteral = false;
598
- state.templateDepth = Math.max(0, (state.templateDepth ?? 1) - 1);
599
- return { tokens, pos: pos + 1 };
603
+ state.templateDepth = 0;
604
+ return { tokens: [createToken('string.template', result, startPos)], pos: pos + 1 };
600
605
  }
601
- result += line[pos];
602
- pos++;
603
- }
604
- if (result) {
605
- tokens.push(createToken('string.template', result, startPos));
606
+ if (ch === '$' && line[pos + 1] === '{') {
607
+ const tokens = [];
608
+ if (result) {
609
+ tokens.push(createToken('string.template', result, startPos));
610
+ }
611
+ tokens.push(createToken('string.template', '${', startPos + result.length));
612
+ state.templateDepth = 1;
613
+ return { tokens, pos: pos + 2 };
614
+ }
615
+ result += ch;
616
+ pos += 1;
606
617
  }
607
- return { tokens, pos };
618
+ // End of line inside the string portion → multi-line template literal.
619
+ state.inTemplateLiteral = true;
620
+ return { tokens: result ? [createToken('string.template', result, startPos)] : [], pos };
608
621
  }
609
622
  tokenizeJSXTag(text, pos, _state) {
610
623
  // Simple JSX tag detection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocturnium/svelte-ide",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Svelte 5 code editor and IDE building blocks — custom editor, syntax highlighting, code folding, multi-cursor, LSP client, and optional realtime collaboration.",
5
5
  "author": "Nocturnium & Jordan Dziat <hello@nocturnium.ai> (https://nocturnium.ai)",
6
6
  "license": "MIT",