@ripple-ts/language-server 0.3.41 → 0.3.43

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,163 +0,0 @@
1
- /** @import { LanguageServicePlugin } from '@volar/language-server' */
2
-
3
- import { getVirtualCode, createLogging, is_ripple_document } from './utils.js';
4
-
5
- const { log } = createLogging('[Ripple Auto-Insert Plugin]');
6
-
7
- /**
8
- * List of HTML void/self-closing elements that don't need closing tags
9
- * https://developer.mozilla.org/en-US/docs/Glossary/Void_element
10
- */
11
- const VOID_ELEMENTS = new Set([
12
- 'area',
13
- 'base',
14
- 'br',
15
- 'col',
16
- 'command',
17
- 'embed',
18
- 'hr',
19
- 'img',
20
- 'input',
21
- 'keygen',
22
- 'link',
23
- 'meta',
24
- 'param',
25
- 'source',
26
- 'track',
27
- 'wbr',
28
- ]);
29
-
30
- /**
31
- * Auto-insert plugin for Ripple
32
- * Handles auto-closing tags when typing '>' after a tag name
33
- * @returns {LanguageServicePlugin}
34
- */
35
- export function createAutoInsertPlugin() {
36
- return {
37
- name: 'ripple-auto-insert',
38
- capabilities: {
39
- autoInsertionProvider: {
40
- triggerCharacters: ['>'],
41
- configurationSections: ['ripple.autoClosingTags.enabled'],
42
- },
43
- documentOnTypeFormattingProvider: {
44
- triggerCharacters: ['>'],
45
- },
46
- },
47
- // leaving context for future use
48
- create(context) {
49
- return {
50
- /**
51
- * @param {import('vscode-languageserver-textdocument').TextDocument} document
52
- * @param {import('@volar/language-server').Position} position
53
- * @param {{ rangeOffset: number; rangeLength: number; text: string }} lastChange
54
- * @param {import('@volar/language-server').CancellationToken} _token
55
- * @returns {Promise<string | null>}
56
- */
57
- async provideAutoInsertSnippet(document, position, lastChange, _token) {
58
- if (!is_ripple_document(document.uri)) {
59
- return null;
60
- }
61
-
62
- // Only checking for '>' insertions
63
- if (!lastChange.text.endsWith('>')) {
64
- return null;
65
- }
66
-
67
- const { virtualCode } = getVirtualCode(document, context);
68
-
69
- if (virtualCode.languageId !== 'ripple') {
70
- log(`Skipping auto-insert processing in the '${virtualCode.languageId}' context`);
71
- return null;
72
- }
73
-
74
- // Map position back to source
75
- const offset = document.offsetAt(position);
76
- const mapping = virtualCode.findMappingByGeneratedRange(lastChange.rangeOffset, offset);
77
-
78
- if (!mapping) {
79
- return null;
80
- }
81
-
82
- const sourceOffset = mapping.sourceOffsets[0];
83
-
84
- // search backwards from sourceOffset to find the line tag
85
- const sourceCode = virtualCode.originalCode;
86
- if (sourceCode[sourceOffset - 1] === '/') {
87
- // self-closing tag '/>'
88
- return null;
89
- }
90
-
91
- let attempts = 0;
92
- let found = false;
93
- let i = sourceOffset - 1;
94
- for (; i >= 0; i--) {
95
- const char = sourceCode[i];
96
- if (char === '<') {
97
- attempts++;
98
- // Confirm that it's definitely the start of the tag
99
- // We have `<` and `>` in source maps
100
- if (virtualCode.findMappingBySourceRange(i, i + 1)) {
101
- found = true;
102
- break;
103
- }
104
- }
105
-
106
- if (attempts === 3) {
107
- break;
108
- }
109
- }
110
-
111
- if (!found) {
112
- // This shouldn't happen in reality
113
- log(`No opening tag position found from source position ${sourceOffset}`);
114
- return null;
115
- }
116
-
117
- const line = sourceCode.slice(i, sourceOffset + 1);
118
-
119
- log('Auto-insert triggered at:', {
120
- selection: `${position.line}:${position.character}`,
121
- line,
122
- change: lastChange,
123
- sourceOffset,
124
- });
125
-
126
- // Check if we just typed '>' after a tag name
127
- // Match patterns like: <div> or <Component> but not <div /> or <Component/>
128
- const tagMatch = line.match(/<([@$\w][\w.-]*)[^>]*?(?<!\/)>$/);
129
- if (!tagMatch) {
130
- log('No tag match found');
131
- return null;
132
- }
133
-
134
- const tagName = tagMatch[1];
135
- log('Tag matched:', tagName);
136
-
137
- // Don't auto-close void elements (self-closing HTML tags)
138
- if (VOID_ELEMENTS.has(tagName.toLowerCase())) {
139
- log('Void element, skipping auto-close:', tagName);
140
- return null;
141
- }
142
-
143
- // Check if there's already a closing tag ahead
144
- const restOfLine = document.getText({
145
- start: position,
146
- end: { line: position.line, character: position.character + 100 },
147
- });
148
- if (restOfLine.startsWith(`</${tagName}>`)) {
149
- log('Closing tag already exists, skipping');
150
- return null;
151
- }
152
-
153
- // Insert the closing tag
154
- const closingTag = `</${tagName}>`;
155
- log('Inserting closing tag:', closingTag);
156
-
157
- // Return a snippet with $0 to place cursor between the tags
158
- return `$0${closingTag}`;
159
- },
160
- };
161
- },
162
- };
163
- }
@@ -1,155 +0,0 @@
1
- /**
2
- * @import {Diagnostic, Range, LanguageServicePlugin, LanguageServiceContext, Position, Mapper} from '@volar/language-server';
3
- * @import {TextDocument} from 'vscode-languageserver-textdocument';
4
- * @import {TSRXVirtualCodeInstance} from '@tsrx/typescript-plugin/src/language.js';
5
- */
6
- /** @import {TSRXCompileError} from '@tsrx/ripple'; */
7
-
8
- import { getVirtualCode, createLogging } from './utils.js';
9
-
10
- const { log } = createLogging('[Ripple Compile Error Diagnostic Plugin]');
11
- import { DiagnosticSeverity } from '@volar/language-server';
12
-
13
- /**
14
- * @returns {LanguageServicePlugin}
15
- */
16
- export function createCompileErrorDiagnosticPlugin() {
17
- log('Creating Ripple diagnostic plugin...');
18
-
19
- return {
20
- name: 'ripple-diagnostics',
21
- capabilities: {
22
- diagnosticProvider: {
23
- interFileDependencies: false,
24
- workspaceDiagnostics: false,
25
- },
26
- },
27
- create(/** @type {LanguageServiceContext} */ context) {
28
- return {
29
- provideDiagnostics(document, _token) {
30
- log('Providing Ripple diagnostics for:', document.uri);
31
-
32
- /** @type {Diagnostic[]} */
33
- const diagnostics = [];
34
- const { virtualCode, sourceMap } = getVirtualCode(document, context);
35
-
36
- if (!virtualCode || virtualCode.languageId !== 'ripple') {
37
- // skip if it's like embedded css
38
- return diagnostics;
39
- }
40
-
41
- if (!virtualCode.fatalErrors.length && !virtualCode.usageErrors.length) {
42
- return diagnostics;
43
- }
44
-
45
- for (const error of [...virtualCode.fatalErrors, ...virtualCode.usageErrors]) {
46
- const diagnostic = parseCompilationErrorWithDocument(
47
- error,
48
- virtualCode,
49
- sourceMap,
50
- document,
51
- );
52
- diagnostics.push(diagnostic);
53
- }
54
-
55
- log('Generated', diagnostics.length, 'diagnostics');
56
- return diagnostics;
57
- },
58
- };
59
- },
60
- };
61
- }
62
-
63
- /**
64
- * @param {TSRXCompileError} error
65
- * @param {TSRXVirtualCodeInstance} virtualCode
66
- * @param {Mapper | undefined} sourceMap
67
- * @param {TextDocument} document
68
- * @returns {Diagnostic}
69
- */
70
- function parseCompilationErrorWithDocument(error, virtualCode, sourceMap, document) {
71
- if (error.type === 'fatal') {
72
- return {
73
- severity: DiagnosticSeverity.Error,
74
- range: get_error_range_from_source(error, document),
75
- message: error.message,
76
- source: 'TSRX',
77
- code: 'tsrx-compile-error',
78
- };
79
- }
80
-
81
- /** @type {Position | null} */
82
- let start = null;
83
- /** @type {Position | null} */
84
- let end = null;
85
-
86
- if (error.pos) {
87
- const start_offset = get_start_offset_from_error(error);
88
- const end_offset = get_end_offset_from_error(error, start_offset);
89
- // try to find exact mapping
90
- // TODO: perhaps it's best to just switch to sourceMap entirely?
91
- const mapping = virtualCode.findMappingBySourceRange(start_offset, end_offset);
92
-
93
- if (mapping) {
94
- start = document.positionAt(mapping.generatedOffsets[0]);
95
- end = document.positionAt(mapping.generatedOffsets[0] + mapping.generatedLengths[0]);
96
- } else if (sourceMap) {
97
- // try to find the match even across multiple mappings
98
- const result = sourceMap.toGeneratedRange(start_offset, end_offset, true).next().value;
99
-
100
- if (result) {
101
- const [gen_start_offset, gen_end_offset] = result;
102
- start = document.positionAt(gen_start_offset);
103
- end = document.positionAt(gen_end_offset);
104
- }
105
- }
106
- }
107
-
108
- if (!start || !end) {
109
- start = { line: 0, character: 0 };
110
- end = { line: 0, character: 1 };
111
- }
112
-
113
- return {
114
- severity: DiagnosticSeverity.Error,
115
- range: { start, end },
116
- message: error.message,
117
- source: 'TSRX',
118
- code: 'tsrx-usage-error',
119
- };
120
- }
121
-
122
- /**
123
- * @param {TSRXCompileError} error
124
- * @param {TextDocument} document
125
- * @returns {Range}
126
- */
127
- function get_error_range_from_source(error, document) {
128
- const start_offset = get_start_offset_from_error(error);
129
- return {
130
- start: document.positionAt(start_offset),
131
- end: document.positionAt(get_end_offset_from_error(error, start_offset)),
132
- };
133
- }
134
-
135
- /**
136
- * @param {TSRXCompileError} error
137
- * @param {number} [start_offset]
138
- * @returns {number}
139
- */
140
- function get_end_offset_from_error(error, start_offset) {
141
- start_offset = start_offset ?? get_start_offset_from_error(error);
142
- return error.end
143
- ? error.end
144
- : error.raisedAt && (error.raisedAt ?? 0) > start_offset
145
- ? error.raisedAt
146
- : start_offset + 1;
147
- }
148
-
149
- /**
150
- * @param {TSRXCompileError} error
151
- * @returns {number}
152
- */
153
- function get_start_offset_from_error(error) {
154
- return error.pos ?? 0;
155
- }