@astrojs/language-server 0.16.1 → 0.18.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 +29 -0
- package/dist/core/config/ConfigManager.d.ts +4 -3
- package/dist/core/config/ConfigManager.js +15 -1
- package/dist/core/config/interfaces.d.ts +5 -0
- 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 +2 -1
- package/dist/plugins/PluginHost.js +4 -0
- package/dist/plugins/astro/AstroPlugin.js +10 -3
- package/dist/plugins/astro/features/CompletionsProvider.d.ts +4 -5
- package/dist/plugins/astro/features/CompletionsProvider.js +53 -58
- package/dist/plugins/html/HTMLPlugin.d.ts +2 -1
- package/dist/plugins/html/HTMLPlugin.js +18 -0
- package/dist/plugins/typescript/TypeScriptPlugin.d.ts +3 -1
- package/dist/plugins/typescript/TypeScriptPlugin.js +5 -0
- package/dist/plugins/typescript/features/CodeActionsProvider.js +76 -17
- package/dist/plugins/typescript/features/CompletionsProvider.d.ts +2 -1
- package/dist/plugins/typescript/features/CompletionsProvider.js +107 -45
- package/dist/plugins/typescript/features/DefinitionsProvider.js +22 -1
- 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/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 +2 -0
- package/package.json +3 -2
|
@@ -23,8 +23,6 @@ class CodeActionsProviderImpl {
|
|
|
23
23
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
24
24
|
const filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
25
25
|
const fragment = await tsDoc.createFragment();
|
|
26
|
-
const start = fragment.offsetAt(fragment.getGeneratedPosition(range.start));
|
|
27
|
-
const end = fragment.offsetAt(fragment.getGeneratedPosition(range.end));
|
|
28
26
|
const tsPreferences = await this.configManager.getTSPreferences(document);
|
|
29
27
|
const formatOptions = await this.configManager.getTSFormatConfig(document);
|
|
30
28
|
let result = [];
|
|
@@ -50,24 +48,57 @@ class CodeActionsProviderImpl {
|
|
|
50
48
|
.map((diag) => Number(diag.code))
|
|
51
49
|
// We currently cannot support quick fix for unreachable code properly due to the way our TSX output is structured
|
|
52
50
|
.filter((code) => code !== 7027);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
const html = document.html;
|
|
52
|
+
const node = html.findNodeAt(document.offsetAt(range.start));
|
|
53
|
+
let codeFixes;
|
|
54
|
+
let isInsideScript = false;
|
|
55
|
+
if (node.tag === 'script') {
|
|
56
|
+
const { snapshot: scriptTagSnapshot, filePath: scriptFilePath } = (0, utils_2.getScriptTagSnapshot)(tsDoc, document, node);
|
|
57
|
+
const start = scriptTagSnapshot.offsetAt(scriptTagSnapshot.getGeneratedPosition(range.start));
|
|
58
|
+
const end = scriptTagSnapshot.offsetAt(scriptTagSnapshot.getGeneratedPosition(range.end));
|
|
59
|
+
codeFixes = lang.getCodeFixesAtPosition(scriptFilePath, start, end, errorCodes, formatOptions, tsPreferences);
|
|
60
|
+
codeFixes = codeFixes.map((fix) => ({
|
|
61
|
+
...fix,
|
|
62
|
+
changes: mapScriptTagFixToOriginal(fix.changes, scriptTagSnapshot),
|
|
63
|
+
}));
|
|
64
|
+
isInsideScript = true;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const start = fragment.offsetAt(fragment.getGeneratedPosition(range.start));
|
|
68
|
+
const end = fragment.offsetAt(fragment.getGeneratedPosition(range.end));
|
|
69
|
+
codeFixes = errorCodes.includes(2304)
|
|
70
|
+
? this.getComponentQuickFix(start, end, lang, filePath, formatOptions, tsPreferences)
|
|
71
|
+
: undefined;
|
|
72
|
+
codeFixes =
|
|
73
|
+
codeFixes ?? lang.getCodeFixesAtPosition(filePath, start, end, errorCodes, formatOptions, tsPreferences);
|
|
74
|
+
}
|
|
75
|
+
const codeActions = codeFixes.map((fix) => codeFixToCodeAction(fix, context.diagnostics, context.only ? vscode_languageserver_types_1.CodeActionKind.QuickFix : vscode_languageserver_types_1.CodeActionKind.Empty, isInsideScript));
|
|
59
76
|
result.push(...codeActions);
|
|
60
77
|
}
|
|
61
78
|
return result;
|
|
62
|
-
function codeFixToCodeAction(codeFix, diagnostics, kind) {
|
|
79
|
+
function codeFixToCodeAction(codeFix, diagnostics, kind, isInsideScript) {
|
|
63
80
|
const documentChanges = codeFix.changes.map((change) => {
|
|
64
81
|
return vscode_languageserver_types_1.TextDocumentEdit.create(vscode_languageserver_types_1.OptionalVersionedTextDocumentIdentifier.create(document.getURL(), null), change.textChanges.map((edit) => {
|
|
65
82
|
let originalRange = (0, documents_1.mapRangeToOriginal)(fragment, (0, utils_2.convertRange)(fragment, edit.span));
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
// Inside scripts, we don't need to restrain the insertion of code inside a specific zone as it will be
|
|
84
|
+
// restricted to the area of the script tag by default
|
|
85
|
+
if (!isInsideScript) {
|
|
86
|
+
if (codeFix.fixName === 'import') {
|
|
87
|
+
return (0, CompletionsProvider_1.codeActionChangeToTextEdit)(document, fragment, false, edit);
|
|
88
|
+
}
|
|
89
|
+
if (codeFix.fixName === 'fixMissingFunctionDeclaration') {
|
|
90
|
+
originalRange = (0, utils_2.checkEndOfFileCodeInsert)(originalRange, document);
|
|
91
|
+
}
|
|
68
92
|
}
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
else {
|
|
94
|
+
// Make sure new imports are not added on the file line of the script tag
|
|
95
|
+
if (codeFix.fixName === 'import') {
|
|
96
|
+
const existingLine = (0, documents_1.getLineAtPosition)(document.positionAt(edit.span.start), document.getText());
|
|
97
|
+
const isNewImport = !existingLine.trim().startsWith('import');
|
|
98
|
+
if (!(edit.newText.startsWith('\n') || edit.newText.startsWith('\r\n')) && isNewImport) {
|
|
99
|
+
edit.newText = typescript_1.default.sys.newLine + edit.newText;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
71
102
|
}
|
|
72
103
|
return vscode_languageserver_types_1.TextEdit.replace(originalRange, edit.newText);
|
|
73
104
|
}));
|
|
@@ -78,6 +109,15 @@ class CodeActionsProviderImpl {
|
|
|
78
109
|
codeAction.diagnostics = diagnostics;
|
|
79
110
|
return codeAction;
|
|
80
111
|
}
|
|
112
|
+
function mapScriptTagFixToOriginal(changes, scriptTagSnapshot) {
|
|
113
|
+
return changes.map((change) => {
|
|
114
|
+
change.textChanges.map((edit) => {
|
|
115
|
+
edit.span.start = fragment.offsetAt(scriptTagSnapshot.getOriginalPosition(scriptTagSnapshot.positionAt(edit.span.start)));
|
|
116
|
+
return edit;
|
|
117
|
+
});
|
|
118
|
+
return change;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
81
121
|
}
|
|
82
122
|
getComponentQuickFix(start, end, lang, filePath, formatOptions, tsPreferences) {
|
|
83
123
|
const sourceFile = lang.getProgram()?.getSourceFile(filePath);
|
|
@@ -107,16 +147,35 @@ class CodeActionsProviderImpl {
|
|
|
107
147
|
return (0, lodash_1.flatten)(completion.entries.filter((c) => c.name === name || c.name === suffixedName).map(toFix));
|
|
108
148
|
}
|
|
109
149
|
async organizeSortImports(document, skipDestructiveCodeActions = false, cancellationToken) {
|
|
110
|
-
if (document.astroMeta.frontmatter.state !== 'closed') {
|
|
111
|
-
return [];
|
|
112
|
-
}
|
|
113
150
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
114
151
|
const filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
115
152
|
const fragment = await tsDoc.createFragment();
|
|
116
153
|
if (cancellationToken?.isCancellationRequested) {
|
|
117
154
|
return [];
|
|
118
155
|
}
|
|
119
|
-
|
|
156
|
+
let changes = [];
|
|
157
|
+
if (document.astroMeta.frontmatter.state === 'closed') {
|
|
158
|
+
changes.push(...lang.organizeImports({ fileName: filePath, type: 'file', skipDestructiveCodeActions }, {}, {}));
|
|
159
|
+
}
|
|
160
|
+
document.scriptTags.forEach((scriptTag) => {
|
|
161
|
+
const { filePath: scriptFilePath, snapshot: scriptTagSnapshot } = (0, utils_2.getScriptTagSnapshot)(tsDoc, document, scriptTag.container);
|
|
162
|
+
const edits = lang.organizeImports({ fileName: scriptFilePath, type: 'file', skipDestructiveCodeActions }, {}, {});
|
|
163
|
+
edits.forEach((edit) => {
|
|
164
|
+
edit.fileName = tsDoc.filePath;
|
|
165
|
+
edit.textChanges = edit.textChanges
|
|
166
|
+
.map((change) => {
|
|
167
|
+
change.span.start = fragment.offsetAt(scriptTagSnapshot.getOriginalPosition(scriptTagSnapshot.positionAt(change.span.start)));
|
|
168
|
+
return change;
|
|
169
|
+
})
|
|
170
|
+
// Since our last line is a (virtual) export, organize imports will try to rewrite it, so let's only take
|
|
171
|
+
// changes that actually happens inside the script tag
|
|
172
|
+
.filter((change) => {
|
|
173
|
+
return scriptTagSnapshot.isInGenerated(document.positionAt(change.span.start));
|
|
174
|
+
});
|
|
175
|
+
return edit;
|
|
176
|
+
});
|
|
177
|
+
changes.push(...edits);
|
|
178
|
+
});
|
|
120
179
|
const documentChanges = changes.map((change) => {
|
|
121
180
|
return vscode_languageserver_types_1.TextDocumentEdit.create(vscode_languageserver_types_1.OptionalVersionedTextDocumentIdentifier.create(document.url, null), change.textChanges.map((edit) => {
|
|
122
181
|
const range = (0, documents_1.mapRangeToOriginal)(fragment, (0, utils_2.convertRange)(fragment, edit.span));
|
|
@@ -8,6 +8,7 @@ import { ConfigManager } from '../../../core/config';
|
|
|
8
8
|
export interface CompletionItemData extends TextDocumentIdentifier {
|
|
9
9
|
filePath: string;
|
|
10
10
|
offset: number;
|
|
11
|
+
scriptTagIndex: number | undefined;
|
|
11
12
|
originalItem: ts.CompletionEntry;
|
|
12
13
|
}
|
|
13
14
|
export declare class CompletionsProviderImpl implements CompletionsProvider<CompletionItemData> {
|
|
@@ -32,4 +33,4 @@ export declare class CompletionsProviderImpl implements CompletionsProvider<Comp
|
|
|
32
33
|
private getExistingImports;
|
|
33
34
|
private isAstroComponentImport;
|
|
34
35
|
}
|
|
35
|
-
export declare function codeActionChangeToTextEdit(document: AstroDocument, fragment: AstroSnapshotFragment, change: ts.TextChange): TextEdit;
|
|
36
|
+
export declare function codeActionChangeToTextEdit(document: AstroDocument, fragment: AstroSnapshotFragment, isInsideScriptTag: boolean, change: ts.TextChange): TextEdit;
|
|
@@ -65,37 +65,64 @@ class CompletionsProviderImpl {
|
|
|
65
65
|
const html = document.html;
|
|
66
66
|
const offset = document.offsetAt(position);
|
|
67
67
|
const node = html.findNodeAt(offset);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
68
|
+
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
69
|
+
let filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
70
|
+
let completions;
|
|
72
71
|
const isCompletionInsideFrontmatter = (0, utils_1.isInsideFrontmatter)(document.getText(), offset);
|
|
73
72
|
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) {
|
|
80
|
-
return null;
|
|
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
73
|
const tsPreferences = await this.configManager.getTSPreferences(document);
|
|
92
74
|
const formatOptions = await this.configManager.getTSFormatConfig(document);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
+
}
|
|
102
|
+
}
|
|
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);
|
|
125
|
+
}
|
|
99
126
|
if (completions === undefined || completions.entries.length === 0) {
|
|
100
127
|
return null;
|
|
101
128
|
}
|
|
@@ -107,7 +134,7 @@ class CompletionsProviderImpl {
|
|
|
107
134
|
const existingImports = this.getExistingImports(document);
|
|
108
135
|
const completionItems = completions.entries
|
|
109
136
|
.filter(this.isValidCompletion)
|
|
110
|
-
.map((entry) => this.toCompletionItem(fragment, entry, filePath, offset, isCompletionInsideFrontmatter, existingImports))
|
|
137
|
+
.map((entry) => this.toCompletionItem(fragment, entry, filePath, offset, isCompletionInsideFrontmatter, scriptTagIndex, existingImports))
|
|
111
138
|
.filter(utils_3.isNotNullOrUndefined)
|
|
112
139
|
.map((comp) => this.fixTextEditRange(wordRangeStartPosition, comp));
|
|
113
140
|
const completionList = vscode_languageserver_2.CompletionList.create(completionItems, true);
|
|
@@ -132,22 +159,37 @@ class CompletionsProviderImpl {
|
|
|
132
159
|
);
|
|
133
160
|
if (detail) {
|
|
134
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
|
+
// }
|
|
135
166
|
item.detail = itemDetail;
|
|
136
167
|
item.documentation = itemDocumentation;
|
|
137
168
|
}
|
|
138
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
|
+
}
|
|
139
176
|
if (actions) {
|
|
140
177
|
const edit = [];
|
|
141
178
|
for (const action of actions) {
|
|
142
179
|
for (const change of action.changes) {
|
|
143
|
-
|
|
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)));
|
|
144
186
|
}
|
|
145
187
|
}
|
|
146
188
|
item.additionalTextEdits = (item.additionalTextEdits ?? []).concat(edit);
|
|
147
189
|
}
|
|
148
190
|
return item;
|
|
149
191
|
}
|
|
150
|
-
toCompletionItem(fragment, comp, filePath, offset, insideFrontmatter, existingImports) {
|
|
192
|
+
toCompletionItem(fragment, comp, filePath, offset, insideFrontmatter, scriptTagIndex, existingImports) {
|
|
151
193
|
let item = vscode_languageserver_protocol_1.CompletionItem.create(comp.name);
|
|
152
194
|
const isAstroComponent = this.isAstroComponentImport(comp.name);
|
|
153
195
|
const isImport = comp.insertText?.includes('import');
|
|
@@ -171,14 +213,23 @@ class CompletionsProviderImpl {
|
|
|
171
213
|
}
|
|
172
214
|
if (comp.kindModifiers) {
|
|
173
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
|
+
}
|
|
174
225
|
if (kindModifiers.has(typescript_1.ScriptElementKindModifier.deprecatedModifier)) {
|
|
175
226
|
item.tags = [vscode_languageserver_1.CompletionItemTag.Deprecated];
|
|
176
227
|
}
|
|
177
228
|
}
|
|
178
|
-
//
|
|
179
|
-
if (comp.sourceDisplay) {
|
|
180
|
-
|
|
181
|
-
}
|
|
229
|
+
// TODO: Add support for labelDetails
|
|
230
|
+
// if (comp.sourceDisplay) {
|
|
231
|
+
// item.labelDetails = { description: ts.displayPartsToString(comp.sourceDisplay) };
|
|
232
|
+
// }
|
|
182
233
|
item.commitCharacters = (0, utils_2.getCommitCharactersForScriptElement)(comp.kind);
|
|
183
234
|
item.sortText = comp.sortText;
|
|
184
235
|
item.preselect = comp.isRecommended;
|
|
@@ -194,6 +245,7 @@ class CompletionsProviderImpl {
|
|
|
194
245
|
data: {
|
|
195
246
|
uri: fragment.getURL(),
|
|
196
247
|
filePath,
|
|
248
|
+
scriptTagIndex,
|
|
197
249
|
offset,
|
|
198
250
|
originalItem: comp,
|
|
199
251
|
},
|
|
@@ -273,23 +325,33 @@ class CompletionsProviderImpl {
|
|
|
273
325
|
}
|
|
274
326
|
}
|
|
275
327
|
exports.CompletionsProviderImpl = CompletionsProviderImpl;
|
|
276
|
-
function codeActionChangeToTextEdit(document, fragment, change) {
|
|
328
|
+
function codeActionChangeToTextEdit(document, fragment, isInsideScriptTag, change) {
|
|
277
329
|
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
330
|
const { span } = change;
|
|
284
331
|
let range;
|
|
285
332
|
const virtualRange = (0, utils_2.convertRange)(fragment, span);
|
|
286
333
|
range = (0, documents_1.mapRangeToOriginal)(fragment, virtualRange);
|
|
287
|
-
if (!
|
|
288
|
-
|
|
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
|
+
}
|
|
289
347
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
+
}
|
|
293
355
|
}
|
|
294
356
|
return vscode_languageserver_1.TextEdit.replace(range, change.newText);
|
|
295
357
|
}
|
|
@@ -15,7 +15,28 @@ class DefinitionsProviderImpl {
|
|
|
15
15
|
const tsFilePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
16
16
|
const fragmentPosition = mainFragment.getGeneratedPosition(position);
|
|
17
17
|
const fragmentOffset = mainFragment.offsetAt(fragmentPosition);
|
|
18
|
-
|
|
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
|
+
}
|
|
19
40
|
if (!defs || !defs.definitions) {
|
|
20
41
|
return [];
|
|
21
42
|
}
|
|
@@ -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
|
+
}
|