@astrojs/language-server 0.15.0 → 0.16.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 +15 -0
- package/dist/check.js +1 -2
- package/dist/core/documents/DocumentMapper.js +2 -4
- package/dist/core/documents/parseAstro.js +1 -1
- package/dist/core/documents/utils.js +3 -4
- package/dist/plugins/PluginHost.d.ts +2 -1
- package/dist/plugins/PluginHost.js +8 -6
- package/dist/plugins/astro/AstroPlugin.js +1 -1
- package/dist/plugins/astro/features/CompletionsProvider.js +9 -10
- package/dist/plugins/css/CSSPlugin.js +20 -4
- package/dist/plugins/html/features/astro-attributes.js +43 -27
- package/dist/plugins/typescript/LanguageServiceManager.js +1 -1
- package/dist/plugins/typescript/TypeScriptPlugin.d.ts +3 -1
- package/dist/plugins/typescript/TypeScriptPlugin.js +9 -1
- package/dist/plugins/typescript/astro-sys.js +3 -5
- package/dist/plugins/typescript/astro2tsx.js +1 -2
- package/dist/plugins/typescript/features/CodeActionsProvider.d.ts +14 -0
- package/dist/plugins/typescript/features/CodeActionsProvider.js +141 -0
- package/dist/plugins/typescript/features/CompletionsProvider.d.ts +2 -1
- package/dist/plugins/typescript/features/CompletionsProvider.js +37 -32
- package/dist/plugins/typescript/features/DiagnosticsProvider.js +2 -3
- package/dist/plugins/typescript/features/DocumentSymbolsProvider.js +3 -4
- package/dist/plugins/typescript/features/SemanticTokenProvider.js +1 -1
- package/dist/plugins/typescript/features/SignatureHelpProvider.js +2 -2
- package/dist/plugins/typescript/features/utils.d.ts +2 -0
- package/dist/plugins/typescript/features/utils.js +19 -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 +3 -6
- package/dist/plugins/typescript/utils.d.ts +1 -0
- package/dist/plugins/typescript/utils.js +13 -1
- package/dist/server.js +28 -7
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +16 -3
- package/package.json +2 -2
|
@@ -0,0 +1,141 @@
|
|
|
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.CodeActionsProviderImpl = exports.sortImportKind = void 0;
|
|
7
|
+
const lodash_1 = require("lodash");
|
|
8
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
9
|
+
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
|
|
10
|
+
const documents_1 = require("../../../core/documents");
|
|
11
|
+
const utils_1 = require("../../../utils");
|
|
12
|
+
const utils_2 = require("../utils");
|
|
13
|
+
const CompletionsProvider_1 = require("./CompletionsProvider");
|
|
14
|
+
const utils_3 = require("./utils");
|
|
15
|
+
// These are VS Code specific CodeActionKind so they're not in the language server protocol
|
|
16
|
+
exports.sortImportKind = `${vscode_languageserver_types_1.CodeActionKind.Source}.sortImports`;
|
|
17
|
+
class CodeActionsProviderImpl {
|
|
18
|
+
constructor(languageServiceManager) {
|
|
19
|
+
this.languageServiceManager = languageServiceManager;
|
|
20
|
+
}
|
|
21
|
+
async getCodeActions(document, range, context, cancellationToken) {
|
|
22
|
+
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
23
|
+
const filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
24
|
+
const fragment = await tsDoc.createFragment();
|
|
25
|
+
const start = fragment.offsetAt(fragment.getGeneratedPosition(range.start));
|
|
26
|
+
const end = fragment.offsetAt(fragment.getGeneratedPosition(range.end));
|
|
27
|
+
let result = [];
|
|
28
|
+
if (cancellationToken?.isCancellationRequested) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
if (context.only?.[0] === vscode_languageserver_types_1.CodeActionKind.SourceOrganizeImports) {
|
|
32
|
+
return await this.organizeSortImports(document, false, cancellationToken);
|
|
33
|
+
}
|
|
34
|
+
// The difference between Sort Imports and Organize Imports is that Sort Imports won't do anything destructive.
|
|
35
|
+
// For example, it won't remove unused imports whereas Organize Imports will
|
|
36
|
+
if (context.only?.[0] === exports.sortImportKind) {
|
|
37
|
+
return await this.organizeSortImports(document, true, cancellationToken);
|
|
38
|
+
}
|
|
39
|
+
if (context.only?.[0] === vscode_languageserver_types_1.CodeActionKind.Source) {
|
|
40
|
+
return [
|
|
41
|
+
...(await this.organizeSortImports(document, true, cancellationToken)),
|
|
42
|
+
...(await this.organizeSortImports(document, false, cancellationToken)),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
if (context.diagnostics.length && (!context.only || context.only.includes(vscode_languageserver_types_1.CodeActionKind.QuickFix))) {
|
|
46
|
+
const errorCodes = context.diagnostics
|
|
47
|
+
.map((diag) => Number(diag.code))
|
|
48
|
+
// We currently cannot support quick fix for unreachable code properly due to the way our TSX output is structured
|
|
49
|
+
.filter((code) => code !== 7027);
|
|
50
|
+
let codeFixes = errorCodes.includes(2304) ? this.getComponentQuickFix(start, end, lang, filePath) : undefined;
|
|
51
|
+
codeFixes = codeFixes ?? lang.getCodeFixesAtPosition(filePath, start, end, errorCodes, {}, {});
|
|
52
|
+
const codeActions = codeFixes.map((fix) => codeFixToCodeAction(fix, context.diagnostics, context.only ? vscode_languageserver_types_1.CodeActionKind.QuickFix : vscode_languageserver_types_1.CodeActionKind.Empty));
|
|
53
|
+
result.push(...codeActions);
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
function codeFixToCodeAction(codeFix, diagnostics, kind) {
|
|
57
|
+
const documentChanges = codeFix.changes.map((change) => {
|
|
58
|
+
return vscode_languageserver_types_1.TextDocumentEdit.create(vscode_languageserver_types_1.OptionalVersionedTextDocumentIdentifier.create(document.getURL(), null), change.textChanges.map((edit) => {
|
|
59
|
+
let originalRange = (0, documents_1.mapRangeToOriginal)(fragment, (0, utils_2.convertRange)(fragment, edit.span));
|
|
60
|
+
if (codeFix.fixName === 'import') {
|
|
61
|
+
return (0, CompletionsProvider_1.codeActionChangeToTextEdit)(document, fragment, edit);
|
|
62
|
+
}
|
|
63
|
+
if (codeFix.fixName === 'fixMissingFunctionDeclaration') {
|
|
64
|
+
originalRange = (0, utils_2.checkEndOfFileCodeInsert)(originalRange, document);
|
|
65
|
+
}
|
|
66
|
+
return vscode_languageserver_types_1.TextEdit.replace(originalRange, edit.newText);
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
const codeAction = vscode_languageserver_types_1.CodeAction.create(codeFix.description, {
|
|
70
|
+
documentChanges,
|
|
71
|
+
}, kind);
|
|
72
|
+
codeAction.diagnostics = diagnostics;
|
|
73
|
+
return codeAction;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
getComponentQuickFix(start, end, lang, filePath) {
|
|
77
|
+
const sourceFile = lang.getProgram()?.getSourceFile(filePath);
|
|
78
|
+
if (!sourceFile) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const node = (0, utils_3.findContainingNode)(sourceFile, {
|
|
82
|
+
start,
|
|
83
|
+
length: end - start,
|
|
84
|
+
}, (n) => typescript_1.default.isJsxClosingElement(n) || typescript_1.default.isJsxOpeningLikeElement(n));
|
|
85
|
+
if (!node) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const tagName = node.tagName;
|
|
89
|
+
// Unlike quick fixes, completions will be able to find the component, so let's use those to get it
|
|
90
|
+
const completion = lang.getCompletionsAtPosition(filePath, tagName.getEnd(), CompletionsProvider_1.completionOptions);
|
|
91
|
+
if (!completion) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const name = tagName.getText();
|
|
95
|
+
const suffixedName = name + '__AstroComponent_';
|
|
96
|
+
const toFix = (c) => lang.getCompletionEntryDetails(filePath, end, c.name, {}, c.source, {}, c.data)?.codeActions?.map((a) => ({
|
|
97
|
+
...a,
|
|
98
|
+
description: (0, utils_2.removeAstroComponentSuffix)(a.description),
|
|
99
|
+
fixName: 'import',
|
|
100
|
+
})) ?? [];
|
|
101
|
+
return (0, lodash_1.flatten)(completion.entries.filter((c) => c.name === name || c.name === suffixedName).map(toFix));
|
|
102
|
+
}
|
|
103
|
+
async organizeSortImports(document, skipDestructiveCodeActions = false, cancellationToken) {
|
|
104
|
+
if (document.astroMeta.frontmatter.state !== 'closed') {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
108
|
+
const filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
109
|
+
const fragment = await tsDoc.createFragment();
|
|
110
|
+
if (cancellationToken?.isCancellationRequested) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
const changes = lang.organizeImports({ fileName: filePath, type: 'file', skipDestructiveCodeActions }, {}, {});
|
|
114
|
+
const documentChanges = changes.map((change) => {
|
|
115
|
+
return vscode_languageserver_types_1.TextDocumentEdit.create(vscode_languageserver_types_1.OptionalVersionedTextDocumentIdentifier.create(document.url, null), change.textChanges.map((edit) => {
|
|
116
|
+
const range = (0, documents_1.mapRangeToOriginal)(fragment, (0, utils_2.convertRange)(fragment, edit.span));
|
|
117
|
+
return vscode_languageserver_types_1.TextEdit.replace(range, this.fixIndentationOfImports(edit.newText, range, document));
|
|
118
|
+
}));
|
|
119
|
+
});
|
|
120
|
+
return [
|
|
121
|
+
vscode_languageserver_types_1.CodeAction.create(skipDestructiveCodeActions ? 'Sort Imports' : 'Organize Imports', {
|
|
122
|
+
documentChanges,
|
|
123
|
+
}, skipDestructiveCodeActions ? exports.sortImportKind : vscode_languageserver_types_1.CodeActionKind.SourceOrganizeImports),
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
// "Organize Imports" will have edits that delete all imports by return empty edits
|
|
127
|
+
// and one edit which contains all the organized imports. Fix indentation
|
|
128
|
+
// of that one by prepending all lines with the indentation of the first line.
|
|
129
|
+
fixIndentationOfImports(edit, range, document) {
|
|
130
|
+
if (!edit || range.start.character === 0) {
|
|
131
|
+
return edit;
|
|
132
|
+
}
|
|
133
|
+
const existingLine = (0, documents_1.getLineAtPosition)(range.start, document.getText());
|
|
134
|
+
const leadingChars = existingLine.substring(0, range.start.character);
|
|
135
|
+
if (leadingChars.trim() !== '') {
|
|
136
|
+
return edit;
|
|
137
|
+
}
|
|
138
|
+
return (0, utils_1.modifyLines)(edit, (line, idx) => (idx === 0 || !line ? line : leadingChars + line));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.CodeActionsProviderImpl = CodeActionsProviderImpl;
|
|
@@ -4,6 +4,7 @@ import { AstroDocument } from '../../../core/documents';
|
|
|
4
4
|
import ts from 'typescript';
|
|
5
5
|
import { AppCompletionItem, AppCompletionList, CompletionsProvider } from '../../interfaces';
|
|
6
6
|
import { AstroSnapshotFragment } from '../snapshots/DocumentSnapshot';
|
|
7
|
+
export declare const completionOptions: ts.GetCompletionsAtPositionOptions;
|
|
7
8
|
export interface CompletionItemData extends TextDocumentIdentifier {
|
|
8
9
|
filePath: string;
|
|
9
10
|
offset: number;
|
|
@@ -19,7 +20,6 @@ export declare class CompletionsProviderImpl implements CompletionsProvider<Comp
|
|
|
19
20
|
resolveCompletion(document: AstroDocument, item: AppCompletionItem<CompletionItemData>, cancellationToken?: CancellationToken): Promise<AppCompletionItem<CompletionItemData>>;
|
|
20
21
|
private toCompletionItem;
|
|
21
22
|
private isValidCompletion;
|
|
22
|
-
codeActionChangeToTextEdit(document: AstroDocument, fragment: AstroSnapshotFragment, change: ts.TextChange): TextEdit;
|
|
23
23
|
private getCompletionDocument;
|
|
24
24
|
/**
|
|
25
25
|
* If the textEdit is out of the word range of the triggered position
|
|
@@ -31,3 +31,4 @@ export declare class CompletionsProviderImpl implements CompletionsProvider<Comp
|
|
|
31
31
|
private getExistingImports;
|
|
32
32
|
private isAstroComponentImport;
|
|
33
33
|
}
|
|
34
|
+
export declare function codeActionChangeToTextEdit(document: AstroDocument, fragment: AstroSnapshotFragment, change: ts.TextChange): TextEdit;
|
|
@@ -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.CompletionsProviderImpl = void 0;
|
|
26
|
+
exports.codeActionChangeToTextEdit = exports.CompletionsProviderImpl = exports.completionOptions = 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,7 +35,7 @@ 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
|
-
|
|
38
|
+
exports.completionOptions = {
|
|
39
39
|
importModuleSpecifierPreference: 'relative',
|
|
40
40
|
importModuleSpecifierEnding: 'auto',
|
|
41
41
|
quotePreference: 'single',
|
|
@@ -57,11 +57,11 @@ class CompletionsProviderImpl {
|
|
|
57
57
|
return this.validTriggerCharacters.includes(character);
|
|
58
58
|
}
|
|
59
59
|
async getCompletions(document, position, completionContext, cancellationToken) {
|
|
60
|
-
const triggerCharacter = completionContext
|
|
61
|
-
const triggerKind = completionContext
|
|
60
|
+
const triggerCharacter = completionContext?.triggerCharacter;
|
|
61
|
+
const triggerKind = completionContext?.triggerKind;
|
|
62
62
|
const validTriggerCharacter = this.isValidTriggerCharacter(triggerCharacter) ? triggerCharacter : undefined;
|
|
63
63
|
const isCustomTriggerCharacter = triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter;
|
|
64
|
-
if ((isCustomTriggerCharacter && !validTriggerCharacter) ||
|
|
64
|
+
if ((isCustomTriggerCharacter && !validTriggerCharacter) || cancellationToken?.isCancellationRequested) {
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
67
|
if (this.canReuseLastCompletion(this.lastCompletion, triggerKind, triggerCharacter, document, position)) {
|
|
@@ -100,7 +100,7 @@ class CompletionsProviderImpl {
|
|
|
100
100
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
101
101
|
const filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
102
102
|
const completions = lang.getCompletionsAtPosition(filePath, offset, {
|
|
103
|
-
...completionOptions,
|
|
103
|
+
...exports.completionOptions,
|
|
104
104
|
triggerCharacter: validTriggerCharacter,
|
|
105
105
|
});
|
|
106
106
|
if (completions === undefined || completions.entries.length === 0) {
|
|
@@ -109,7 +109,7 @@ class CompletionsProviderImpl {
|
|
|
109
109
|
const wordRange = completions.optionalReplacementSpan
|
|
110
110
|
? vscode_languageserver_1.Range.create(document.positionAt(completions.optionalReplacementSpan.start), document.positionAt(completions.optionalReplacementSpan.start + completions.optionalReplacementSpan.length))
|
|
111
111
|
: undefined;
|
|
112
|
-
const wordRangeStartPosition = wordRange
|
|
112
|
+
const wordRangeStartPosition = wordRange?.start;
|
|
113
113
|
const fragment = await tsDoc.createFragment();
|
|
114
114
|
const existingImports = this.getExistingImports(document);
|
|
115
115
|
const completionItems = completions.entries
|
|
@@ -122,10 +122,9 @@ class CompletionsProviderImpl {
|
|
|
122
122
|
return completionList;
|
|
123
123
|
}
|
|
124
124
|
async resolveCompletion(document, item, cancellationToken) {
|
|
125
|
-
var _a;
|
|
126
125
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
127
126
|
const data = item.data;
|
|
128
|
-
if (!data || !data.filePath ||
|
|
127
|
+
if (!data || !data.filePath || cancellationToken?.isCancellationRequested) {
|
|
129
128
|
return item;
|
|
130
129
|
}
|
|
131
130
|
const fragment = await tsDoc.createFragment();
|
|
@@ -134,7 +133,7 @@ class CompletionsProviderImpl {
|
|
|
134
133
|
data.originalItem.name, // entryName
|
|
135
134
|
{}, // formatOptions
|
|
136
135
|
data.originalItem.source, // source
|
|
137
|
-
completionOptions, // preferences
|
|
136
|
+
exports.completionOptions, // preferences
|
|
138
137
|
data.originalItem.data // data
|
|
139
138
|
);
|
|
140
139
|
if (detail) {
|
|
@@ -142,23 +141,22 @@ class CompletionsProviderImpl {
|
|
|
142
141
|
item.detail = itemDetail;
|
|
143
142
|
item.documentation = itemDocumentation;
|
|
144
143
|
}
|
|
145
|
-
const actions = detail
|
|
144
|
+
const actions = detail?.codeActions;
|
|
146
145
|
if (actions) {
|
|
147
146
|
const edit = [];
|
|
148
147
|
for (const action of actions) {
|
|
149
148
|
for (const change of action.changes) {
|
|
150
|
-
edit.push(...change.textChanges.map((textChange) =>
|
|
149
|
+
edit.push(...change.textChanges.map((textChange) => codeActionChangeToTextEdit(document, fragment, textChange)));
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
|
-
item.additionalTextEdits = (
|
|
152
|
+
item.additionalTextEdits = (item.additionalTextEdits ?? []).concat(edit);
|
|
154
153
|
}
|
|
155
154
|
return item;
|
|
156
155
|
}
|
|
157
156
|
toCompletionItem(fragment, comp, filePath, offset, insideFrontmatter, existingImports) {
|
|
158
|
-
var _a, _b, _c;
|
|
159
157
|
let item = vscode_languageserver_protocol_1.CompletionItem.create(comp.name);
|
|
160
158
|
const isAstroComponent = this.isAstroComponentImport(comp.name);
|
|
161
|
-
const isImport =
|
|
159
|
+
const isImport = comp.insertText?.includes('import');
|
|
162
160
|
// Avoid showing completions for using components as functions
|
|
163
161
|
if (isAstroComponent && !isImport && insideFrontmatter) {
|
|
164
162
|
return null;
|
|
@@ -167,7 +165,7 @@ class CompletionsProviderImpl {
|
|
|
167
165
|
item.label = (0, utils_2.removeAstroComponentSuffix)(comp.name);
|
|
168
166
|
// Set component imports as file completion, that way we get cool icons
|
|
169
167
|
item.kind = vscode_languageserver_protocol_1.CompletionItemKind.File;
|
|
170
|
-
item.detail =
|
|
168
|
+
item.detail = comp.data?.moduleSpecifier;
|
|
171
169
|
}
|
|
172
170
|
else {
|
|
173
171
|
item.kind = (0, utils_2.scriptElementKindToCompletionItemKind)(comp.kind);
|
|
@@ -194,7 +192,7 @@ class CompletionsProviderImpl {
|
|
|
194
192
|
item.insertText = comp.insertText ? (0, utils_2.removeAstroComponentSuffix)(comp.insertText) : undefined;
|
|
195
193
|
item.insertTextFormat = comp.isSnippet ? vscode_languageserver_1.InsertTextFormat.Snippet : vscode_languageserver_1.InsertTextFormat.PlainText;
|
|
196
194
|
item.textEdit = comp.replacementSpan
|
|
197
|
-
? vscode_languageserver_1.TextEdit.replace((0, utils_2.convertRange)(fragment, comp.replacementSpan),
|
|
195
|
+
? vscode_languageserver_1.TextEdit.replace((0, utils_2.convertRange)(fragment, comp.replacementSpan), item.insertText ?? item.label)
|
|
198
196
|
: undefined;
|
|
199
197
|
}
|
|
200
198
|
return {
|
|
@@ -214,20 +212,6 @@ class CompletionsProviderImpl {
|
|
|
214
212
|
}
|
|
215
213
|
return true;
|
|
216
214
|
}
|
|
217
|
-
codeActionChangeToTextEdit(document, fragment, change) {
|
|
218
|
-
change.newText = (0, utils_2.removeAstroComponentSuffix)(change.newText);
|
|
219
|
-
// If we don't have a frontmatter already, create one with the import
|
|
220
|
-
const frontmatterState = document.astroMeta.frontmatter.state;
|
|
221
|
-
if (frontmatterState === null) {
|
|
222
|
-
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}`);
|
|
223
|
-
}
|
|
224
|
-
const { span } = change;
|
|
225
|
-
let range;
|
|
226
|
-
const virtualRange = (0, utils_2.convertRange)(fragment, span);
|
|
227
|
-
range = (0, documents_1.mapRangeToOriginal)(fragment, virtualRange);
|
|
228
|
-
range = (0, utils_2.ensureFrontmatterInsert)(range, document);
|
|
229
|
-
return vscode_languageserver_1.TextEdit.replace(range, change.newText);
|
|
230
|
-
}
|
|
231
215
|
getCompletionDocument(compDetail) {
|
|
232
216
|
const { sourceDisplay, documentation: tsDocumentation, displayParts } = compDetail;
|
|
233
217
|
let detail = (0, utils_2.removeAstroComponentSuffix)(typescript_1.default.displayPartsToString(displayParts));
|
|
@@ -286,7 +270,7 @@ class CompletionsProviderImpl {
|
|
|
286
270
|
(triggerCharacter === '.' && (0, utils_4.isPartOfImportStatement)(document.getText(), position))));
|
|
287
271
|
}
|
|
288
272
|
getExistingImports(document) {
|
|
289
|
-
const rawImports = (0, utils_3.getRegExpMatches)(scriptImportRegex, document.getText()).map((match) =>
|
|
273
|
+
const rawImports = (0, utils_3.getRegExpMatches)(scriptImportRegex, document.getText()).map((match) => (match[1] ?? match[2]).split(','));
|
|
290
274
|
const tidiedImports = (0, lodash_1.flatten)(rawImports).map((match) => match.trim());
|
|
291
275
|
return new Set(tidiedImports);
|
|
292
276
|
}
|
|
@@ -295,3 +279,24 @@ class CompletionsProviderImpl {
|
|
|
295
279
|
}
|
|
296
280
|
}
|
|
297
281
|
exports.CompletionsProviderImpl = CompletionsProviderImpl;
|
|
282
|
+
function codeActionChangeToTextEdit(document, fragment, change) {
|
|
283
|
+
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
|
+
const { span } = change;
|
|
290
|
+
let range;
|
|
291
|
+
const virtualRange = (0, utils_2.convertRange)(fragment, span);
|
|
292
|
+
range = (0, documents_1.mapRangeToOriginal)(fragment, virtualRange);
|
|
293
|
+
if (!(0, utils_1.isInsideFrontmatter)(document.getText(), document.offsetAt(range.start))) {
|
|
294
|
+
range = (0, utils_2.ensureFrontmatterInsert)(range, document);
|
|
295
|
+
}
|
|
296
|
+
// First import in a file will wrongly have a newline before it due to how the frontmatter is replaced by a comment
|
|
297
|
+
if (range.start.line === 1 && (change.newText.startsWith('\n') || change.newText.startsWith('\r\n'))) {
|
|
298
|
+
change.newText = change.newText.trimStart();
|
|
299
|
+
}
|
|
300
|
+
return vscode_languageserver_1.TextEdit.replace(range, change.newText);
|
|
301
|
+
}
|
|
302
|
+
exports.codeActionChangeToTextEdit = codeActionChangeToTextEdit;
|
|
@@ -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,17 +26,17 @@ 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
|
}
|
|
@@ -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);
|
|
@@ -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,6 +1,7 @@
|
|
|
1
1
|
import type { SnapshotFragment, DocumentSnapshot } from '../snapshots/DocumentSnapshot';
|
|
2
2
|
import type { LanguageServiceManager } from '../LanguageServiceManager';
|
|
3
3
|
import { Position } from 'vscode-languageserver';
|
|
4
|
+
import ts from 'typescript';
|
|
4
5
|
export declare function isPartOfImportStatement(text: string, position: Position): boolean;
|
|
5
6
|
export declare class SnapshotFragmentMap {
|
|
6
7
|
private languageServiceManager;
|
|
@@ -21,3 +22,4 @@ export declare class SnapshotFragmentMap {
|
|
|
21
22
|
}>;
|
|
22
23
|
retrieveFragment(fileName: string): Promise<SnapshotFragment>;
|
|
23
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,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SnapshotFragmentMap = exports.isPartOfImportStatement = void 0;
|
|
3
|
+
exports.findContainingNode = exports.SnapshotFragmentMap = exports.isPartOfImportStatement = void 0;
|
|
4
4
|
const documents_1 = require("../../../core/documents");
|
|
5
5
|
function isPartOfImportStatement(text, position) {
|
|
6
6
|
const line = (0, documents_1.getLineAtPosition)(position, text);
|
|
@@ -19,8 +19,7 @@ class SnapshotFragmentMap {
|
|
|
19
19
|
return this.map.get(fileName);
|
|
20
20
|
}
|
|
21
21
|
getFragment(fileName) {
|
|
22
|
-
|
|
23
|
-
return (_a = this.map.get(fileName)) === null || _a === void 0 ? void 0 : _a.fragment;
|
|
22
|
+
return this.map.get(fileName)?.fragment;
|
|
24
23
|
}
|
|
25
24
|
async retrieve(fileName) {
|
|
26
25
|
let snapshotFragment = this.get(fileName);
|
|
@@ -37,3 +36,20 @@ class SnapshotFragmentMap {
|
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
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;
|
|
@@ -132,7 +132,7 @@ async function createLanguageService(tsconfigPath, docContext, workspaceUris) {
|
|
|
132
132
|
function updateSnapshotFromDocument(document) {
|
|
133
133
|
const filePath = document.getFilePath() || '';
|
|
134
134
|
const prevSnapshot = snapshotManager.get(filePath);
|
|
135
|
-
if (
|
|
135
|
+
if (prevSnapshot?.version === document.version) {
|
|
136
136
|
return prevSnapshot;
|
|
137
137
|
}
|
|
138
138
|
if (!prevSnapshot) {
|
|
@@ -186,16 +186,15 @@ async function createLanguageService(tsconfigPath, docContext, workspaceUris) {
|
|
|
186
186
|
snapshotManager.updateNonAstroFile(fileName, changes);
|
|
187
187
|
}
|
|
188
188
|
function getParsedTSConfig() {
|
|
189
|
-
var _a, _b, _c, _d;
|
|
190
189
|
let configJson = (tsconfigPath && typescript_1.default.readConfigFile(tsconfigPath, typescript_1.default.sys.readFile).config) || {};
|
|
191
190
|
// If our user has types in their config but it doesn't include the types needed for Astro, add them to the config
|
|
192
|
-
if (
|
|
193
|
-
if (!
|
|
191
|
+
if (configJson.compilerOptions?.types) {
|
|
192
|
+
if (!configJson.compilerOptions?.types.includes('astro/env')) {
|
|
194
193
|
configJson.compilerOptions.types.push('astro/env');
|
|
195
194
|
}
|
|
196
195
|
if (astroVersion.major >= 1 &&
|
|
197
196
|
astroVersion.full !== '1.0.0-beta.0' &&
|
|
198
|
-
!
|
|
197
|
+
!configJson.compilerOptions?.types.includes('astro/astro-jsx')) {
|
|
199
198
|
configJson.compilerOptions.types.push('astro/astro-jsx');
|
|
200
199
|
}
|
|
201
200
|
}
|
|
@@ -203,7 +202,7 @@ async function createLanguageService(tsconfigPath, docContext, workspaceUris) {
|
|
|
203
202
|
// Delete include so that .astro files don't get mistakenly excluded by the user
|
|
204
203
|
delete configJson.include;
|
|
205
204
|
// If the user supplied exclude, let's use theirs otherwise, use ours
|
|
206
|
-
|
|
205
|
+
configJson.exclude ?? (configJson.exclude = getDefaultExclude());
|
|
207
206
|
// Everything here will always, unconditionally, be in the resulting config
|
|
208
207
|
const forcedCompilerOptions = {
|
|
209
208
|
noEmit: true,
|
|
@@ -39,7 +39,7 @@ class ModuleResolutionCache {
|
|
|
39
39
|
*/
|
|
40
40
|
delete(resolvedModuleName) {
|
|
41
41
|
this.cache.forEach((val, key) => {
|
|
42
|
-
if (
|
|
42
|
+
if (val?.resolvedFileName === resolvedModuleName) {
|
|
43
43
|
this.cache.delete(key);
|
|
44
44
|
}
|
|
45
45
|
});
|
|
@@ -72,7 +72,7 @@ function getTagBodyText(tag) {
|
|
|
72
72
|
function getTagDocumentation(tag) {
|
|
73
73
|
function getWithType() {
|
|
74
74
|
const body = (typescript_1.default.displayPartsToString(tag.text) || '').split(/^(\S+)\s*-?\s*/);
|
|
75
|
-
if (
|
|
75
|
+
if (body?.length === 3) {
|
|
76
76
|
const param = body[1];
|
|
77
77
|
const doc = body[2];
|
|
78
78
|
const label = `*@${tag.name}* \`${param}\``;
|
|
@@ -129,7 +129,7 @@ class SnapshotManager {
|
|
|
129
129
|
const { include, exclude } = this.fileSpec;
|
|
130
130
|
// Since we default to not include anything,
|
|
131
131
|
// just don't waste time on this
|
|
132
|
-
if (
|
|
132
|
+
if (include?.length === 0) {
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
135
|
const projectFiles = typescript_1.default.sys
|
|
@@ -55,8 +55,7 @@ exports.createFromNonAstroFilePath = createFromNonAstroFilePath;
|
|
|
55
55
|
* @param options options that apply in case it's a svelte file
|
|
56
56
|
*/
|
|
57
57
|
function createFromTSFilePath(filePath) {
|
|
58
|
-
|
|
59
|
-
const originalText = (_a = typescript_1.default.sys.readFile(filePath)) !== null && _a !== void 0 ? _a : '';
|
|
58
|
+
const originalText = typescript_1.default.sys.readFile(filePath) ?? '';
|
|
60
59
|
return new DocumentSnapshot_1.TypeScriptDocumentSnapshot(0, filePath, originalText);
|
|
61
60
|
}
|
|
62
61
|
exports.createFromTSFilePath = createFromTSFilePath;
|
|
@@ -66,15 +65,13 @@ exports.createFromTSFilePath = createFromTSFilePath;
|
|
|
66
65
|
* @param createDocument function that is used to create a document
|
|
67
66
|
*/
|
|
68
67
|
function createFromAstroFilePath(filePath, createDocument) {
|
|
69
|
-
|
|
70
|
-
const originalText = (_a = typescript_1.default.sys.readFile(filePath)) !== null && _a !== void 0 ? _a : '';
|
|
68
|
+
const originalText = typescript_1.default.sys.readFile(filePath) ?? '';
|
|
71
69
|
return createFromDocument(createDocument(filePath, originalText));
|
|
72
70
|
}
|
|
73
71
|
exports.createFromAstroFilePath = createFromAstroFilePath;
|
|
74
72
|
function createFromFrameworkFilePath(filePath, framework) {
|
|
75
|
-
var _a;
|
|
76
73
|
const className = classNameFromFilename(filePath);
|
|
77
|
-
const originalText =
|
|
74
|
+
const originalText = typescript_1.default.sys.readFile(filePath) ?? '';
|
|
78
75
|
let code = '';
|
|
79
76
|
if (framework === 'svelte') {
|
|
80
77
|
code = (0, svelte_language_integration_1.toTSX)(originalText, className);
|
|
@@ -41,6 +41,7 @@ export declare function convertRange(document: {
|
|
|
41
41
|
}): Range;
|
|
42
42
|
export declare function convertToLocationRange(defDoc: SnapshotFragment, textSpan: ts.TextSpan): Range;
|
|
43
43
|
export declare function ensureFrontmatterInsert(resultRange: Range, document: AstroDocument): Range;
|
|
44
|
+
export declare function checkEndOfFileCodeInsert(resultRange: Range, document: AstroDocument): Range;
|
|
44
45
|
export declare function removeAstroComponentSuffix(name: string): string;
|
|
45
46
|
export declare type FrameworkExt = 'astro' | 'vue' | 'jsx' | 'tsx' | 'svelte';
|
|
46
47
|
declare type FrameworkVirtualExt = 'ts' | 'tsx';
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ensureRealFilePath = exports.ensureRealAstroFilePath = exports.toRealAstroFilePath = exports.toVirtualFilePath = exports.toVirtualAstroFilePath = exports.isVirtualFilePath = exports.isVirtualSvelteFilePath = exports.isVirtualVueFilePath = exports.isVirtualAstroFilePath = exports.isFrameworkFilePath = exports.isAstroFilePath = exports.isVirtualFrameworkFilePath = exports.getFrameworkFromFilePath = exports.removeAstroComponentSuffix = exports.ensureFrontmatterInsert = exports.convertToLocationRange = exports.convertRange = exports.mapSeverity = exports.getScriptKindFromFileName = exports.isSubPath = exports.findTsConfigPath = exports.getExtensionFromScriptKind = exports.getCommitCharactersForScriptElement = exports.scriptElementKindToCompletionItemKind = exports.symbolKindFromString = exports.getSemanticTokenLegend = void 0;
|
|
6
|
+
exports.ensureRealFilePath = exports.ensureRealAstroFilePath = exports.toRealAstroFilePath = exports.toVirtualFilePath = exports.toVirtualAstroFilePath = exports.isVirtualFilePath = exports.isVirtualSvelteFilePath = exports.isVirtualVueFilePath = exports.isVirtualAstroFilePath = exports.isFrameworkFilePath = exports.isAstroFilePath = exports.isVirtualFrameworkFilePath = exports.getFrameworkFromFilePath = exports.removeAstroComponentSuffix = exports.checkEndOfFileCodeInsert = exports.ensureFrontmatterInsert = exports.convertToLocationRange = exports.convertRange = exports.mapSeverity = exports.getScriptKindFromFileName = exports.isSubPath = exports.findTsConfigPath = exports.getExtensionFromScriptKind = exports.getCommitCharactersForScriptElement = exports.scriptElementKindToCompletionItemKind = exports.symbolKindFromString = exports.getSemanticTokenLegend = void 0;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
8
|
const path_1 = require("path");
|
|
9
9
|
const utils_1 = require("../../utils");
|
|
@@ -251,6 +251,18 @@ function ensureFrontmatterInsert(resultRange, document) {
|
|
|
251
251
|
return resultRange;
|
|
252
252
|
}
|
|
253
253
|
exports.ensureFrontmatterInsert = ensureFrontmatterInsert;
|
|
254
|
+
// Some code actions ill insert code at the end of the generated TSX file, so we'll manually
|
|
255
|
+
// redirect it to the end of the frontmatter instead
|
|
256
|
+
function checkEndOfFileCodeInsert(resultRange, document) {
|
|
257
|
+
if (resultRange.start.line > document.lineCount) {
|
|
258
|
+
if (document.astroMeta.frontmatter.state === 'closed') {
|
|
259
|
+
const position = document.positionAt(document.astroMeta.frontmatter.endOffset);
|
|
260
|
+
return vscode_languageserver_1.Range.create(position, position);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return resultRange;
|
|
264
|
+
}
|
|
265
|
+
exports.checkEndOfFileCodeInsert = checkEndOfFileCodeInsert;
|
|
254
266
|
function removeAstroComponentSuffix(name) {
|
|
255
267
|
return name.replace(/(\w+)__AstroComponent_/, '$1');
|
|
256
268
|
}
|