@astrojs/language-server 0.16.0 → 0.18.0
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 +30 -0
- package/dist/core/config/ConfigManager.d.ts +25 -16
- package/dist/core/config/ConfigManager.js +160 -46
- package/dist/core/config/interfaces.d.ts +4 -51
- package/dist/core/documents/AstroDocument.d.ts +1 -0
- package/dist/core/documents/AstroDocument.js +1 -0
- package/dist/core/documents/DocumentMapper.d.ts +2 -0
- package/dist/core/documents/DocumentMapper.js +7 -5
- package/dist/core/documents/utils.d.ts +1 -0
- package/dist/core/documents/utils.js +16 -1
- package/dist/plugins/PluginHost.d.ts +3 -1
- package/dist/plugins/PluginHost.js +8 -0
- package/dist/plugins/astro/AstroPlugin.d.ts +1 -6
- package/dist/plugins/astro/AstroPlugin.js +10 -85
- package/dist/plugins/astro/features/CompletionsProvider.d.ts +4 -5
- package/dist/plugins/astro/features/CompletionsProvider.js +53 -58
- package/dist/plugins/css/CSSPlugin.d.ts +5 -5
- package/dist/plugins/css/CSSPlugin.js +36 -31
- package/dist/plugins/html/HTMLPlugin.d.ts +6 -5
- package/dist/plugins/html/HTMLPlugin.js +38 -16
- package/dist/plugins/html/features/astro-attributes.js +1 -0
- package/dist/plugins/interfaces.d.ts +5 -2
- package/dist/plugins/typescript/TypeScriptPlugin.d.ts +7 -4
- package/dist/plugins/typescript/TypeScriptPlugin.js +34 -110
- package/dist/plugins/typescript/features/CodeActionsProvider.d.ts +3 -1
- package/dist/plugins/typescript/features/CodeActionsProvider.js +82 -17
- package/dist/plugins/typescript/features/CompletionsProvider.d.ts +5 -3
- package/dist/plugins/typescript/features/CompletionsProvider.js +112 -56
- package/dist/plugins/typescript/features/DefinitionsProvider.d.ts +9 -0
- package/dist/plugins/typescript/features/DefinitionsProvider.js +57 -0
- package/dist/plugins/typescript/features/DiagnosticsProvider.js +58 -15
- package/dist/plugins/typescript/features/FoldingRangesProvider.js +13 -6
- package/dist/plugins/typescript/features/FormattingProvider.d.ts +11 -0
- package/dist/plugins/typescript/features/FormattingProvider.js +132 -0
- package/dist/plugins/typescript/features/HoverProvider.js +14 -1
- package/dist/plugins/typescript/features/InlayHintsProvider.d.ts +12 -0
- package/dist/plugins/typescript/features/InlayHintsProvider.js +36 -0
- package/dist/plugins/typescript/features/SignatureHelpProvider.js +9 -1
- package/dist/plugins/typescript/language-service.js +18 -0
- package/dist/plugins/typescript/snapshots/DocumentSnapshot.d.ts +22 -2
- package/dist/plugins/typescript/snapshots/DocumentSnapshot.js +48 -1
- package/dist/plugins/typescript/snapshots/SnapshotManager.js +1 -0
- package/dist/plugins/typescript/snapshots/utils.js +3 -2
- package/dist/plugins/typescript/utils.d.ts +11 -1
- package/dist/plugins/typescript/utils.js +17 -1
- package/dist/server.js +27 -15
- package/package.json +7 -6
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.codeActionChangeToTextEdit = exports.CompletionsProviderImpl =
|
|
26
|
+
exports.codeActionChangeToTextEdit = exports.CompletionsProviderImpl = void 0;
|
|
27
27
|
const vscode_languageserver_1 = require("vscode-languageserver");
|
|
28
28
|
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
|
|
29
29
|
const utils_1 = require("../../../core/documents/utils");
|
|
@@ -35,22 +35,13 @@ const utils_3 = require("../../../utils");
|
|
|
35
35
|
const lodash_1 = require("lodash");
|
|
36
36
|
const previewer_1 = require("../previewer");
|
|
37
37
|
const utils_4 = require("./utils");
|
|
38
|
-
exports.completionOptions = {
|
|
39
|
-
importModuleSpecifierPreference: 'relative',
|
|
40
|
-
importModuleSpecifierEnding: 'auto',
|
|
41
|
-
quotePreference: 'single',
|
|
42
|
-
includeCompletionsForModuleExports: true,
|
|
43
|
-
includeCompletionsForImportStatements: true,
|
|
44
|
-
includeCompletionsWithInsertText: true,
|
|
45
|
-
allowIncompleteCompletions: true,
|
|
46
|
-
includeCompletionsWithSnippetText: true,
|
|
47
|
-
};
|
|
48
38
|
// `import {...} from '..'` or `import ... from '..'`
|
|
49
39
|
// Note: Does not take into account if import is within a comment.
|
|
50
40
|
const scriptImportRegex = /\bimport\s+{([^}]*?)}\s+?from\s+['"`].+?['"`]|\bimport\s+(\w+?)\s+from\s+['"`].+?['"`]/g;
|
|
51
41
|
class CompletionsProviderImpl {
|
|
52
|
-
constructor(languageServiceManager) {
|
|
42
|
+
constructor(languageServiceManager, configManager) {
|
|
53
43
|
this.languageServiceManager = languageServiceManager;
|
|
44
|
+
this.configManager = configManager;
|
|
54
45
|
this.validTriggerCharacters = ['.', '"', "'", '`', '/', '@', '<', '#'];
|
|
55
46
|
}
|
|
56
47
|
isValidTriggerCharacter(character) {
|
|
@@ -74,35 +65,64 @@ class CompletionsProviderImpl {
|
|
|
74
65
|
const html = document.html;
|
|
75
66
|
const offset = document.offsetAt(position);
|
|
76
67
|
const node = html.findNodeAt(offset);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
68
|
+
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
69
|
+
let filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
70
|
+
let completions;
|
|
81
71
|
const isCompletionInsideFrontmatter = (0, utils_1.isInsideFrontmatter)(document.getText(), offset);
|
|
82
72
|
const isCompletionInsideExpression = (0, utils_1.isInsideExpression)(document.getText(), node.start, offset);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
73
|
+
const tsPreferences = await this.configManager.getTSPreferences(document);
|
|
74
|
+
const formatOptions = await this.configManager.getTSFormatConfig(document);
|
|
75
|
+
let scriptTagIndex = undefined;
|
|
76
|
+
if (node.tag === 'script') {
|
|
77
|
+
const { filePath: scriptFilePath, offset: scriptOffset, index: scriptIndex, } = (0, utils_2.getScriptTagSnapshot)(tsDoc, document, node, position);
|
|
78
|
+
filePath = scriptFilePath;
|
|
79
|
+
scriptTagIndex = scriptIndex;
|
|
80
|
+
completions = lang.getCompletionsAtPosition(scriptFilePath, scriptOffset, {
|
|
81
|
+
...tsPreferences,
|
|
82
|
+
// File extensions are required inside script tags, however TypeScript can't return completions with the `ts`
|
|
83
|
+
// extension, so what we'll do instead is force `minimal` (aka, no extension) and manually add the extensions
|
|
84
|
+
importModuleSpecifierEnding: 'minimal',
|
|
85
|
+
triggerCharacter: validTriggerCharacter,
|
|
86
|
+
}, formatOptions);
|
|
87
|
+
if (completions) {
|
|
88
|
+
// Manually adds file extensions to js and ts files
|
|
89
|
+
completions.entries = completions?.entries.map((comp) => {
|
|
90
|
+
if (comp.kind === typescript_1.ScriptElementKind.scriptElement &&
|
|
91
|
+
(comp.kindModifiers === '.js' || comp.kindModifiers === '.ts')) {
|
|
92
|
+
return {
|
|
93
|
+
...comp,
|
|
94
|
+
name: comp.name + comp.kindModifiers,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
return comp;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
94
102
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
else {
|
|
104
|
+
// PERF: Getting TS completions is fairly slow and I am currently not sure how to speed it up
|
|
105
|
+
// As such, we'll try to avoid getting them when unneeded, such as when we're doing HTML stuff
|
|
106
|
+
// When at the root of the document TypeScript offer all kinds of completions, because it doesn't know yet that
|
|
107
|
+
// it's JSX and not JS. As such, people who are using Emmet to write their template suffer from a very degraded experience
|
|
108
|
+
// from what they're used to in HTML files (which is instant completions). So let's disable ourselves when we're at the root
|
|
109
|
+
if (!isCompletionInsideFrontmatter && !node.parent && !isCompletionInsideExpression) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// If the user just typed `<` with nothing else, let's disable ourselves until we're more sure if the user wants TS completions
|
|
113
|
+
if (!isCompletionInsideFrontmatter && node.parent && node.tag === undefined && !isCompletionInsideExpression) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
// If the current node is not a component (aka, it doesn't start with a caps), let's disable ourselves as the user
|
|
117
|
+
// is most likely looking for HTML completions
|
|
118
|
+
if (!isCompletionInsideFrontmatter && !(0, utils_1.isComponentTag)(node) && !isCompletionInsideExpression) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
completions = lang.getCompletionsAtPosition(filePath, offset, {
|
|
122
|
+
...tsPreferences,
|
|
123
|
+
triggerCharacter: validTriggerCharacter,
|
|
124
|
+
}, formatOptions);
|
|
99
125
|
}
|
|
100
|
-
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
101
|
-
const filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
102
|
-
const completions = lang.getCompletionsAtPosition(filePath, offset, {
|
|
103
|
-
...exports.completionOptions,
|
|
104
|
-
triggerCharacter: validTriggerCharacter,
|
|
105
|
-
});
|
|
106
126
|
if (completions === undefined || completions.entries.length === 0) {
|
|
107
127
|
return null;
|
|
108
128
|
}
|
|
@@ -114,7 +134,7 @@ class CompletionsProviderImpl {
|
|
|
114
134
|
const existingImports = this.getExistingImports(document);
|
|
115
135
|
const completionItems = completions.entries
|
|
116
136
|
.filter(this.isValidCompletion)
|
|
117
|
-
.map((entry) => this.toCompletionItem(fragment, entry, filePath, offset, isCompletionInsideFrontmatter, existingImports))
|
|
137
|
+
.map((entry) => this.toCompletionItem(fragment, entry, filePath, offset, isCompletionInsideFrontmatter, scriptTagIndex, existingImports))
|
|
118
138
|
.filter(utils_3.isNotNullOrUndefined)
|
|
119
139
|
.map((comp) => this.fixTextEditRange(wordRangeStartPosition, comp));
|
|
120
140
|
const completionList = vscode_languageserver_2.CompletionList.create(completionItems, true);
|
|
@@ -123,6 +143,7 @@ class CompletionsProviderImpl {
|
|
|
123
143
|
}
|
|
124
144
|
async resolveCompletion(document, item, cancellationToken) {
|
|
125
145
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
146
|
+
const tsPreferences = await this.configManager.getTSPreferences(document);
|
|
126
147
|
const data = item.data;
|
|
127
148
|
if (!data || !data.filePath || cancellationToken?.isCancellationRequested) {
|
|
128
149
|
return item;
|
|
@@ -133,27 +154,42 @@ class CompletionsProviderImpl {
|
|
|
133
154
|
data.originalItem.name, // entryName
|
|
134
155
|
{}, // formatOptions
|
|
135
156
|
data.originalItem.source, // source
|
|
136
|
-
|
|
157
|
+
tsPreferences, // preferences
|
|
137
158
|
data.originalItem.data // data
|
|
138
159
|
);
|
|
139
160
|
if (detail) {
|
|
140
161
|
const { detail: itemDetail, documentation: itemDocumentation } = this.getCompletionDocument(detail);
|
|
162
|
+
// TODO: Add support for labelDetails
|
|
163
|
+
// if (data.originalItem.source) {
|
|
164
|
+
// item.labelDetails = { description: data.originalItem.source };
|
|
165
|
+
// }
|
|
141
166
|
item.detail = itemDetail;
|
|
142
167
|
item.documentation = itemDocumentation;
|
|
143
168
|
}
|
|
144
169
|
const actions = detail?.codeActions;
|
|
170
|
+
const isInsideScriptTag = data.scriptTagIndex !== undefined;
|
|
171
|
+
let scriptTagSnapshot;
|
|
172
|
+
if (isInsideScriptTag) {
|
|
173
|
+
const { snapshot } = (0, utils_2.getScriptTagSnapshot)(tsDoc, document, document.scriptTags[data.scriptTagIndex].container);
|
|
174
|
+
scriptTagSnapshot = snapshot;
|
|
175
|
+
}
|
|
145
176
|
if (actions) {
|
|
146
177
|
const edit = [];
|
|
147
178
|
for (const action of actions) {
|
|
148
179
|
for (const change of action.changes) {
|
|
149
|
-
|
|
180
|
+
if (isInsideScriptTag) {
|
|
181
|
+
change.textChanges.forEach((textChange) => {
|
|
182
|
+
textChange.span.start = fragment.offsetAt(scriptTagSnapshot.getOriginalPosition(scriptTagSnapshot.positionAt(textChange.span.start)));
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
edit.push(...change.textChanges.map((textChange) => codeActionChangeToTextEdit(document, fragment, isInsideScriptTag, textChange)));
|
|
150
186
|
}
|
|
151
187
|
}
|
|
152
188
|
item.additionalTextEdits = (item.additionalTextEdits ?? []).concat(edit);
|
|
153
189
|
}
|
|
154
190
|
return item;
|
|
155
191
|
}
|
|
156
|
-
toCompletionItem(fragment, comp, filePath, offset, insideFrontmatter, existingImports) {
|
|
192
|
+
toCompletionItem(fragment, comp, filePath, offset, insideFrontmatter, scriptTagIndex, existingImports) {
|
|
157
193
|
let item = vscode_languageserver_protocol_1.CompletionItem.create(comp.name);
|
|
158
194
|
const isAstroComponent = this.isAstroComponentImport(comp.name);
|
|
159
195
|
const isImport = comp.insertText?.includes('import');
|
|
@@ -177,14 +213,23 @@ class CompletionsProviderImpl {
|
|
|
177
213
|
}
|
|
178
214
|
if (comp.kindModifiers) {
|
|
179
215
|
const kindModifiers = new Set(comp.kindModifiers.split(/,|\s+/g));
|
|
216
|
+
if (kindModifiers.has(typescript_1.ScriptElementKindModifier.optionalModifier)) {
|
|
217
|
+
if (!item.insertText) {
|
|
218
|
+
item.insertText = item.label;
|
|
219
|
+
}
|
|
220
|
+
if (!item.filterText) {
|
|
221
|
+
item.filterText = item.label;
|
|
222
|
+
}
|
|
223
|
+
item.label += '?';
|
|
224
|
+
}
|
|
180
225
|
if (kindModifiers.has(typescript_1.ScriptElementKindModifier.deprecatedModifier)) {
|
|
181
226
|
item.tags = [vscode_languageserver_1.CompletionItemTag.Deprecated];
|
|
182
227
|
}
|
|
183
228
|
}
|
|
184
|
-
//
|
|
185
|
-
if (comp.sourceDisplay) {
|
|
186
|
-
|
|
187
|
-
}
|
|
229
|
+
// TODO: Add support for labelDetails
|
|
230
|
+
// if (comp.sourceDisplay) {
|
|
231
|
+
// item.labelDetails = { description: ts.displayPartsToString(comp.sourceDisplay) };
|
|
232
|
+
// }
|
|
188
233
|
item.commitCharacters = (0, utils_2.getCommitCharactersForScriptElement)(comp.kind);
|
|
189
234
|
item.sortText = comp.sortText;
|
|
190
235
|
item.preselect = comp.isRecommended;
|
|
@@ -200,6 +245,7 @@ class CompletionsProviderImpl {
|
|
|
200
245
|
data: {
|
|
201
246
|
uri: fragment.getURL(),
|
|
202
247
|
filePath,
|
|
248
|
+
scriptTagIndex,
|
|
203
249
|
offset,
|
|
204
250
|
originalItem: comp,
|
|
205
251
|
},
|
|
@@ -279,23 +325,33 @@ class CompletionsProviderImpl {
|
|
|
279
325
|
}
|
|
280
326
|
}
|
|
281
327
|
exports.CompletionsProviderImpl = CompletionsProviderImpl;
|
|
282
|
-
function codeActionChangeToTextEdit(document, fragment, change) {
|
|
328
|
+
function codeActionChangeToTextEdit(document, fragment, isInsideScriptTag, change) {
|
|
283
329
|
change.newText = (0, utils_2.removeAstroComponentSuffix)(change.newText);
|
|
284
|
-
// If we don't have a frontmatter already, create one with the import
|
|
285
|
-
const frontmatterState = document.astroMeta.frontmatter.state;
|
|
286
|
-
if (frontmatterState === null) {
|
|
287
|
-
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}`);
|
|
288
|
-
}
|
|
289
330
|
const { span } = change;
|
|
290
331
|
let range;
|
|
291
332
|
const virtualRange = (0, utils_2.convertRange)(fragment, span);
|
|
292
333
|
range = (0, documents_1.mapRangeToOriginal)(fragment, virtualRange);
|
|
293
|
-
if (!
|
|
294
|
-
|
|
334
|
+
if (!isInsideScriptTag) {
|
|
335
|
+
// If we don't have a frontmatter already, create one with the import
|
|
336
|
+
const frontmatterState = document.astroMeta.frontmatter.state;
|
|
337
|
+
if (frontmatterState === null) {
|
|
338
|
+
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}`);
|
|
339
|
+
}
|
|
340
|
+
if (!(0, utils_1.isInsideFrontmatter)(document.getText(), document.offsetAt(range.start))) {
|
|
341
|
+
range = (0, utils_2.ensureFrontmatterInsert)(range, document);
|
|
342
|
+
}
|
|
343
|
+
// First import in a file will wrongly have a newline before it due to how the frontmatter is replaced by a comment
|
|
344
|
+
if (range.start.line === 1 && (change.newText.startsWith('\n') || change.newText.startsWith('\r\n'))) {
|
|
345
|
+
change.newText = change.newText.trimStart();
|
|
346
|
+
}
|
|
295
347
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
348
|
+
else {
|
|
349
|
+
const existingLine = (0, utils_1.getLineAtPosition)(document.positionAt(span.start), document.getText());
|
|
350
|
+
const isNewImport = !existingLine.trim().startsWith('import');
|
|
351
|
+
// Avoid putting new imports on the same line as the script tag opening
|
|
352
|
+
if (!(change.newText.startsWith('\n') || change.newText.startsWith('\r\n')) && isNewImport) {
|
|
353
|
+
change.newText = typescript_1.default.sys.newLine + change.newText;
|
|
354
|
+
}
|
|
299
355
|
}
|
|
300
356
|
return vscode_languageserver_1.TextEdit.replace(range, change.newText);
|
|
301
357
|
}
|
|
@@ -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,57 @@
|
|
|
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
|
+
let defs;
|
|
19
|
+
const html = document.html;
|
|
20
|
+
const offset = document.offsetAt(position);
|
|
21
|
+
const node = html.findNodeAt(offset);
|
|
22
|
+
if (node.tag === 'script') {
|
|
23
|
+
const { snapshot: scriptTagSnapshot, filePath: scriptFilePath, offset: scriptOffset, } = (0, utils_2.getScriptTagSnapshot)(tsDoc, document, node, position);
|
|
24
|
+
defs = lang.getDefinitionAndBoundSpan(scriptFilePath, scriptOffset);
|
|
25
|
+
if (defs) {
|
|
26
|
+
defs.definitions = defs.definitions?.map((def) => {
|
|
27
|
+
const isInSameFile = def.fileName === scriptFilePath;
|
|
28
|
+
def.fileName = isInSameFile ? tsFilePath : def.fileName;
|
|
29
|
+
if (isInSameFile) {
|
|
30
|
+
def.textSpan.start = mainFragment.offsetAt(scriptTagSnapshot.getOriginalPosition(scriptTagSnapshot.positionAt(def.textSpan.start)));
|
|
31
|
+
}
|
|
32
|
+
return def;
|
|
33
|
+
});
|
|
34
|
+
defs.textSpan.start = mainFragment.offsetAt(scriptTagSnapshot.getOriginalPosition(scriptTagSnapshot.positionAt(defs.textSpan.start)));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
defs = lang.getDefinitionAndBoundSpan(tsFilePath, fragmentOffset);
|
|
39
|
+
}
|
|
40
|
+
if (!defs || !defs.definitions) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const docs = new utils_3.SnapshotFragmentMap(this.languageServiceManager);
|
|
44
|
+
docs.set(tsFilePath, { fragment: mainFragment, snapshot: tsDoc });
|
|
45
|
+
const result = await Promise.all(defs.definitions.map(async (def) => {
|
|
46
|
+
const { fragment, snapshot } = await docs.retrieve(def.fileName);
|
|
47
|
+
const fileName = (0, utils_2.ensureRealFilePath)(def.fileName);
|
|
48
|
+
// For Astro, Svelte and Vue, the position is wrongly mapped to the end of the file due to the TSX output
|
|
49
|
+
// So we'll instead redirect to the beginning of the file
|
|
50
|
+
const isFramework = (0, utils_2.isFrameworkFilePath)(def.fileName) || (0, utils_2.isAstroFilePath)(def.fileName);
|
|
51
|
+
const textSpan = isFramework && tsDoc.filePath !== def.fileName ? { start: 0, length: 0 } : def.textSpan;
|
|
52
|
+
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));
|
|
53
|
+
}));
|
|
54
|
+
return result.filter(utils_1.isNotNullOrUndefined);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.DefinitionsProviderImpl = DefinitionsProviderImpl;
|
|
@@ -20,24 +20,52 @@ class DiagnosticsProviderImpl {
|
|
|
20
20
|
}
|
|
21
21
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
22
22
|
const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
23
|
+
const fragment = await tsDoc.createFragment();
|
|
24
|
+
let scriptDiagnostics = [];
|
|
25
|
+
document.scriptTags.forEach((scriptTag) => {
|
|
26
|
+
const { filePath: scriptFilePath, snapshot: scriptTagSnapshot } = (0, utils_1.getScriptTagSnapshot)(tsDoc, document, scriptTag.container);
|
|
27
|
+
const scriptDiagnostic = [
|
|
28
|
+
...lang.getSyntacticDiagnostics(scriptFilePath),
|
|
29
|
+
...lang.getSuggestionDiagnostics(scriptFilePath),
|
|
30
|
+
...lang.getSemanticDiagnostics(scriptFilePath),
|
|
31
|
+
]
|
|
32
|
+
// We need to duplicate the diagnostic creation here because we can't map TS's diagnostics range to the original
|
|
33
|
+
// file due to some internal cache inside TS that would cause it to being mapped twice in some cases
|
|
34
|
+
.map((diagnostic) => ({
|
|
35
|
+
range: (0, utils_1.convertRange)(scriptTagSnapshot, diagnostic),
|
|
36
|
+
severity: (0, utils_1.mapSeverity)(diagnostic.category),
|
|
37
|
+
source: 'ts',
|
|
38
|
+
message: typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
39
|
+
code: diagnostic.code,
|
|
40
|
+
tags: getDiagnosticTag(diagnostic),
|
|
41
|
+
}))
|
|
42
|
+
.map(mapRange(scriptTagSnapshot, document));
|
|
43
|
+
scriptDiagnostics.push(...scriptDiagnostic);
|
|
44
|
+
});
|
|
23
45
|
const { script: scriptBoundaries } = this.getTagBoundaries(lang, filePath);
|
|
24
46
|
const syntaxDiagnostics = lang.getSyntacticDiagnostics(filePath);
|
|
25
47
|
const suggestionDiagnostics = lang.getSuggestionDiagnostics(filePath);
|
|
26
|
-
const semanticDiagnostics = lang.getSemanticDiagnostics(filePath)
|
|
27
|
-
|
|
48
|
+
const semanticDiagnostics = lang.getSemanticDiagnostics(filePath);
|
|
49
|
+
const diagnostics = [
|
|
50
|
+
...syntaxDiagnostics,
|
|
51
|
+
...suggestionDiagnostics,
|
|
52
|
+
...semanticDiagnostics,
|
|
53
|
+
].filter((diag) => {
|
|
54
|
+
return isNoWithinBoundary(scriptBoundaries, diag);
|
|
28
55
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
return [
|
|
57
|
+
...diagnostics
|
|
58
|
+
.map((diagnostic) => ({
|
|
59
|
+
range: (0, utils_1.convertRange)(tsDoc, diagnostic),
|
|
60
|
+
severity: (0, utils_1.mapSeverity)(diagnostic.category),
|
|
61
|
+
source: 'ts',
|
|
62
|
+
message: typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
63
|
+
code: diagnostic.code,
|
|
64
|
+
tags: getDiagnosticTag(diagnostic),
|
|
65
|
+
}))
|
|
66
|
+
.map(mapRange(fragment, document)),
|
|
67
|
+
...scriptDiagnostics,
|
|
68
|
+
]
|
|
41
69
|
.filter((diag) => {
|
|
42
70
|
return (hasNoNegativeLines(diag) &&
|
|
43
71
|
isNoJSXImplicitRuntimeWarning(diag) &&
|
|
@@ -46,6 +74,7 @@ class DiagnosticsProviderImpl {
|
|
|
46
74
|
isNoSpreadExpected(diag) &&
|
|
47
75
|
isNoCantResolveJSONModule(diag) &&
|
|
48
76
|
isNoCantReturnOutsideFunction(diag) &&
|
|
77
|
+
isNoIsolatedModuleError(diag) &&
|
|
49
78
|
isNoJsxCannotHaveMultipleAttrsError(diag));
|
|
50
79
|
})
|
|
51
80
|
.map(enhanceIfNecessary);
|
|
@@ -146,12 +175,26 @@ function isNoCantReturnOutsideFunction(diagnostic) {
|
|
|
146
175
|
function isNoCantResolveJSONModule(diagnostic) {
|
|
147
176
|
return diagnostic.code !== 2732;
|
|
148
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* When the content of the file is invalid and can't be parsed properly for TSX generation, TS will show an error about
|
|
180
|
+
* how the current module can't be compiled under --isolatedModule, this is confusing to users so let's ignore this
|
|
181
|
+
*/
|
|
182
|
+
function isNoIsolatedModuleError(diagnostic) {
|
|
183
|
+
return diagnostic.code !== 1208;
|
|
184
|
+
}
|
|
149
185
|
/**
|
|
150
186
|
* Some diagnostics have JSX-specific nomenclature or unclear description. Enhance them for more clarity.
|
|
151
187
|
*/
|
|
152
188
|
function enhanceIfNecessary(diagnostic) {
|
|
189
|
+
// JSX element has no closing tag. JSX -> HTML
|
|
190
|
+
if (diagnostic.code === 17008) {
|
|
191
|
+
return {
|
|
192
|
+
...diagnostic,
|
|
193
|
+
message: diagnostic.message.replace('JSX', 'HTML'),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// For the rare case where an user might try to put a client directive on something that is not a component
|
|
153
197
|
if (diagnostic.code === 2322) {
|
|
154
|
-
// For the rare case where an user might try to put a client directive on something that is not a component
|
|
155
198
|
if (diagnostic.message.includes("Property 'client:") && diagnostic.message.includes("to type 'HTMLProps")) {
|
|
156
199
|
return {
|
|
157
200
|
...diagnostic,
|
|
@@ -15,15 +15,22 @@ class FoldingRangesProviderImpl {
|
|
|
15
15
|
const html = document.html;
|
|
16
16
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
17
17
|
const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
18
|
-
const outliningSpans = lang.getOutliningSpans(filePath)
|
|
19
|
-
const foldingRanges = [];
|
|
20
|
-
for (const span of outliningSpans) {
|
|
18
|
+
const outliningSpans = lang.getOutliningSpans(filePath).filter((span) => {
|
|
21
19
|
const node = html.findNodeAt(span.textSpan.start);
|
|
22
20
|
// Due to how our TSX output transform those tags into function calls or template literals
|
|
23
21
|
// TypeScript thinks of those as outlining spans, which is fine but we don't want folding ranges for those
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
return node.tag !== 'script' && node.tag !== 'style' && node.tag !== 'Markdown';
|
|
23
|
+
});
|
|
24
|
+
const scriptOutliningSpans = [];
|
|
25
|
+
document.scriptTags.forEach((scriptTag) => {
|
|
26
|
+
const { snapshot: scriptTagSnapshot, filePath: scriptFilePath } = (0, utils_1.getScriptTagSnapshot)(tsDoc, document, scriptTag.container);
|
|
27
|
+
scriptOutliningSpans.push(...lang.getOutliningSpans(scriptFilePath).map((span) => {
|
|
28
|
+
span.textSpan.start = document.offsetAt(scriptTagSnapshot.getOriginalPosition(scriptTagSnapshot.positionAt(span.textSpan.start)));
|
|
29
|
+
return span;
|
|
30
|
+
}));
|
|
31
|
+
});
|
|
32
|
+
const foldingRanges = [];
|
|
33
|
+
for (const span of [...outliningSpans, ...scriptOutliningSpans]) {
|
|
27
34
|
const start = document.positionAt(span.textSpan.start);
|
|
28
35
|
const end = adjustFoldingEnd(start, document.positionAt(span.textSpan.start + span.textSpan.length), document);
|
|
29
36
|
// When using this method for generating folding ranges, TypeScript tend to return some
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FormattingOptions, TextEdit } from 'vscode-languageserver-types';
|
|
2
|
+
import { ConfigManager } from '../../../core/config';
|
|
3
|
+
import { AstroDocument } from '../../../core/documents';
|
|
4
|
+
import { FormattingProvider } from '../../interfaces';
|
|
5
|
+
import { LanguageServiceManager } from '../LanguageServiceManager';
|
|
6
|
+
export declare class FormattingProviderImpl implements FormattingProvider {
|
|
7
|
+
private languageServiceManager;
|
|
8
|
+
private configManager;
|
|
9
|
+
constructor(languageServiceManager: LanguageServiceManager, configManager: ConfigManager);
|
|
10
|
+
formatDocument(document: AstroDocument, options: FormattingOptions): Promise<TextEdit[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
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.FormattingProviderImpl = void 0;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
|
|
9
|
+
const utils_1 = require("../utils");
|
|
10
|
+
class FormattingProviderImpl {
|
|
11
|
+
constructor(languageServiceManager, configManager) {
|
|
12
|
+
this.languageServiceManager = languageServiceManager;
|
|
13
|
+
this.configManager = configManager;
|
|
14
|
+
}
|
|
15
|
+
async formatDocument(document, options) {
|
|
16
|
+
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
17
|
+
const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
18
|
+
const formatConfig = await this.configManager.getTSFormatConfig(document, options);
|
|
19
|
+
let frontmatterEdits = [];
|
|
20
|
+
let scriptTagsEdits = [];
|
|
21
|
+
if (document.astroMeta.frontmatter.state === 'closed') {
|
|
22
|
+
const start = document.positionAt(document.astroMeta.frontmatter.startOffset + 3);
|
|
23
|
+
start.line += 1;
|
|
24
|
+
start.character = 0;
|
|
25
|
+
const startOffset = document.offsetAt(start);
|
|
26
|
+
const endOffset = document.astroMeta.frontmatter.endOffset;
|
|
27
|
+
const astroFormatConfig = await this.configManager.getAstroFormatConfig(document);
|
|
28
|
+
const settings = {
|
|
29
|
+
...formatConfig,
|
|
30
|
+
baseIndentSize: astroFormatConfig.indentFrontmatter ? formatConfig.tabSize ?? 0 : undefined,
|
|
31
|
+
};
|
|
32
|
+
frontmatterEdits = lang.getFormattingEditsForRange(filePath, startOffset, endOffset, settings);
|
|
33
|
+
if (astroFormatConfig.newLineAfterFrontmatter) {
|
|
34
|
+
const templateStart = document.positionAt(endOffset + 3);
|
|
35
|
+
templateStart.line += 1;
|
|
36
|
+
templateStart.character = 0;
|
|
37
|
+
frontmatterEdits.push({
|
|
38
|
+
span: { start: document.offsetAt(templateStart), length: 0 },
|
|
39
|
+
newText: '\n',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
document.scriptTags.forEach((scriptTag) => {
|
|
44
|
+
const { filePath: scriptFilePath, snapshot: scriptTagSnapshot } = (0, utils_1.getScriptTagSnapshot)(tsDoc, document, scriptTag.container);
|
|
45
|
+
const startLine = document.offsetAt(vscode_languageserver_types_1.Position.create(scriptTag.startPos.line, 0));
|
|
46
|
+
const initialIndentLevel = computeInitialIndent(document, startLine, options);
|
|
47
|
+
const baseIndent = (formatConfig.tabSize ?? 0) * (initialIndentLevel + 1);
|
|
48
|
+
const formatSettings = {
|
|
49
|
+
baseIndentSize: baseIndent,
|
|
50
|
+
indentStyle: typescript_1.default.IndentStyle.Smart,
|
|
51
|
+
...formatConfig,
|
|
52
|
+
};
|
|
53
|
+
let edits = lang.getFormattingEditsForDocument(scriptFilePath, formatSettings);
|
|
54
|
+
if (edits) {
|
|
55
|
+
edits = edits
|
|
56
|
+
.map((edit) => {
|
|
57
|
+
edit.span.start = document.offsetAt(scriptTagSnapshot.getOriginalPosition(scriptTagSnapshot.positionAt(edit.span.start)));
|
|
58
|
+
return edit;
|
|
59
|
+
})
|
|
60
|
+
.filter((edit) => {
|
|
61
|
+
return (scriptTagSnapshot.isInGenerated(document.positionAt(edit.span.start)) &&
|
|
62
|
+
scriptTag.end !== edit.span.start &&
|
|
63
|
+
// Don't format the last line of the file as it's in most case the indentation
|
|
64
|
+
scriptTag.endPos.line !== document.positionAt(edit.span.start).line);
|
|
65
|
+
});
|
|
66
|
+
const endLine = document.getLineUntilOffset(document.offsetAt(scriptTag.endPos));
|
|
67
|
+
if (isWhitespaceOnly(endLine)) {
|
|
68
|
+
const endLineStartOffset = document.offsetAt(vscode_languageserver_types_1.Position.create(scriptTag.endPos.line, 0));
|
|
69
|
+
const lastLineIndentRange = vscode_languageserver_types_1.Range.create(vscode_languageserver_types_1.Position.create(scriptTag.endPos.line, 0), scriptTag.endPos);
|
|
70
|
+
const newText = generateIndent(initialIndentLevel, options);
|
|
71
|
+
if (endLine !== newText) {
|
|
72
|
+
edits.push({
|
|
73
|
+
span: {
|
|
74
|
+
start: endLineStartOffset,
|
|
75
|
+
length: lastLineIndentRange.end.character,
|
|
76
|
+
},
|
|
77
|
+
newText,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
scriptTagsEdits.push(...edits);
|
|
83
|
+
});
|
|
84
|
+
return [...frontmatterEdits, ...scriptTagsEdits].map((edit) => ({
|
|
85
|
+
range: (0, utils_1.convertRange)(document, edit.span),
|
|
86
|
+
newText: edit.newText,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.FormattingProviderImpl = FormattingProviderImpl;
|
|
91
|
+
function computeInitialIndent(document, lineStart, options) {
|
|
92
|
+
let content = document.getText();
|
|
93
|
+
let i = lineStart;
|
|
94
|
+
let nChars = 0;
|
|
95
|
+
let tabSize = options.tabSize || 4;
|
|
96
|
+
while (i < content.length) {
|
|
97
|
+
let ch = content.charAt(i);
|
|
98
|
+
if (ch === ' ') {
|
|
99
|
+
nChars++;
|
|
100
|
+
}
|
|
101
|
+
else if (ch === '\t') {
|
|
102
|
+
nChars += tabSize;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
return Math.floor(nChars / tabSize);
|
|
110
|
+
}
|
|
111
|
+
function generateIndent(level, options) {
|
|
112
|
+
if (options.insertSpaces) {
|
|
113
|
+
return repeat(' ', level * options.tabSize);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
return repeat('\t', level);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function repeat(value, count) {
|
|
120
|
+
let s = '';
|
|
121
|
+
while (count > 0) {
|
|
122
|
+
if ((count & 1) === 1) {
|
|
123
|
+
s += value;
|
|
124
|
+
}
|
|
125
|
+
value += value;
|
|
126
|
+
count = count >>> 1;
|
|
127
|
+
}
|
|
128
|
+
return s;
|
|
129
|
+
}
|
|
130
|
+
function isWhitespaceOnly(str) {
|
|
131
|
+
return /^\s*$/.test(str);
|
|
132
|
+
}
|
|
@@ -18,7 +18,20 @@ class HoverProviderImpl {
|
|
|
18
18
|
const fragment = await tsDoc.createFragment();
|
|
19
19
|
const offset = fragment.offsetAt(fragment.getGeneratedPosition(position));
|
|
20
20
|
const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
21
|
-
|
|
21
|
+
const html = document.html;
|
|
22
|
+
const documentOffset = document.offsetAt(position);
|
|
23
|
+
const node = html.findNodeAt(documentOffset);
|
|
24
|
+
let info;
|
|
25
|
+
if (node.tag === 'script') {
|
|
26
|
+
const { snapshot: scriptTagSnapshot, filePath: scriptFilePath, offset: scriptOffset, } = (0, utils_1.getScriptTagSnapshot)(tsDoc, document, node, position);
|
|
27
|
+
info = lang.getQuickInfoAtPosition(scriptFilePath, scriptOffset);
|
|
28
|
+
if (info) {
|
|
29
|
+
info.textSpan.start = fragment.offsetAt(scriptTagSnapshot.getOriginalPosition(scriptTagSnapshot.positionAt(info.textSpan.start)));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
info = lang.getQuickInfoAtPosition(filePath, offset);
|
|
34
|
+
}
|
|
22
35
|
if (!info) {
|
|
23
36
|
return null;
|
|
24
37
|
}
|