@ripple-ts/language-server 0.2.176 → 0.2.178

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ripple-ts/language-server",
3
- "version": "0.2.176",
3
+ "version": "0.2.178",
4
4
  "description": "Language Server Protocol implementation for Ripple",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -19,7 +19,7 @@
19
19
  "volar-service-typescript": "0.0.65",
20
20
  "vscode-languageserver-textdocument": "^1.0.12",
21
21
  "vscode-uri": "^3.1.0",
22
- "@ripple-ts/typescript-plugin": "0.2.176"
22
+ "@ripple-ts/typescript-plugin": "0.2.178"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "typescript": "^5.9.2"
@@ -31,7 +31,7 @@ function logError(...args) {
31
31
  /**
32
32
  * @returns {LanguageServicePlugin}
33
33
  */
34
- function createDiagnosticPlugin() {
34
+ function createCompileErrorDiagnosticPlugin() {
35
35
  log('Creating Ripple diagnostic plugin...');
36
36
 
37
37
  return {
@@ -55,37 +55,37 @@ function createDiagnosticPlugin() {
55
55
 
56
56
  const info = getEmbeddedInfo(context, document);
57
57
 
58
- if (info && info.virtualCode.errors && info.virtualCode.errors.length > 0) {
59
- const virtualCode = info.virtualCode;
60
- const diagnostics = [];
61
-
62
- log('Processing', virtualCode.errors.length, 'errors');
63
-
64
- // Convert each stored error to a diagnostic
65
- for (const error of virtualCode.errors) {
66
- try {
67
- // Use the actual snapshot text that Volar is working with
68
- const snapshotText = virtualCode.snapshot.getText(
69
- 0,
70
- virtualCode.snapshot.getLength(),
71
- );
72
- const diagnostic = parseCompilationErrorWithDocument(
73
- error,
74
- virtualCode.fileName,
75
- snapshotText,
76
- document,
77
- );
78
- diagnostics.push(diagnostic);
79
- } catch (parseError) {
80
- logError('Failed to parse compilation error:', parseError);
81
- }
82
- }
58
+ if (!info || !info.virtualCode.errors || info.virtualCode.errors.length === 0) {
59
+ return [];
60
+ }
83
61
 
84
- log('Generated', diagnostics.length, 'diagnostics');
85
- return diagnostics;
62
+ const virtualCode = info.virtualCode;
63
+ const diagnostics = [];
64
+
65
+ log('Processing', virtualCode.errors.length, 'errors');
66
+
67
+ // Convert each stored error to a diagnostic
68
+ for (const error of virtualCode.errors) {
69
+ try {
70
+ // Use the actual snapshot text that Volar is working with
71
+ const snapshotText = virtualCode.snapshot.getText(
72
+ 0,
73
+ virtualCode.snapshot.getLength(),
74
+ );
75
+ const diagnostic = parseCompilationErrorWithDocument(
76
+ error,
77
+ virtualCode.fileName,
78
+ snapshotText,
79
+ document,
80
+ );
81
+ diagnostics.push(diagnostic);
82
+ } catch (parseError) {
83
+ logError('Failed to parse compilation error:', parseError);
84
+ }
86
85
  }
87
86
 
88
- return [];
87
+ log('Generated', diagnostics.length, 'diagnostics');
88
+ return diagnostics;
89
89
  } catch (err) {
90
90
  logError('Failed to provide diagnostics:', err);
91
91
  return [];
@@ -236,5 +236,5 @@ function getEmbeddedInfo(context, document) {
236
236
  }
237
237
 
238
238
  module.exports = {
239
- createDiagnosticPlugin,
239
+ createCompileErrorDiagnosticPlugin,
240
240
  };
@@ -1,6 +1,6 @@
1
1
  /** @import { LanguageServicePlugin } from '@volar/language-server' */
2
2
  /** @import { RippleVirtualCode } from '@ripple-ts/typescript-plugin/src/language.js') */
3
- /** @typedef {import('@volar/language-server').LanguageServicePluginInstance} LanguageServicePluginInstance */
3
+ /** @import { LanguageServicePluginInstance } from '@volar/language-server' */
4
4
 
5
5
  const { URI } = require('vscode-uri');
6
6
 
@@ -95,6 +95,26 @@ function createHoverPlugin() {
95
95
  return tsHover;
96
96
  }
97
97
 
98
+ const customHover = mapping?.data?.customData?.hover;
99
+ if (customHover) {
100
+ log('Found custom hover data in mapping');
101
+ return {
102
+ contents: {
103
+ kind: 'markdown',
104
+ value: customHover.contents,
105
+ },
106
+ range: {
107
+ start: position,
108
+ end: position,
109
+ },
110
+ };
111
+ } else if (customHover === false) {
112
+ log(
113
+ `Hover explicitly suppressed in mapping at range start: ${rangeStart}, end: ${rangeEnd}`,
114
+ );
115
+ return null;
116
+ }
117
+
98
118
  log('Found mapping for hover at range', 'start: ', rangeStart, 'end: ', rangeEnd);
99
119
 
100
120
  // Check if source length is greater than generated length (component -> function)
package/src/server.js CHANGED
@@ -3,11 +3,12 @@
3
3
  createServer,
4
4
  createTypeScriptProject,
5
5
  } = require('@volar/language-server/node');
6
- const { createDiagnosticPlugin } = require('./diagnosticPlugin.js');
6
+ const { createCompileErrorDiagnosticPlugin } = require('./compileErrorDiagnosticPlugin.js');
7
7
  const { createDefinitionPlugin } = require('./definitionPlugin.js');
8
8
  const { createHoverPlugin } = require('./hoverPlugin.js');
9
9
  const { createCompletionPlugin } = require('./completionPlugin.js');
10
10
  const { createAutoInsertPlugin } = require('./autoInsertPlugin.js');
11
+ const { createTypeScriptDiagnosticFilterPlugin } = require('./typescriptDiagnosticPlugin.js');
11
12
  const {
12
13
  getRippleLanguagePlugin,
13
14
  resolveConfig,
@@ -110,12 +111,14 @@ function createRippleLanguageServer() {
110
111
  [
111
112
  createAutoInsertPlugin(),
112
113
  createCompletionPlugin(),
113
- createDiagnosticPlugin(),
114
+ createCompileErrorDiagnosticPlugin(),
114
115
  createDefinitionPlugin(),
115
116
  createCssService(),
116
117
  ...createTypeScriptServices(ts),
117
- // !IMPORTANT createHoverPlugin has to be loaded after Volar's ts plugins
118
- // to overwrite `typescript-semantic` plugin's `provideHover`
118
+ // !IMPORTANT 'createTypeScriptDiagnosticFilterPlugin' and 'createHoverPlugin'
119
+ // must come after TypeScript services
120
+ // to intercept volar's and vscode default providers
121
+ createTypeScriptDiagnosticFilterPlugin(),
119
122
  createHoverPlugin(),
120
123
  ],
121
124
  );
@@ -0,0 +1,171 @@
1
+ /** @import { LanguageServicePlugin } from '@volar/language-server' */
2
+ /** @import { LanguageServicePluginInstance } from '@volar/language-server' */
3
+
4
+ // @ts-expect-error type-only import from ESM module into CJS is fine
5
+ /** @import { CodeMapping } from 'ripple/compiler' */
6
+
7
+ const { getVirtualCode } = require('./utils.js');
8
+
9
+ const DEBUG = process.env.RIPPLE_DEBUG === 'true';
10
+
11
+ /**
12
+ * @param {...unknown} args
13
+ */
14
+ function log(...args) {
15
+ if (DEBUG) {
16
+ console.log('[Ripple Typescript Diagnostics]', ...args);
17
+ }
18
+ }
19
+
20
+ /**
21
+ * @param {...unknown} args
22
+ */
23
+ function logError(...args) {
24
+ console.error('[Ripple Typescript Diagnostics]', ...args);
25
+ }
26
+
27
+ /**
28
+ * Create a plugin to filter TypeScript diagnostics based on customData in mappings
29
+ * @returns {LanguageServicePlugin}
30
+ */
31
+ function createTypeScriptDiagnosticFilterPlugin() {
32
+ log('Creating TypeScript diagnostic filter plugin...');
33
+
34
+ return {
35
+ name: 'ripple-typescript-diagnostic-filter',
36
+ capabilities: {
37
+ diagnosticProvider: {
38
+ interFileDependencies: false,
39
+ workspaceDiagnostics: false,
40
+ },
41
+ },
42
+ create(context) {
43
+ /** @type {LanguageServicePluginInstance['provideDiagnostics']} */
44
+ let originalProvideDiagnostics;
45
+ /** @type {LanguageServicePluginInstance} */
46
+ let originalInstance;
47
+
48
+ // Disable typescript-semantic's provideHover so it doesn't merge with ours
49
+ for (const [plugin, instance] of context.plugins) {
50
+ if (plugin.name === 'typescript-semantic') {
51
+ originalInstance = instance;
52
+ originalProvideDiagnostics = instance.provideDiagnostics;
53
+ instance.provideDiagnostics = undefined;
54
+ break;
55
+ }
56
+ }
57
+
58
+ if (!originalProvideDiagnostics) {
59
+ logError(
60
+ "'typescript-semantic plugin' was not found or has no 'provideDiagnostics'. \
61
+ This plugin must be loaded after Volar's typescript-semantic plugin.",
62
+ );
63
+ }
64
+
65
+ return {
66
+ async provideDiagnostics(document, token) {
67
+ let diagnostics;
68
+
69
+ if (originalProvideDiagnostics) {
70
+ diagnostics = await originalProvideDiagnostics.call(originalInstance, document, token);
71
+ }
72
+
73
+ if (!diagnostics || diagnostics.length === 0) {
74
+ return diagnostics;
75
+ }
76
+
77
+ log(`Filtering ${diagnostics.length} TypeScript diagnostics for ${document.uri}`);
78
+
79
+ const virtualCode = getVirtualCode(document, context);
80
+
81
+ const filtered = diagnostics.filter((diagnostic) => {
82
+ const range = diagnostic.range;
83
+ const rangeStart = document.offsetAt(range.start);
84
+ const rangeEnd = document.offsetAt(range.end);
85
+ // Get the mapping at this diagnostic position
86
+ const mapping = virtualCode.findMappingByGeneratedRange(rangeStart, rangeEnd);
87
+
88
+ if (!mapping) {
89
+ return true;
90
+ }
91
+
92
+ const suppressedCodes = mapping.data.customData?.suppressedDiagnostics;
93
+
94
+ if (!suppressedCodes || suppressedCodes.length === 0) {
95
+ return true;
96
+ }
97
+
98
+ const diagnosticCode =
99
+ typeof diagnostic.code === 'number'
100
+ ? diagnostic.code
101
+ : typeof diagnostic.code === 'string'
102
+ ? parseInt(diagnostic.code)
103
+ : null;
104
+
105
+ if (diagnosticCode && suppressedCodes.includes(diagnosticCode)) {
106
+ log(`Suppressing diagnostic ${diagnosticCode}: ${diagnostic.message}`);
107
+ return false; // Filter out this diagnostic
108
+ }
109
+
110
+ return true; // Keep this diagnostic
111
+ });
112
+
113
+ log(`Filtered from ${diagnostics.length} to ${filtered.length} diagnostics`);
114
+ return filtered;
115
+ },
116
+ };
117
+ },
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Get the mapping at a specific position in the document
123
+ * @param {import('@volar/language-server').LanguageServiceContext} context
124
+ * @param {import('vscode-languageserver-textdocument').TextDocument} document
125
+ * @param {import('@volar/language-server').Position} position
126
+ * @returns {CodeMapping | null}
127
+ */
128
+ function getMappingAtPosition(context, document, position) {
129
+ try {
130
+ const { URI } = require('vscode-uri');
131
+ const { RippleVirtualCode } = require('@ripple-ts/typescript-plugin/src/language.js');
132
+
133
+ const uri = URI.parse(document.uri);
134
+ const decoded = context.decodeEmbeddedDocumentUri(uri);
135
+ if (!decoded) {
136
+ return null;
137
+ }
138
+
139
+ const [sourceUri, virtualCodeId] = decoded;
140
+ const sourceScript = context.language.scripts.get(sourceUri);
141
+ const virtualCode = /** @type {RippleVirtualCode} */ (
142
+ sourceScript?.generated?.embeddedCodes.get(virtualCodeId)
143
+ );
144
+
145
+ if (!virtualCode?.mappings) {
146
+ return null;
147
+ }
148
+
149
+ // Convert position to offset in the virtual document
150
+ const offset = document.offsetAt(position);
151
+
152
+ // Find mapping that contains this offset
153
+ for (const mapping of virtualCode.mappings) {
154
+ const genStart = mapping.generatedOffsets[0];
155
+ const genEnd = genStart + mapping.lengths[0];
156
+
157
+ if (offset >= genStart && offset < genEnd) {
158
+ return mapping;
159
+ }
160
+ }
161
+
162
+ return null;
163
+ } catch (err) {
164
+ log('Error getting mapping at position:', err);
165
+ return null;
166
+ }
167
+ }
168
+
169
+ module.exports = {
170
+ createTypeScriptDiagnosticFilterPlugin,
171
+ };
package/src/utils.js ADDED
@@ -0,0 +1,29 @@
1
+ /** @import { TextDocument } from 'vscode-languageserver-textdocument' */
2
+ /** @import { LanguageServiceContext } from '@volar/language-server' */
3
+ /** @import {RippleVirtualCode} from '@ripple-ts/typescript-plugin/src/language.js' */
4
+
5
+ const { URI } = require('vscode-uri');
6
+
7
+ /**
8
+ * Get virtual code from the encoded document URI
9
+ * @param {LanguageServiceContext} context
10
+ * @param {TextDocument} document
11
+ * @returns {RippleVirtualCode}
12
+ */
13
+ function getVirtualCode(document, context) {
14
+ const uri = URI.parse(document.uri);
15
+ const decoded = /** @type {[documentUri: URI, embeddedCodeId: string]} */ (
16
+ context.decodeEmbeddedDocumentUri(uri)
17
+ );
18
+ const [sourceUri, virtualCodeId] = decoded;
19
+ const sourceScript = context.language.scripts.get(sourceUri);
20
+ const virtualCode = /** @type {RippleVirtualCode} */ (
21
+ sourceScript?.generated?.embeddedCodes.get(virtualCodeId)
22
+ );
23
+
24
+ return virtualCode;
25
+ }
26
+
27
+ module.exports = {
28
+ getVirtualCode,
29
+ };