@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.
- package/CHANGELOG.md +39 -0
- package/dist/check.js +1 -2
- package/dist/core/config/ConfigManager.d.ts +20 -16
- package/dist/core/config/ConfigManager.js +112 -46
- package/dist/core/config/interfaces.d.ts +0 -52
- package/dist/core/documents/DocumentMapper.js +2 -4
- package/dist/core/documents/parseAstro.js +1 -1
- package/dist/core/documents/utils.d.ts +5 -0
- package/dist/core/documents/utils.js +18 -5
- package/dist/plugins/PluginHost.d.ts +3 -2
- package/dist/plugins/PluginHost.js +37 -10
- package/dist/plugins/astro/AstroPlugin.d.ts +1 -6
- package/dist/plugins/astro/AstroPlugin.js +0 -82
- package/dist/plugins/astro/features/CompletionsProvider.js +30 -15
- package/dist/plugins/css/CSSPlugin.d.ts +5 -5
- package/dist/plugins/css/CSSPlugin.js +41 -20
- package/dist/plugins/html/HTMLPlugin.d.ts +4 -4
- package/dist/plugins/html/HTMLPlugin.js +20 -16
- package/dist/plugins/html/features/astro-attributes.js +44 -27
- package/dist/plugins/interfaces.d.ts +2 -2
- package/dist/plugins/typescript/LanguageServiceManager.js +1 -1
- package/dist/plugins/typescript/TypeScriptPlugin.d.ts +10 -7
- package/dist/plugins/typescript/TypeScriptPlugin.js +39 -112
- package/dist/plugins/typescript/astro-sys.js +3 -5
- package/dist/plugins/typescript/astro2tsx.d.ts +1 -1
- package/dist/plugins/typescript/astro2tsx.js +7 -7
- package/dist/plugins/typescript/features/CodeActionsProvider.d.ts +16 -0
- package/dist/plugins/typescript/features/CodeActionsProvider.js +147 -0
- package/dist/plugins/typescript/features/CompletionsProvider.d.ts +26 -7
- package/dist/plugins/typescript/features/CompletionsProvider.js +260 -56
- package/dist/plugins/typescript/features/DefinitionsProvider.d.ts +9 -0
- package/dist/plugins/typescript/features/DefinitionsProvider.js +36 -0
- package/dist/plugins/typescript/features/DiagnosticsProvider.js +2 -3
- package/dist/plugins/typescript/features/DocumentSymbolsProvider.js +5 -6
- package/dist/plugins/typescript/features/FoldingRangesProvider.d.ts +9 -0
- package/dist/plugins/typescript/features/FoldingRangesProvider.js +64 -0
- package/dist/plugins/typescript/features/SemanticTokenProvider.js +2 -2
- package/dist/plugins/typescript/features/SignatureHelpProvider.js +2 -2
- package/dist/plugins/typescript/features/utils.d.ts +4 -0
- package/dist/plugins/typescript/features/utils.js +25 -3
- package/dist/plugins/typescript/language-service.js +5 -6
- package/dist/plugins/typescript/module-loader.js +1 -1
- package/dist/plugins/typescript/previewer.js +1 -1
- package/dist/plugins/typescript/snapshots/SnapshotManager.js +1 -1
- package/dist/plugins/typescript/snapshots/utils.js +27 -9
- package/dist/plugins/typescript/utils.d.ts +4 -0
- package/dist/plugins/typescript/utils.js +29 -1
- package/dist/server.js +43 -14
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +39 -3
- package/package.json +2 -2
|
@@ -1,92 +1,296 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
.map((entry) => this.toCompletionItem(fragment, entry,
|
|
33
|
-
.filter(
|
|
34
|
-
|
|
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,
|
|
37
|
-
const { data: comp } = completionItem;
|
|
117
|
+
async resolveCompletion(document, item, cancellationToken) {
|
|
38
118
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
125
|
+
const detail = lang.getCompletionEntryDetails(data.filePath, // fileName
|
|
126
|
+
data.offset, // position
|
|
127
|
+
data.originalItem.name, // entryName
|
|
47
128
|
{}, // formatOptions
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
135
|
+
item.detail = itemDetail;
|
|
136
|
+
item.documentation = itemDocumentation;
|
|
56
137
|
}
|
|
57
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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 {
|
|
78
|
-
let detail = typescript_1.default.displayPartsToString(displayParts);
|
|
79
|
-
if (
|
|
80
|
-
const importPath = typescript_1.default.displayPartsToString(
|
|
81
|
-
detail =
|
|
82
|
-
}
|
|
83
|
-
const documentation =
|
|
84
|
-
|
|
85
|
-
:
|
|
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 (
|
|
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
|
|
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(
|
|
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) >= (
|
|
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 (
|
|
39
|
+
if (node.attributes?.class) {
|
|
41
40
|
continue;
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
|
-
// Remove the
|
|
46
|
-
if (symbol.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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;
|