@astrojs/language-server 0.19.3 → 0.19.6

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,26 @@
1
1
  # @astrojs/language-server
2
2
 
3
+ ## 0.19.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 4c1045d: Empty changeset because publish failed
8
+
9
+ ## 0.19.5
10
+
11
+ ### Patch Changes
12
+
13
+ - 421ab52: Added a new setting (`astro.typescript.allowArbitraryAttributes`) to enable support for arbitrary attributes
14
+ - 06e3c95: Updated behaviour when no settings are provided. All features are now considered enabled by default
15
+ - 301dcfb: Remove Lodash from the code base, significally reducing the file count of the package
16
+ - dd1283b: Updated Component detection so completions now work for namespaced components (for example, typing `<myMarkdown.` will now give you a completion for the Content component)
17
+
18
+ ## 0.19.4
19
+
20
+ ### Patch Changes
21
+
22
+ - 1033856: Enable support for TypeScript inside hoisted script tags
23
+
3
24
  ## 0.19.3
4
25
 
5
26
  ### Patch Changes
@@ -15,12 +15,13 @@ declare type DeepPartial<T> = T extends Record<string, unknown> ? {
15
15
  export declare class ConfigManager {
16
16
  private globalConfig;
17
17
  private documentSettings;
18
+ shouldRefreshTSServices: boolean;
18
19
  private isTrusted;
19
20
  private connection;
20
21
  constructor(connection?: Connection);
21
22
  updateConfig(): void;
22
23
  removeDocument(scopeUri: string): void;
23
- getConfig<T>(section: string, scopeUri: string): Promise<T>;
24
+ getConfig<T>(section: string, scopeUri: string): Promise<T | Record<string, any>>;
24
25
  getEmmetConfig(document: TextDocument): Promise<VSCodeEmmetConfig>;
25
26
  getAstroFormatConfig(document: TextDocument): Promise<LSFormatConfig>;
26
27
  getTSFormatConfig(document: TextDocument, vscodeOptions?: FormattingOptions): Promise<FormatCodeSettings>;
@@ -1,11 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConfigManager = exports.defaultLSConfig = void 0;
4
- const lodash_1 = require("lodash");
5
4
  const typescript_1 = require("typescript");
5
+ const utils_1 = require("../../utils");
6
+ // The default language server configuration is used only in two cases:
7
+ // 1. When the client does not support `workspace/configuration` requests and as such, needs a global config
8
+ // 2. Inside tests, where we don't have a client connection because.. well.. we don't have a client
9
+ // Additionally, the default config is used to set default settings for some settings (ex: formatting settings)
6
10
  exports.defaultLSConfig = {
7
11
  typescript: {
8
12
  enabled: true,
13
+ allowArbitraryAttributes: false,
9
14
  diagnostics: { enabled: true },
10
15
  hover: { enabled: true },
11
16
  completions: { enabled: true },
@@ -31,7 +36,7 @@ exports.defaultLSConfig = {
31
36
  documentSymbols: { enabled: true },
32
37
  },
33
38
  format: {
34
- indentFrontmatter: true,
39
+ indentFrontmatter: false,
35
40
  newLineAfterFrontmatter: true,
36
41
  },
37
42
  };
@@ -44,19 +49,22 @@ class ConfigManager {
44
49
  constructor(connection) {
45
50
  this.globalConfig = { astro: exports.defaultLSConfig };
46
51
  this.documentSettings = {};
52
+ // If set to true, the next time we need a TypeScript language service, we'll rebuild it so it gets the new config
53
+ this.shouldRefreshTSServices = false;
47
54
  this.isTrusted = true;
48
55
  this.connection = connection;
49
56
  }
50
57
  updateConfig() {
51
58
  // Reset all cached document settings
52
59
  this.documentSettings = {};
60
+ this.shouldRefreshTSServices = true;
53
61
  }
54
62
  removeDocument(scopeUri) {
55
63
  delete this.documentSettings[scopeUri];
56
64
  }
57
65
  async getConfig(section, scopeUri) {
58
66
  if (!this.connection) {
59
- return this.globalConfig[section];
67
+ return (0, utils_1.get)(this.globalConfig, section) ?? {};
60
68
  }
61
69
  if (!this.documentSettings[scopeUri]) {
62
70
  this.documentSettings[scopeUri] = {};
@@ -71,13 +79,22 @@ class ConfigManager {
71
79
  }
72
80
  async getEmmetConfig(document) {
73
81
  const emmetConfig = (await this.getConfig('emmet', document.uri)) ?? {};
74
- return emmetConfig;
82
+ return {
83
+ ...emmetConfig,
84
+ preferences: emmetConfig.preferences ?? {},
85
+ showExpandedAbbreviation: emmetConfig.showExpandedAbbreviation ?? 'always',
86
+ showAbbreviationSuggestions: emmetConfig.showAbbreviationSuggestions ?? true,
87
+ syntaxProfiles: emmetConfig.syntaxProfiles ?? {},
88
+ variables: emmetConfig.variables ?? {},
89
+ excludeLanguages: emmetConfig.excludeLanguages ?? [],
90
+ showSuggestionsAsSnippets: emmetConfig.showSuggestionsAsSnippets ?? false,
91
+ };
75
92
  }
76
93
  async getAstroFormatConfig(document) {
77
94
  const astroFormatConfig = (await this.getConfig('astro.format', document.uri)) ?? {};
78
95
  return {
79
- indentFrontmatter: astroFormatConfig.indentFrontmatter ?? true,
80
- newLineAfterFrontmatter: astroFormatConfig.newLineAfterFrontmatter ?? true,
96
+ indentFrontmatter: astroFormatConfig.indentFrontmatter ?? exports.defaultLSConfig.format.indentFrontmatter,
97
+ newLineAfterFrontmatter: astroFormatConfig.newLineAfterFrontmatter ?? exports.defaultLSConfig.format.newLineAfterFrontmatter,
81
98
  };
82
99
  }
83
100
  async getTSFormatConfig(document, vscodeOptions) {
@@ -144,13 +161,13 @@ class ConfigManager {
144
161
  async isEnabled(document, plugin, feature) {
145
162
  const config = (await this.getConfig('astro', document.uri)) ?? {};
146
163
  if (config[plugin]) {
147
- let res = config[plugin].enabled;
164
+ let res = config[plugin].enabled ?? true;
148
165
  if (feature && config[plugin][feature]) {
149
- res = res && config[plugin][feature].enabled;
166
+ res = (res && config[plugin][feature].enabled) ?? true;
150
167
  }
151
168
  return res;
152
169
  }
153
- return false;
170
+ return true;
154
171
  }
155
172
  /**
156
173
  * Updating the global config should only be done in cases where the client doesn't support `workspace/configuration`
@@ -161,11 +178,12 @@ class ConfigManager {
161
178
  */
162
179
  updateGlobalConfig(config, outsideAstro) {
163
180
  if (outsideAstro) {
164
- this.globalConfig = (0, lodash_1.merge)({}, this.globalConfig, config);
181
+ this.globalConfig = (0, utils_1.mergeDeep)({}, this.globalConfig, config);
165
182
  }
166
183
  else {
167
- this.globalConfig.astro = (0, lodash_1.merge)({}, exports.defaultLSConfig, this.globalConfig.astro, config);
184
+ this.globalConfig.astro = (0, utils_1.mergeDeep)({}, exports.defaultLSConfig, this.globalConfig.astro, config);
168
185
  }
186
+ this.shouldRefreshTSServices = true;
169
187
  }
170
188
  }
171
189
  exports.ConfigManager = ConfigManager;
@@ -3,74 +3,75 @@
3
3
  * Make sure that this is kept in sync with the `package.json` of the VS Code extension
4
4
  */
5
5
  export interface LSConfig {
6
- typescript: LSTypescriptConfig;
7
- html: LSHTMLConfig;
8
- css: LSCSSConfig;
9
- format: LSFormatConfig;
6
+ typescript?: LSTypescriptConfig;
7
+ html?: LSHTMLConfig;
8
+ css?: LSCSSConfig;
9
+ format?: LSFormatConfig;
10
10
  }
11
11
  export interface LSFormatConfig {
12
- indentFrontmatter: boolean;
13
- newLineAfterFrontmatter: boolean;
12
+ indentFrontmatter?: boolean;
13
+ newLineAfterFrontmatter?: boolean;
14
14
  }
15
15
  export interface LSTypescriptConfig {
16
- enabled: boolean;
17
- diagnostics: {
18
- enabled: boolean;
16
+ enabled?: boolean;
17
+ allowArbitraryAttributes?: boolean;
18
+ diagnostics?: {
19
+ enabled?: boolean;
19
20
  };
20
- hover: {
21
- enabled: boolean;
21
+ hover?: {
22
+ enabled?: boolean;
22
23
  };
23
- documentSymbols: {
24
- enabled: boolean;
24
+ documentSymbols?: {
25
+ enabled?: boolean;
25
26
  };
26
- completions: {
27
- enabled: boolean;
27
+ completions?: {
28
+ enabled?: boolean;
28
29
  };
29
- definitions: {
30
- enabled: boolean;
30
+ definitions?: {
31
+ enabled?: boolean;
31
32
  };
32
- codeActions: {
33
- enabled: boolean;
33
+ codeActions?: {
34
+ enabled?: boolean;
34
35
  };
35
- rename: {
36
- enabled: boolean;
36
+ rename?: {
37
+ enabled?: boolean;
37
38
  };
38
- signatureHelp: {
39
- enabled: boolean;
39
+ signatureHelp?: {
40
+ enabled?: boolean;
40
41
  };
41
- semanticTokens: {
42
- enabled: boolean;
42
+ semanticTokens?: {
43
+ enabled?: boolean;
43
44
  };
44
45
  }
45
46
  export interface LSHTMLConfig {
46
- enabled: boolean;
47
- hover: {
48
- enabled: boolean;
47
+ enabled?: boolean;
48
+ hover?: {
49
+ enabled?: boolean;
49
50
  };
50
- completions: {
51
- enabled: boolean;
52
- emmet: boolean;
51
+ completions?: {
52
+ enabled?: boolean;
53
+ emmet?: boolean;
53
54
  };
54
- tagComplete: {
55
- enabled: boolean;
55
+ tagComplete?: {
56
+ enabled?: boolean;
56
57
  };
57
- documentSymbols: {
58
- enabled: boolean;
58
+ documentSymbols?: {
59
+ enabled?: boolean;
59
60
  };
60
61
  }
61
62
  export interface LSCSSConfig {
62
- enabled: boolean;
63
- hover: {
64
- enabled: boolean;
63
+ enabled?: boolean;
64
+ hover?: {
65
+ enabled?: boolean;
65
66
  };
66
- completions: {
67
- enabled: boolean;
68
- emmet: boolean;
67
+ completions?: {
68
+ enabled?: boolean;
69
+ emmet?: boolean;
69
70
  };
70
- documentColors: {
71
- enabled: boolean;
71
+ documentColors?: {
72
+ enabled?: boolean;
72
73
  };
73
- documentSymbols: {
74
- enabled: boolean;
74
+ documentSymbols?: {
75
+ enabled?: boolean;
75
76
  };
76
77
  }
@@ -89,6 +89,7 @@ class SourceMapDocumentMapper {
89
89
  return { line: -1, character: -1 };
90
90
  }
91
91
  if (mapped.line === 0) {
92
+ // eslint-disable-next-line no-console
92
93
  console.log('Got 0 mapped line from', generatedPosition, 'col was', mapped.column);
93
94
  }
94
95
  return {
@@ -17,18 +17,15 @@ export declare function walk(node: Node): Generator<Node, void, unknown>;
17
17
  export declare function extractStyleTags(source: string, html?: HTMLDocument): TagInformation[];
18
18
  export declare function extractScriptTags(source: string, html?: HTMLDocument): TagInformation[];
19
19
  export declare function getLineAtPosition(position: Position, text: string): string;
20
- /**
21
- * Returns the node if offset is inside a HTML start tag
22
- */
23
- export declare function getNodeIfIsInHTMLStartTag(html: HTMLDocument, offset: number): Node | undefined;
24
- /**
25
- * Return if a Node is a Component
26
- */
27
- export declare function isComponentTag(node: Node): boolean;
28
20
  /**
29
21
  * Return if a given offset is inside the start tag of a component
30
22
  */
31
23
  export declare function isInComponentStartTag(html: HTMLDocument, offset: number): boolean;
24
+ /**
25
+ * Return true if a specific node could be a component.
26
+ * 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
27
+ */
28
+ export declare function isPossibleComponent(node: Node): boolean;
32
29
  /**
33
30
  * Return if the current position is in a specific tag
34
31
  */
@@ -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.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.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");
@@ -97,35 +97,22 @@ function getLineAtPosition(position, text) {
97
97
  return text.substring(offsetAt({ line: position.line, character: 0 }, text), offsetAt({ line: position.line, character: Number.MAX_VALUE }, text));
98
98
  }
99
99
  exports.getLineAtPosition = getLineAtPosition;
100
- /**
101
- * Returns the node if offset is inside a HTML start tag
102
- */
103
- function getNodeIfIsInHTMLStartTag(html, offset) {
104
- const node = html.findNodeAt(offset);
105
- if (!!node.tag && node.tag[0] === node.tag[0].toLowerCase() && (!node.startTagEnd || offset < node.startTagEnd)) {
106
- return node;
107
- }
108
- }
109
- exports.getNodeIfIsInHTMLStartTag = getNodeIfIsInHTMLStartTag;
110
- /**
111
- * Return if a Node is a Component
112
- */
113
- function isComponentTag(node) {
114
- if (!node.tag) {
115
- return false;
116
- }
117
- const firstChar = node.tag[0];
118
- return /[A-Z]/.test(firstChar);
119
- }
120
- exports.isComponentTag = isComponentTag;
121
100
  /**
122
101
  * Return if a given offset is inside the start tag of a component
123
102
  */
124
103
  function isInComponentStartTag(html, offset) {
125
104
  const node = html.findNodeAt(offset);
126
- return isComponentTag(node) && (!node.startTagEnd || offset < node.startTagEnd);
105
+ return isPossibleComponent(node) && (!node.startTagEnd || offset < node.startTagEnd);
127
106
  }
128
107
  exports.isInComponentStartTag = isInComponentStartTag;
108
+ /**
109
+ * Return true if a specific node could be a component.
110
+ * 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
111
+ */
112
+ function isPossibleComponent(node) {
113
+ return !!node.tag?.[0].match(/[A-Z]/) || !!node.tag?.match(/.+[.][A-Z]?/);
114
+ }
115
+ exports.isPossibleComponent = isPossibleComponent;
129
116
  /**
130
117
  * Return if the current position is in a specific tag
131
118
  */
@@ -1,7 +1,7 @@
1
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, InlayHint, FormattingOptions, TextEdit, LinkedEditingRanges } from 'vscode-languageserver';
2
2
  import type { AppCompletionItem, Plugin } from './interfaces';
3
3
  import { DocumentManager } from '../core/documents/DocumentManager';
4
- interface PluginHostConfig {
4
+ export interface PluginHostConfig {
5
5
  filterIncompleteCompletions: boolean;
6
6
  definitionLinkSupport: boolean;
7
7
  }
@@ -17,22 +17,21 @@ export declare class PluginHost {
17
17
  getDiagnostics(textDocument: TextDocumentIdentifier): Promise<Diagnostic[]>;
18
18
  doHover(textDocument: TextDocumentIdentifier, position: Position): Promise<Hover | null>;
19
19
  formatDocument(textDocument: TextDocumentIdentifier, options: FormattingOptions): Promise<TextEdit[]>;
20
- getCodeActions(textDocument: TextDocumentIdentifier, range: Range, context: CodeActionContext, cancellationToken: CancellationToken): Promise<CodeAction[]>;
20
+ getCodeActions(textDocument: TextDocumentIdentifier, range: Range, context: CodeActionContext, cancellationToken?: CancellationToken): Promise<CodeAction[]>;
21
21
  doTagComplete(textDocument: TextDocumentIdentifier, position: Position): Promise<string | null>;
22
22
  getFoldingRanges(textDocument: TextDocumentIdentifier): Promise<FoldingRange[] | null>;
23
- getDocumentSymbols(textDocument: TextDocumentIdentifier, cancellationToken: CancellationToken): Promise<SymbolInformation[]>;
23
+ getDocumentSymbols(textDocument: TextDocumentIdentifier, cancellationToken?: CancellationToken): Promise<SymbolInformation[]>;
24
24
  getSemanticTokens(textDocument: TextDocumentIdentifier, range?: Range, cancellationToken?: CancellationToken): Promise<SemanticTokens | null>;
25
25
  getLinkedEditingRanges(textDocument: TextDocumentIdentifier, position: Position): Promise<LinkedEditingRanges | null>;
26
26
  getDefinitions(textDocument: TextDocumentIdentifier, position: Position): Promise<DefinitionLink[] | Location[]>;
27
27
  rename(textDocument: TextDocumentIdentifier, position: Position, newName: string): Promise<WorkspaceEdit | null>;
28
28
  getDocumentColors(textDocument: TextDocumentIdentifier): Promise<ColorInformation[]>;
29
- getInlayHints(textDocument: TextDocumentIdentifier, range: Range, cancellationToken: CancellationToken): Promise<InlayHint[]>;
30
29
  getColorPresentations(textDocument: TextDocumentIdentifier, range: Range, color: Color): Promise<ColorPresentation[]>;
31
- getSignatureHelp(textDocument: TextDocumentIdentifier, position: Position, context: SignatureHelpContext | undefined, cancellationToken: CancellationToken): Promise<SignatureHelp | null>;
30
+ getInlayHints(textDocument: TextDocumentIdentifier, range: Range, cancellationToken?: CancellationToken): Promise<InlayHint[]>;
31
+ getSignatureHelp(textDocument: TextDocumentIdentifier, position: Position, context: SignatureHelpContext | undefined, cancellationToken?: CancellationToken): Promise<SignatureHelp | null>;
32
32
  onWatchFileChanges(onWatchFileChangesParams: any[]): void;
33
33
  updateNonAstroFile(fileName: string, changes: TextDocumentContentChangeEvent[]): void;
34
34
  private getDocument;
35
35
  private execute;
36
36
  private tryExecutePlugin;
37
37
  }
38
- export {};
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PluginHost = void 0;
4
4
  const vscode_languageserver_1 = require("vscode-languageserver");
5
- const lodash_1 = require("lodash");
6
5
  const utils_1 = require("../utils");
7
6
  const documents_1 = require("../core/documents");
8
7
  var ExecuteMode;
@@ -38,11 +37,12 @@ class PluginHost {
38
37
  const ts = completions.find((completion) => completion.plugin === 'typescript');
39
38
  const astro = completions.find((completion) => completion.plugin === 'astro');
40
39
  if (html && ts) {
41
- if ((0, documents_1.getNodeIfIsInHTMLStartTag)(document.html, document.offsetAt(position))) {
40
+ const inComponentStartTag = (0, documents_1.isInComponentStartTag)(document.html, document.offsetAt(position));
41
+ if (!inComponentStartTag) {
42
42
  ts.result.items = [];
43
43
  }
44
44
  // If the Astro plugin has completions for us, don't show TypeScript's as they're most likely duplicates
45
- if (astro && astro.result.items.length > 0 && (0, documents_1.isInComponentStartTag)(document.html, document.offsetAt(position))) {
45
+ if (astro && astro.result.items.length > 0 && inComponentStartTag) {
46
46
  ts.result.items = [];
47
47
  }
48
48
  ts.result.items = ts.result.items.map((item) => {
@@ -52,8 +52,20 @@ class PluginHost {
52
52
  return item;
53
53
  });
54
54
  }
55
- let flattenedCompletions = (0, lodash_1.flatten)(completions.map((completion) => completion.result.items));
55
+ let flattenedCompletions = completions.flatMap((completion) => completion.result.items);
56
56
  const isIncomplete = completions.reduce((incomplete, completion) => incomplete || completion.result.isIncomplete, false);
57
+ // If the result is incomplete, we need to filter the results ourselves
58
+ // to throw out non-matching results. VSCode does filter client-side,
59
+ // but other IDEs might not.
60
+ if (isIncomplete && this.pluginHostConfig.filterIncompleteCompletions) {
61
+ const offset = document.offsetAt(position);
62
+ // Assumption for performance reasons:
63
+ // Noone types import names longer than 20 characters and still expects perfect autocompletion.
64
+ const text = document.getText().substring(Math.max(0, offset - 20), offset);
65
+ const start = (0, utils_1.regexLastIndexOf)(text, /[\W\s]/g) + 1;
66
+ const filterValue = text.substring(start).toLowerCase();
67
+ flattenedCompletions = flattenedCompletions.filter((comp) => comp.label.toLowerCase().includes(filterValue));
68
+ }
57
69
  return vscode_languageserver_1.CompletionList.create(flattenedCompletions, isIncomplete);
58
70
  }
59
71
  async resolveCompletion(textDocument, completionItem) {
@@ -63,7 +75,8 @@ class PluginHost {
63
75
  }
64
76
  async getDiagnostics(textDocument) {
65
77
  const document = this.getDocument(textDocument.uri);
66
- return (0, lodash_1.flatten)(await this.execute('getDiagnostics', [document], ExecuteMode.Collect));
78
+ const diagnostics = await this.execute('getDiagnostics', [document], ExecuteMode.Collect);
79
+ return diagnostics;
67
80
  }
68
81
  async doHover(textDocument, position) {
69
82
  const document = this.getDocument(textDocument.uri);
@@ -71,11 +84,11 @@ class PluginHost {
71
84
  }
72
85
  async formatDocument(textDocument, options) {
73
86
  const document = this.getDocument(textDocument.uri);
74
- return (0, lodash_1.flatten)(await this.execute('formatDocument', [document, options], ExecuteMode.Collect));
87
+ return await this.execute('formatDocument', [document, options], ExecuteMode.Collect);
75
88
  }
76
89
  async getCodeActions(textDocument, range, context, cancellationToken) {
77
90
  const document = this.getDocument(textDocument.uri);
78
- return (0, lodash_1.flatten)(await this.execute('getCodeActions', [document, range, context, cancellationToken], ExecuteMode.Collect));
91
+ return await this.execute('getCodeActions', [document, range, context, cancellationToken], ExecuteMode.Collect);
79
92
  }
80
93
  async doTagComplete(textDocument, position) {
81
94
  const document = this.getDocument(textDocument.uri);
@@ -83,12 +96,11 @@ class PluginHost {
83
96
  }
84
97
  async getFoldingRanges(textDocument) {
85
98
  const document = this.getDocument(textDocument.uri);
86
- const foldingRanges = (0, lodash_1.flatten)(await this.execute('getFoldingRanges', [document], ExecuteMode.Collect));
87
- return foldingRanges;
99
+ return await this.execute('getFoldingRanges', [document], ExecuteMode.Collect);
88
100
  }
89
101
  async getDocumentSymbols(textDocument, cancellationToken) {
90
102
  const document = this.getDocument(textDocument.uri);
91
- return (0, lodash_1.flatten)(await this.execute('getDocumentSymbols', [document, cancellationToken], ExecuteMode.Collect));
103
+ return await this.execute('getDocumentSymbols', [document, cancellationToken], ExecuteMode.Collect);
92
104
  }
93
105
  async getSemanticTokens(textDocument, range, cancellationToken) {
94
106
  const document = this.getDocument(textDocument.uri);
@@ -100,7 +112,7 @@ class PluginHost {
100
112
  }
101
113
  async getDefinitions(textDocument, position) {
102
114
  const document = this.getDocument(textDocument.uri);
103
- const definitions = (0, lodash_1.flatten)(await this.execute('getDefinitions', [document, position], ExecuteMode.Collect));
115
+ const definitions = await this.execute('getDefinitions', [document, position], ExecuteMode.Collect);
104
116
  if (this.pluginHostConfig.definitionLinkSupport) {
105
117
  return definitions;
106
118
  }
@@ -114,21 +126,18 @@ class PluginHost {
114
126
  }
115
127
  async getDocumentColors(textDocument) {
116
128
  const document = this.getDocument(textDocument.uri);
117
- return (0, lodash_1.flatten)(await this.execute('getDocumentColors', [document], ExecuteMode.Collect));
129
+ return await this.execute('getDocumentColors', [document], ExecuteMode.Collect);
118
130
  }
119
- async getInlayHints(textDocument, range, cancellationToken) {
131
+ async getColorPresentations(textDocument, range, color) {
120
132
  const document = this.getDocument(textDocument.uri);
121
- return (0, lodash_1.flatten)(await this.execute('getInlayHints', [document, range], ExecuteMode.FirstNonNull));
133
+ return await this.execute('getColorPresentations', [document, range, color], ExecuteMode.Collect);
122
134
  }
123
- async getColorPresentations(textDocument, range, color) {
135
+ async getInlayHints(textDocument, range, cancellationToken) {
124
136
  const document = this.getDocument(textDocument.uri);
125
- return (0, lodash_1.flatten)(await this.execute('getColorPresentations', [document, range, color], ExecuteMode.Collect));
137
+ return (await this.execute('getInlayHints', [document, range], ExecuteMode.FirstNonNull)) ?? [];
126
138
  }
127
139
  async getSignatureHelp(textDocument, position, context, cancellationToken) {
128
140
  const document = this.getDocument(textDocument.uri);
129
- if (!document) {
130
- throw new Error('Cannot call methods on an unopened document');
131
- }
132
141
  return await this.execute('getSignatureHelp', [document, position, context, cancellationToken], ExecuteMode.FirstNonNull);
133
142
  }
134
143
  onWatchFileChanges(onWatchFileChangesParams) {
@@ -160,10 +169,10 @@ class PluginHost {
160
169
  }
161
170
  return null;
162
171
  case ExecuteMode.Collect:
163
- return Promise.all(plugins.map((plugin) => {
172
+ return (await Promise.all(plugins.map((plugin) => {
164
173
  let ret = this.tryExecutePlugin(plugin, name, args, []);
165
174
  return ret;
166
- }));
175
+ }))).flat();
167
176
  case ExecuteMode.None:
168
177
  await Promise.all(plugins.map((plugin) => this.tryExecutePlugin(plugin, name, args, null)));
169
178
  return;
@@ -7,11 +7,10 @@ exports.CompletionsProviderImpl = void 0;
7
7
  const vscode_languageserver_1 = require("vscode-languageserver");
8
8
  const typescript_1 = __importDefault(require("typescript"));
9
9
  const utils_1 = require("../../../core/documents/utils");
10
- const utils_2 = require("../../../utils");
11
- const utils_3 = require("../../typescript/utils");
10
+ const utils_2 = require("../../typescript/utils");
12
11
  const vscode_html_languageservice_1 = require("vscode-html-languageservice");
13
12
  const astro_attributes_1 = require("../../html/features/astro-attributes");
14
- const utils_4 = require("../../html/utils");
13
+ const utils_3 = require("../../html/utils");
15
14
  class CompletionsProviderImpl {
16
15
  constructor(languageServiceManager) {
17
16
  this.lastCompletion = null;
@@ -39,7 +38,7 @@ class CompletionsProviderImpl {
39
38
  }
40
39
  const isAstro = componentFilePath?.endsWith('.astro');
41
40
  if (!isAstro) {
42
- const directives = (0, utils_4.removeDataAttrCompletion)(this.directivesHTMLLang.doComplete(document, position, html).items);
41
+ const directives = (0, utils_3.removeDataAttrCompletion)(this.directivesHTMLLang.doComplete(document, position, html).items);
43
42
  items.push(...directives);
44
43
  }
45
44
  }
@@ -87,7 +86,7 @@ class CompletionsProviderImpl {
87
86
  const offset = document.offsetAt(position);
88
87
  const html = document.html;
89
88
  const node = html.findNodeAt(offset);
90
- if (!(0, utils_2.isPossibleComponent)(node)) {
89
+ if (!(0, utils_1.isPossibleComponent)(node)) {
91
90
  return { completions: [], componentFilePath: null };
92
91
  }
93
92
  const inAttribute = node.start + node.tag.length < offset;
@@ -106,7 +105,7 @@ class CompletionsProviderImpl {
106
105
  const componentName = node.tag;
107
106
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
108
107
  // Get the source file
109
- const tsFilePath = (0, utils_3.toVirtualAstroFilePath)(tsDoc.filePath);
108
+ const tsFilePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
110
109
  const program = lang.getProgram();
111
110
  const sourceFile = program?.getSourceFile(tsFilePath);
112
111
  const typeChecker = program?.getTypeChecker();
@@ -183,7 +182,7 @@ class CompletionsProviderImpl {
183
182
  }
184
183
  getPropType(declarations, typeChecker) {
185
184
  for (const decl of declarations) {
186
- const fileName = (0, utils_3.toVirtualFilePath)(decl.getSourceFile().fileName);
185
+ const fileName = (0, utils_2.toVirtualFilePath)(decl.getSourceFile().fileName);
187
186
  if (fileName.endsWith('.tsx') || fileName.endsWith('.jsx') || fileName.endsWith('.d.ts')) {
188
187
  if (!typescript_1.default.isFunctionDeclaration(decl) && !typescript_1.default.isFunctionTypeNode(decl)) {
189
188
  console.error(`We only support functions declarations at the moment`);
@@ -9,7 +9,6 @@ const language_service_1 = require("./language-service");
9
9
  const parseHtml_1 = require("../../core/documents/parseHtml");
10
10
  const StyleAttributeDocument_1 = require("./StyleAttributeDocument");
11
11
  const getIdClassCompletions_1 = require("./features/getIdClassCompletions");
12
- const lodash_1 = require("lodash");
13
12
  class CSSPlugin {
14
13
  constructor(configManager) {
15
14
  this.__name = 'css';
@@ -103,7 +102,7 @@ class CSSPlugin {
103
102
  items: [],
104
103
  };
105
104
  const extensionConfig = await this.configManager.getConfig('astro', document.uri);
106
- if (extensionConfig.css.completions.emmet) {
105
+ if (extensionConfig?.css?.completions?.emmet ?? true) {
107
106
  langService.setCompletionParticipants([
108
107
  {
109
108
  onCssProperty: (context) => {
@@ -130,7 +129,7 @@ class CSSPlugin {
130
129
  if (!(await this.featureEnabled(document, 'documentColors'))) {
131
130
  return [];
132
131
  }
133
- const allColorInfo = this.getCSSDocumentsForDocument(document).map((cssDoc) => {
132
+ const allColorInfo = this.getCSSDocumentsForDocument(document).flatMap((cssDoc) => {
134
133
  const cssLang = extractLanguage(cssDoc);
135
134
  const langService = (0, language_service_1.getLanguageService)(cssLang);
136
135
  if (!isSupportedByLangService(cssLang)) {
@@ -140,13 +139,13 @@ class CSSPlugin {
140
139
  .findDocumentColors(cssDoc, cssDoc.stylesheet)
141
140
  .map((colorInfo) => (0, documents_1.mapObjWithRangeToOriginal)(cssDoc, colorInfo));
142
141
  });
143
- return (0, lodash_1.flatten)(allColorInfo);
142
+ return allColorInfo;
144
143
  }
145
144
  async getColorPresentations(document, range, color) {
146
145
  if (!(await this.featureEnabled(document, 'documentColors'))) {
147
146
  return [];
148
147
  }
149
- const allColorPres = this.getCSSDocumentsForDocument(document).map((cssDoc) => {
148
+ const allColorPres = this.getCSSDocumentsForDocument(document).flatMap((cssDoc) => {
150
149
  const cssLang = extractLanguage(cssDoc);
151
150
  const langService = (0, language_service_1.getLanguageService)(cssLang);
152
151
  if ((!cssDoc.isInGenerated(range.start) && !cssDoc.isInGenerated(range.end)) ||
@@ -157,26 +156,26 @@ class CSSPlugin {
157
156
  .getColorPresentations(cssDoc, cssDoc.stylesheet, color, (0, documents_1.mapRangeToGenerated)(cssDoc, range))
158
157
  .map((colorPres) => (0, documents_1.mapColorPresentationToOriginal)(cssDoc, colorPres));
159
158
  });
160
- return (0, lodash_1.flatten)(allColorPres);
159
+ return allColorPres;
161
160
  }
162
161
  getFoldingRanges(document) {
163
- const allFoldingRanges = this.getCSSDocumentsForDocument(document).map((cssDoc) => {
162
+ const allFoldingRanges = this.getCSSDocumentsForDocument(document).flatMap((cssDoc) => {
164
163
  const cssLang = extractLanguage(cssDoc);
165
164
  const langService = (0, language_service_1.getLanguageService)(cssLang);
166
165
  return langService.getFoldingRanges(cssDoc).map((foldingRange) => (0, documents_1.mapFoldingRangeToParent)(cssDoc, foldingRange));
167
166
  });
168
- return (0, lodash_1.flatten)(allFoldingRanges);
167
+ return allFoldingRanges;
169
168
  }
170
169
  async getDocumentSymbols(document) {
171
170
  if (!(await this.featureEnabled(document, 'documentSymbols'))) {
172
171
  return [];
173
172
  }
174
- const allDocumentSymbols = this.getCSSDocumentsForDocument(document).map((cssDoc) => {
173
+ const allDocumentSymbols = this.getCSSDocumentsForDocument(document).flatMap((cssDoc) => {
175
174
  return (0, language_service_1.getLanguageService)(extractLanguage(cssDoc))
176
175
  .findDocumentSymbols(cssDoc, cssDoc.stylesheet)
177
176
  .map((symbol) => (0, documents_1.mapSymbolInformationToOriginal)(cssDoc, symbol));
178
177
  });
179
- return (0, lodash_1.flatten)(allDocumentSymbols);
178
+ return allDocumentSymbols;
180
179
  }
181
180
  inStyleAttributeWithoutInterpolation(attrContext, text) {
182
181
  return (attrContext.name === 'style' &&
@@ -5,9 +5,8 @@ const vscode_languageserver_1 = require("vscode-languageserver");
5
5
  const emmet_helper_1 = require("@vscode/emmet-helper");
6
6
  const vscode_html_languageservice_1 = require("vscode-html-languageservice");
7
7
  const utils_1 = require("../../core/documents/utils");
8
- const utils_2 = require("../../utils");
9
8
  const astro_attributes_1 = require("./features/astro-attributes");
10
- const utils_3 = require("./utils");
9
+ const utils_2 = require("./utils");
11
10
  class HTMLPlugin {
12
11
  constructor(configManager) {
13
12
  this.__name = 'html';
@@ -38,7 +37,7 @@ class HTMLPlugin {
38
37
  return null;
39
38
  }
40
39
  // If the node we're hovering on is a component, instead only provide astro-specific hover info
41
- if ((0, utils_2.isPossibleComponent)(node)) {
40
+ if ((0, utils_1.isPossibleComponent)(node)) {
42
41
  return this.componentLang.doHover(document, position, html);
43
42
  }
44
43
  return this.lang.doHover(document, position, html);
@@ -64,7 +63,7 @@ class HTMLPlugin {
64
63
  };
65
64
  const emmetConfig = await this.configManager.getEmmetConfig(document);
66
65
  const extensionConfig = (await this.configManager.getConfig('astro', document.uri)) ?? {};
67
- if (extensionConfig?.html?.completions?.emmet) {
66
+ if (extensionConfig?.html?.completions?.emmet ?? true) {
68
67
  this.lang.setCompletionParticipants([
69
68
  {
70
69
  onHtmlContent: () => (emmetResults = (0, emmet_helper_1.doComplete)(document, position, 'html', emmetConfig) || emmetResults),
@@ -74,7 +73,7 @@ class HTMLPlugin {
74
73
  // If we're in a component starting tag, we do not want HTML language completions
75
74
  // as HTML attributes are not valid for components
76
75
  const results = (0, utils_1.isInComponentStartTag)(html, document.offsetAt(position))
77
- ? (0, utils_3.removeDataAttrCompletion)(this.attributeOnlyLang.doComplete(document, position, html).items)
76
+ ? (0, utils_2.removeDataAttrCompletion)(this.attributeOnlyLang.doComplete(document, position, html).items)
78
77
  : this.lang.doComplete(document, position, html).items;
79
78
  return vscode_languageserver_1.CompletionList.create([...results, ...this.getLangCompletions(results), ...emmetResults.items],
80
79
  // Emmet completions change on every keystroke, so they are never complete
@@ -25,6 +25,7 @@ class LanguageServiceManager {
25
25
  this.docContext = {
26
26
  createDocument: this.createDocument,
27
27
  globalSnapshotManager: this.globalSnapshotManager,
28
+ configManager: this.configManager,
28
29
  };
29
30
  const handleDocumentChange = (document) => {
30
31
  this.getSnapshot(document);
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CodeActionsProviderImpl = exports.sortImportKind = void 0;
7
- const lodash_1 = require("lodash");
8
7
  const typescript_1 = __importDefault(require("typescript"));
9
8
  const vscode_languageserver_types_1 = require("vscode-languageserver-types");
10
9
  const documents_1 = require("../../../core/documents");
@@ -144,7 +143,7 @@ class CodeActionsProviderImpl {
144
143
  description: (0, utils_2.removeAstroComponentSuffix)(a.description),
145
144
  fixName: 'import',
146
145
  })) ?? [];
147
- return (0, lodash_1.flatten)(completion.entries.filter((c) => c.name === name || c.name === suffixedName).map(toFix));
146
+ return completion.entries.filter((c) => c.name === name || c.name === suffixedName).flatMap(toFix);
148
147
  }
149
148
  async organizeSortImports(document, skipDestructiveCodeActions = false, cancellationToken) {
150
149
  const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
@@ -32,7 +32,6 @@ const typescript_1 = __importStar(require("typescript"));
32
32
  const vscode_languageserver_2 = require("vscode-languageserver");
33
33
  const utils_2 = require("../utils");
34
34
  const utils_3 = require("../../../utils");
35
- const lodash_1 = require("lodash");
36
35
  const previewer_1 = require("../previewer");
37
36
  const utils_4 = require("./utils");
38
37
  // `import {...} from '..'` or `import ... from '..'`
@@ -113,9 +112,9 @@ class CompletionsProviderImpl {
113
112
  if (!isCompletionInsideFrontmatter && node.parent && node.tag === undefined && !isCompletionInsideExpression) {
114
113
  return null;
115
114
  }
116
- // If the current node is not a component (aka, it doesn't start with a caps), let's disable ourselves as the user
115
+ // If the current node is not a component, let's disable ourselves as the user
117
116
  // is most likely looking for HTML completions
118
- if (!isCompletionInsideFrontmatter && !(0, utils_1.isComponentTag)(node) && !isCompletionInsideExpression) {
117
+ if (!isCompletionInsideFrontmatter && !(0, utils_1.isPossibleComponent)(node) && !isCompletionInsideExpression) {
119
118
  return null;
120
119
  }
121
120
  completions = lang.getCompletionsAtPosition(filePath, offset, {
@@ -317,7 +316,7 @@ class CompletionsProviderImpl {
317
316
  }
318
317
  getExistingImports(document) {
319
318
  const rawImports = (0, utils_3.getRegExpMatches)(scriptImportRegex, document.getText()).map((match) => (match[1] ?? match[2]).split(','));
320
- const tidiedImports = (0, lodash_1.flatten)(rawImports).map((match) => match.trim());
319
+ const tidiedImports = rawImports.flat().map((match) => match.trim());
321
320
  return new Set(tidiedImports);
322
321
  }
323
322
  isAstroComponentImport(className) {
@@ -7,7 +7,6 @@ export declare enum DiagnosticCodes {
7
7
  SPREAD_EXPECTED = 1005,
8
8
  DUPLICATED_JSX_ATTRIBUTES = 17001,
9
9
  MUST_HAVE_PARENT_ELEMENT = 2657,
10
- CANNOT_IMPORT_TS_EXT = 2691,
11
10
  CANT_RETURN_OUTSIDE_FUNC = 1108,
12
11
  ISOLATED_MODULE_COMPILE_ERR = 1208,
13
12
  TYPE_NOT_ASSIGNABLE = 2322,
@@ -15,7 +15,6 @@ var DiagnosticCodes;
15
15
  DiagnosticCodes[DiagnosticCodes["SPREAD_EXPECTED"] = 1005] = "SPREAD_EXPECTED";
16
16
  DiagnosticCodes[DiagnosticCodes["DUPLICATED_JSX_ATTRIBUTES"] = 17001] = "DUPLICATED_JSX_ATTRIBUTES";
17
17
  DiagnosticCodes[DiagnosticCodes["MUST_HAVE_PARENT_ELEMENT"] = 2657] = "MUST_HAVE_PARENT_ELEMENT";
18
- DiagnosticCodes[DiagnosticCodes["CANNOT_IMPORT_TS_EXT"] = 2691] = "CANNOT_IMPORT_TS_EXT";
19
18
  DiagnosticCodes[DiagnosticCodes["CANT_RETURN_OUTSIDE_FUNC"] = 1108] = "CANT_RETURN_OUTSIDE_FUNC";
20
19
  DiagnosticCodes[DiagnosticCodes["ISOLATED_MODULE_COMPILE_ERR"] = 1208] = "ISOLATED_MODULE_COMPILE_ERR";
21
20
  DiagnosticCodes[DiagnosticCodes["TYPE_NOT_ASSIGNABLE"] = 2322] = "TYPE_NOT_ASSIGNABLE";
@@ -54,7 +53,6 @@ class DiagnosticsProviderImpl {
54
53
  code: diagnostic.code,
55
54
  tags: getDiagnosticTag(diagnostic),
56
55
  }))
57
- .filter(isNoCantEndWithTS)
58
56
  .map(mapRange(scriptTagSnapshot, document));
59
57
  scriptDiagnostics.push(...scriptDiagnostic);
60
58
  });
@@ -190,10 +188,6 @@ function isNoSpreadExpected(diagnostic, document) {
190
188
  }
191
189
  return true;
192
190
  }
193
- /** Inside script tags, Astro currently require the `.ts` file extension for imports */
194
- function isNoCantEndWithTS(diagnostic) {
195
- return diagnostic.code !== DiagnosticCodes.CANNOT_IMPORT_TS_EXT;
196
- }
197
191
  /**
198
192
  * Ignore "Can't return outside of function body"
199
193
  * Since the frontmatter is at the top level, users trying to return a Response for SSR mode run into this
@@ -237,14 +231,6 @@ function enhanceIfNecessary(diagnostic) {
237
231
  };
238
232
  }
239
233
  }
240
- // An import path cannot end with '.ts(x)' consider importing with no extension
241
- // TODO: Remove this when https://github.com/withastro/astro/issues/3415 is fixed
242
- if (diagnostic.code === DiagnosticCodes.CANNOT_IMPORT_TS_EXT) {
243
- return {
244
- ...diagnostic,
245
- message: diagnostic.message.replace(/\.jsx?/, ''),
246
- };
247
- }
248
234
  return diagnostic;
249
235
  }
250
236
  function getDiagnosticTag(diagnostic) {
@@ -3,6 +3,7 @@ import ts from 'typescript';
3
3
  import { TextDocumentContentChangeEvent } from 'vscode-languageserver';
4
4
  import { GlobalSnapshotManager, SnapshotManager } from './snapshots/SnapshotManager';
5
5
  import { DocumentSnapshot } from './snapshots/DocumentSnapshot';
6
+ import { ConfigManager } from '../../core/config';
6
7
  export interface LanguageServiceContainer {
7
8
  readonly tsconfigPath: string;
8
9
  readonly compilerOptions: ts.CompilerOptions;
@@ -28,6 +29,7 @@ export interface LanguageServiceContainer {
28
29
  export interface LanguageServiceDocumentContext {
29
30
  createDocument: (fileName: string, content: string) => AstroDocument;
30
31
  globalSnapshotManager: GlobalSnapshotManager;
32
+ configManager: ConfigManager;
31
33
  }
32
34
  export declare function getLanguageService(path: string, workspaceUris: string[], docContext: LanguageServiceDocumentContext): Promise<LanguageServiceContainer>;
33
35
  export declare function forAllLanguageServices(cb: (service: LanguageServiceContainer) => any): Promise<void>;
@@ -53,6 +53,10 @@ exports.forAllLanguageServices = forAllLanguageServices;
53
53
  */
54
54
  async function getLanguageServiceForTsconfig(tsconfigPath, docContext, workspaceUris) {
55
55
  let service;
56
+ if (docContext.configManager.shouldRefreshTSServices) {
57
+ services.clear();
58
+ docContext.configManager.shouldRefreshTSServices = false;
59
+ }
56
60
  if (services.has(tsconfigPath)) {
57
61
  service = await services.get(tsconfigPath);
58
62
  }
@@ -69,26 +73,32 @@ async function createLanguageService(tsconfigPath, docContext, workspaceUris) {
69
73
  const workspacePaths = workspaceUris.map((uri) => (0, utils_1.urlToPath)(uri));
70
74
  const workspacePath = workspacePaths.find((uri) => tsconfigRoot.startsWith(uri)) || workspacePaths[0];
71
75
  const astroVersion = (0, utils_1.getUserAstroVersion)(workspacePath);
76
+ const config = (await docContext.configManager.getConfig('astro.typescript', workspacePath)) ?? {};
77
+ const allowArbitraryAttrs = config.allowArbitraryAttributes ?? false;
72
78
  // `raw` here represent the tsconfig merged with any extended config
73
79
  const { compilerOptions, fileNames: files, raw: fullConfig } = getParsedTSConfig();
74
80
  let projectVersion = 0;
75
81
  const snapshotManager = new SnapshotManager_1.SnapshotManager(docContext.globalSnapshotManager, files, fullConfig, tsconfigRoot || process.cwd());
76
82
  const astroModuleLoader = (0, module_loader_1.createAstroModuleLoader)(getScriptSnapshot, compilerOptions);
83
+ let languageServerDirectory;
84
+ try {
85
+ languageServerDirectory = (0, path_1.dirname)(require.resolve('@astrojs/language-server'));
86
+ }
87
+ catch (e) {
88
+ languageServerDirectory = __dirname;
89
+ }
77
90
  const scriptFileNames = [];
78
91
  // Before Astro 1.0, JSX definitions were inside of the language-server instead of inside Astro
79
92
  // TODO: Remove this and astro-jsx.d.ts in types when we consider having support for Astro < 1.0 unnecessary
80
93
  if (astroVersion.major === 0 || astroVersion.full === '1.0.0-beta.0') {
81
- let languageServerDirectory;
82
- try {
83
- languageServerDirectory = (0, path_1.dirname)(require.resolve('@astrojs/language-server'));
84
- }
85
- catch (e) {
86
- languageServerDirectory = __dirname;
87
- }
88
94
  const astroTSXFile = typescript_1.default.sys.resolvePath((0, path_1.resolve)(languageServerDirectory, '../types/astro-jsx.d.ts'));
89
95
  scriptFileNames.push(astroTSXFile);
90
96
  console.warn("Version lower than 1.0 detected, using internal types instead of Astro's");
91
97
  }
98
+ if (allowArbitraryAttrs) {
99
+ const arbitraryAttrsDTS = typescript_1.default.sys.resolvePath((0, path_1.resolve)(languageServerDirectory, '../types/arbitrary-attrs.d.ts'));
100
+ scriptFileNames.push(arbitraryAttrsDTS);
101
+ }
92
102
  const host = {
93
103
  getNewLine: () => typescript_1.default.sys.newLine,
94
104
  useCaseSensitiveFileNames: () => typescript_1.default.sys.useCaseSensitiveFileNames,
@@ -141,11 +151,10 @@ async function createLanguageService(tsconfigPath, docContext, workspaceUris) {
141
151
  }
142
152
  const newSnapshot = DocumentSnapshotUtils.createFromDocument(document);
143
153
  snapshotManager.set(filePath, newSnapshot);
144
- document.scriptTags.forEach((scriptTag, index) => {
145
- const scriptFilePath = filePath + `.__script${index}.js`;
146
- const scriptSnapshot = new DocumentSnapshot_1.ScriptTagDocumentSnapshot(scriptTag, document, scriptFilePath);
147
- snapshotManager.set(scriptFilePath, scriptSnapshot);
148
- newSnapshot.scriptTagSnapshots?.push(scriptSnapshot);
154
+ const scriptTagSnapshots = createScriptTagsSnapshots(filePath, document);
155
+ scriptTagSnapshots.forEach((snapshot) => {
156
+ snapshotManager.set(snapshot.filePath, snapshot);
157
+ newSnapshot.scriptTagSnapshots?.push(snapshot);
149
158
  });
150
159
  if (prevSnapshot && prevSnapshot.scriptKind !== newSnapshot.scriptKind) {
151
160
  // Restart language service as it doesn't handle script kind changes.
@@ -176,11 +185,10 @@ async function createLanguageService(tsconfigPath, docContext, workspaceUris) {
176
185
  // If we needed to create an Astro snapshot, also create its script tags snapshots
177
186
  if ((0, utils_2.isAstroFilePath)(fileName)) {
178
187
  const document = doc.parent;
179
- document.scriptTags.forEach((scriptTag, index) => {
180
- const scriptFilePath = fileName + `.__script${index}.js`;
181
- const scriptSnapshot = new DocumentSnapshot_1.ScriptTagDocumentSnapshot(scriptTag, document, scriptFilePath);
182
- snapshotManager.set(scriptFilePath, scriptSnapshot);
183
- doc.scriptTagSnapshots?.push(scriptSnapshot);
188
+ const scriptTagSnapshots = createScriptTagsSnapshots(fileName, document);
189
+ scriptTagSnapshots.forEach((snapshot) => {
190
+ snapshotManager.set(snapshot.filePath, snapshot);
191
+ doc.scriptTagSnapshots?.push(snapshot);
184
192
  });
185
193
  }
186
194
  return doc;
@@ -202,6 +210,14 @@ async function createLanguageService(tsconfigPath, docContext, workspaceUris) {
202
210
  }
203
211
  snapshotManager.updateNonAstroFile(fileName, changes);
204
212
  }
213
+ function createScriptTagsSnapshots(fileName, document) {
214
+ return document.scriptTags.map((scriptTag, index) => {
215
+ const scriptTagLanguage = (0, utils_2.getScriptTagLanguage)(scriptTag);
216
+ const scriptFilePath = fileName + `.__script${index}.${scriptTagLanguage}`;
217
+ const scriptSnapshot = new DocumentSnapshot_1.ScriptTagDocumentSnapshot(scriptTag, document, scriptFilePath);
218
+ return scriptSnapshot;
219
+ });
220
+ }
205
221
  function getParsedTSConfig() {
206
222
  let configJson = (tsconfigPath && typescript_1.default.readConfigFile(tsconfigPath, typescript_1.default.sys.readFile).config) || {};
207
223
  // If our user has types in their config but it doesn't include the types needed for Astro, add them to the config
@@ -16,12 +16,12 @@ const utils_1 = require("../../utils");
16
16
  function replaceLinks(text) {
17
17
  return (text
18
18
  // Http(s) links
19
- .replace(/\{@(link|linkplain|linkcode) (https?:\/\/[^ |}]+?)(?:[| ]([^{}\n]+?))?\}/gi, (_, tag, link, text) => {
19
+ .replace(/\{@(link|linkplain|linkcode) (https?:\/\/[^ |}]+?)(?:[| ]([^{}\n]+?))?\}/gi, (_, tag, link, label) => {
20
20
  switch (tag) {
21
21
  case 'linkcode':
22
- return `[\`${text ? text.trim() : link}\`](${link})`;
22
+ return `[\`${label ? label.trim() : link}\`](${link})`;
23
23
  default:
24
- return `[${text ? text.trim() : link}](${link})`;
24
+ return `[${label ? label.trim() : link}](${link})`;
25
25
  }
26
26
  }));
27
27
  }
@@ -1,6 +1,6 @@
1
1
  import ts from 'typescript';
2
2
  import { CompletionItemKind, DiagnosticSeverity, Position, Range, SymbolKind, SemanticTokensLegend } from 'vscode-languageserver';
3
- import { AstroDocument } from '../../core/documents';
3
+ import { AstroDocument, TagInformation } from '../../core/documents';
4
4
  import { AstroSnapshot, ScriptTagDocumentSnapshot, SnapshotFragment } from './snapshots/DocumentSnapshot';
5
5
  import { Node } from 'vscode-html-languageservice';
6
6
  export declare const enum TokenType {
@@ -59,6 +59,10 @@ export declare function toVirtualFilePath(filePath: string): string;
59
59
  export declare function toRealAstroFilePath(filePath: string): string;
60
60
  export declare function ensureRealAstroFilePath(filePath: string): string;
61
61
  export declare function ensureRealFilePath(filePath: string): string;
62
+ /**
63
+ * Return if a script tag is TypeScript or JavaScript
64
+ */
65
+ export declare function getScriptTagLanguage(scriptTag: TagInformation): 'js' | 'ts';
62
66
  export declare function getScriptTagSnapshot(snapshot: AstroSnapshot, document: AstroDocument, tagInfo: Node | {
63
67
  start: number;
64
68
  end: number;
@@ -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.getScriptTagSnapshot = 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;
6
+ exports.getScriptTagSnapshot = exports.getScriptTagLanguage = 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");
@@ -346,9 +346,21 @@ function ensureRealFilePath(filePath) {
346
346
  }
347
347
  }
348
348
  exports.ensureRealFilePath = ensureRealFilePath;
349
+ /**
350
+ * Return if a script tag is TypeScript or JavaScript
351
+ */
352
+ function getScriptTagLanguage(scriptTag) {
353
+ // Using any kind of attributes on the script tag will disable hoisting, so we can just check if there's any
354
+ if (Object.entries(scriptTag.attributes).length === 0) {
355
+ return 'ts';
356
+ }
357
+ return 'js';
358
+ }
359
+ exports.getScriptTagLanguage = getScriptTagLanguage;
349
360
  function getScriptTagSnapshot(snapshot, document, tagInfo, position) {
350
361
  const index = document.scriptTags.findIndex((value) => value.container.start == tagInfo.start);
351
- const scriptFilePath = snapshot.filePath + `.__script${index}.js`;
362
+ const scriptTagLanguage = getScriptTagLanguage(document.scriptTags[index]);
363
+ const scriptFilePath = snapshot.filePath + `.__script${index}.${scriptTagLanguage}`;
352
364
  const scriptTagSnapshot = snapshot.scriptTagSnapshots[index];
353
365
  let offset = 0;
354
366
  if (position) {
package/dist/server.js CHANGED
@@ -155,6 +155,7 @@ function startLanguageServer(connection) {
155
155
  else {
156
156
  configManager.updateGlobalConfig(change.settings.astro || ConfigManager_1.defaultLSConfig);
157
157
  }
158
+ updateAllDiagnostics();
158
159
  });
159
160
  // Documents
160
161
  connection.onDidOpenTextDocument((params) => {
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Position, Range } from 'vscode-languageserver';
2
- import { Node } from 'vscode-html-languageservice';
3
2
  /** Normalizes a document URI */
4
3
  export declare function normalizeUri(uri: string): string;
5
4
  /**
@@ -16,6 +15,17 @@ export declare function pathToUrl(path: string): string;
16
15
  * (bar or bar.astro in this example).
17
16
  */
18
17
  export declare function getLastPartOfPath(path: string): string;
18
+ /**
19
+ * Return an element in an object using a path as a string (ex: `astro.typescript.format` will return astro['typescript']['format']).
20
+ * From: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_get
21
+ */
22
+ export declare function get<T>(obj: Record<string, any>, path: string): T | undefined;
23
+ /**
24
+ * Performs a deep merge of objects and returns new object. Does not modify
25
+ * objects (immutable) and merges arrays via concatenation.
26
+ * From: https://stackoverflow.com/a/48218209
27
+ */
28
+ export declare function mergeDeep(...objects: Record<string, any>[]): Record<string, any>;
19
29
  /**
20
30
  * Transform a string into PascalCase
21
31
  */
@@ -24,18 +34,15 @@ export declare function toPascalCase(string: string): string;
24
34
  * Function to modify each line of a text, preserving the line break style (`\n` or `\r\n`)
25
35
  */
26
36
  export declare function modifyLines(text: string, replacementFn: (line: string, lineIdx: number) => string): string;
27
- /**
28
- * Return true if a specific node could be a component.
29
- * 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
30
- */
31
- export declare function isPossibleComponent(node: Node): boolean;
32
- /** Flattens an array */
33
- export declare function flatten<T>(arr: T[][]): T[];
34
37
  /** Clamps a number between min and max */
35
38
  export declare function clamp(num: number, min: number, max: number): number;
36
39
  export declare function isNotNullOrUndefined<T>(val: T | undefined | null): val is T;
37
40
  export declare function isInRange(range: Range, positionToTest: Position): boolean;
38
41
  export declare function isBeforeOrEqualToPosition(position: Position, positionToTest: Position): boolean;
42
+ /**
43
+ * Like str.lastIndexOf, but for regular expressions. Note that you need to provide the g-flag to your RegExp!
44
+ */
45
+ export declare function regexLastIndexOf(text: string, regex: RegExp, endPos?: number): number;
39
46
  /**
40
47
  * Get all matches of a regexp.
41
48
  */
package/dist/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUserAstroVersion = exports.debounceThrottle = exports.debounceSameArg = exports.getRegExpMatches = exports.isBeforeOrEqualToPosition = exports.isInRange = exports.isNotNullOrUndefined = exports.clamp = exports.flatten = exports.isPossibleComponent = exports.modifyLines = exports.toPascalCase = exports.getLastPartOfPath = exports.pathToUrl = exports.urlToPath = exports.normalizePath = exports.normalizeUri = void 0;
3
+ exports.getUserAstroVersion = exports.debounceThrottle = exports.debounceSameArg = exports.getRegExpMatches = exports.regexLastIndexOf = exports.isBeforeOrEqualToPosition = exports.isInRange = exports.isNotNullOrUndefined = exports.clamp = exports.modifyLines = exports.toPascalCase = exports.mergeDeep = exports.get = exports.getLastPartOfPath = exports.pathToUrl = exports.urlToPath = exports.normalizePath = exports.normalizeUri = void 0;
4
4
  const vscode_uri_1 = require("vscode-uri");
5
5
  /** Normalizes a document URI */
6
6
  function normalizeUri(uri) {
@@ -37,6 +37,44 @@ function getLastPartOfPath(path) {
37
37
  return path.replace(/\\/g, '/').split('/').pop() || '';
38
38
  }
39
39
  exports.getLastPartOfPath = getLastPartOfPath;
40
+ /**
41
+ * Return an element in an object using a path as a string (ex: `astro.typescript.format` will return astro['typescript']['format']).
42
+ * From: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_get
43
+ */
44
+ function get(obj, path) {
45
+ const travel = (regexp) => String.prototype.split
46
+ .call(path, regexp)
47
+ .filter(Boolean)
48
+ .reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj);
49
+ const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/);
50
+ return result === undefined ? undefined : result;
51
+ }
52
+ exports.get = get;
53
+ /**
54
+ * Performs a deep merge of objects and returns new object. Does not modify
55
+ * objects (immutable) and merges arrays via concatenation.
56
+ * From: https://stackoverflow.com/a/48218209
57
+ */
58
+ function mergeDeep(...objects) {
59
+ const isObject = (obj) => obj && typeof obj === 'object';
60
+ return objects.reduce((prev, obj) => {
61
+ Object.keys(obj).forEach((key) => {
62
+ const pVal = prev[key];
63
+ const oVal = obj[key];
64
+ if (Array.isArray(pVal) && Array.isArray(oVal)) {
65
+ prev[key] = pVal.concat(...oVal);
66
+ }
67
+ else if (isObject(pVal) && isObject(oVal)) {
68
+ prev[key] = mergeDeep(pVal, oVal);
69
+ }
70
+ else {
71
+ prev[key] = oVal;
72
+ }
73
+ });
74
+ return prev;
75
+ }, {});
76
+ }
77
+ exports.mergeDeep = mergeDeep;
40
78
  /**
41
79
  * Transform a string into PascalCase
42
80
  */
@@ -62,19 +100,6 @@ function modifyLines(text, replacementFn) {
62
100
  .join('\r\n');
63
101
  }
64
102
  exports.modifyLines = modifyLines;
65
- /**
66
- * Return true if a specific node could be a component.
67
- * 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
68
- */
69
- function isPossibleComponent(node) {
70
- return !!node.tag?.[0].match(/[A-Z]/) || !!node.tag?.match(/.+[.][A-Z]/);
71
- }
72
- exports.isPossibleComponent = isPossibleComponent;
73
- /** Flattens an array */
74
- function flatten(arr) {
75
- return arr.reduce((all, item) => [...all, ...item], []);
76
- }
77
- exports.flatten = flatten;
78
103
  /** Clamps a number between min and max */
79
104
  function clamp(num, min, max) {
80
105
  return Math.max(min, Math.min(max, num));
@@ -93,6 +118,25 @@ function isBeforeOrEqualToPosition(position, positionToTest) {
93
118
  (positionToTest.line === position.line && positionToTest.character <= position.character));
94
119
  }
95
120
  exports.isBeforeOrEqualToPosition = isBeforeOrEqualToPosition;
121
+ /**
122
+ * Like str.lastIndexOf, but for regular expressions. Note that you need to provide the g-flag to your RegExp!
123
+ */
124
+ function regexLastIndexOf(text, regex, endPos) {
125
+ if (endPos === undefined) {
126
+ endPos = text.length;
127
+ }
128
+ else if (endPos < 0) {
129
+ endPos = 0;
130
+ }
131
+ const stringToWorkWith = text.substring(0, endPos + 1);
132
+ let lastIndexOf = -1;
133
+ let result = null;
134
+ while ((result = regex.exec(stringToWorkWith)) !== null) {
135
+ lastIndexOf = result.index;
136
+ }
137
+ return lastIndexOf;
138
+ }
139
+ exports.regexLastIndexOf = regexLastIndexOf;
96
140
  /**
97
141
  * Get all matches of a regexp.
98
142
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/language-server",
3
- "version": "0.19.3",
3
+ "version": "0.19.6",
4
4
  "author": "withastro",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -22,7 +22,6 @@
22
22
  "@astrojs/vue-language-integration": "^0.1.1",
23
23
  "@astrojs/svelte-language-integration": "^0.1.6",
24
24
  "@vscode/emmet-helper": "^2.8.4",
25
- "lodash": "^4.17.21",
26
25
  "source-map": "^0.7.3",
27
26
  "typescript": "~4.6.4",
28
27
  "vscode-css-languageservice": "^6.0.1",
@@ -35,7 +34,6 @@
35
34
  },
36
35
  "devDependencies": {
37
36
  "@types/chai": "^4.3.0",
38
- "@types/lodash": "^4.14.179",
39
37
  "@types/mocha": "^9.1.0",
40
38
  "@types/sinon": "^10.0.11",
41
39
  "astro": "^1.0.0-beta.1",
@@ -0,0 +1,5 @@
1
+ declare namespace astroHTML.JSX {
2
+ interface HTMLAttributes {
3
+ [name: string]: any;
4
+ }
5
+ }