@tsrx/language-server 0.3.130 → 0.3.132

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-DBoQjnqH.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`);
@@ -1865,21 +1867,6 @@ const resolveConfig = (config) => {
1865
1867
  /** @type {CompilerOptions} */
1866
1868
  const options = { ...config.options ?? {} };
1867
1869
  if (options.target === void 0) options.target = typescript.default.ScriptTarget.ESNext;
1868
- /** @param {string} libName */
1869
- const normalizeLibName = (libName) => {
1870
- if (typeof libName !== "string" || libName.length === 0) return;
1871
- const trimmed = libName.trim();
1872
- if (trimmed.startsWith("lib.")) return trimmed.toLowerCase();
1873
- return `lib.${trimmed.toLowerCase().replace(/\s+/g, "").replace(/_/g, ".")}\.d.ts`;
1874
- };
1875
- const normalizedLibs = new Set((options.lib ?? []).map(normalizeLibName).filter((lib) => typeof lib === "string"));
1876
- if (normalizedLibs.size === 0) {
1877
- const defaultLibFileName = typescript.default.createCompilerHost(options).getDefaultLibFileName(options).toLowerCase();
1878
- normalizedLibs.add(defaultLibFileName);
1879
- normalizedLibs.add("lib.dom.d.ts");
1880
- normalizedLibs.add("lib.dom.iterable.d.ts");
1881
- }
1882
- options.lib = [...normalizedLibs];
1883
1870
  if (!options.types) {
1884
1871
  const host = typescript.default.createCompilerHost(options);
1885
1872
  const typeRoots = typescript.default.getEffectiveTypeRoots(options, host);
@@ -21604,7 +21591,7 @@ function parseCompilationErrorWithDocument(error, virtualCode, sourceMap, docume
21604
21591
  },
21605
21592
  message: error.message,
21606
21593
  source: "TSRX",
21607
- code: "tsrx-usage-error"
21594
+ code: error.code ?? "tsrx-usage-error"
21608
21595
  };
21609
21596
  }
21610
21597
  /**
@@ -22205,6 +22192,31 @@ const TSRX_SNIPPETS = [
22205
22192
  }
22206
22193
  ];
22207
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
+ /**
22208
22220
  * Ripple-runtime-only snippets: reactivity primitives (`track`/`effect`/`untrack`)
22209
22221
  * and server modules. These reference the `ripple` runtime API, so they are only
22210
22222
  * offered when the file is compiled by the Ripple target (see `is_ripple_target_file`).
@@ -22297,6 +22309,20 @@ const RIPPLE_IMPORTS = [
22297
22309
  }
22298
22310
  ];
22299
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
+ /**
22300
22326
  * @returns {LanguageServicePlugin}
22301
22327
  */
22302
22328
  function createCompletionPlugin() {
@@ -22386,6 +22412,25 @@ function createCompletionPlugin() {
22386
22412
  isIncomplete: false
22387
22413
  };
22388
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
+ }
22389
22434
  const trackedMatch = is_ripple && line.match(/(new\s+)?[R,M]([\w\.]*)$/);
22390
22435
  if (trackedMatch) {
22391
22436
  const hasNew = !!trackedMatch[1];
@@ -22421,6 +22466,7 @@ function createCompletionPlugin() {
22421
22466
  const wordMatch = line.match(/(\w+)$/);
22422
22467
  log$5("Current word:", wordMatch ? wordMatch[1] : "");
22423
22468
  items.push(COMPONENT_SNIPPET, ...TSRX_SNIPPETS);
22469
+ if (!tagStartMatch) items.push(...STYLE_SNIPPETS);
22424
22470
  if (is_ripple) items.push(...RIPPLE_API_SNIPPETS);
22425
22471
  return {
22426
22472
  items,
@@ -22491,63 +22537,94 @@ async provideAutoInsertSnippet(document, position, lastChange, _token) {
22491
22537
  return null;
22492
22538
  }
22493
22539
  const offset = document.offsetAt(position);
22494
- const mapping = virtualCode.findMappingByGeneratedRange(lastChange.rangeOffset, offset);
22495
- if (!mapping) return null;
22496
- 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;
22497
22550
  const sourceCode = virtualCode.originalCode;
22498
22551
  if (sourceCode[sourceOffset - 1] === "/") return null;
22552
+ /** @type {string | null} */
22553
+ let tagName = null;
22554
+ /** @type {string} */
22555
+ let line = "";
22499
22556
  let attempts = 0;
22500
- let found = false;
22501
- let i = sourceOffset - 1;
22502
- for (; i >= 0; i--) {
22503
- if (sourceCode[i] === "<") {
22504
- attempts++;
22505
- if (virtualCode.findMappingBySourceRange(i, i + 1)) {
22506
- found = true;
22507
- break;
22508
- }
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;
22509
22566
  }
22510
- if (attempts === 3) break;
22511
- }
22512
- if (!found) {
22513
- log$4(`No opening tag position found from source position ${sourceOffset}`);
22514
- return null;
22515
22567
  }
22516
- const line = sourceCode.slice(i, sourceOffset + 1);
22517
22568
  log$4("Auto-insert triggered at:", {
22518
22569
  selection: `${position.line}:${position.character}`,
22519
22570
  line,
22520
22571
  change: lastChange,
22521
- sourceOffset
22572
+ sourceOffset,
22573
+ isFallback
22522
22574
  });
22523
- const tagMatch = line.match(/<([@$\w][\w.-]*)[^>]*?(?<!\/)>$/);
22524
- if (!tagMatch) {
22575
+ if (!tagName) {
22525
22576
  log$4("No tag match found");
22526
22577
  return null;
22527
22578
  }
22528
- const tagName = tagMatch[1];
22529
22579
  log$4("Tag matched:", tagName);
22530
22580
  if (VOID_ELEMENTS.has(tagName.toLowerCase())) {
22531
22581
  log$4("Void element, skipping auto-close:", tagName);
22532
22582
  return null;
22533
22583
  }
22534
- if (document.getText({
22535
- start: position,
22536
- end: {
22537
- line: position.line,
22538
- character: position.character + 100
22539
- }
22540
- }).startsWith(`</${tagName}>`)) {
22584
+ const closingTag = `</${tagName}>`;
22585
+ if (sourceCode.startsWith(closingTag, sourceOffset + 1)) {
22541
22586
  log$4("Closing tag already exists, skipping");
22542
22587
  return null;
22543
22588
  }
22544
- const closingTag = `</${tagName}>`;
22545
22589
  log$4("Inserting closing tag:", closingTag);
22546
22590
  return `$0${closingTag}`;
22547
22591
  } };
22548
22592
  }
22549
22593
  };
22550
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
+ }
22551
22628
 
22552
22629
  //#endregion
22553
22630
  //#region src/typescriptDiagnosticPlugin.js
@@ -23485,4 +23562,4 @@ Object.defineProperty(exports, 'createTsrxLanguageServer', {
23485
23562
  return createTsrxLanguageServer;
23486
23563
  }
23487
23564
  });
23488
- //# sourceMappingURL=server-DBoQjnqH.js.map
23565
+ //# sourceMappingURL=server-iYrbcyFX.js.map