@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.
package/dist/language-server.js
CHANGED
|
@@ -1699,21 +1699,33 @@ function logTSRXErrors(file_name, errors) {
|
|
|
1699
1699
|
}
|
|
1700
1700
|
}
|
|
1701
1701
|
/**
|
|
1702
|
-
* Extract CSS content from
|
|
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[^>]
|
|
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
|
|
1714
|
-
const cssContent = match[
|
|
1715
|
-
const cssStart = match.index + (
|
|
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:
|
|
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(
|
|
22495
|
-
|
|
22496
|
-
|
|
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
|
|
22501
|
-
|
|
22502
|
-
|
|
22503
|
-
|
|
22504
|
-
|
|
22505
|
-
|
|
22506
|
-
|
|
22507
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22535
|
-
|
|
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-
|
|
23565
|
+
//# sourceMappingURL=server-iYrbcyFX.js.map
|