@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/core/config/ConfigManager.d.ts +4 -3
  3. package/dist/core/config/ConfigManager.js +15 -1
  4. package/dist/core/config/interfaces.d.ts +5 -0
  5. package/dist/core/documents/AstroDocument.d.ts +1 -0
  6. package/dist/core/documents/AstroDocument.js +1 -0
  7. package/dist/core/documents/DocumentMapper.d.ts +2 -0
  8. package/dist/core/documents/DocumentMapper.js +7 -5
  9. package/dist/core/documents/utils.d.ts +1 -0
  10. package/dist/core/documents/utils.js +16 -1
  11. package/dist/plugins/PluginHost.d.ts +2 -1
  12. package/dist/plugins/PluginHost.js +4 -0
  13. package/dist/plugins/astro/AstroPlugin.js +10 -3
  14. package/dist/plugins/astro/features/CompletionsProvider.d.ts +4 -5
  15. package/dist/plugins/astro/features/CompletionsProvider.js +53 -58
  16. package/dist/plugins/html/HTMLPlugin.d.ts +2 -1
  17. package/dist/plugins/html/HTMLPlugin.js +18 -0
  18. package/dist/plugins/typescript/TypeScriptPlugin.d.ts +3 -1
  19. package/dist/plugins/typescript/TypeScriptPlugin.js +5 -0
  20. package/dist/plugins/typescript/features/CodeActionsProvider.js +76 -17
  21. package/dist/plugins/typescript/features/CompletionsProvider.d.ts +2 -1
  22. package/dist/plugins/typescript/features/CompletionsProvider.js +107 -45
  23. package/dist/plugins/typescript/features/DefinitionsProvider.js +22 -1
  24. package/dist/plugins/typescript/features/DiagnosticsProvider.js +58 -15
  25. package/dist/plugins/typescript/features/FoldingRangesProvider.js +13 -6
  26. package/dist/plugins/typescript/features/FormattingProvider.d.ts +11 -0
  27. package/dist/plugins/typescript/features/FormattingProvider.js +132 -0
  28. package/dist/plugins/typescript/features/HoverProvider.js +14 -1
  29. package/dist/plugins/typescript/features/SignatureHelpProvider.js +9 -1
  30. package/dist/plugins/typescript/language-service.js +18 -0
  31. package/dist/plugins/typescript/snapshots/DocumentSnapshot.d.ts +22 -2
  32. package/dist/plugins/typescript/snapshots/DocumentSnapshot.js +48 -1
  33. package/dist/plugins/typescript/snapshots/SnapshotManager.js +1 -0
  34. package/dist/plugins/typescript/snapshots/utils.js +3 -2
  35. package/dist/plugins/typescript/utils.d.ts +11 -1
  36. package/dist/plugins/typescript/utils.js +17 -1
  37. package/dist/server.js +2 -0
  38. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @astrojs/language-server
2
2
 
3
+ ## 0.18.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 666739a: Revert update to latest LSP and inlay hints support
8
+
9
+ ## 0.18.0
10
+
11
+ ### Minor Changes
12
+
13
+ - d3c6fd8: Add support for formatting
14
+ - 09e1163: Updated language server to latest version of LSP, added support for Inlay Hints
15
+ - fcaba8e: Add support for completions and type checking for Vue props
16
+
17
+ ### Patch Changes
18
+
19
+ - 4138005: Fix frontmatter folding not working properly when last few lines of frontmatter are empty
20
+ - 76ff46a: Add `?` in the label of completions of optional parameters (including component props)
21
+
22
+ ## 0.17.0
23
+
24
+ ### Minor Changes
25
+
26
+ - 3ad0f65: Add support for TypeScript features inside script tags (completions, diagnostics, hover etc)
27
+
28
+ ### Patch Changes
29
+
30
+ - 2e9da14: Add support for loading props completions from .d.ts files, improve performance of props completions
31
+
3
32
  ## 0.16.1
4
33
 
5
34
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  import { VSCodeEmmetConfig } from '@vscode/emmet-helper';
2
- import { LSConfig, LSCSSConfig, LSHTMLConfig, LSTypescriptConfig } from './interfaces';
3
- import { Connection } from 'vscode-languageserver';
2
+ import { LSConfig, LSCSSConfig, LSFormatConfig, LSHTMLConfig, LSTypescriptConfig } from './interfaces';
3
+ import { Connection, FormattingOptions } from 'vscode-languageserver';
4
4
  import { TextDocument } from 'vscode-languageserver-textdocument';
5
5
  import { FormatCodeSettings, UserPreferences } from 'typescript';
6
6
  export declare const defaultLSConfig: LSConfig;
@@ -22,7 +22,8 @@ export declare class ConfigManager {
22
22
  removeDocument(scopeUri: string): void;
23
23
  getConfig<T>(section: string, scopeUri: string): Promise<T>;
24
24
  getEmmetConfig(document: TextDocument): Promise<VSCodeEmmetConfig>;
25
- getTSFormatConfig(document: TextDocument): Promise<FormatCodeSettings>;
25
+ getAstroFormatConfig(document: TextDocument): Promise<LSFormatConfig>;
26
+ getTSFormatConfig(document: TextDocument, vscodeOptions?: FormattingOptions): Promise<FormatCodeSettings>;
26
27
  getTSPreferences(document: TextDocument): Promise<UserPreferences>;
27
28
  /**
28
29
  * Return true if a plugin and an optional feature is enabled
@@ -30,6 +30,10 @@ exports.defaultLSConfig = {
30
30
  tagComplete: { enabled: true },
31
31
  documentSymbols: { enabled: true },
32
32
  },
33
+ format: {
34
+ indentFrontmatter: true,
35
+ newLineAfterFrontmatter: true,
36
+ },
33
37
  };
34
38
  /**
35
39
  * Manager class to facilitate accessing and updating the user's config
@@ -69,9 +73,19 @@ class ConfigManager {
69
73
  const emmetConfig = (await this.getConfig('emmet', document.uri)) ?? {};
70
74
  return emmetConfig;
71
75
  }
72
- async getTSFormatConfig(document) {
76
+ async getAstroFormatConfig(document) {
77
+ const astroFormatConfig = (await this.getConfig('astro.format', document.uri)) ?? {};
78
+ return {
79
+ indentFrontmatter: astroFormatConfig.indentFrontmatter ?? true,
80
+ newLineAfterFrontmatter: astroFormatConfig.newLineAfterFrontmatter ?? true,
81
+ };
82
+ }
83
+ async getTSFormatConfig(document, vscodeOptions) {
73
84
  const formatConfig = (await this.getConfig('typescript.format', document.uri)) ?? {};
74
85
  return {
86
+ tabSize: vscodeOptions?.tabSize,
87
+ indentSize: vscodeOptions?.tabSize,
88
+ convertTabsToSpaces: vscodeOptions?.insertSpaces,
75
89
  // We can use \n here since the editor normalizes later on to its line endings.
76
90
  newLineCharacter: '\n',
77
91
  insertSpaceAfterCommaDelimiter: formatConfig.insertSpaceAfterCommaDelimiter ?? true,
@@ -6,6 +6,11 @@ export interface LSConfig {
6
6
  typescript: LSTypescriptConfig;
7
7
  html: LSHTMLConfig;
8
8
  css: LSCSSConfig;
9
+ format: LSFormatConfig;
10
+ }
11
+ export interface LSFormatConfig {
12
+ indentFrontmatter: boolean;
13
+ newLineAfterFrontmatter: boolean;
9
14
  }
10
15
  export interface LSTypescriptConfig {
11
16
  enabled: boolean;
@@ -9,6 +9,7 @@ export declare class AstroDocument extends WritableDocument {
9
9
  astroMeta: AstroMetadata;
10
10
  html: HTMLDocument;
11
11
  styleTags: TagInformation[];
12
+ scriptTags: TagInformation[];
12
13
  constructor(url: string, content: string);
13
14
  private updateDocInfo;
14
15
  setText(text: string): void;
@@ -18,6 +18,7 @@ class AstroDocument extends DocumentBase_1.WritableDocument {
18
18
  this.astroMeta = (0, parseAstro_1.parseAstro)(this.content);
19
19
  this.html = (0, parseHtml_1.parseHtml)(this.content);
20
20
  this.styleTags = (0, utils_2.extractStyleTags)(this.content, this.html);
21
+ this.scriptTags = (0, utils_2.extractScriptTags)(this.content, this.html);
21
22
  }
22
23
  setText(text) {
23
24
  this.content = text;
@@ -46,6 +46,8 @@ export declare class FragmentMapper implements DocumentMapper {
46
46
  private originalText;
47
47
  private tagInfo;
48
48
  private url;
49
+ private lineOffsetsOriginal;
50
+ private lineOffsetsGenerated;
49
51
  constructor(originalText: string, tagInfo: TagInformation, url: string);
50
52
  getOriginalPosition(generatedPosition: Position): Position;
51
53
  private offsetInParent;
@@ -45,20 +45,22 @@ class FragmentMapper {
45
45
  this.originalText = originalText;
46
46
  this.tagInfo = tagInfo;
47
47
  this.url = url;
48
+ this.lineOffsetsOriginal = (0, utils_1.getLineOffsets)(this.originalText);
49
+ this.lineOffsetsGenerated = (0, utils_1.getLineOffsets)(this.tagInfo.content);
48
50
  }
49
51
  getOriginalPosition(generatedPosition) {
50
- const parentOffset = this.offsetInParent((0, utils_1.offsetAt)(generatedPosition, this.tagInfo.content));
51
- return (0, utils_1.positionAt)(parentOffset, this.originalText);
52
+ const parentOffset = this.offsetInParent((0, utils_1.offsetAt)(generatedPosition, this.tagInfo.content, this.lineOffsetsGenerated));
53
+ return (0, utils_1.positionAt)(parentOffset, this.originalText, this.lineOffsetsOriginal);
52
54
  }
53
55
  offsetInParent(offset) {
54
56
  return this.tagInfo.start + offset;
55
57
  }
56
58
  getGeneratedPosition(originalPosition) {
57
- const fragmentOffset = (0, utils_1.offsetAt)(originalPosition, this.originalText) - this.tagInfo.start;
58
- return (0, utils_1.positionAt)(fragmentOffset, this.tagInfo.content);
59
+ const fragmentOffset = (0, utils_1.offsetAt)(originalPosition, this.originalText, this.lineOffsetsOriginal) - this.tagInfo.start;
60
+ return (0, utils_1.positionAt)(fragmentOffset, this.tagInfo.content, this.lineOffsetsGenerated);
59
61
  }
60
62
  isInGenerated(pos) {
61
- const offset = (0, utils_1.offsetAt)(pos, this.originalText);
63
+ const offset = (0, utils_1.offsetAt)(pos, this.originalText, this.lineOffsetsOriginal);
62
64
  return offset >= this.tagInfo.start && offset <= this.tagInfo.end;
63
65
  }
64
66
  getURL() {
@@ -15,6 +15,7 @@ export interface TagInformation {
15
15
  }
16
16
  export declare function walk(node: Node): Generator<Node, void, unknown>;
17
17
  export declare function extractStyleTags(source: string, html?: HTMLDocument): TagInformation[];
18
+ export declare function extractScriptTags(source: string, html?: HTMLDocument): TagInformation[];
18
19
  export declare function getLineAtPosition(position: Position, text: string): string;
19
20
  /**
20
21
  * Returns the node if offset is inside a HTML start tag
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getFirstNonWhitespaceIndex = exports.getLineOffsets = exports.offsetAt = exports.positionAt = exports.isInsideFrontmatter = exports.isInsideExpression = exports.isInTag = exports.isInComponentStartTag = exports.isComponentTag = exports.getNodeIfIsInHTMLStartTag = exports.getLineAtPosition = exports.extractStyleTags = exports.walk = void 0;
3
+ exports.getFirstNonWhitespaceIndex = exports.getLineOffsets = exports.offsetAt = exports.positionAt = exports.isInsideFrontmatter = exports.isInsideExpression = exports.isInTag = exports.isInComponentStartTag = exports.isComponentTag = exports.getNodeIfIsInHTMLStartTag = exports.getLineAtPosition = exports.extractScriptTags = exports.extractStyleTags = exports.walk = void 0;
4
4
  const vscode_languageserver_1 = require("vscode-languageserver");
5
5
  const utils_1 = require("../../utils");
6
6
  const parseHtml_1 = require("./parseHtml");
@@ -28,6 +28,13 @@ function extractTags(text, tag, html) {
28
28
  }
29
29
  }
30
30
  }
31
+ if (tag === 'script' && !matchedNodes.length && rootNodes.length) {
32
+ for (let child of walk(rootNodes[0])) {
33
+ if (child.tag === 'script') {
34
+ matchedNodes.push(child);
35
+ }
36
+ }
37
+ }
31
38
  return matchedNodes.map(transformToTagInfo);
32
39
  function transformToTagInfo(matchedNode) {
33
40
  const start = matchedNode.startTagEnd ?? matchedNode.start;
@@ -60,6 +67,14 @@ function extractStyleTags(source, html) {
60
67
  return styles;
61
68
  }
62
69
  exports.extractStyleTags = extractStyleTags;
70
+ function extractScriptTags(source, html) {
71
+ const scripts = extractTags(source, 'script', html);
72
+ if (!scripts.length) {
73
+ return [];
74
+ }
75
+ return scripts;
76
+ }
77
+ exports.extractScriptTags = extractScriptTags;
63
78
  function parseAttributes(rawAttrs) {
64
79
  const attrs = {};
65
80
  if (!rawAttrs) {
@@ -1,4 +1,4 @@
1
- import { CancellationToken, Color, ColorInformation, ColorPresentation, CompletionContext, CompletionItem, CompletionList, DefinitionLink, Diagnostic, FoldingRange, Hover, Position, Range, Location, SignatureHelp, SignatureHelpContext, TextDocumentContentChangeEvent, TextDocumentIdentifier, WorkspaceEdit, SymbolInformation, SemanticTokens, CodeActionContext, CodeAction } from 'vscode-languageserver';
1
+ import { CancellationToken, Color, ColorInformation, ColorPresentation, CompletionContext, CompletionItem, CompletionList, DefinitionLink, Diagnostic, FoldingRange, Hover, Position, Range, Location, SignatureHelp, SignatureHelpContext, TextDocumentContentChangeEvent, TextDocumentIdentifier, WorkspaceEdit, SymbolInformation, SemanticTokens, CodeActionContext, CodeAction, FormattingOptions, TextEdit } from 'vscode-languageserver';
2
2
  import type { AppCompletionItem, Plugin } from './interfaces';
3
3
  import { DocumentManager } from '../core/documents/DocumentManager';
4
4
  interface PluginHostConfig {
@@ -16,6 +16,7 @@ export declare class PluginHost {
16
16
  resolveCompletion(textDocument: TextDocumentIdentifier, completionItem: AppCompletionItem): Promise<CompletionItem>;
17
17
  getDiagnostics(textDocument: TextDocumentIdentifier): Promise<Diagnostic[]>;
18
18
  doHover(textDocument: TextDocumentIdentifier, position: Position): Promise<Hover | null>;
19
+ formatDocument(textDocument: TextDocumentIdentifier, options: FormattingOptions): Promise<TextEdit[]>;
19
20
  getCodeActions(textDocument: TextDocumentIdentifier, range: Range, context: CodeActionContext, cancellationToken: CancellationToken): Promise<CodeAction[]>;
20
21
  doTagComplete(textDocument: TextDocumentIdentifier, position: Position): Promise<string | null>;
21
22
  getFoldingRanges(textDocument: TextDocumentIdentifier): Promise<FoldingRange[] | null>;
@@ -69,6 +69,10 @@ class PluginHost {
69
69
  const document = this.getDocument(textDocument.uri);
70
70
  return this.execute('doHover', [document, position], ExecuteMode.FirstNonNull);
71
71
  }
72
+ async formatDocument(textDocument, options) {
73
+ const document = this.getDocument(textDocument.uri);
74
+ return (0, lodash_1.flatten)(await this.execute('formatDocument', [document, options], ExecuteMode.Collect));
75
+ }
72
76
  async getCodeActions(textDocument, range, context, cancellationToken) {
73
77
  const document = this.getDocument(textDocument.uri);
74
78
  return (0, lodash_1.flatten)(await this.execute('getCodeActions', [document, range, context, cancellationToken], ExecuteMode.Collect));
@@ -9,7 +9,7 @@ class AstroPlugin {
9
9
  this.__name = 'astro';
10
10
  this.configManager = configManager;
11
11
  this.languageServiceManager = new LanguageServiceManager_1.LanguageServiceManager(docManager, workspaceUris, configManager);
12
- this.completionProvider = new CompletionsProvider_1.CompletionsProviderImpl(docManager, this.languageServiceManager);
12
+ this.completionProvider = new CompletionsProvider_1.CompletionsProviderImpl(this.languageServiceManager);
13
13
  }
14
14
  async getCompletions(document, position, completionContext) {
15
15
  const completions = this.completionProvider.getCompletions(document, position, completionContext);
@@ -21,8 +21,15 @@ class AstroPlugin {
21
21
  // Currently editing frontmatter, don't fold
22
22
  if (frontmatter.state !== 'closed')
23
23
  return foldingRanges;
24
- const start = document.positionAt(frontmatter.startOffset);
25
- const end = document.positionAt(frontmatter.endOffset - 3);
24
+ // The way folding ranges work is by folding anything between the starting position and the ending one, as such
25
+ // the start in this case should be after the frontmatter start (after the starting ---) until the last character
26
+ // of the last line of the frontmatter before its ending (before the closing ---)
27
+ // ---
28
+ // ^ -- start
29
+ // console.log("Astro")
30
+ // --- ^ -- end
31
+ const start = document.positionAt(frontmatter.startOffset + 3);
32
+ const end = document.positionAt(frontmatter.endOffset - 1);
26
33
  return [
27
34
  {
28
35
  startLine: start.line,
@@ -1,17 +1,16 @@
1
1
  import type { AppCompletionList, CompletionsProvider } from '../../interfaces';
2
- import type { AstroDocument, DocumentManager } from '../../../core/documents';
2
+ import type { AstroDocument } from '../../../core/documents';
3
3
  import { CompletionContext, Position } from 'vscode-languageserver';
4
4
  import { LanguageServiceManager as TypeScriptLanguageServiceManager } from '../../typescript/LanguageServiceManager';
5
5
  export declare class CompletionsProviderImpl implements CompletionsProvider {
6
- private readonly docManager;
7
6
  private readonly languageServiceManager;
7
+ private lastCompletion;
8
8
  directivesHTMLLang: import("vscode-html-languageservice").LanguageService;
9
- constructor(docManager: DocumentManager, languageServiceManager: TypeScriptLanguageServiceManager);
9
+ constructor(languageServiceManager: TypeScriptLanguageServiceManager);
10
10
  getCompletions(document: AstroDocument, position: Position, completionContext?: CompletionContext): Promise<AppCompletionList | null>;
11
11
  private getComponentScriptCompletion;
12
- private getPropCompletions;
12
+ private getPropCompletionsAndFilePath;
13
13
  private getImportedSymbol;
14
14
  private getPropType;
15
15
  private getCompletionItemForProperty;
16
- private isAstroComponent;
17
16
  }
@@ -13,21 +13,18 @@ const vscode_html_languageservice_1 = require("vscode-html-languageservice");
13
13
  const astro_attributes_1 = require("../../html/features/astro-attributes");
14
14
  const utils_4 = require("../../html/utils");
15
15
  class CompletionsProviderImpl {
16
- constructor(docManager, languageServiceManager) {
16
+ constructor(languageServiceManager) {
17
+ this.lastCompletion = null;
17
18
  this.directivesHTMLLang = (0, vscode_html_languageservice_1.getLanguageService)({
18
19
  customDataProviders: [astro_attributes_1.astroDirectives],
19
20
  useDefaultDataProvider: false,
20
21
  });
21
- this.docManager = docManager;
22
22
  this.languageServiceManager = languageServiceManager;
23
23
  }
24
24
  async getCompletions(document, position, completionContext) {
25
- const doc = this.docManager.get(document.uri);
26
- if (!doc)
27
- return null;
28
25
  let items = [];
29
26
  if (completionContext?.triggerCharacter === '-') {
30
- const frontmatter = this.getComponentScriptCompletion(doc, position, completionContext);
27
+ const frontmatter = this.getComponentScriptCompletion(document, position);
31
28
  if (frontmatter)
32
29
  items.push(frontmatter);
33
30
  }
@@ -35,11 +32,11 @@ class CompletionsProviderImpl {
35
32
  const offset = document.offsetAt(position);
36
33
  const node = html.findNodeAt(offset);
37
34
  if ((0, utils_1.isInComponentStartTag)(html, offset) && !(0, utils_1.isInsideExpression)(document.getText(), node.start, offset)) {
38
- const props = await this.getPropCompletions(document, position, completionContext);
35
+ const { completions: props, componentFilePath } = await this.getPropCompletionsAndFilePath(document, position, completionContext);
39
36
  if (props.length) {
40
37
  items.push(...props);
41
38
  }
42
- const isAstro = await this.isAstroComponent(document, node);
39
+ const isAstro = componentFilePath?.endsWith('.astro');
43
40
  if (!isAstro) {
44
41
  const directives = (0, utils_4.removeDataAttrCompletion)(this.directivesHTMLLang.doComplete(document, position, html).items);
45
42
  items.push(...directives);
@@ -47,7 +44,7 @@ class CompletionsProviderImpl {
47
44
  }
48
45
  return vscode_languageserver_1.CompletionList.create(items, true);
49
46
  }
50
- getComponentScriptCompletion(document, position, completionContext) {
47
+ getComponentScriptCompletion(document, position) {
51
48
  const base = {
52
49
  kind: vscode_languageserver_1.CompletionItemKind.Snippet,
53
50
  label: '---',
@@ -55,7 +52,7 @@ class CompletionsProviderImpl {
55
52
  preselect: true,
56
53
  detail: 'Component script',
57
54
  insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
58
- commitCharacters: ['-'],
55
+ commitCharacters: [],
59
56
  };
60
57
  const prefix = document.getLineUntilOffset(document.offsetAt(position));
61
58
  if (document.astroMeta.frontmatter.state === null) {
@@ -78,25 +75,25 @@ class CompletionsProviderImpl {
78
75
  }
79
76
  return null;
80
77
  }
81
- async getPropCompletions(document, position, completionContext) {
78
+ async getPropCompletionsAndFilePath(document, position, completionContext) {
82
79
  const offset = document.offsetAt(position);
83
80
  const html = document.html;
84
81
  const node = html.findNodeAt(offset);
85
82
  if (!(0, utils_2.isPossibleComponent)(node)) {
86
- return [];
83
+ return { completions: [], componentFilePath: null };
87
84
  }
88
85
  const inAttribute = node.start + node.tag.length < offset;
89
86
  if (!inAttribute) {
90
- return [];
87
+ return { completions: [], componentFilePath: null };
91
88
  }
92
89
  if (completionContext?.triggerCharacter === '/' || completionContext?.triggerCharacter === '>') {
93
- return [];
90
+ return { completions: [], componentFilePath: null };
94
91
  }
95
92
  // If inside of attribute value, skip.
96
93
  if (completionContext &&
97
94
  completionContext.triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter &&
98
95
  completionContext.triggerCharacter === '"') {
99
- return [];
96
+ return { completions: [], componentFilePath: null };
100
97
  }
101
98
  const componentName = node.tag;
102
99
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
@@ -106,18 +103,34 @@ class CompletionsProviderImpl {
106
103
  const sourceFile = program?.getSourceFile(tsFilePath);
107
104
  const typeChecker = program?.getTypeChecker();
108
105
  if (!sourceFile || !typeChecker) {
109
- return [];
106
+ return { completions: [], componentFilePath: null };
110
107
  }
111
108
  // Get the import statement
112
109
  const imp = this.getImportedSymbol(sourceFile, componentName);
113
110
  const importType = imp && typeChecker.getTypeAtLocation(imp);
114
111
  if (!importType) {
115
- return [];
112
+ return { completions: [], componentFilePath: null };
113
+ }
114
+ const symbol = importType.getSymbol();
115
+ if (!symbol) {
116
+ return { completions: [], componentFilePath: null };
117
+ }
118
+ const symbolDeclaration = symbol.declarations;
119
+ if (!symbolDeclaration) {
120
+ return { completions: [], componentFilePath: null };
121
+ }
122
+ const filePath = symbolDeclaration[0].getSourceFile().fileName;
123
+ const componentSnapshot = await this.languageServiceManager.getSnapshot(filePath);
124
+ if (this.lastCompletion) {
125
+ if (this.lastCompletion.tag === componentName &&
126
+ this.lastCompletion.documentVersion == componentSnapshot.version) {
127
+ return { completions: this.lastCompletion.completions, componentFilePath: filePath };
128
+ }
116
129
  }
117
130
  // Get the component's props type
118
- const componentType = this.getPropType(importType, typeChecker);
131
+ const componentType = this.getPropType(symbolDeclaration, typeChecker);
119
132
  if (!componentType) {
120
- return [];
133
+ return { completions: [], componentFilePath: null };
121
134
  }
122
135
  let completionItems = [];
123
136
  // Add completions for this component's props type properties
@@ -127,11 +140,12 @@ class CompletionsProviderImpl {
127
140
  let completionItem = this.getCompletionItemForProperty(property, typeChecker, type);
128
141
  completionItems.push(completionItem);
129
142
  });
130
- // Ensure that props shows up first as a completion, despite this plugin being ran after the HTML one
131
- completionItems = completionItems.map((item) => {
132
- return { ...item, sortText: '_' };
133
- });
134
- return completionItems;
143
+ this.lastCompletion = {
144
+ tag: componentName,
145
+ documentVersion: componentSnapshot.version,
146
+ completions: completionItems,
147
+ };
148
+ return { completions: completionItems, componentFilePath: filePath };
135
149
  }
136
150
  getImportedSymbol(sourceFile, identifier) {
137
151
  for (let list of sourceFile.getChildren()) {
@@ -159,24 +173,20 @@ class CompletionsProviderImpl {
159
173
  }
160
174
  return null;
161
175
  }
162
- getPropType(type, typeChecker) {
163
- const sym = type?.getSymbol();
164
- if (!sym) {
165
- return null;
166
- }
167
- for (const decl of sym?.getDeclarations() || []) {
176
+ getPropType(declarations, typeChecker) {
177
+ for (const decl of declarations) {
168
178
  const fileName = (0, utils_3.toVirtualFilePath)(decl.getSourceFile().fileName);
169
- if (fileName.endsWith('.tsx') || fileName.endsWith('.jsx')) {
170
- if (!typescript_1.default.isFunctionDeclaration(decl)) {
171
- console.error(`We only support function components for tsx/jsx at the moment.`);
179
+ if (fileName.endsWith('.tsx') || fileName.endsWith('.jsx') || fileName.endsWith('.d.ts')) {
180
+ if (!typescript_1.default.isFunctionDeclaration(decl) && !typescript_1.default.isFunctionTypeNode(decl)) {
181
+ console.error(`We only support functions declarations at the moment`);
172
182
  continue;
173
183
  }
174
184
  const fn = decl;
175
185
  if (!fn.parameters.length)
176
186
  continue;
177
187
  const param1 = fn.parameters[0];
178
- const type = typeChecker.getTypeAtLocation(param1);
179
- return type;
188
+ const propType = typeChecker.getTypeAtLocation(param1);
189
+ return propType;
180
190
  }
181
191
  }
182
192
  return null;
@@ -201,7 +211,15 @@ class CompletionsProviderImpl {
201
211
  insertText: insertText,
202
212
  insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
203
213
  commitCharacters: [],
214
+ // Ensure that props shows up first as a completion, despite this plugin being ran after the HTML one
215
+ sortText: '\0',
204
216
  };
217
+ if (mem.flags & typescript_1.default.SymbolFlags.Optional) {
218
+ item.filterText = item.label;
219
+ item.label += '?';
220
+ // Put optional props at a lower priority
221
+ item.sortText = '_';
222
+ }
205
223
  mem.getDocumentationComment(typeChecker);
206
224
  let description = mem
207
225
  .getDocumentationComment(typeChecker)
@@ -216,28 +234,5 @@ class CompletionsProviderImpl {
216
234
  }
217
235
  return item;
218
236
  }
219
- async isAstroComponent(document, node) {
220
- const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
221
- // Get the source file
222
- const tsFilePath = (0, utils_3.toVirtualAstroFilePath)(tsDoc.filePath);
223
- const program = lang.getProgram();
224
- const sourceFile = program?.getSourceFile(tsFilePath);
225
- const typeChecker = program?.getTypeChecker();
226
- if (!sourceFile || !typeChecker) {
227
- return false;
228
- }
229
- const componentName = node.tag;
230
- const imp = this.getImportedSymbol(sourceFile, componentName);
231
- const importType = imp && typeChecker.getTypeAtLocation(imp);
232
- if (!importType) {
233
- return false;
234
- }
235
- const symbolDeclaration = importType.getSymbol()?.declarations;
236
- if (symbolDeclaration) {
237
- const fileName = symbolDeclaration[0].getSourceFile().fileName;
238
- return fileName.endsWith('.astro');
239
- }
240
- return false;
241
- }
242
237
  }
243
238
  exports.CompletionsProviderImpl = CompletionsProviderImpl;
@@ -1,4 +1,4 @@
1
- import { CompletionList, Position, FoldingRange, Hover, SymbolInformation } from 'vscode-languageserver';
1
+ import { CompletionList, Position, TextEdit, FoldingRange, Hover, SymbolInformation, FormattingOptions } from 'vscode-languageserver';
2
2
  import type { Plugin } from '../interfaces';
3
3
  import { ConfigManager } from '../../core/config/ConfigManager';
4
4
  import { AstroDocument } from '../../core/documents/AstroDocument';
@@ -15,6 +15,7 @@ export declare class HTMLPlugin implements Plugin {
15
15
  * Get HTML completions
16
16
  */
17
17
  getCompletions(document: AstroDocument, position: Position): Promise<CompletionList | null>;
18
+ formatDocument(document: AstroDocument, options: FormattingOptions): Promise<TextEdit[]>;
18
19
  getFoldingRanges(document: AstroDocument): FoldingRange[] | null;
19
20
  doTagComplete(document: AstroDocument, position: Position): Promise<string | null>;
20
21
  getDocumentSymbols(document: AstroDocument): Promise<SymbolInformation[]>;
@@ -80,6 +80,24 @@ class HTMLPlugin {
80
80
  // Emmet completions change on every keystroke, so they are never complete
81
81
  emmetResults.items.length > 0);
82
82
  }
83
+ async formatDocument(document, options) {
84
+ const start = document.positionAt(document.astroMeta.frontmatter.state === 'closed' ? document.astroMeta.frontmatter.endOffset + 3 : 0);
85
+ if (document.astroMeta.frontmatter.state === 'closed') {
86
+ start.line += 1;
87
+ start.character = 0;
88
+ }
89
+ const end = document.positionAt(document.getTextLength());
90
+ const htmlFormatConfig = (await this.configManager.getConfig('html.format', document.uri)) ?? {};
91
+ // The HTML plugin can't format script tags properly, we'll handle those inside the TypeScript plugin
92
+ if (htmlFormatConfig.contentUnformatted) {
93
+ htmlFormatConfig.contentUnformatted = htmlFormatConfig.contentUnformatted + ',script';
94
+ }
95
+ else {
96
+ htmlFormatConfig.contentUnformatted = 'script';
97
+ }
98
+ const edits = this.lang.format(document, vscode_languageserver_1.Range.create(start, end), { ...htmlFormatConfig, ...options });
99
+ return edits;
100
+ }
83
101
  getFoldingRanges(document) {
84
102
  const html = document.html;
85
103
  if (!html) {
@@ -1,4 +1,4 @@
1
- import { CancellationToken, CodeAction, CodeActionContext, CompletionContext, DefinitionLink, Diagnostic, FoldingRange, Hover, Position, Range, SemanticTokens, SignatureHelp, SignatureHelpContext, SymbolInformation, TextDocumentContentChangeEvent, WorkspaceEdit } from 'vscode-languageserver';
1
+ import { CancellationToken, CodeAction, CodeActionContext, CompletionContext, DefinitionLink, Diagnostic, FoldingRange, FormattingOptions, Hover, Position, Range, SemanticTokens, SignatureHelp, SignatureHelpContext, SymbolInformation, TextDocumentContentChangeEvent, TextEdit, WorkspaceEdit } from 'vscode-languageserver';
2
2
  import { ConfigManager } from '../../core/config';
3
3
  import { AstroDocument, DocumentManager } from '../../core/documents';
4
4
  import { AppCompletionItem, AppCompletionList, OnWatchFileChangesParam, Plugin } from '../interfaces';
@@ -16,9 +16,11 @@ export declare class TypeScriptPlugin implements Plugin {
16
16
  private readonly documentSymbolsProvider;
17
17
  private readonly semanticTokensProvider;
18
18
  private readonly foldingRangesProvider;
19
+ private readonly formattingProvider;
19
20
  constructor(docManager: DocumentManager, configManager: ConfigManager, workspaceUris: string[]);
20
21
  doHover(document: AstroDocument, position: Position): Promise<Hover | null>;
21
22
  rename(document: AstroDocument, position: Position, newName: string): Promise<WorkspaceEdit | null>;
23
+ formatDocument(document: AstroDocument, options: FormattingOptions): Promise<TextEdit[]>;
22
24
  getFoldingRanges(document: AstroDocument): Promise<FoldingRange[] | null>;
23
25
  getSemanticTokens(document: AstroDocument, range?: Range, cancellationToken?: CancellationToken): Promise<SemanticTokens | null>;
24
26
  getDocumentSymbols(document: AstroDocument): Promise<SymbolInformation[]>;
@@ -17,6 +17,7 @@ const SemanticTokenProvider_1 = require("./features/SemanticTokenProvider");
17
17
  const FoldingRangesProvider_1 = require("./features/FoldingRangesProvider");
18
18
  const CodeActionsProvider_1 = require("./features/CodeActionsProvider");
19
19
  const DefinitionsProvider_1 = require("./features/DefinitionsProvider");
20
+ const FormattingProvider_1 = require("./features/FormattingProvider");
20
21
  class TypeScriptPlugin {
21
22
  constructor(docManager, configManager, workspaceUris) {
22
23
  this.__name = 'typescript';
@@ -31,6 +32,7 @@ class TypeScriptPlugin {
31
32
  this.documentSymbolsProvider = new DocumentSymbolsProvider_1.DocumentSymbolsProviderImpl(this.languageServiceManager);
32
33
  this.semanticTokensProvider = new SemanticTokenProvider_1.SemanticTokensProviderImpl(this.languageServiceManager);
33
34
  this.foldingRangesProvider = new FoldingRangesProvider_1.FoldingRangesProviderImpl(this.languageServiceManager);
35
+ this.formattingProvider = new FormattingProvider_1.FormattingProviderImpl(this.languageServiceManager, this.configManager);
34
36
  }
35
37
  async doHover(document, position) {
36
38
  if (!(await this.featureEnabled(document, 'hover'))) {
@@ -61,6 +63,9 @@ class TypeScriptPlugin {
61
63
  });
62
64
  return edit;
63
65
  }
66
+ async formatDocument(document, options) {
67
+ return this.formattingProvider.formatDocument(document, options);
68
+ }
64
69
  async getFoldingRanges(document) {
65
70
  return this.foldingRangesProvider.getFoldingRanges(document);
66
71
  }