@tsrx/language-server 0.3.131 → 0.3.133

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_server = require('./server-BZ2bdQ1a.js');
2
+ const require_server = require('./server-iYrbcyFX.js');
3
3
 
4
4
  //#region src/language-server.js
5
5
  require_server.createTsrxLanguageServer();
@@ -1699,21 +1699,33 @@ function logTSRXErrors(file_name, errors) {
1699
1699
  }
1700
1700
  }
1701
1701
  /**
1702
- * Extract CSS content from <style>...</style> tags in source code
1702
+ * Extract raw CSS content from `<style>...</style>` tags in source code, used as a
1703
+ * fallback for CSS intellisense while the file has a fatal compile error (no AST
1704
+ * available — the normal path derives regions from the compiler's `cssMappings`
1705
+ * instead). Parallels {@link extractScriptFromSource}.
1706
+ * The opening-tag pattern is attribute-aware: a `>` inside a quoted value or an
1707
+ * `{...}` expression container (one level of nesting) does not end the tag, so
1708
+ * `apply={cond ? a : b}` and `apply={(x) => y}` are handled. It refuses to match
1709
+ * self-closing `<style apply={theme} />` blocks (the `/` before `>` must not close
1710
+ * the tag): they carry no CSS body, so — like the compiler's `cssMappings` — they
1711
+ * yield no region and can't swallow a later bodied block. One region is produced
1712
+ * per bodied block, in source order, whether the blocks share a scope or sit in
1713
+ * nested `@{ ... }` / control-flow bodies.
1703
1714
  * @param {string} code - The source code to extract CSS from
1704
1715
  * @returns {VirtualCode[]} Array of embedded CSS virtual codes
1705
1716
  */
1706
1717
  function extractCssFromSource(code) {
1707
1718
  /** @type {VirtualCode[]} */
1708
1719
  const embeddedCodes = [];
1709
- const styleRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
1720
+ const styleRegex = /<style\b((?:[^>"'{}/]|"[^"]*"|'[^']*'|\{(?:[^{}]|\{[^{}]*\})*\}|\/(?!>))*)>([\s\S]*?)<\/style>/gi;
1710
1721
  let match;
1711
1722
  let index = 0;
1712
1723
  while ((match = styleRegex.exec(code)) !== null) {
1713
- const fullMatch = match[0];
1714
- const cssContent = match[1];
1715
- const cssStart = match.index + (fullMatch.indexOf(">") + 1);
1724
+ const attrs = match[1];
1725
+ const cssContent = match[2];
1726
+ const cssStart = match.index + (6 + attrs.length + 1);
1716
1727
  const cssLength = cssContent.length;
1728
+ const id = `style_${index}`;
1717
1729
  log$9(`Extracted CSS region ${index}: offset ${cssStart}, length ${cssLength}`);
1718
1730
  /** @type {CodeMapping} */
1719
1731
  const mapping = {
@@ -1730,21 +1742,11 @@ function extractCssFromSource(code) {
1730
1742
  format: false,
1731
1743
  customData: {
1732
1744
  content: cssContent,
1733
- embeddedId: `style_${index}`
1745
+ embeddedId: id
1734
1746
  }
1735
1747
  }
1736
1748
  };
1737
- embeddedCodes.push({
1738
- id: `style_${index}`,
1739
- languageId: "css",
1740
- snapshot: {
1741
- getText: (start, end) => cssContent.substring(start, end),
1742
- getLength: () => cssLength,
1743
- getChangeRange: () => void 0
1744
- },
1745
- mappings: [mapping],
1746
- embeddedCodes: []
1747
- });
1749
+ embeddedCodes.push(create_embedded_code_from_mapping(mapping, "css"));
1748
1750
  index++;
1749
1751
  }
1750
1752
  if (embeddedCodes.length > 0) log$9(`Extracted ${embeddedCodes.length} CSS embedded codes from style tags`);
@@ -21589,7 +21591,7 @@ function parseCompilationErrorWithDocument(error, virtualCode, sourceMap, docume
21589
21591
  },
21590
21592
  message: error.message,
21591
21593
  source: "TSRX",
21592
- code: "tsrx-usage-error"
21594
+ code: error.code ?? "tsrx-usage-error"
21593
21595
  };
21594
21596
  }
21595
21597
  /**
@@ -22190,6 +22192,31 @@ const TSRX_SNIPPETS = [
22190
22192
  }
22191
22193
  ];
22192
22194
  /**
22195
+ * Target-neutral `<style>` block snippets. Scoped `<style>` blocks, `$class`, and `apply` are
22196
+ * shared TSRX syntax lowered by every target, so these are offered in every `.tsrx` file. They
22197
+ * live outside `TSRX_SNIPPETS` because they are not `@`-directives: they are offered when the
22198
+ * user types `<` (or `<sty…`) in a template, anchored at the `<` like the `@` path anchors at `@`.
22199
+ */
22200
+ const STYLE_SNIPPETS = [{
22201
+ label: "<style>",
22202
+ filterText: "style",
22203
+ kind: import_language_server.CompletionItemKind.Snippet,
22204
+ detail: "Scoped style block",
22205
+ documentation: "A sibling-scoped `<style>` block: a child of an element or fragment that styles its siblings and everything below them, never the element that contains it; sibling blocks share one hash (in a `@{ … }` or control-flow body, wrap it with its output in a fragment). Raw CSS here is TSRX template syntax, so it needs an enclosing `@{ … }` or control-flow body. Assign it (`const theme = <style>…</style>`) to reuse its classes as `theme.card` and apply it elsewhere with `<style apply={theme} />`.",
22206
+ insertText: "<style>\n $0\n</style>",
22207
+ insertTextFormat: import_language_server.InsertTextFormat.Snippet,
22208
+ sortText: "0-style"
22209
+ }, {
22210
+ label: "<style apply={…} />",
22211
+ filterText: "style apply",
22212
+ kind: import_language_server.CompletionItemKind.Snippet,
22213
+ detail: "Apply an assigned style block",
22214
+ documentation: "Stamp the classes of an assigned `<style>` block onto the items beside this block and everything below them. `apply` takes a style block (`{theme}`) or an array of them (`{[base, theme]}`); a self-closing block has no CSS of its own.",
22215
+ insertText: "<style apply={${1:theme}} />",
22216
+ insertTextFormat: import_language_server.InsertTextFormat.Snippet,
22217
+ sortText: "0-style-apply"
22218
+ }];
22219
+ /**
22193
22220
  * Ripple-runtime-only snippets: reactivity primitives (`track`/`effect`/`untrack`)
22194
22221
  * and server modules. These reference the `ripple` runtime API, so they are only
22195
22222
  * offered when the file is compiled by the Ripple target (see `is_ripple_target_file`).
@@ -22282,6 +22309,20 @@ const RIPPLE_IMPORTS = [
22282
22309
  }
22283
22310
  ];
22284
22311
  /**
22312
+ * Whether the line before the cursor ends in a `<` that starts a tag (`<`, `<sty`) rather than a
22313
+ * comparison operator (`a <`, `if (x <`). A tag `<` is at the start of the line or follows
22314
+ * punctuation that cannot end an operand (`>`, `{`, `(`, `,`, `;`, `=`, `?`, `:`, `&`, `|`, `!`,
22315
+ * `[`) or an expression keyword (`return <`).
22316
+ * @param {string} line - Line text up to the cursor
22317
+ * @returns {boolean}
22318
+ */
22319
+ function isTagStart(line) {
22320
+ const match = line.match(/<(\w*)$/);
22321
+ if (!match) return false;
22322
+ const before = line.slice(0, match.index).trimEnd();
22323
+ return before === "" || /[>{(,;=?:&|!\[]$/.test(before) || /\b(?:return|yield|await|case|else|do|in|of|typeof|void)$/.test(before);
22324
+ }
22325
+ /**
22285
22326
  * @returns {LanguageServicePlugin}
22286
22327
  */
22287
22328
  function createCompletionPlugin() {
@@ -22371,6 +22412,25 @@ function createCompletionPlugin() {
22371
22412
  isIncomplete: false
22372
22413
  };
22373
22414
  }
22415
+ const tagStartMatch = isTagStart(line) ? line.match(/<(\w*)$/) : null;
22416
+ if (tagStartMatch) {
22417
+ const typed = `<${tagStartMatch[1]}`;
22418
+ const replaceRange = {
22419
+ start: {
22420
+ line: position.line,
22421
+ character: position.character - typed.length
22422
+ },
22423
+ end: position
22424
+ };
22425
+ for (const snippet of STYLE_SNIPPETS) items.push({
22426
+ ...snippet,
22427
+ filterText: `<${snippet.filterText}`,
22428
+ textEdit: {
22429
+ range: replaceRange,
22430
+ newText: snippet.insertText
22431
+ }
22432
+ });
22433
+ }
22374
22434
  const trackedMatch = is_ripple && line.match(/(new\s+)?[R,M]([\w\.]*)$/);
22375
22435
  if (trackedMatch) {
22376
22436
  const hasNew = !!trackedMatch[1];
@@ -22406,6 +22466,7 @@ function createCompletionPlugin() {
22406
22466
  const wordMatch = line.match(/(\w+)$/);
22407
22467
  log$5("Current word:", wordMatch ? wordMatch[1] : "");
22408
22468
  items.push(COMPONENT_SNIPPET, ...TSRX_SNIPPETS);
22469
+ if (!tagStartMatch) items.push(...STYLE_SNIPPETS);
22409
22470
  if (is_ripple) items.push(...RIPPLE_API_SNIPPETS);
22410
22471
  return {
22411
22472
  items,
@@ -22476,63 +22537,94 @@ async provideAutoInsertSnippet(document, position, lastChange, _token) {
22476
22537
  return null;
22477
22538
  }
22478
22539
  const offset = document.offsetAt(position);
22479
- const mapping = virtualCode.findMappingByGeneratedRange(lastChange.rangeOffset, offset);
22480
- if (!mapping) return null;
22481
- const sourceOffset = mapping.sourceOffsets[0];
22540
+ const mapping = virtualCode.findMappingByGeneratedRange(offset - 1, offset);
22541
+ /** @type {number} */
22542
+ let sourceOffset;
22543
+ /** @type {boolean} */
22544
+ let isFallback = false;
22545
+ if (mapping) sourceOffset = mapping.sourceOffsets[0];
22546
+ else if (virtualCode.fatalErrors.length > 0 && virtualCode.generatedCode === virtualCode.originalCode) {
22547
+ sourceOffset = offset - 1;
22548
+ isFallback = true;
22549
+ } else return null;
22482
22550
  const sourceCode = virtualCode.originalCode;
22483
22551
  if (sourceCode[sourceOffset - 1] === "/") return null;
22552
+ /** @type {string | null} */
22553
+ let tagName = null;
22554
+ /** @type {string} */
22555
+ let line = "";
22484
22556
  let attempts = 0;
22485
- let found = false;
22486
- let i = sourceOffset - 1;
22487
- for (; i >= 0; i--) {
22488
- if (sourceCode[i] === "<") {
22489
- attempts++;
22490
- if (virtualCode.findMappingBySourceRange(i, i + 1)) {
22491
- found = true;
22492
- break;
22493
- }
22557
+ for (let i = sourceOffset - 1; i >= 0 && attempts < 3; i--) {
22558
+ if (sourceCode[i] !== "<") continue;
22559
+ attempts++;
22560
+ line = sourceCode.slice(i, sourceOffset + 1);
22561
+ const candidate = matchOpeningTag(line);
22562
+ if (!candidate) continue;
22563
+ if (isFallback || virtualCode.findMappingBySourceRange(i, i + 1) || virtualCode.findMappingBySourceRange(i + 1, i + 1 + candidate.length)) {
22564
+ tagName = candidate;
22565
+ break;
22494
22566
  }
22495
- if (attempts === 3) break;
22496
- }
22497
- if (!found) {
22498
- log$4(`No opening tag position found from source position ${sourceOffset}`);
22499
- return null;
22500
22567
  }
22501
- const line = sourceCode.slice(i, sourceOffset + 1);
22502
22568
  log$4("Auto-insert triggered at:", {
22503
22569
  selection: `${position.line}:${position.character}`,
22504
22570
  line,
22505
22571
  change: lastChange,
22506
- sourceOffset
22572
+ sourceOffset,
22573
+ isFallback
22507
22574
  });
22508
- const tagMatch = line.match(/<([@$\w][\w.-]*)[^>]*?(?<!\/)>$/);
22509
- if (!tagMatch) {
22575
+ if (!tagName) {
22510
22576
  log$4("No tag match found");
22511
22577
  return null;
22512
22578
  }
22513
- const tagName = tagMatch[1];
22514
22579
  log$4("Tag matched:", tagName);
22515
22580
  if (VOID_ELEMENTS.has(tagName.toLowerCase())) {
22516
22581
  log$4("Void element, skipping auto-close:", tagName);
22517
22582
  return null;
22518
22583
  }
22519
- if (document.getText({
22520
- start: position,
22521
- end: {
22522
- line: position.line,
22523
- character: position.character + 100
22524
- }
22525
- }).startsWith(`</${tagName}>`)) {
22584
+ const closingTag = `</${tagName}>`;
22585
+ if (sourceCode.startsWith(closingTag, sourceOffset + 1)) {
22526
22586
  log$4("Closing tag already exists, skipping");
22527
22587
  return null;
22528
22588
  }
22529
- const closingTag = `</${tagName}>`;
22530
22589
  log$4("Inserting closing tag:", closingTag);
22531
22590
  return `$0${closingTag}`;
22532
22591
  } };
22533
22592
  }
22534
22593
  };
22535
22594
  }
22595
+ /**
22596
+ * Match an opening tag `<name …>` that ends exactly at the end of `text` and return its name.
22597
+ *
22598
+ * Attribute expressions may themselves contain `>` (`<style apply={x > y ? a : b}>`,
22599
+ * `<div hidden={a > b}>`) and quoted values may contain anything, so the attribute region is
22600
+ * walked with brace depth and quote tracking instead of a `[^>]*` regex. Returns null when
22601
+ * `text` is not a single opening tag: the trailing `>` sits inside `{…}` (the user is typing an
22602
+ * expression, not closing the tag), an earlier `>` already closed the tag, or the tag is
22603
+ * self-closing (`<style apply={theme} />`).
22604
+ *
22605
+ * @param {string} text - Source text from the tag's `<` up to and including the typed `>`
22606
+ * @returns {string | null}
22607
+ */
22608
+ function matchOpeningTag(text) {
22609
+ const nameMatch = text.match(/^<([@$\w][\w.-]*)/);
22610
+ if (!nameMatch) return null;
22611
+ let depth = 0;
22612
+ /** @type {string | null} */
22613
+ let quote = null;
22614
+ for (let i = nameMatch[0].length; i < text.length; i++) {
22615
+ const char = text[i];
22616
+ if (quote) {
22617
+ if (char === "\\" && depth > 0) i++;
22618
+ else if (char === quote) quote = null;
22619
+ continue;
22620
+ }
22621
+ if (char === "\"" || char === "'" || depth > 0 && char === "`") quote = char;
22622
+ else if (char === "{") depth++;
22623
+ else if (char === "}") depth = Math.max(0, depth - 1);
22624
+ else if (char === ">" && depth === 0) return i === text.length - 1 && text[i - 1] !== "/" ? nameMatch[1] : null;
22625
+ }
22626
+ return null;
22627
+ }
22536
22628
 
22537
22629
  //#endregion
22538
22630
  //#region src/typescriptDiagnosticPlugin.js
@@ -23470,4 +23562,4 @@ Object.defineProperty(exports, 'createTsrxLanguageServer', {
23470
23562
  return createTsrxLanguageServer;
23471
23563
  }
23472
23564
  });
23473
- //# sourceMappingURL=server-BZ2bdQ1a.js.map
23565
+ //# sourceMappingURL=server-iYrbcyFX.js.map