@astrojs/language-server 0.20.1 → 0.20.3

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @astrojs/language-server
2
2
 
3
+ ## 0.20.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 081cf24: Fix completions not working inside script tags, fix duplicate completions in some cases, added completions for the slot element
8
+
3
9
  ## 0.20.1
4
10
 
5
11
  ### Patch Changes
@@ -21,6 +21,10 @@ export declare function getLineAtPosition(position: Position, text: string): str
21
21
  * Return if a given offset is inside the start tag of a component
22
22
  */
23
23
  export declare function isInComponentStartTag(html: HTMLDocument, offset: number): boolean;
24
+ /**
25
+ * Return if a given offset is inside the name of a tag
26
+ */
27
+ export declare function isInTagName(html: HTMLDocument, offset: number): boolean;
24
28
  /**
25
29
  * Return true if a specific node could be a component.
26
30
  * This is not a 100% sure test as it'll return false for any component that does not match the standard format for a component
@@ -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.isPossibleComponent = exports.isInComponentStartTag = exports.getLineAtPosition = exports.extractScriptTags = exports.extractStyleTags = exports.walk = void 0;
3
+ exports.getFirstNonWhitespaceIndex = exports.getLineOffsets = exports.offsetAt = exports.positionAt = exports.isInsideFrontmatter = exports.isInsideExpression = exports.isInTag = exports.isPossibleComponent = exports.isInTagName = exports.isInComponentStartTag = 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");
@@ -105,6 +105,14 @@ function isInComponentStartTag(html, offset) {
105
105
  return isPossibleComponent(node) && (!node.startTagEnd || offset < node.startTagEnd);
106
106
  }
107
107
  exports.isInComponentStartTag = isInComponentStartTag;
108
+ /**
109
+ * Return if a given offset is inside the name of a tag
110
+ */
111
+ function isInTagName(html, offset) {
112
+ const node = html.findNodeAt(offset);
113
+ return offset > node.start && offset < node.start + (node.tag?.length ?? 0);
114
+ }
115
+ exports.isInTagName = isInTagName;
108
116
  /**
109
117
  * Return true if a specific node could be a component.
110
118
  * This is not a 100% sure test as it'll return false for any component that does not match the standard format for a component
@@ -38,19 +38,14 @@ class PluginHost {
38
38
  const astro = completions.find((completion) => completion.plugin === 'astro');
39
39
  if (html && ts) {
40
40
  const inComponentStartTag = (0, documents_1.isInComponentStartTag)(document.html, document.offsetAt(position));
41
- if (!inComponentStartTag) {
41
+ // If the HTML plugin returned completions, it's highly likely that TS ones are duplicate
42
+ if (html.result.items.length > 0) {
42
43
  ts.result.items = [];
43
44
  }
44
- // If the Astro plugin has completions for us, don't show TypeScript's as they're most likely duplicates
45
+ // Inside components, if the Astro plugin has completions we don't want the TS ones are they're duplicates
45
46
  if (astro && astro.result.items.length > 0 && inComponentStartTag) {
46
47
  ts.result.items = [];
47
48
  }
48
- ts.result.items = ts.result.items.map((item) => {
49
- if (item.sortText != '-1') {
50
- item.sortText = 'Z' + (item.sortText || '');
51
- }
52
- return item;
53
- });
54
49
  }
55
50
  let flattenedCompletions = completions.flatMap((completion) => completion.result.items);
56
51
  const isIncomplete = completions.reduce((incomplete, completion) => incomplete || completion.result.isIncomplete, false);
@@ -11,7 +11,7 @@ class HTMLPlugin {
11
11
  constructor(configManager) {
12
12
  this.__name = 'html';
13
13
  this.lang = (0, vscode_html_languageservice_1.getLanguageService)({
14
- customDataProviders: [astro_attributes_1.astroAttributes, astro_attributes_1.classListAttribute],
14
+ customDataProviders: [astro_attributes_1.astroAttributes, astro_attributes_1.astroElements, astro_attributes_1.classListAttribute],
15
15
  });
16
16
  this.attributeOnlyLang = (0, vscode_html_languageservice_1.getLanguageService)({
17
17
  customDataProviders: [astro_attributes_1.astroAttributes],
@@ -72,10 +72,14 @@ class HTMLPlugin {
72
72
  }
73
73
  // If we're in a component starting tag, we do not want HTML language completions
74
74
  // as HTML attributes are not valid for components
75
- const results = (0, utils_1.isInComponentStartTag)(html, document.offsetAt(position))
75
+ const inComponentTag = (0, utils_1.isInComponentStartTag)(html, offset);
76
+ const inTagName = (0, utils_1.isInTagName)(html, offset);
77
+ const results = inComponentTag && !inTagName
76
78
  ? (0, utils_2.removeDataAttrCompletion)(this.attributeOnlyLang.doComplete(document, position, html).items)
77
- : this.lang.doComplete(document, position, html).items;
78
- return vscode_languageserver_1.CompletionList.create([...results, ...this.getLangCompletions(results), ...emmetResults.items],
79
+ : // We filter items with no documentation to prevent duplicates with our own defined script and style tags
80
+ this.lang.doComplete(document, position, html).items.filter((item) => item.documentation !== undefined);
81
+ const langCompletions = inComponentTag ? [] : this.getLangCompletions(results);
82
+ return vscode_languageserver_1.CompletionList.create([...results, ...langCompletions, ...emmetResults.items],
79
83
  // Emmet completions change on every keystroke, so they are never complete
80
84
  emmetResults.items.length > 0);
81
85
  }
@@ -1,3 +1,4 @@
1
1
  export declare const classListAttribute: import("vscode-html-languageservice").IHTMLDataProvider;
2
+ export declare const astroElements: import("vscode-html-languageservice").IHTMLDataProvider;
2
3
  export declare const astroAttributes: import("vscode-html-languageservice").IHTMLDataProvider;
3
4
  export declare const astroDirectives: import("vscode-html-languageservice").IHTMLDataProvider;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.astroDirectives = exports.astroAttributes = exports.classListAttribute = void 0;
3
+ exports.astroDirectives = exports.astroAttributes = exports.astroElements = exports.classListAttribute = void 0;
4
4
  const vscode_html_languageservice_1 = require("vscode-html-languageservice");
5
5
  exports.classListAttribute = (0, vscode_html_languageservice_1.newHTMLDataProvider)('class-list', {
6
6
  version: 1,
@@ -17,42 +17,31 @@ exports.classListAttribute = (0, vscode_html_languageservice_1.newHTMLDataProvid
17
17
  },
18
18
  ],
19
19
  });
20
- exports.astroAttributes = (0, vscode_html_languageservice_1.newHTMLDataProvider)('astro-attributes', {
20
+ exports.astroElements = (0, vscode_html_languageservice_1.newHTMLDataProvider)('astro-elements', {
21
21
  version: 1,
22
- globalAttributes: [
23
- {
24
- name: 'set:html',
25
- description: 'Inject unescaped HTML into this tag',
26
- references: [
27
- {
28
- name: 'Astro reference',
29
- url: 'https://docs.astro.build/en/reference/directives-reference/#sethtml',
30
- },
31
- ],
32
- },
22
+ tags: [
33
23
  {
34
- name: 'set:text',
35
- description: 'Inject escaped text into this tag',
24
+ name: 'slot',
25
+ description: 'The slot element is a placeholder for external HTML content, allowing you to inject (or “slot”) child elements from other files into your component template.',
36
26
  references: [
37
27
  {
38
28
  name: 'Astro reference',
39
- url: 'https://docs.astro.build/en/reference/directives-reference/#settext',
29
+ url: 'https://docs.astro.build/en/core-concepts/astro-components/#slots',
40
30
  },
41
31
  ],
42
- },
43
- {
44
- name: 'is:raw',
45
- description: 'Instructs the Astro compiler to treat any children of this element as text',
46
- valueSet: 'v',
47
- references: [
32
+ attributes: [
48
33
  {
49
- name: 'Astro reference',
50
- url: 'https://docs.astro.build/en/reference/directives-reference/#israw',
34
+ name: 'name',
35
+ description: 'The name attribute allows you to pass only HTML elements with the corresponding slot name into a slot’s location.',
36
+ references: [
37
+ {
38
+ name: 'Astro reference',
39
+ url: 'https://docs.astro.build/en/core-concepts/astro-components/#named-slots',
40
+ },
41
+ ],
51
42
  },
52
43
  ],
53
44
  },
54
- ],
55
- tags: [
56
45
  {
57
46
  name: 'script',
58
47
  attributes: [
@@ -146,6 +135,42 @@ exports.astroAttributes = (0, vscode_html_languageservice_1.newHTMLDataProvider)
146
135
  },
147
136
  ],
148
137
  });
138
+ exports.astroAttributes = (0, vscode_html_languageservice_1.newHTMLDataProvider)('astro-attributes', {
139
+ version: 1,
140
+ globalAttributes: [
141
+ {
142
+ name: 'set:html',
143
+ description: 'Inject unescaped HTML into this tag',
144
+ references: [
145
+ {
146
+ name: 'Astro reference',
147
+ url: 'https://docs.astro.build/en/reference/directives-reference/#sethtml',
148
+ },
149
+ ],
150
+ },
151
+ {
152
+ name: 'set:text',
153
+ description: 'Inject escaped text into this tag',
154
+ references: [
155
+ {
156
+ name: 'Astro reference',
157
+ url: 'https://docs.astro.build/en/reference/directives-reference/#settext',
158
+ },
159
+ ],
160
+ },
161
+ {
162
+ name: 'is:raw',
163
+ description: 'Instructs the Astro compiler to treat any children of this element as text',
164
+ valueSet: 'v',
165
+ references: [
166
+ {
167
+ name: 'Astro reference',
168
+ url: 'https://docs.astro.build/en/reference/directives-reference/#israw',
169
+ },
170
+ ],
171
+ },
172
+ ],
173
+ });
149
174
  exports.astroDirectives = (0, vscode_html_languageservice_1.newHTMLDataProvider)('astro-directives', {
150
175
  version: 1,
151
176
  globalAttributes: [
@@ -21,7 +21,6 @@ export declare class CompletionsProviderImpl implements CompletionsProvider<Comp
21
21
  getCompletions(document: AstroDocument, position: Position, completionContext?: CompletionContext, cancellationToken?: CancellationToken): Promise<AppCompletionList<CompletionItemData> | null>;
22
22
  resolveCompletion(document: AstroDocument, item: AppCompletionItem<CompletionItemData>, cancellationToken?: CancellationToken): Promise<AppCompletionItem<CompletionItemData>>;
23
23
  private toCompletionItem;
24
- private isValidCompletion;
25
24
  private getCompletionDocument;
26
25
  /**
27
26
  * If the textEdit is out of the word range of the triggered position
@@ -78,26 +78,8 @@ class CompletionsProviderImpl {
78
78
  scriptTagIndex = scriptIndex;
79
79
  completions = lang.getCompletionsAtPosition(scriptFilePath, scriptOffset, {
80
80
  ...tsPreferences,
81
- // File extensions are required inside script tags, however TypeScript can't return completions with the `ts`
82
- // extension, so what we'll do instead is force `minimal` (aka, no extension) and manually add the extensions
83
- importModuleSpecifierEnding: 'minimal',
84
81
  triggerCharacter: validTriggerCharacter,
85
82
  }, formatOptions);
86
- if (completions) {
87
- // Manually adds file extensions to js and ts files
88
- completions.entries = completions?.entries.map((comp) => {
89
- if (comp.kind === typescript_1.ScriptElementKind.scriptElement &&
90
- (comp.kindModifiers === '.js' || comp.kindModifiers === '.ts')) {
91
- return {
92
- ...comp,
93
- name: comp.name + comp.kindModifiers,
94
- };
95
- }
96
- else {
97
- return comp;
98
- }
99
- });
100
- }
101
83
  }
102
84
  else {
103
85
  // PERF: Getting TS completions is fairly slow and I am currently not sure how to speed it up
@@ -132,7 +114,7 @@ class CompletionsProviderImpl {
132
114
  const fragment = await tsDoc.createFragment();
133
115
  const existingImports = this.getExistingImports(document);
134
116
  const completionItems = completions.entries
135
- .filter(this.isValidCompletion)
117
+ .filter(isValidCompletion)
136
118
  .map((entry) => this.toCompletionItem(fragment, entry, filePath, offset, isCompletionInsideFrontmatter, scriptTagIndex, existingImports))
137
119
  .filter(utils_3.isNotNullOrUndefined)
138
120
  .map((comp) => this.fixTextEditRange(wordRangeStartPosition, comp));
@@ -250,13 +232,6 @@ class CompletionsProviderImpl {
250
232
  },
251
233
  };
252
234
  }
253
- isValidCompletion(completion) {
254
- // Remove completion for default exported function
255
- if (completion.name === 'default' && completion.kindModifiers == typescript_1.ScriptElementKindModifier.exportedModifier) {
256
- return false;
257
- }
258
- return true;
259
- }
260
235
  getCompletionDocument(compDetail) {
261
236
  const { sourceDisplay, documentation: tsDocumentation, displayParts } = compDetail;
262
237
  let detail = (0, utils_2.removeAstroComponentSuffix)(typescript_1.default.displayPartsToString(displayParts));
@@ -355,3 +330,31 @@ function codeActionChangeToTextEdit(document, fragment, isInsideScriptTag, chang
355
330
  return vscode_languageserver_1.TextEdit.replace(range, change.newText);
356
331
  }
357
332
  exports.codeActionChangeToTextEdit = codeActionChangeToTextEdit;
333
+ // When Svelte components are imported, we have to reference the svelte2tsx's types to properly type the component
334
+ // An unfortunate downside of this is that it polutes completions, so let's filter those internal types manually
335
+ const svelte2tsxTypes = new Set([
336
+ 'Svelte2TsxComponent',
337
+ 'Svelte2TsxComponentConstructorParameters',
338
+ 'SvelteComponentConstructor',
339
+ 'SvelteActionReturnType',
340
+ 'SvelteTransitionConfig',
341
+ 'SvelteTransitionReturnType',
342
+ 'SvelteAnimationReturnType',
343
+ 'SvelteWithOptionalProps',
344
+ 'SvelteAllProps',
345
+ 'SveltePropsAnyFallback',
346
+ 'SvelteSlotsAnyFallback',
347
+ 'SvelteRestProps',
348
+ 'SvelteSlots',
349
+ 'SvelteStore',
350
+ ]);
351
+ function isValidCompletion(completion) {
352
+ // Remove completion for default exported function
353
+ const isDefaultExport = completion.name === 'default' && completion.kindModifiers == typescript_1.ScriptElementKindModifier.exportedModifier;
354
+ // Remove completion for svelte2tsx internal types
355
+ const isSvelte2tsxCompletion = completion.name.startsWith('__sveltets_') || svelte2tsxTypes.has(completion.name);
356
+ if (isDefaultExport || isSvelte2tsxCompletion) {
357
+ return false;
358
+ }
359
+ return true;
360
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/language-server",
3
- "version": "0.20.1",
3
+ "version": "0.20.3",
4
4
  "author": "withastro",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -31,8 +31,8 @@
31
31
  "vscode-uri": "^3.0.3"
32
32
  },
33
33
  "devDependencies": {
34
- "@astrojs/svelte": "^0.2.1",
35
- "@astrojs/vue": "^0.2.1",
34
+ "@astrojs/svelte": "^0.5.0",
35
+ "@astrojs/vue": "^0.5.0",
36
36
  "@types/chai": "^4.3.0",
37
37
  "@types/mocha": "^9.1.0",
38
38
  "@types/sinon": "^10.0.11",