@astrojs/language-server 0.19.0 → 0.19.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @astrojs/language-server
2
2
 
3
+ ## 0.19.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 729dff5: Add support for giving linked editing ranges
8
+ - 05a48c2: Fix some TypeScript diagnostics not showing up in certain cases
9
+ - fe2d26b: Add support for showing Svelte components documentation on hover
10
+ - Updated dependencies [fe2d26b]
11
+ - @astrojs/svelte-language-integration@0.1.5
12
+
3
13
  ## 0.19.0
4
14
 
5
15
  ### Minor Changes
@@ -1,4 +1,4 @@
1
- import { CancellationToken, Color, ColorInformation, ColorPresentation, CompletionContext, CompletionItem, CompletionList, DefinitionLink, Diagnostic, FoldingRange, Hover, Position, Range, Location, SignatureHelp, SignatureHelpContext, TextDocumentContentChangeEvent, TextDocumentIdentifier, WorkspaceEdit, SymbolInformation, SemanticTokens, CodeActionContext, CodeAction, InlayHint, FormattingOptions, TextEdit } from 'vscode-languageserver';
1
+ import { CancellationToken, Color, ColorInformation, ColorPresentation, CompletionContext, CompletionItem, CompletionList, DefinitionLink, Diagnostic, FoldingRange, Hover, Position, Range, Location, SignatureHelp, SignatureHelpContext, TextDocumentContentChangeEvent, TextDocumentIdentifier, WorkspaceEdit, SymbolInformation, SemanticTokens, CodeActionContext, CodeAction, InlayHint, FormattingOptions, TextEdit, LinkedEditingRanges } from 'vscode-languageserver';
2
2
  import type { AppCompletionItem, Plugin } from './interfaces';
3
3
  import { DocumentManager } from '../core/documents/DocumentManager';
4
4
  interface PluginHostConfig {
@@ -22,6 +22,7 @@ export declare class PluginHost {
22
22
  getFoldingRanges(textDocument: TextDocumentIdentifier): Promise<FoldingRange[] | null>;
23
23
  getDocumentSymbols(textDocument: TextDocumentIdentifier, cancellationToken: CancellationToken): Promise<SymbolInformation[]>;
24
24
  getSemanticTokens(textDocument: TextDocumentIdentifier, range?: Range, cancellationToken?: CancellationToken): Promise<SemanticTokens | null>;
25
+ getLinkedEditingRanges(textDocument: TextDocumentIdentifier, position: Position): Promise<LinkedEditingRanges | null>;
25
26
  getDefinitions(textDocument: TextDocumentIdentifier, position: Position): Promise<DefinitionLink[] | Location[]>;
26
27
  rename(textDocument: TextDocumentIdentifier, position: Position, newName: string): Promise<WorkspaceEdit | null>;
27
28
  getDocumentColors(textDocument: TextDocumentIdentifier): Promise<ColorInformation[]>;
@@ -94,6 +94,10 @@ class PluginHost {
94
94
  const document = this.getDocument(textDocument.uri);
95
95
  return await this.execute('getSemanticTokens', [document, range, cancellationToken], ExecuteMode.FirstNonNull);
96
96
  }
97
+ async getLinkedEditingRanges(textDocument, position) {
98
+ const document = this.getDocument(textDocument.uri);
99
+ return await this.execute('getLinkedEditingRanges', [document, position], ExecuteMode.FirstNonNull);
100
+ }
97
101
  async getDefinitions(textDocument, position) {
98
102
  const document = this.getDocument(textDocument.uri);
99
103
  const definitions = (0, lodash_1.flatten)(await this.execute('getDefinitions', [document, position], ExecuteMode.Collect));
@@ -1,4 +1,4 @@
1
- import { CompletionList, Position, TextEdit, FoldingRange, Hover, SymbolInformation, FormattingOptions } from 'vscode-languageserver';
1
+ import { CompletionList, Position, TextEdit, FoldingRange, Hover, SymbolInformation, FormattingOptions, LinkedEditingRanges } from 'vscode-languageserver';
2
2
  import type { Plugin } from '../interfaces';
3
3
  import { ConfigManager } from '../../core/config/ConfigManager';
4
4
  import { AstroDocument } from '../../core/documents/AstroDocument';
@@ -17,6 +17,7 @@ export declare class HTMLPlugin implements Plugin {
17
17
  getCompletions(document: AstroDocument, position: Position): Promise<CompletionList | null>;
18
18
  formatDocument(document: AstroDocument, options: FormattingOptions): Promise<TextEdit[]>;
19
19
  getFoldingRanges(document: AstroDocument): FoldingRange[] | null;
20
+ getLinkedEditingRanges(document: AstroDocument, position: Position): LinkedEditingRanges | null;
20
21
  doTagComplete(document: AstroDocument, position: Position): Promise<string | null>;
21
22
  getDocumentSymbols(document: AstroDocument): Promise<SymbolInformation[]>;
22
23
  /**
@@ -105,6 +105,17 @@ class HTMLPlugin {
105
105
  }
106
106
  return this.lang.getFoldingRanges(document);
107
107
  }
108
+ getLinkedEditingRanges(document, position) {
109
+ const html = document.html;
110
+ if (!html) {
111
+ return null;
112
+ }
113
+ const ranges = this.lang.findLinkedEditingRanges(document, position, html);
114
+ if (!ranges) {
115
+ return null;
116
+ }
117
+ return { ranges };
118
+ }
108
119
  async doTagComplete(document, position) {
109
120
  if (!(await this.featureEnabled(document, 'tagComplete'))) {
110
121
  return null;
@@ -3,6 +3,17 @@ import { Diagnostic } from 'vscode-languageserver-types';
3
3
  import { AstroDocument } from '../../../core/documents';
4
4
  import { DiagnosticsProvider } from '../../interfaces';
5
5
  import { LanguageServiceManager } from '../LanguageServiceManager';
6
+ export declare enum DiagnosticCodes {
7
+ SPREAD_EXPECTED = 1005,
8
+ DUPLICATED_JSX_ATTRIBUTES = 17001,
9
+ MUST_HAVE_PARENT_ELEMENT = 2657,
10
+ CANNOT_IMPORT_TS_EXT = 2691,
11
+ CANT_RETURN_OUTSIDE_FUNC = 1108,
12
+ ISOLATED_MODULE_COMPILE_ERR = 1208,
13
+ TYPE_NOT_ASSIGNABLE = 2322,
14
+ JSX_NO_CLOSING_TAG = 17008,
15
+ NO_DECL_IMPLICIT_ANY_TYPE = 7016
16
+ }
6
17
  export declare class DiagnosticsProviderImpl implements DiagnosticsProvider {
7
18
  private readonly languageServiceManager;
8
19
  constructor(languageServiceManager: LanguageServiceManager);
@@ -3,11 +3,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DiagnosticsProviderImpl = void 0;
6
+ exports.DiagnosticsProviderImpl = exports.DiagnosticCodes = void 0;
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
8
  const vscode_languageserver_types_1 = require("vscode-languageserver-types");
9
9
  const documents_1 = require("../../../core/documents");
10
10
  const utils_1 = require("../utils");
11
+ // List of codes:
12
+ // https://github.com/Microsoft/TypeScript/blob/main/src/compiler/diagnosticMessages.json
13
+ var DiagnosticCodes;
14
+ (function (DiagnosticCodes) {
15
+ DiagnosticCodes[DiagnosticCodes["SPREAD_EXPECTED"] = 1005] = "SPREAD_EXPECTED";
16
+ DiagnosticCodes[DiagnosticCodes["DUPLICATED_JSX_ATTRIBUTES"] = 17001] = "DUPLICATED_JSX_ATTRIBUTES";
17
+ DiagnosticCodes[DiagnosticCodes["MUST_HAVE_PARENT_ELEMENT"] = 2657] = "MUST_HAVE_PARENT_ELEMENT";
18
+ DiagnosticCodes[DiagnosticCodes["CANNOT_IMPORT_TS_EXT"] = 2691] = "CANNOT_IMPORT_TS_EXT";
19
+ DiagnosticCodes[DiagnosticCodes["CANT_RETURN_OUTSIDE_FUNC"] = 1108] = "CANT_RETURN_OUTSIDE_FUNC";
20
+ DiagnosticCodes[DiagnosticCodes["ISOLATED_MODULE_COMPILE_ERR"] = 1208] = "ISOLATED_MODULE_COMPILE_ERR";
21
+ DiagnosticCodes[DiagnosticCodes["TYPE_NOT_ASSIGNABLE"] = 2322] = "TYPE_NOT_ASSIGNABLE";
22
+ DiagnosticCodes[DiagnosticCodes["JSX_NO_CLOSING_TAG"] = 17008] = "JSX_NO_CLOSING_TAG";
23
+ DiagnosticCodes[DiagnosticCodes["NO_DECL_IMPLICIT_ANY_TYPE"] = 7016] = "NO_DECL_IMPLICIT_ANY_TYPE";
24
+ })(DiagnosticCodes = exports.DiagnosticCodes || (exports.DiagnosticCodes = {}));
11
25
  class DiagnosticsProviderImpl {
12
26
  constructor(languageServiceManager) {
13
27
  this.languageServiceManager = languageServiceManager;
@@ -39,17 +53,15 @@ class DiagnosticsProviderImpl {
39
53
  code: diagnostic.code,
40
54
  tags: getDiagnosticTag(diagnostic),
41
55
  }))
56
+ .filter(isNoCantEndWithTS)
42
57
  .map(mapRange(scriptTagSnapshot, document));
43
58
  scriptDiagnostics.push(...scriptDiagnostic);
44
59
  });
45
60
  const { script: scriptBoundaries } = this.getTagBoundaries(lang, filePath);
46
- const syntaxDiagnostics = lang.getSyntacticDiagnostics(filePath);
47
- const suggestionDiagnostics = lang.getSuggestionDiagnostics(filePath);
48
- const semanticDiagnostics = lang.getSemanticDiagnostics(filePath);
49
61
  const diagnostics = [
50
- ...syntaxDiagnostics,
51
- ...suggestionDiagnostics,
52
- ...semanticDiagnostics,
62
+ ...lang.getSyntacticDiagnostics(filePath),
63
+ ...lang.getSuggestionDiagnostics(filePath),
64
+ ...lang.getSemanticDiagnostics(filePath),
53
65
  ].filter((diag) => {
54
66
  return isNoWithinBoundary(scriptBoundaries, diag);
55
67
  });
@@ -67,14 +79,15 @@ class DiagnosticsProviderImpl {
67
79
  ...scriptDiagnostics,
68
80
  ]
69
81
  .filter((diag) => {
70
- return (hasNoNegativeLines(diag) &&
71
- isNoJSXImplicitRuntimeWarning(diag) &&
82
+ return (
83
+ // Make sure the diagnostic is inside the document and not in generated code
84
+ diag.range.start.line <= document.lineCount &&
85
+ hasNoNegativeLines(diag) &&
72
86
  isNoJSXMustHaveOneParent(diag) &&
73
- isNoCantEndWithTS(diag) &&
74
- isNoSpreadExpected(diag) &&
75
- isNoCantResolveJSONModule(diag) &&
87
+ isNoSpreadExpected(diag, document) &&
76
88
  isNoCantReturnOutsideFunction(diag) &&
77
89
  isNoIsolatedModuleError(diag) &&
90
+ isNoImportImplicitAnyType(diag) &&
78
91
  isNoJsxCannotHaveMultipleAttrsError(diag));
79
92
  })
80
93
  .map(enhanceIfNecessary);
@@ -113,30 +126,39 @@ class DiagnosticsProviderImpl {
113
126
  }
114
127
  }
115
128
  exports.DiagnosticsProviderImpl = DiagnosticsProviderImpl;
116
- function getDiagnosticTag(diagnostic) {
117
- const tags = [];
118
- if (diagnostic.reportsUnnecessary) {
119
- tags.push(vscode_languageserver_types_1.DiagnosticTag.Unnecessary);
129
+ function isWithinBoundaries(boundaries, start) {
130
+ for (let [bstart, bend] of boundaries) {
131
+ if (start > bstart && start < bend) {
132
+ return true;
133
+ }
120
134
  }
121
- if (diagnostic.reportsDeprecated) {
122
- tags.push(vscode_languageserver_types_1.DiagnosticTag.Deprecated);
135
+ return false;
136
+ }
137
+ function diagnosticIsWithinBoundaries(sourceFile, boundaries, diagnostic) {
138
+ if ('start' in diagnostic) {
139
+ if (diagnostic.start == null)
140
+ return false;
141
+ return isWithinBoundaries(boundaries, diagnostic.start);
123
142
  }
124
- return tags;
143
+ if (!sourceFile)
144
+ return false;
145
+ let startRange = diagnostic.range.start;
146
+ let pos = typescript_1.default.getPositionOfLineAndCharacter(sourceFile, startRange.line, startRange.character);
147
+ return isWithinBoundaries(boundaries, pos);
148
+ }
149
+ function isNoWithinBoundary(boundaries, diagnostic) {
150
+ return !diagnosticIsWithinBoundaries(undefined, boundaries, diagnostic);
125
151
  }
126
152
  function mapRange(fragment, _document) {
127
153
  return (diagnostic) => {
128
154
  let range = (0, documents_1.mapRangeToOriginal)(fragment, diagnostic.range);
129
- if (range.start.line < 0) {
130
- // Could be a props error?
131
- // From svelte
132
- }
133
155
  return { ...diagnostic, range };
134
156
  };
135
157
  }
136
158
  /**
137
159
  * In some rare cases mapping of diagnostics does not work and produces negative lines.
138
160
  * We filter out these diagnostics with negative lines because else the LSP
139
- * apparently has a hickup and does not show any diagnostics at all.
161
+ * apparently has a hiccup and does not show any diagnostics at all.
140
162
  */
141
163
  function hasNoNegativeLines(diagnostic) {
142
164
  return diagnostic.range.start.line >= 0 && diagnostic.range.end.line >= 0;
@@ -145,56 +167,59 @@ function hasNoNegativeLines(diagnostic) {
145
167
  * Astro allows multiple attributes to have the same name
146
168
  */
147
169
  function isNoJsxCannotHaveMultipleAttrsError(diagnostic) {
148
- return diagnostic.code !== 17001;
170
+ return diagnostic.code !== DiagnosticCodes.DUPLICATED_JSX_ATTRIBUTES;
149
171
  }
150
172
  /** Astro allows component with multiple root elements */
151
173
  function isNoJSXMustHaveOneParent(diagnostic) {
152
- return diagnostic.code !== 2657;
174
+ return diagnostic.code !== DiagnosticCodes.MUST_HAVE_PARENT_ELEMENT;
153
175
  }
154
- /** Astro allows `.ts` ending for imports, unlike TypeScript */
155
- function isNoCantEndWithTS(diagnostic) {
156
- return diagnostic.code !== 2691;
176
+ function isNoImportImplicitAnyType(diagnostic) {
177
+ return diagnostic.code !== DiagnosticCodes.NO_DECL_IMPLICIT_ANY_TYPE;
157
178
  }
158
- function isNoSpreadExpected(diagnostic) {
159
- return diagnostic.code !== 1005;
179
+ /**
180
+ * When using the shorthand syntax for props TSX expects you to use the spread operator
181
+ * Since the shorthand syntax works differently in Astro and this is not required, hide this message
182
+ * However, the error code used here is quite generic, as such we need to make we only ignore in valid cases
183
+ */
184
+ function isNoSpreadExpected(diagnostic, document) {
185
+ if (diagnostic.code === DiagnosticCodes.SPREAD_EXPECTED &&
186
+ diagnostic.message.includes('...') &&
187
+ document.offsetAt(diagnostic.range.start) > (document.astroMeta.frontmatter.endOffset ?? 0)) {
188
+ return false;
189
+ }
190
+ return true;
160
191
  }
161
- function isNoJSXImplicitRuntimeWarning(diagnostic) {
162
- return diagnostic.code !== 7016 && diagnostic.code !== 2792;
192
+ /** Inside script tags, Astro currently require the `.ts` file extension for imports */
193
+ function isNoCantEndWithTS(diagnostic) {
194
+ return diagnostic.code !== DiagnosticCodes.CANNOT_IMPORT_TS_EXT;
163
195
  }
164
196
  /**
165
197
  * Ignore "Can't return outside of function body"
166
- * This is technically a valid diagnostic, but due to how we format our TSX, the frontmatter is at top-level so we have
167
- * to ignore this. It wasn't a problem before because users didn't need to return things but they can now with SSR
198
+ * Since the frontmatter is at the top level, users trying to return a Response for SSR mode run into this
168
199
  */
169
200
  function isNoCantReturnOutsideFunction(diagnostic) {
170
- return diagnostic.code !== 1108;
171
- }
172
- /**
173
- * Astro allows users to import JSON modules
174
- */
175
- function isNoCantResolveJSONModule(diagnostic) {
176
- return diagnostic.code !== 2732;
201
+ return diagnostic.code !== DiagnosticCodes.CANT_RETURN_OUTSIDE_FUNC;
177
202
  }
178
203
  /**
179
204
  * When the content of the file is invalid and can't be parsed properly for TSX generation, TS will show an error about
180
205
  * how the current module can't be compiled under --isolatedModule, this is confusing to users so let's ignore this
181
206
  */
182
207
  function isNoIsolatedModuleError(diagnostic) {
183
- return diagnostic.code !== 1208;
208
+ return diagnostic.code !== DiagnosticCodes.ISOLATED_MODULE_COMPILE_ERR;
184
209
  }
185
210
  /**
186
211
  * Some diagnostics have JSX-specific nomenclature or unclear description. Enhance them for more clarity.
187
212
  */
188
213
  function enhanceIfNecessary(diagnostic) {
189
214
  // JSX element has no closing tag. JSX -> HTML
190
- if (diagnostic.code === 17008) {
215
+ if (diagnostic.code === DiagnosticCodes.JSX_NO_CLOSING_TAG) {
191
216
  return {
192
217
  ...diagnostic,
193
218
  message: diagnostic.message.replace('JSX', 'HTML'),
194
219
  };
195
220
  }
196
221
  // For the rare case where an user might try to put a client directive on something that is not a component
197
- if (diagnostic.code === 2322) {
222
+ if (diagnostic.code === DiagnosticCodes.TYPE_NOT_ASSIGNABLE) {
198
223
  if (diagnostic.message.includes("Property 'client:") && diagnostic.message.includes("to type 'HTMLProps")) {
199
224
  return {
200
225
  ...diagnostic,
@@ -202,28 +227,23 @@ function enhanceIfNecessary(diagnostic) {
202
227
  };
203
228
  }
204
229
  }
230
+ // An import path cannot end with '.ts(x)' consider importing with no extension
231
+ // TODO: Remove this when https://github.com/withastro/astro/issues/3415 is fixed
232
+ if (diagnostic.code === DiagnosticCodes.CANNOT_IMPORT_TS_EXT) {
233
+ return {
234
+ ...diagnostic,
235
+ message: diagnostic.message.replace(/\.jsx?/, ''),
236
+ };
237
+ }
205
238
  return diagnostic;
206
239
  }
207
- function isWithinBoundaries(boundaries, start) {
208
- for (let [bstart, bend] of boundaries) {
209
- if (start > bstart && start < bend) {
210
- return true;
211
- }
240
+ function getDiagnosticTag(diagnostic) {
241
+ const tags = [];
242
+ if (diagnostic.reportsUnnecessary) {
243
+ tags.push(vscode_languageserver_types_1.DiagnosticTag.Unnecessary);
212
244
  }
213
- return false;
214
- }
215
- function diagnosticIsWithinBoundaries(sourceFile, boundaries, diagnostic) {
216
- if ('start' in diagnostic) {
217
- if (diagnostic.start == null)
218
- return false;
219
- return isWithinBoundaries(boundaries, diagnostic.start);
245
+ if (diagnostic.reportsDeprecated) {
246
+ tags.push(vscode_languageserver_types_1.DiagnosticTag.Deprecated);
220
247
  }
221
- if (!sourceFile)
222
- return false;
223
- let startRange = diagnostic.range.start;
224
- let pos = typescript_1.default.getPositionOfLineAndCharacter(sourceFile, startRange.line, startRange.character);
225
- return isWithinBoundaries(boundaries, pos);
226
- }
227
- function isNoWithinBoundary(boundaries, diagnostic) {
228
- return !diagnosticIsWithinBoundaries(undefined, boundaries, diagnostic);
248
+ return tags;
229
249
  }
@@ -224,6 +224,7 @@ async function createLanguageService(tsconfigPath, docContext, workspaceUris) {
224
224
  const forcedCompilerOptions = {
225
225
  noEmit: true,
226
226
  declaration: false,
227
+ resolveJsonModule: true,
227
228
  allowNonTsExtensions: true,
228
229
  allowJs: true,
229
230
  jsx: typescript_1.default.JsxEmit.Preserve,
package/dist/server.js CHANGED
@@ -129,6 +129,7 @@ function startLanguageServer(connection) {
129
129
  colorProvider: true,
130
130
  hoverProvider: true,
131
131
  documentSymbolProvider: true,
132
+ linkedEditingRangeProvider: true,
132
133
  semanticTokensProvider: {
133
134
  legend: (0, utils_2.getSemanticTokenLegend)(),
134
135
  range: true,
@@ -195,6 +196,7 @@ function startLanguageServer(connection) {
195
196
  connection.onDocumentSymbol((params, cancellationToken) => pluginHost.getDocumentSymbols(params.textDocument, cancellationToken));
196
197
  connection.onRequest(vscode_languageserver_1.SemanticTokensRequest.type, (evt, cancellationToken) => pluginHost.getSemanticTokens(evt.textDocument, undefined, cancellationToken));
197
198
  connection.onRequest(vscode_languageserver_1.SemanticTokensRangeRequest.type, (evt, cancellationToken) => pluginHost.getSemanticTokens(evt.textDocument, evt.range, cancellationToken));
199
+ connection.onRequest(vscode_languageserver_1.LinkedEditingRangeRequest.type, async (evt) => await pluginHost.getLinkedEditingRanges(evt.textDocument, evt.position));
198
200
  connection.onDocumentFormatting((params) => pluginHost.formatDocument(params.textDocument, params.options));
199
201
  connection.onDocumentColor((params) => pluginHost.getDocumentColors(params.textDocument));
200
202
  connection.onColorPresentation((params) => pluginHost.getColorPresentations(params.textDocument, params.range, params.color));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/language-server",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "author": "withastro",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@astrojs/vue-language-integration": "^0.1.0",
23
- "@astrojs/svelte-language-integration": "^0.1.4",
23
+ "@astrojs/svelte-language-integration": "^0.1.5",
24
24
  "@vscode/emmet-helper": "^2.8.4",
25
25
  "lodash": "^4.17.21",
26
26
  "source-map": "^0.7.3",