@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.
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`);
|
|
@@ -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(
|
|
22480
|
-
|
|
22481
|
-
|
|
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
|
|
22486
|
-
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22520
|
-
|
|
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-
|
|
23565
|
+
//# sourceMappingURL=server-iYrbcyFX.js.map
|