@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.
Files changed (37) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/check.js +1 -2
  3. package/dist/core/documents/DocumentMapper.js +2 -4
  4. package/dist/core/documents/parseAstro.js +1 -1
  5. package/dist/core/documents/utils.js +3 -4
  6. package/dist/plugins/PluginHost.d.ts +2 -1
  7. package/dist/plugins/PluginHost.js +8 -6
  8. package/dist/plugins/astro/AstroPlugin.js +1 -1
  9. package/dist/plugins/astro/features/CompletionsProvider.js +9 -10
  10. package/dist/plugins/css/CSSPlugin.js +20 -4
  11. package/dist/plugins/html/features/astro-attributes.js +43 -27
  12. package/dist/plugins/typescript/LanguageServiceManager.js +1 -1
  13. package/dist/plugins/typescript/TypeScriptPlugin.d.ts +3 -1
  14. package/dist/plugins/typescript/TypeScriptPlugin.js +9 -1
  15. package/dist/plugins/typescript/astro-sys.js +3 -5
  16. package/dist/plugins/typescript/astro2tsx.js +1 -2
  17. package/dist/plugins/typescript/features/CodeActionsProvider.d.ts +14 -0
  18. package/dist/plugins/typescript/features/CodeActionsProvider.js +141 -0
  19. package/dist/plugins/typescript/features/CompletionsProvider.d.ts +2 -1
  20. package/dist/plugins/typescript/features/CompletionsProvider.js +37 -32
  21. package/dist/plugins/typescript/features/DiagnosticsProvider.js +2 -3
  22. package/dist/plugins/typescript/features/DocumentSymbolsProvider.js +3 -4
  23. package/dist/plugins/typescript/features/SemanticTokenProvider.js +1 -1
  24. package/dist/plugins/typescript/features/SignatureHelpProvider.js +2 -2
  25. package/dist/plugins/typescript/features/utils.d.ts +2 -0
  26. package/dist/plugins/typescript/features/utils.js +19 -3
  27. package/dist/plugins/typescript/language-service.js +5 -6
  28. package/dist/plugins/typescript/module-loader.js +1 -1
  29. package/dist/plugins/typescript/previewer.js +1 -1
  30. package/dist/plugins/typescript/snapshots/SnapshotManager.js +1 -1
  31. package/dist/plugins/typescript/snapshots/utils.js +3 -6
  32. package/dist/plugins/typescript/utils.d.ts +1 -0
  33. package/dist/plugins/typescript/utils.js +13 -1
  34. package/dist/server.js +28 -7
  35. package/dist/utils.d.ts +4 -0
  36. package/dist/utils.js +16 -3
  37. 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
- const completionOptions = {
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 === null || completionContext === void 0 ? void 0 : completionContext.triggerCharacter;
61
- const triggerKind = completionContext === null || completionContext === void 0 ? void 0 : completionContext.triggerKind;
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) || (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested)) {
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 === null || wordRange === void 0 ? void 0 : wordRange.start;
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 || (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested)) {
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 === null || detail === void 0 ? void 0 : detail.codeActions;
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) => this.codeActionChangeToTextEdit(document, fragment, textChange)));
149
+ edit.push(...change.textChanges.map((textChange) => codeActionChangeToTextEdit(document, fragment, textChange)));
151
150
  }
152
151
  }
153
- item.additionalTextEdits = ((_a = item.additionalTextEdits) !== null && _a !== void 0 ? _a : []).concat(edit);
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 = (_a = comp.insertText) === null || _a === void 0 ? void 0 : _a.includes('import');
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 = (_b = comp.data) === null || _b === void 0 ? void 0 : _b.moduleSpecifier;
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), (_c = item.insertText) !== null && _c !== void 0 ? _c : item.label)
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) => { var _a; return ((_a = match[1]) !== null && _a !== void 0 ? _a : match[2]).split(','); });
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 (((_a = document.getFilePath()) === null || _a === void 0 ? void 0 : _a.includes('/node_modules/')) || ((_b = document.getFilePath()) === null || _b === void 0 ? void 0 : _b.includes('\\node_modules\\'))) {
18
+ if (document.getFilePath()?.includes('/node_modules/') || document.getFilePath()?.includes('\\node_modules\\')) {
20
19
  return [];
21
20
  }
22
21
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
@@ -53,7 +52,7 @@ class DiagnosticsProviderImpl {
53
52
  }
54
53
  getTagBoundaries(lang, tsFilePath) {
55
54
  const program = lang.getProgram();
56
- const sourceFile = program === null || program === void 0 ? void 0 : program.getSourceFile(tsFilePath);
55
+ const sourceFile = program?.getSourceFile(tsFilePath);
57
56
  const boundaries = {
58
57
  script: [],
59
58
  markdown: [],
@@ -11,7 +11,6 @@ class DocumentSymbolsProviderImpl {
11
11
  this.languageServiceManager = languageServiceManager;
12
12
  }
13
13
  async getDocumentSymbols(document) {
14
- var _a, _b, _c;
15
14
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
16
15
  const fragment = await tsDoc.createFragment();
17
16
  const navTree = lang.getNavigationTree(tsDoc.filePath);
@@ -27,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((_a = document.astroMeta.frontmatter.endOffset) !== null && _a !== void 0 ? _a : 0), document.positionAt(document.getTextLength())), document.getURL()));
29
+ result.push(vscode_languageserver_types_1.SymbolInformation.create('Template', vscode_languageserver_types_1.SymbolKind.Namespace, vscode_languageserver_types_1.Range.create(document.positionAt(document.astroMeta.frontmatter.endOffset ?? 0), document.positionAt(document.getTextLength())), document.getURL()));
31
30
  for (let symbol of symbols.splice(1)) {
32
31
  symbol = (0, documents_1.mapSymbolInformationToOriginal)(fragment, symbol);
33
- if (document.offsetAt(symbol.location.range.end) >= ((_b = document.astroMeta.content.firstNonWhitespaceOffset) !== null && _b !== void 0 ? _b : 0)) {
32
+ if (document.offsetAt(symbol.location.range.end) >= (document.astroMeta.content.firstNonWhitespaceOffset ?? 0)) {
34
33
  if (symbol.containerName === originalContainerName) {
35
34
  symbol.containerName = 'Template';
36
35
  }
37
36
  // For some reason, it seems like TypeScript thinks that the "class" attribute is a real class, weird
38
37
  if (symbol.kind === vscode_languageserver_types_1.SymbolKind.Class && symbol.name === '<class>') {
39
38
  const node = document.html.findNodeAt(document.offsetAt(symbol.location.range.start));
40
- if ((_c = node.attributes) === null || _c === void 0 ? void 0 : _c.class) {
39
+ if (node.attributes?.class) {
41
40
  continue;
42
41
  }
43
42
  }
@@ -15,7 +15,7 @@ class SemanticTokensProviderImpl {
15
15
  async getSemanticTokens(document, range, cancellationToken) {
16
16
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
17
17
  const fragment = (await tsDoc.createFragment());
18
- if (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested) {
18
+ if (cancellationToken?.isCancellationRequested) {
19
19
  return null;
20
20
  }
21
21
  const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
@@ -15,7 +15,7 @@ class SignatureHelpProviderImpl {
15
15
  async getSignatureHelp(document, position, context, cancellationToken) {
16
16
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
17
17
  const fragment = await tsDoc.createFragment();
18
- if (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested) {
18
+ if (cancellationToken?.isCancellationRequested) {
19
19
  return null;
20
20
  }
21
21
  const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
@@ -44,7 +44,7 @@ class SignatureHelpProviderImpl {
44
44
  * adopted from https://github.com/microsoft/vscode/blob/265a2f6424dfbd3a9788652c7d376a7991d049a3/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts#L103
45
45
  */
46
46
  toTsTriggerReason(context) {
47
- switch (context === null || context === void 0 ? void 0 : context.triggerKind) {
47
+ switch (context?.triggerKind) {
48
48
  case vscode_languageserver_1.SignatureHelpTriggerKind.TriggerCharacter:
49
49
  if (context.triggerCharacter) {
50
50
  if (this.isReTrigger(context.isRetrigger, context.triggerCharacter)) {
@@ -1,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
- var _a;
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 ((prevSnapshot === null || prevSnapshot === void 0 ? void 0 : prevSnapshot.version) === document.version) {
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 ((_a = configJson.compilerOptions) === null || _a === void 0 ? void 0 : _a.types) {
193
- if (!((_b = configJson.compilerOptions) === null || _b === void 0 ? void 0 : _b.types.includes('astro/env'))) {
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
- !((_c = configJson.compilerOptions) === null || _c === void 0 ? void 0 : _c.types.includes('astro/astro-jsx'))) {
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
- (_d = configJson.exclude) !== null && _d !== void 0 ? _d : (configJson.exclude = getDefaultExclude());
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 ((val === null || val === void 0 ? void 0 : val.resolvedFileName) === resolvedModuleName) {
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 ((body === null || body === void 0 ? void 0 : body.length) === 3) {
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 ((include === null || include === void 0 ? void 0 : include.length) === 0) {
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
- var _a;
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
- var _a;
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 = (_a = typescript_1.default.sys.readFile(filePath)) !== null && _a !== void 0 ? _a : '';
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
  }