@ripple-ts/language-server 0.2.192 → 0.2.194

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.192",
3
+ "version": "0.2.194",
4
4
  "description": "Language Server Protocol implementation for Ripple",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -19,10 +19,10 @@
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.192"
22
+ "@ripple-ts/typescript-plugin": "0.2.194"
23
23
  },
24
24
  "devDependencies": {
25
- "ripple": "0.2.192"
25
+ "ripple": "0.2.194"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "typescript": "^5.9.2"
@@ -64,7 +64,7 @@ function createAutoInsertPlugin() {
64
64
  return null;
65
65
  }
66
66
 
67
- const [virtualCode] = getVirtualCode(document, context);
67
+ const { virtualCode } = getVirtualCode(document, context);
68
68
 
69
69
  if (virtualCode.languageId !== 'ripple') {
70
70
  log(`Skipping auto-insert processing in the '${virtualCode.languageId}' context`);
@@ -1,11 +1,14 @@
1
1
  /**
2
- * @import {Diagnostic, LanguageServicePlugin, LanguageServiceContext} from '@volar/language-server'
3
- * @import {TextDocument} from 'vscode-languageserver-textdocument'
2
+ * @import {Diagnostic, Range, LanguageServicePlugin, LanguageServiceContext, Position, Mapper} from '@volar/language-server';
3
+ * @import {TextDocument} from 'vscode-languageserver-textdocument';
4
+ * @import {RippleVirtualCode} from '@ripple-ts/typescript-plugin/src/language.js';
4
5
  */
6
+ // @ts-expect-error: ESM type import is fine
7
+ /** @import {RippleCompileError} from 'ripple/compiler'; */
5
8
 
6
9
  const { getVirtualCode, createLogging } = require('./utils.js');
7
10
 
8
- const { log, logError } = createLogging('[Ripple Compile Error Diagnostic Plugin]');
11
+ const { log } = createLogging('[Ripple Compile Error Diagnostic Plugin]');
9
12
  const { DiagnosticSeverity } = require('@volar/language-server');
10
13
 
11
14
  /**
@@ -25,154 +28,134 @@ function createCompileErrorDiagnosticPlugin() {
25
28
  create(/** @type {LanguageServiceContext} */ context) {
26
29
  return {
27
30
  provideDiagnostics(document, _token) {
28
- try {
29
- log('Providing Ripple diagnostics for:', document.uri);
30
-
31
- const [virtualCode] = getVirtualCode(document, context);
32
-
33
- if (!virtualCode || !virtualCode.errors || virtualCode.errors.length === 0) {
34
- return [];
35
- }
36
-
37
- const diagnostics = [];
38
-
39
- log('Processing', virtualCode.errors.length, 'errors');
40
-
41
- // Convert each stored error to a diagnostic
42
- for (const error of virtualCode.errors) {
43
- try {
44
- // Use the actual snapshot text that Volar is working with
45
- const snapshotText = virtualCode.snapshot.getText(
46
- 0,
47
- virtualCode.snapshot.getLength(),
48
- );
49
- const diagnostic = parseCompilationErrorWithDocument(
50
- error,
51
- virtualCode.fileName,
52
- snapshotText,
53
- document,
54
- );
55
- diagnostics.push(diagnostic);
56
- } catch (parseError) {
57
- logError('Failed to parse compilation error:', parseError);
58
- }
59
- }
60
-
61
- log('Generated', diagnostics.length, 'diagnostics');
31
+ log('Providing Ripple diagnostics for:', document.uri);
32
+
33
+ /** @type {Diagnostic[]} */
34
+ const diagnostics = [];
35
+ const { virtualCode, sourceMap } = getVirtualCode(document, context);
36
+
37
+ if (!virtualCode || virtualCode.languageId !== 'ripple') {
38
+ // skip if it's like embedded css
39
+ return diagnostics;
40
+ }
41
+
42
+ if (!virtualCode.fatalErrors.length && !virtualCode.usageErrors.length) {
62
43
  return diagnostics;
63
- } catch (err) {
64
- logError('Failed to provide diagnostics:', err);
65
- return [];
66
44
  }
45
+
46
+ for (const error of [...virtualCode.fatalErrors, ...virtualCode.usageErrors]) {
47
+ const diagnostic = parseCompilationErrorWithDocument(
48
+ error,
49
+ virtualCode,
50
+ sourceMap,
51
+ document,
52
+ );
53
+ diagnostics.push(diagnostic);
54
+ }
55
+
56
+ log('Generated', diagnostics.length, 'diagnostics');
57
+ return diagnostics;
67
58
  },
68
59
  };
69
60
  },
70
61
  };
71
62
  }
72
63
 
73
- // Helper function to parse compilation errors using document.positionAt (Glint style)
74
64
  /**
75
- * @param {unknown} error
76
- * @param {string} fallbackFileName
77
- * @param {string} sourceText
65
+ * @param {RippleCompileError} error
66
+ * @param {RippleVirtualCode} virtualCode
67
+ * @param {Mapper | undefined} sourceMap
78
68
  * @param {TextDocument} document
79
69
  * @returns {Diagnostic}
80
70
  */
81
- function parseCompilationErrorWithDocument(error, fallbackFileName, sourceText, document) {
82
- const errorObject = /** @type {{ message?: string }} */ (error);
83
- const message = errorObject.message || String(error);
84
-
85
- try {
86
- // First check if there's a GitHub-style range in the error
87
- // Format: filename#L39C24-L39C32
88
- const githubRangeMatch = message.match(/\(([^#]+)#L(\d+)C(\d+)-L(\d+)C(\d+)\)/);
89
-
90
- if (githubRangeMatch) {
91
- // Use the GitHub range data directly
92
- const startLine = parseInt(githubRangeMatch[2]);
93
- const startColumn = parseInt(githubRangeMatch[3]);
94
- const endLine = parseInt(githubRangeMatch[4]);
95
- const endColumn = parseInt(githubRangeMatch[5]);
96
-
97
- // Convert to zero-based
98
- const zeroBasedStartLine = Math.max(0, startLine - 1);
99
- const zeroBasedStartColumn = Math.max(0, startColumn);
100
- const zeroBasedEndLine = Math.max(0, endLine - 1);
101
- const zeroBasedEndColumn = Math.max(0, endColumn);
71
+ function parseCompilationErrorWithDocument(error, virtualCode, sourceMap, document) {
72
+ if (error.type === 'fatal') {
73
+ return {
74
+ severity: DiagnosticSeverity.Error,
75
+ range: get_error_range_from_source(error, document),
76
+ message: error.message,
77
+ source: 'Ripple',
78
+ code: 'ripple-compile-error',
79
+ };
80
+ }
102
81
 
103
- return {
104
- severity: DiagnosticSeverity.Error,
105
- range: {
106
- start: { line: zeroBasedStartLine, character: zeroBasedStartColumn },
107
- end: { line: zeroBasedEndLine, character: zeroBasedEndColumn },
108
- },
109
- message: message.replace(/\s*\([^#]+#L\d+C\d+-L\d+C\d+\)/, '').trim(), // Remove the range part from message
110
- source: 'Ripple',
111
- code: 'ripple-compile-error',
112
- };
82
+ /** @type {Position | null} */
83
+ let start = null;
84
+ /** @type {Position | null} */
85
+ let end = null;
86
+
87
+ if (error.pos) {
88
+ const start_offset = get_start_offset_from_error(error);
89
+ const end_offset = get_end_offset_from_error(error, start_offset);
90
+ // try to find exact mapping
91
+ // TODO: perhaps it's best to just switch to sourceMap entirely?
92
+ const mapping = virtualCode.findMappingBySourceRange(start_offset, end_offset);
93
+
94
+ if (mapping) {
95
+ start = document.positionAt(mapping.generatedOffsets[0]);
96
+ end = document.positionAt(
97
+ mapping.generatedOffsets[0] +
98
+ (mapping.generatedLengths || mapping.data.customData.generatedLengths)[0],
99
+ );
100
+ } else if (sourceMap) {
101
+ // try to find the match even across multiple mappings
102
+ const result = sourceMap.toGeneratedRange(start_offset, end_offset, true).next().value;
103
+
104
+ if (result) {
105
+ const [gen_start_offset, gen_end_offset] = result;
106
+ start = document.positionAt(gen_start_offset);
107
+ end = document.positionAt(gen_end_offset);
108
+ }
113
109
  }
110
+ }
114
111
 
115
- // Fallback to old parsing method if no range found
116
- // Try to parse location from error message
117
- // Format: "Error message (filename:line:column)"
118
- const locationMatch = message.match(/\(([^:]+):(\d+):(\d+)\)$/);
119
-
120
- if (locationMatch) {
121
- const [, fileName, lineStr, columnStr] = locationMatch;
122
- const line = parseInt(lineStr, 10);
123
- const column = parseInt(columnStr, 10);
124
-
125
- // Extract the main error message (without location)
126
- const cleanMessage = message.replace(/\s*\([^:]+:\d+:\d+\)$/, '');
127
-
128
- // Convert 1-based line/column to 0-based for VS Code
129
- const zeroBasedLine = Math.max(0, line - 1);
130
- const actualColumn = Math.max(0, column - 1);
112
+ if (!start || !end) {
113
+ start = { line: 0, character: 0 };
114
+ end = { line: 0, character: 1 };
115
+ }
131
116
 
132
- // Use the original error coordinates from the Ripple compiler
133
- // Just use the compiler's position as-is, with a simple 1-character highlight
134
- let length = Math.min(1, sourceText.split('\n')[zeroBasedLine]?.length - actualColumn || 1);
117
+ return {
118
+ severity: DiagnosticSeverity.Error,
119
+ range: { start, end },
120
+ message: error.message,
121
+ source: 'Ripple',
122
+ code: 'ripple-usage-error',
123
+ };
124
+ }
135
125
 
136
- return {
137
- severity: DiagnosticSeverity.Error,
138
- range: {
139
- start: { line: zeroBasedLine, character: actualColumn },
140
- end: { line: zeroBasedLine, character: actualColumn + length },
141
- },
142
- message: cleanMessage,
143
- source: 'Ripple',
144
- code: 'ripple-compile-error',
145
- };
146
- } else {
147
- // Fallback for errors without location information
148
- const startPosition = document.positionAt(0);
149
- const endPosition = document.positionAt(Math.min(1, sourceText.length));
126
+ /**
127
+ * @param {RippleCompileError} error
128
+ * @param {TextDocument} document
129
+ * @returns {Range}
130
+ */
131
+ function get_error_range_from_source(error, document) {
132
+ const start_offset = get_start_offset_from_error(error);
133
+ return {
134
+ start: document.positionAt(start_offset),
135
+ end: document.positionAt(get_end_offset_from_error(error, start_offset)),
136
+ };
137
+ }
150
138
 
151
- return {
152
- severity: DiagnosticSeverity.Error,
153
- range: {
154
- start: startPosition,
155
- end: endPosition,
156
- },
157
- message: `Ripple compilation error: ${message}`,
158
- source: 'Ripple',
159
- code: 'ripple-compile-error',
160
- };
161
- }
162
- } catch (parseError) {
163
- logError('Error parsing compilation error:', parseError);
139
+ /**
140
+ * @param {RippleCompileError} error
141
+ * @param {number} [start_offset]
142
+ * @returns {number}
143
+ */
144
+ function get_end_offset_from_error(error, start_offset) {
145
+ start_offset = start_offset ?? get_start_offset_from_error(error);
146
+ return error.end
147
+ ? error.end
148
+ : error.raisedAt && (error.raisedAt ?? 0) > start_offset
149
+ ? error.raisedAt
150
+ : start_offset + 1;
151
+ }
164
152
 
165
- return {
166
- severity: DiagnosticSeverity.Error,
167
- range: {
168
- start: { line: 0, character: 0 },
169
- end: { line: 0, character: 1 },
170
- },
171
- message: `Ripple compilation error: ${message}`,
172
- source: 'Ripple',
173
- code: 'ripple-parse-error',
174
- };
175
- }
153
+ /**
154
+ * @param {RippleCompileError} error
155
+ * @returns {number}
156
+ */
157
+ function get_start_offset_from_error(error) {
158
+ return error.pos ?? 0;
176
159
  }
177
160
 
178
161
  module.exports = {
@@ -410,7 +410,7 @@ function createCompletionPlugin() {
410
410
  return { items: [], isIncomplete: false };
411
411
  }
412
412
 
413
- const [virtualCode] = getVirtualCode(document, context);
413
+ const { virtualCode } = getVirtualCode(document, context);
414
414
 
415
415
  if (virtualCode.languageId !== 'ripple') {
416
416
  // Check if we're inside an embedded code (like CSS in <style> blocks)
@@ -43,7 +43,7 @@ function createDefinitionPlugin() {
43
43
  }
44
44
  }
45
45
 
46
- const [virtualCode, sourceUri] = getVirtualCode(document, context);
46
+ const { virtualCode, sourceUri } = getVirtualCode(document, context);
47
47
 
48
48
  if (virtualCode.languageId !== 'ripple') {
49
49
  // like embedded css
@@ -56,7 +56,7 @@ function createDocumentHighlightPlugin() {
56
56
  return tsHighlights;
57
57
  }
58
58
 
59
- const [virtualCode] = getVirtualCode(document, context);
59
+ const { virtualCode } = getVirtualCode(document, context);
60
60
 
61
61
  if (virtualCode.languageId !== 'ripple') {
62
62
  log(`Skipping highlight processing in the '${virtualCode.languageId}' context`);
@@ -60,7 +60,7 @@ function createHoverPlugin() {
60
60
  );
61
61
  }
62
62
 
63
- const [virtualCode] = getVirtualCode(document, context);
63
+ const { virtualCode } = getVirtualCode(document, context);
64
64
 
65
65
  if (!virtualCode) {
66
66
  return tsHover;
@@ -35,9 +35,9 @@ function processDiagnostics(document, context, diagnostics) {
35
35
 
36
36
  log(`Filtering ${diagnostics.length} TypeScript diagnostics for ${document.uri}`);
37
37
 
38
- const [virtualCode] = getVirtualCode(document, context);
38
+ const { virtualCode } = getVirtualCode(document, context);
39
39
 
40
- if (!virtualCode) {
40
+ if (!virtualCode || virtualCode.languageId !== 'ripple') {
41
41
  return diagnostics;
42
42
  }
43
43
 
@@ -45,6 +45,11 @@ function processDiagnostics(document, context, diagnostics) {
45
45
  const result = [];
46
46
 
47
47
  for (const diagnostic of diagnostics) {
48
+ if (virtualCode.fatalErrors.length > 0 && diagnostic.code !== 'ripple-compile-error') {
49
+ // skip all TS diagnostics since we're dealing directly with the source code
50
+ continue;
51
+ }
52
+
48
53
  const range = diagnostic.range;
49
54
  const rangeStart = document.offsetAt(range.start);
50
55
  const rangeEnd = document.offsetAt(range.end);
package/src/utils.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** @import { TextDocument } from 'vscode-languageserver-textdocument' */
2
- /** @import { LanguageServiceContext } from '@volar/language-server' */
2
+ /** @import { LanguageServiceContext, Mapper, SourceScript } from '@volar/language-server' */
3
3
  /** @import {RippleVirtualCode} from '@ripple-ts/typescript-plugin/src/language.js' */
4
4
  // @ts-expect-error: ESM type import is fine
5
5
  /** @import {is_identifier_obfuscated, deobfuscate_identifier, IDENTIFIER_OBFUSCATION_PREFIX} from 'ripple/compiler/internal/identifier/utils' */
@@ -68,7 +68,13 @@ function concatMarkdownContents(...contents) {
68
68
  * Get virtual code from the encoded document URI
69
69
  * @param {LanguageServiceContext} context
70
70
  * @param {TextDocument} document
71
- * @returns {[RippleVirtualCode, URI]}
71
+ * @returns
72
+ {{
73
+ virtualCode: RippleVirtualCode;
74
+ sourceUri: URI;
75
+ sourceScript: SourceScript<URI> | undefined;
76
+ sourceMap: Mapper | undefined;
77
+ }}
72
78
  */
73
79
  function getVirtualCode(document, context) {
74
80
  const uri = URI.parse(document.uri);
@@ -81,7 +87,10 @@ function getVirtualCode(document, context) {
81
87
  sourceScript?.generated?.embeddedCodes.get(virtualCodeId)
82
88
  );
83
89
 
84
- return [virtualCode, sourceUri];
90
+ const sourceMap =
91
+ sourceScript && virtualCode ? context.language.maps.get(virtualCode, sourceScript) : undefined;
92
+
93
+ return { virtualCode, sourceUri, sourceScript, sourceMap };
85
94
  }
86
95
 
87
96
  /**