@astrojs/language-server 0.14.0 → 0.16.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/check.js +1 -2
  3. package/dist/core/config/ConfigManager.d.ts +20 -16
  4. package/dist/core/config/ConfigManager.js +112 -46
  5. package/dist/core/config/interfaces.d.ts +0 -52
  6. package/dist/core/documents/DocumentMapper.js +2 -4
  7. package/dist/core/documents/parseAstro.js +1 -1
  8. package/dist/core/documents/utils.d.ts +5 -0
  9. package/dist/core/documents/utils.js +18 -5
  10. package/dist/plugins/PluginHost.d.ts +3 -2
  11. package/dist/plugins/PluginHost.js +37 -10
  12. package/dist/plugins/astro/AstroPlugin.d.ts +1 -6
  13. package/dist/plugins/astro/AstroPlugin.js +0 -82
  14. package/dist/plugins/astro/features/CompletionsProvider.js +30 -15
  15. package/dist/plugins/css/CSSPlugin.d.ts +5 -5
  16. package/dist/plugins/css/CSSPlugin.js +41 -20
  17. package/dist/plugins/html/HTMLPlugin.d.ts +4 -4
  18. package/dist/plugins/html/HTMLPlugin.js +20 -16
  19. package/dist/plugins/html/features/astro-attributes.js +44 -27
  20. package/dist/plugins/interfaces.d.ts +2 -2
  21. package/dist/plugins/typescript/LanguageServiceManager.js +1 -1
  22. package/dist/plugins/typescript/TypeScriptPlugin.d.ts +10 -7
  23. package/dist/plugins/typescript/TypeScriptPlugin.js +39 -112
  24. package/dist/plugins/typescript/astro-sys.js +3 -5
  25. package/dist/plugins/typescript/astro2tsx.d.ts +1 -1
  26. package/dist/plugins/typescript/astro2tsx.js +7 -7
  27. package/dist/plugins/typescript/features/CodeActionsProvider.d.ts +16 -0
  28. package/dist/plugins/typescript/features/CodeActionsProvider.js +147 -0
  29. package/dist/plugins/typescript/features/CompletionsProvider.d.ts +26 -7
  30. package/dist/plugins/typescript/features/CompletionsProvider.js +260 -56
  31. package/dist/plugins/typescript/features/DefinitionsProvider.d.ts +9 -0
  32. package/dist/plugins/typescript/features/DefinitionsProvider.js +36 -0
  33. package/dist/plugins/typescript/features/DiagnosticsProvider.js +2 -3
  34. package/dist/plugins/typescript/features/DocumentSymbolsProvider.js +5 -6
  35. package/dist/plugins/typescript/features/FoldingRangesProvider.d.ts +9 -0
  36. package/dist/plugins/typescript/features/FoldingRangesProvider.js +64 -0
  37. package/dist/plugins/typescript/features/SemanticTokenProvider.js +2 -2
  38. package/dist/plugins/typescript/features/SignatureHelpProvider.js +2 -2
  39. package/dist/plugins/typescript/features/utils.d.ts +4 -0
  40. package/dist/plugins/typescript/features/utils.js +25 -3
  41. package/dist/plugins/typescript/language-service.js +5 -6
  42. package/dist/plugins/typescript/module-loader.js +1 -1
  43. package/dist/plugins/typescript/previewer.js +1 -1
  44. package/dist/plugins/typescript/snapshots/SnapshotManager.js +1 -1
  45. package/dist/plugins/typescript/snapshots/utils.js +27 -9
  46. package/dist/plugins/typescript/utils.d.ts +4 -0
  47. package/dist/plugins/typescript/utils.js +29 -1
  48. package/dist/server.js +43 -14
  49. package/dist/utils.d.ts +12 -0
  50. package/dist/utils.js +39 -3
  51. package/package.json +2 -2
@@ -1,92 +1,296 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
4
24
  };
5
25
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.CompletionsProviderImpl = void 0;
7
- const utils_1 = require("../../../core/documents/utils");
8
- const typescript_1 = __importDefault(require("typescript"));
26
+ exports.codeActionChangeToTextEdit = exports.CompletionsProviderImpl = void 0;
9
27
  const vscode_languageserver_1 = require("vscode-languageserver");
28
+ const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
29
+ const utils_1 = require("../../../core/documents/utils");
30
+ const documents_1 = require("../../../core/documents");
31
+ const typescript_1 = __importStar(require("typescript"));
32
+ const vscode_languageserver_2 = require("vscode-languageserver");
10
33
  const utils_2 = require("../utils");
11
- const completionOptions = Object.freeze({
12
- importModuleSpecifierPreference: 'relative',
13
- importModuleSpecifierEnding: 'auto',
14
- quotePreference: 'single',
15
- });
34
+ const utils_3 = require("../../../utils");
35
+ const lodash_1 = require("lodash");
36
+ const previewer_1 = require("../previewer");
37
+ const utils_4 = require("./utils");
38
+ // `import {...} from '..'` or `import ... from '..'`
39
+ // Note: Does not take into account if import is within a comment.
40
+ const scriptImportRegex = /\bimport\s+{([^}]*?)}\s+?from\s+['"`].+?['"`]|\bimport\s+(\w+?)\s+from\s+['"`].+?['"`]/g;
16
41
  class CompletionsProviderImpl {
17
- constructor(languageServiceManager) {
42
+ constructor(languageServiceManager, configManager) {
18
43
  this.languageServiceManager = languageServiceManager;
44
+ this.configManager = configManager;
45
+ this.validTriggerCharacters = ['.', '"', "'", '`', '/', '@', '<', '#'];
46
+ }
47
+ isValidTriggerCharacter(character) {
48
+ return this.validTriggerCharacters.includes(character);
19
49
  }
20
- async getCompletions(document, position, _completionContext) {
21
- var _a;
22
- // TODO: handle inside expression and script tags
23
- if (!(0, utils_1.isInsideFrontmatter)(document.getText(), document.offsetAt(position))) {
50
+ async getCompletions(document, position, completionContext, cancellationToken) {
51
+ const triggerCharacter = completionContext?.triggerCharacter;
52
+ const triggerKind = completionContext?.triggerKind;
53
+ const validTriggerCharacter = this.isValidTriggerCharacter(triggerCharacter) ? triggerCharacter : undefined;
54
+ const isCustomTriggerCharacter = triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter;
55
+ if ((isCustomTriggerCharacter && !validTriggerCharacter) || cancellationToken?.isCancellationRequested) {
56
+ return null;
57
+ }
58
+ if (this.canReuseLastCompletion(this.lastCompletion, triggerKind, triggerCharacter, document, position)) {
59
+ this.lastCompletion.position = position;
60
+ return this.lastCompletion.completionList;
61
+ }
62
+ else {
63
+ this.lastCompletion = undefined;
64
+ }
65
+ const html = document.html;
66
+ const offset = document.offsetAt(position);
67
+ const node = html.findNodeAt(offset);
68
+ // TODO: Add support for script tags
69
+ if (node.tag === 'script') {
70
+ return null;
71
+ }
72
+ const isCompletionInsideFrontmatter = (0, utils_1.isInsideFrontmatter)(document.getText(), offset);
73
+ const isCompletionInsideExpression = (0, utils_1.isInsideExpression)(document.getText(), node.start, offset);
74
+ // PERF: Getting TS completions is fairly slow and I am currently not sure how to speed it up
75
+ // As such, we'll try to avoid getting them when unneeded, such as when we're doing HTML stuff
76
+ // When at the root of the document TypeScript offer all kinds of completions, because it doesn't know yet that
77
+ // it's JSX and not JS. As such, people who are using Emmet to write their template suffer from a very degraded experience
78
+ // from what they're used to in HTML files (which is instant completions). So let's disable ourselves when we're at the root
79
+ if (!isCompletionInsideFrontmatter && !node.parent && !isCompletionInsideExpression) {
24
80
  return null;
25
81
  }
82
+ // If the user just typed `<` with nothing else, let's disable ourselves until we're more sure if the user wants TS completions
83
+ if (!isCompletionInsideFrontmatter && node.parent && node.tag === undefined && !isCompletionInsideExpression) {
84
+ return null;
85
+ }
86
+ // If the current node is not a component (aka, it doesn't start with a caps), let's disable ourselves as the user
87
+ // is most likely looking for HTML completions
88
+ if (!isCompletionInsideFrontmatter && !(0, utils_1.isComponentTag)(node) && !isCompletionInsideExpression) {
89
+ return null;
90
+ }
91
+ const tsPreferences = await this.configManager.getTSPreferences(document);
92
+ const formatOptions = await this.configManager.getTSFormatConfig(document);
26
93
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
27
94
  const filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
95
+ const completions = lang.getCompletionsAtPosition(filePath, offset, {
96
+ ...tsPreferences,
97
+ triggerCharacter: validTriggerCharacter,
98
+ }, formatOptions);
99
+ if (completions === undefined || completions.entries.length === 0) {
100
+ return null;
101
+ }
102
+ const wordRange = completions.optionalReplacementSpan
103
+ ? vscode_languageserver_1.Range.create(document.positionAt(completions.optionalReplacementSpan.start), document.positionAt(completions.optionalReplacementSpan.start + completions.optionalReplacementSpan.length))
104
+ : undefined;
105
+ const wordRangeStartPosition = wordRange?.start;
28
106
  const fragment = await tsDoc.createFragment();
29
- const offset = document.offsetAt(position);
30
- const entries = ((_a = lang.getCompletionsAtPosition(filePath, offset, completionOptions)) === null || _a === void 0 ? void 0 : _a.entries) || [];
31
- const completionItems = entries
32
- .map((entry) => this.toCompletionItem(fragment, entry, document.uri, position, new Set()))
33
- .filter((i) => i);
34
- return vscode_languageserver_1.CompletionList.create(completionItems, true);
107
+ const existingImports = this.getExistingImports(document);
108
+ const completionItems = completions.entries
109
+ .filter(this.isValidCompletion)
110
+ .map((entry) => this.toCompletionItem(fragment, entry, filePath, offset, isCompletionInsideFrontmatter, existingImports))
111
+ .filter(utils_3.isNotNullOrUndefined)
112
+ .map((comp) => this.fixTextEditRange(wordRangeStartPosition, comp));
113
+ const completionList = vscode_languageserver_2.CompletionList.create(completionItems, true);
114
+ this.lastCompletion = { key: document.getFilePath() || '', position, completionList };
115
+ return completionList;
35
116
  }
36
- async resolveCompletion(document, completionItem) {
37
- const { data: comp } = completionItem;
117
+ async resolveCompletion(document, item, cancellationToken) {
38
118
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
39
- let filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
40
- if (!comp || !filePath) {
41
- return completionItem;
119
+ const tsPreferences = await this.configManager.getTSPreferences(document);
120
+ const data = item.data;
121
+ if (!data || !data.filePath || cancellationToken?.isCancellationRequested) {
122
+ return item;
42
123
  }
43
124
  const fragment = await tsDoc.createFragment();
44
- const detail = lang.getCompletionEntryDetails(filePath, // fileName
45
- fragment.offsetAt(comp.position), // position
46
- comp.name, // entryName
125
+ const detail = lang.getCompletionEntryDetails(data.filePath, // fileName
126
+ data.offset, // position
127
+ data.originalItem.name, // entryName
47
128
  {}, // formatOptions
48
- comp.source, // source
49
- {}, // preferences
50
- comp.data // data
129
+ data.originalItem.source, // source
130
+ tsPreferences, // preferences
131
+ data.originalItem.data // data
51
132
  );
52
133
  if (detail) {
53
134
  const { detail: itemDetail, documentation: itemDocumentation } = this.getCompletionDocument(detail);
54
- completionItem.detail = itemDetail;
55
- completionItem.documentation = itemDocumentation;
135
+ item.detail = itemDetail;
136
+ item.documentation = itemDocumentation;
56
137
  }
57
- return completionItem;
138
+ const actions = detail?.codeActions;
139
+ if (actions) {
140
+ const edit = [];
141
+ for (const action of actions) {
142
+ for (const change of action.changes) {
143
+ edit.push(...change.textChanges.map((textChange) => codeActionChangeToTextEdit(document, fragment, textChange)));
144
+ }
145
+ }
146
+ item.additionalTextEdits = (item.additionalTextEdits ?? []).concat(edit);
147
+ }
148
+ return item;
58
149
  }
59
- toCompletionItem(fragment, comp, uri, position, existingImports) {
150
+ toCompletionItem(fragment, comp, filePath, offset, insideFrontmatter, existingImports) {
151
+ let item = vscode_languageserver_protocol_1.CompletionItem.create(comp.name);
152
+ const isAstroComponent = this.isAstroComponentImport(comp.name);
153
+ const isImport = comp.insertText?.includes('import');
154
+ // Avoid showing completions for using components as functions
155
+ if (isAstroComponent && !isImport && insideFrontmatter) {
156
+ return null;
157
+ }
158
+ if (isAstroComponent) {
159
+ item.label = (0, utils_2.removeAstroComponentSuffix)(comp.name);
160
+ // Set component imports as file completion, that way we get cool icons
161
+ item.kind = vscode_languageserver_protocol_1.CompletionItemKind.File;
162
+ item.detail = comp.data?.moduleSpecifier;
163
+ }
164
+ else {
165
+ item.kind = (0, utils_2.scriptElementKindToCompletionItemKind)(comp.kind);
166
+ }
167
+ // TS may suggest another component even if there already exists an import with the same.
168
+ // This happens because internally, components get suffixed with __AstroComponent_
169
+ if (isAstroComponent && existingImports.has(item.label)) {
170
+ return null;
171
+ }
172
+ if (comp.kindModifiers) {
173
+ const kindModifiers = new Set(comp.kindModifiers.split(/,|\s+/g));
174
+ if (kindModifiers.has(typescript_1.ScriptElementKindModifier.deprecatedModifier)) {
175
+ item.tags = [vscode_languageserver_1.CompletionItemTag.Deprecated];
176
+ }
177
+ }
178
+ // Label details are currently unsupported, however, they'll be supported in the next version of LSP
179
+ if (comp.sourceDisplay) {
180
+ item.labelDetails = { description: typescript_1.default.displayPartsToString(comp.sourceDisplay) };
181
+ }
182
+ item.commitCharacters = (0, utils_2.getCommitCharactersForScriptElement)(comp.kind);
183
+ item.sortText = comp.sortText;
184
+ item.preselect = comp.isRecommended;
185
+ if (comp.replacementSpan) {
186
+ item.insertText = comp.insertText ? (0, utils_2.removeAstroComponentSuffix)(comp.insertText) : undefined;
187
+ item.insertTextFormat = comp.isSnippet ? vscode_languageserver_1.InsertTextFormat.Snippet : vscode_languageserver_1.InsertTextFormat.PlainText;
188
+ item.textEdit = comp.replacementSpan
189
+ ? vscode_languageserver_1.TextEdit.replace((0, utils_2.convertRange)(fragment, comp.replacementSpan), item.insertText ?? item.label)
190
+ : undefined;
191
+ }
60
192
  return {
61
- label: comp.name,
62
- insertText: comp.insertText,
63
- kind: (0, utils_2.scriptElementKindToCompletionItemKind)(comp.kind),
64
- commitCharacters: (0, utils_2.getCommitCharactersForScriptElement)(comp.kind),
65
- // Make sure svelte component takes precedence
66
- sortText: comp.sortText,
67
- preselect: comp.isRecommended,
68
- // pass essential data for resolving completion
193
+ ...item,
69
194
  data: {
70
- ...comp,
71
- uri,
72
- position,
195
+ uri: fragment.getURL(),
196
+ filePath,
197
+ offset,
198
+ originalItem: comp,
73
199
  },
74
200
  };
75
201
  }
202
+ isValidCompletion(completion) {
203
+ // Remove completion for default exported function
204
+ if (completion.name === 'default' && completion.kindModifiers == typescript_1.ScriptElementKindModifier.exportedModifier) {
205
+ return false;
206
+ }
207
+ return true;
208
+ }
76
209
  getCompletionDocument(compDetail) {
77
- const { source, documentation: tsDocumentation, displayParts, tags } = compDetail;
78
- let detail = typescript_1.default.displayPartsToString(displayParts);
79
- if (source) {
80
- const importPath = typescript_1.default.displayPartsToString(source);
81
- detail = `Auto import from ${importPath}\n${detail}`;
82
- }
83
- const documentation = tsDocumentation
84
- ? { value: tsDocumentation.join('\n'), kind: vscode_languageserver_1.MarkupKind.Markdown }
85
- : undefined;
210
+ const { sourceDisplay, documentation: tsDocumentation, displayParts } = compDetail;
211
+ let detail = (0, utils_2.removeAstroComponentSuffix)(typescript_1.default.displayPartsToString(displayParts));
212
+ if (sourceDisplay) {
213
+ const importPath = typescript_1.default.displayPartsToString(sourceDisplay);
214
+ detail = importPath;
215
+ }
216
+ const documentation = {
217
+ kind: 'markdown',
218
+ value: (0, previewer_1.getMarkdownDocumentation)(tsDocumentation, compDetail.tags),
219
+ };
86
220
  return {
87
221
  documentation,
88
222
  detail,
89
223
  };
90
224
  }
225
+ /**
226
+ * If the textEdit is out of the word range of the triggered position
227
+ * vscode would refuse to show the completions
228
+ * split those edits into additionalTextEdit to fix it
229
+ */
230
+ fixTextEditRange(wordRangePosition, completionItem) {
231
+ const { textEdit } = completionItem;
232
+ if (!textEdit || !vscode_languageserver_1.TextEdit.is(textEdit) || !wordRangePosition) {
233
+ return completionItem;
234
+ }
235
+ const { newText, range: { start }, } = textEdit;
236
+ const wordRangeStartCharacter = wordRangePosition.character;
237
+ if (wordRangePosition.line !== wordRangePosition.line || start.character > wordRangePosition.character) {
238
+ return completionItem;
239
+ }
240
+ textEdit.newText = newText.substring(wordRangeStartCharacter - start.character);
241
+ textEdit.range.start = {
242
+ line: start.line,
243
+ character: wordRangeStartCharacter,
244
+ };
245
+ completionItem.additionalTextEdits = [
246
+ vscode_languageserver_1.TextEdit.replace({
247
+ start,
248
+ end: {
249
+ line: start.line,
250
+ character: wordRangeStartCharacter,
251
+ },
252
+ }, newText.substring(0, wordRangeStartCharacter - start.character)),
253
+ ];
254
+ return completionItem;
255
+ }
256
+ canReuseLastCompletion(lastCompletion, triggerKind, triggerCharacter, document, position) {
257
+ return (!!lastCompletion &&
258
+ lastCompletion.key === document.getFilePath() &&
259
+ lastCompletion.position.line === position.line &&
260
+ Math.abs(lastCompletion.position.character - position.character) < 2 &&
261
+ (triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerForIncompleteCompletions ||
262
+ // Special case: `.` is a trigger character, but inside import path completions
263
+ // it shouldn't trigger another completion because we can reuse the old one
264
+ (triggerCharacter === '.' && (0, utils_4.isPartOfImportStatement)(document.getText(), position))));
265
+ }
266
+ getExistingImports(document) {
267
+ const rawImports = (0, utils_3.getRegExpMatches)(scriptImportRegex, document.getText()).map((match) => (match[1] ?? match[2]).split(','));
268
+ const tidiedImports = (0, lodash_1.flatten)(rawImports).map((match) => match.trim());
269
+ return new Set(tidiedImports);
270
+ }
271
+ isAstroComponentImport(className) {
272
+ return className.endsWith('__AstroComponent_');
273
+ }
91
274
  }
92
275
  exports.CompletionsProviderImpl = CompletionsProviderImpl;
276
+ function codeActionChangeToTextEdit(document, fragment, change) {
277
+ change.newText = (0, utils_2.removeAstroComponentSuffix)(change.newText);
278
+ // If we don't have a frontmatter already, create one with the import
279
+ const frontmatterState = document.astroMeta.frontmatter.state;
280
+ if (frontmatterState === null) {
281
+ return vscode_languageserver_1.TextEdit.replace(vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(0, 0), vscode_languageserver_1.Position.create(0, 0)), `---${typescript_1.default.sys.newLine}${change.newText}---${typescript_1.default.sys.newLine}${typescript_1.default.sys.newLine}`);
282
+ }
283
+ const { span } = change;
284
+ let range;
285
+ const virtualRange = (0, utils_2.convertRange)(fragment, span);
286
+ range = (0, documents_1.mapRangeToOriginal)(fragment, virtualRange);
287
+ if (!(0, utils_1.isInsideFrontmatter)(document.getText(), document.offsetAt(range.start))) {
288
+ range = (0, utils_2.ensureFrontmatterInsert)(range, document);
289
+ }
290
+ // First import in a file will wrongly have a newline before it due to how the frontmatter is replaced by a comment
291
+ if (range.start.line === 1 && (change.newText.startsWith('\n') || change.newText.startsWith('\r\n'))) {
292
+ change.newText = change.newText.trimStart();
293
+ }
294
+ return vscode_languageserver_1.TextEdit.replace(range, change.newText);
295
+ }
296
+ exports.codeActionChangeToTextEdit = codeActionChangeToTextEdit;
@@ -0,0 +1,9 @@
1
+ import { Position, LocationLink } from 'vscode-languageserver-types';
2
+ import { AstroDocument } from '../../../core/documents';
3
+ import { DefinitionsProvider } from '../../interfaces';
4
+ import { LanguageServiceManager } from '../LanguageServiceManager';
5
+ export declare class DefinitionsProviderImpl implements DefinitionsProvider {
6
+ private languageServiceManager;
7
+ constructor(languageServiceManager: LanguageServiceManager);
8
+ getDefinitions(document: AstroDocument, position: Position): Promise<LocationLink[]>;
9
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefinitionsProviderImpl = void 0;
4
+ const vscode_languageserver_types_1 = require("vscode-languageserver-types");
5
+ const utils_1 = require("../../../utils");
6
+ const utils_2 = require("../utils");
7
+ const utils_3 = require("./utils");
8
+ class DefinitionsProviderImpl {
9
+ constructor(languageServiceManager) {
10
+ this.languageServiceManager = languageServiceManager;
11
+ }
12
+ async getDefinitions(document, position) {
13
+ const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
14
+ const mainFragment = await tsDoc.createFragment();
15
+ const tsFilePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
16
+ const fragmentPosition = mainFragment.getGeneratedPosition(position);
17
+ const fragmentOffset = mainFragment.offsetAt(fragmentPosition);
18
+ const defs = lang.getDefinitionAndBoundSpan(tsFilePath, fragmentOffset);
19
+ if (!defs || !defs.definitions) {
20
+ return [];
21
+ }
22
+ const docs = new utils_3.SnapshotFragmentMap(this.languageServiceManager);
23
+ docs.set(tsFilePath, { fragment: mainFragment, snapshot: tsDoc });
24
+ const result = await Promise.all(defs.definitions.map(async (def) => {
25
+ const { fragment, snapshot } = await docs.retrieve(def.fileName);
26
+ const fileName = (0, utils_2.ensureRealFilePath)(def.fileName);
27
+ // For Astro, Svelte and Vue, the position is wrongly mapped to the end of the file due to the TSX output
28
+ // So we'll instead redirect to the beginning of the file
29
+ const isFramework = (0, utils_2.isFrameworkFilePath)(def.fileName) || (0, utils_2.isAstroFilePath)(def.fileName);
30
+ const textSpan = isFramework && tsDoc.filePath !== def.fileName ? { start: 0, length: 0 } : def.textSpan;
31
+ return vscode_languageserver_types_1.LocationLink.create((0, utils_1.pathToUrl)(fileName), (0, utils_2.convertToLocationRange)(fragment, textSpan), (0, utils_2.convertToLocationRange)(fragment, textSpan), (0, utils_2.convertToLocationRange)(mainFragment, defs.textSpan));
32
+ }));
33
+ return result.filter(utils_1.isNotNullOrUndefined);
34
+ }
35
+ }
36
+ exports.DefinitionsProviderImpl = DefinitionsProviderImpl;
@@ -13,10 +13,9 @@ class DiagnosticsProviderImpl {
13
13
  this.languageServiceManager = languageServiceManager;
14
14
  }
15
15
  async getDiagnostics(document, _cancellationToken) {
16
- var _a, _b;
17
16
  // Don't return diagnostics for files inside node_modules. These are considered read-only
18
17
  // and they would pollute the output for astro check
19
- if (((_a = document.getFilePath()) === null || _a === void 0 ? void 0 : _a.includes('/node_modules/')) || ((_b = document.getFilePath()) === null || _b === void 0 ? void 0 : _b.includes('\\node_modules\\'))) {
18
+ if (document.getFilePath()?.includes('/node_modules/') || document.getFilePath()?.includes('\\node_modules\\')) {
20
19
  return [];
21
20
  }
22
21
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
@@ -53,7 +52,7 @@ class DiagnosticsProviderImpl {
53
52
  }
54
53
  getTagBoundaries(lang, tsFilePath) {
55
54
  const program = lang.getProgram();
56
- const sourceFile = program === null || program === void 0 ? void 0 : program.getSourceFile(tsFilePath);
55
+ const sourceFile = program?.getSourceFile(tsFilePath);
57
56
  const boundaries = {
58
57
  script: [],
59
58
  markdown: [],
@@ -11,7 +11,6 @@ class DocumentSymbolsProviderImpl {
11
11
  this.languageServiceManager = languageServiceManager;
12
12
  }
13
13
  async getDocumentSymbols(document) {
14
- var _a, _b, _c;
15
14
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
16
15
  const fragment = await tsDoc.createFragment();
17
16
  const navTree = lang.getNavigationTree(tsDoc.filePath);
@@ -27,23 +26,23 @@ class DocumentSymbolsProviderImpl {
27
26
  result.push(vscode_languageserver_types_1.SymbolInformation.create('Frontmatter', vscode_languageserver_types_1.SymbolKind.Namespace, vscode_languageserver_types_1.Range.create(document.positionAt(document.astroMeta.frontmatter.startOffset), document.positionAt(document.astroMeta.frontmatter.endOffset)), document.getURL()));
28
27
  }
29
28
  // Add a "Template" namespace for everything under the frontmatter
30
- result.push(vscode_languageserver_types_1.SymbolInformation.create('Template', vscode_languageserver_types_1.SymbolKind.Namespace, vscode_languageserver_types_1.Range.create(document.positionAt((_a = document.astroMeta.frontmatter.endOffset) !== null && _a !== void 0 ? _a : 0), document.positionAt(document.getTextLength())), document.getURL()));
29
+ result.push(vscode_languageserver_types_1.SymbolInformation.create('Template', vscode_languageserver_types_1.SymbolKind.Namespace, vscode_languageserver_types_1.Range.create(document.positionAt(document.astroMeta.frontmatter.endOffset ?? 0), document.positionAt(document.getTextLength())), document.getURL()));
31
30
  for (let symbol of symbols.splice(1)) {
32
31
  symbol = (0, documents_1.mapSymbolInformationToOriginal)(fragment, symbol);
33
- if (document.offsetAt(symbol.location.range.end) >= ((_b = document.astroMeta.content.firstNonWhitespaceOffset) !== null && _b !== void 0 ? _b : 0)) {
32
+ if (document.offsetAt(symbol.location.range.end) >= (document.astroMeta.content.firstNonWhitespaceOffset ?? 0)) {
34
33
  if (symbol.containerName === originalContainerName) {
35
34
  symbol.containerName = 'Template';
36
35
  }
37
36
  // For some reason, it seems like TypeScript thinks that the "class" attribute is a real class, weird
38
37
  if (symbol.kind === vscode_languageserver_types_1.SymbolKind.Class && symbol.name === '<class>') {
39
38
  const node = document.html.findNodeAt(document.offsetAt(symbol.location.range.start));
40
- if ((_c = node.attributes) === null || _c === void 0 ? void 0 : _c.class) {
39
+ if (node.attributes?.class) {
41
40
  continue;
42
41
  }
43
42
  }
44
43
  }
45
- // Remove the default function detected in our TSX output
46
- if (symbol.kind === vscode_languageserver_types_1.SymbolKind.Function && symbol.name == 'default') {
44
+ // Remove the exported function in our TSX output from the symbols
45
+ if (document.offsetAt(symbol.location.range.start) >= document.getTextLength()) {
47
46
  continue;
48
47
  }
49
48
  result.push(symbol);
@@ -0,0 +1,9 @@
1
+ import { FoldingRange } from 'vscode-languageserver';
2
+ import { AstroDocument } from '../../../core/documents';
3
+ import { FoldingRangesProvider } from '../../interfaces';
4
+ import { LanguageServiceManager } from '../LanguageServiceManager';
5
+ export declare class FoldingRangesProviderImpl implements FoldingRangesProvider {
6
+ private readonly languageServiceManager;
7
+ constructor(languageServiceManager: LanguageServiceManager);
8
+ getFoldingRanges(document: AstroDocument): Promise<FoldingRange[] | null>;
9
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FoldingRangesProviderImpl = void 0;
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ const vscode_languageserver_1 = require("vscode-languageserver");
9
+ const utils_1 = require("../utils");
10
+ class FoldingRangesProviderImpl {
11
+ constructor(languageServiceManager) {
12
+ this.languageServiceManager = languageServiceManager;
13
+ }
14
+ async getFoldingRanges(document) {
15
+ const html = document.html;
16
+ const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
17
+ const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
18
+ const outliningSpans = lang.getOutliningSpans(filePath);
19
+ const foldingRanges = [];
20
+ for (const span of outliningSpans) {
21
+ const node = html.findNodeAt(span.textSpan.start);
22
+ // Due to how our TSX output transform those tags into function calls or template literals
23
+ // TypeScript thinks of those as outlining spans, which is fine but we don't want folding ranges for those
24
+ if (node.tag === 'script' || node.tag === 'Markdown' || node.tag === 'style') {
25
+ continue;
26
+ }
27
+ const start = document.positionAt(span.textSpan.start);
28
+ const end = adjustFoldingEnd(start, document.positionAt(span.textSpan.start + span.textSpan.length), document);
29
+ // When using this method for generating folding ranges, TypeScript tend to return some
30
+ // one line / one character ones that we should be able to safely ignore
31
+ if (start.line === end.line && start.character === end.character) {
32
+ continue;
33
+ }
34
+ foldingRanges.push(vscode_languageserver_1.FoldingRange.create(start.line, end.line, start.character, end.character, transformFoldingRangeKind(span.kind)));
35
+ }
36
+ return foldingRanges;
37
+ }
38
+ }
39
+ exports.FoldingRangesProviderImpl = FoldingRangesProviderImpl;
40
+ function transformFoldingRangeKind(tsKind) {
41
+ switch (tsKind) {
42
+ case typescript_1.default.OutliningSpanKind.Comment:
43
+ return vscode_languageserver_1.FoldingRangeKind.Comment;
44
+ case typescript_1.default.OutliningSpanKind.Imports:
45
+ return vscode_languageserver_1.FoldingRangeKind.Imports;
46
+ case typescript_1.default.OutliningSpanKind.Region:
47
+ return vscode_languageserver_1.FoldingRangeKind.Region;
48
+ }
49
+ }
50
+ // https://github.com/microsoft/vscode/blob/bed61166fb604e519e82e4d1d1ed839bc45d65f8/extensions/typescript-language-features/src/languageFeatures/folding.ts#L61-L73
51
+ function adjustFoldingEnd(start, end, document) {
52
+ // workaround for #47240
53
+ if (end.character > 0) {
54
+ const foldEndCharacter = document.getText({
55
+ start: { line: end.line, character: end.character - 1 },
56
+ end,
57
+ });
58
+ if (['}', ']', ')', '`'].includes(foldEndCharacter)) {
59
+ const endOffset = Math.max(document.offsetAt({ line: end.line, character: 0 }) - 1, document.offsetAt(start));
60
+ return document.positionAt(endOffset);
61
+ }
62
+ }
63
+ return end;
64
+ }
@@ -15,7 +15,7 @@ class SemanticTokensProviderImpl {
15
15
  async getSemanticTokens(document, range, cancellationToken) {
16
16
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
17
17
  const fragment = (await tsDoc.createFragment());
18
- if (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested) {
18
+ if (cancellationToken?.isCancellationRequested) {
19
19
  return null;
20
20
  }
21
21
  const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
@@ -25,7 +25,7 @@ class SemanticTokensProviderImpl {
25
25
  length: range
26
26
  ? fragment.offsetAt(fragment.getGeneratedPosition(range.end)) - start
27
27
  : // We don't want tokens for things added by astro2tsx
28
- fragment.text.lastIndexOf('export default function (_props:') || fragment.text.length,
28
+ fragment.text.lastIndexOf('export default function ') || fragment.text.length,
29
29
  }, typescript_1.default.SemanticClassificationFormat.TwentyTwenty);
30
30
  const tokens = [];
31
31
  let i = 0;
@@ -15,7 +15,7 @@ class SignatureHelpProviderImpl {
15
15
  async getSignatureHelp(document, position, context, cancellationToken) {
16
16
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
17
17
  const fragment = await tsDoc.createFragment();
18
- if (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested) {
18
+ if (cancellationToken?.isCancellationRequested) {
19
19
  return null;
20
20
  }
21
21
  const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
@@ -44,7 +44,7 @@ class SignatureHelpProviderImpl {
44
44
  * adopted from https://github.com/microsoft/vscode/blob/265a2f6424dfbd3a9788652c7d376a7991d049a3/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts#L103
45
45
  */
46
46
  toTsTriggerReason(context) {
47
- switch (context === null || context === void 0 ? void 0 : context.triggerKind) {
47
+ switch (context?.triggerKind) {
48
48
  case vscode_languageserver_1.SignatureHelpTriggerKind.TriggerCharacter:
49
49
  if (context.triggerCharacter) {
50
50
  if (this.isReTrigger(context.isRetrigger, context.triggerCharacter)) {
@@ -1,5 +1,8 @@
1
1
  import type { SnapshotFragment, DocumentSnapshot } from '../snapshots/DocumentSnapshot';
2
2
  import type { LanguageServiceManager } from '../LanguageServiceManager';
3
+ import { Position } from 'vscode-languageserver';
4
+ import ts from 'typescript';
5
+ export declare function isPartOfImportStatement(text: string, position: Position): boolean;
3
6
  export declare class SnapshotFragmentMap {
4
7
  private languageServiceManager;
5
8
  private map;
@@ -19,3 +22,4 @@ export declare class SnapshotFragmentMap {
19
22
  }>;
20
23
  retrieveFragment(fileName: string): Promise<SnapshotFragment>;
21
24
  }
25
+ export declare function findContainingNode<T extends ts.Node>(node: ts.Node, textSpan: ts.TextSpan, predicate: (node: ts.Node) => node is T): T | undefined;
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SnapshotFragmentMap = void 0;
3
+ exports.findContainingNode = exports.SnapshotFragmentMap = exports.isPartOfImportStatement = void 0;
4
+ const documents_1 = require("../../../core/documents");
5
+ function isPartOfImportStatement(text, position) {
6
+ const line = (0, documents_1.getLineAtPosition)(position, text);
7
+ return /\s*from\s+["'][^"']*/.test(line.slice(0, position.character));
8
+ }
9
+ exports.isPartOfImportStatement = isPartOfImportStatement;
4
10
  class SnapshotFragmentMap {
5
11
  constructor(languageServiceManager) {
6
12
  this.languageServiceManager = languageServiceManager;
@@ -13,8 +19,7 @@ class SnapshotFragmentMap {
13
19
  return this.map.get(fileName);
14
20
  }
15
21
  getFragment(fileName) {
16
- var _a;
17
- return (_a = this.map.get(fileName)) === null || _a === void 0 ? void 0 : _a.fragment;
22
+ return this.map.get(fileName)?.fragment;
18
23
  }
19
24
  async retrieve(fileName) {
20
25
  let snapshotFragment = this.get(fileName);
@@ -31,3 +36,20 @@ class SnapshotFragmentMap {
31
36
  }
32
37
  }
33
38
  exports.SnapshotFragmentMap = SnapshotFragmentMap;
39
+ function findContainingNode(node, textSpan, predicate) {
40
+ const children = node.getChildren();
41
+ const end = textSpan.start + textSpan.length;
42
+ for (const child of children) {
43
+ if (!(child.getStart() <= textSpan.start && child.getEnd() >= end)) {
44
+ continue;
45
+ }
46
+ if (predicate(child)) {
47
+ return child;
48
+ }
49
+ const foundInChildren = findContainingNode(child, textSpan, predicate);
50
+ if (foundInChildren) {
51
+ return foundInChildren;
52
+ }
53
+ }
54
+ }
55
+ exports.findContainingNode = findContainingNode;