@astrojs/language-server 2.5.2 → 2.5.4

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/dist/check.js CHANGED
@@ -72,9 +72,9 @@ class AstroCheck {
72
72
  // Filter diagnostics based on the logErrors level
73
73
  const fileDiagnosticsToPrint = fileDiagnostics.filter((diag) => {
74
74
  const severity = diag.severity ?? language_server_1.DiagnosticSeverity.Error;
75
- switch (logErrors?.level ?? 'error') {
75
+ switch (logErrors?.level ?? 'hint') {
76
76
  case 'error':
77
- return true;
77
+ return severity <= language_server_1.DiagnosticSeverity.Error;
78
78
  case 'warning':
79
79
  return severity <= language_server_1.DiagnosticSeverity.Warning;
80
80
  case 'hint':
@@ -106,9 +106,10 @@ class AstroCheck {
106
106
  initialize() {
107
107
  this.ts = this.typescriptPath ? require(this.typescriptPath) : require('typescript');
108
108
  const tsconfigPath = this.getTsconfig();
109
+ const astroInstall = (0, utils_js_1.getAstroInstall)([this.workspacePath]);
109
110
  const config = {
110
111
  languages: {
111
- astro: (0, index_js_1.getLanguageModule)((0, utils_js_1.getAstroInstall)([this.workspacePath]), this.ts),
112
+ astro: (0, index_js_1.getLanguageModule)(typeof astroInstall === 'string' ? undefined : astroInstall, this.ts),
112
113
  svelte: (0, svelte_js_1.getSvelteLanguageModule)(),
113
114
  vue: (0, vue_js_1.getVueLanguageModule)(),
114
115
  },
@@ -28,7 +28,7 @@ function safeConvertToTSX(content, options) {
28
28
  code: 1000,
29
29
  location: { file: options.filename, line: 1, column: 1, length: content.length },
30
30
  severity: 1,
31
- text: `The Astro compiler encountered an unknown error while parsing this file. Please create an issue with your code and the error shown in the server's logs: https://github.com/withastro/language-tools/issues`,
31
+ text: `The Astro compiler encountered an unknown error while transform this file to TSX. Please create an issue with your code and the error shown in the server's logs: https://github.com/withastro/language-tools/issues`,
32
32
  },
33
33
  ],
34
34
  };
@@ -111,7 +111,11 @@ class AstroFile {
111
111
  data: language_core_1.FileRangeCapabilities.full,
112
112
  },
113
113
  ];
114
- this.astroMeta = (0, parseAstro_1.getAstroMetadata)(this.snapshot.getText(0, this.snapshot.getLength()));
114
+ this.compilerDiagnostics = [];
115
+ this.astroMeta = (0, parseAstro_1.getAstroMetadata)(this.fileName, this.snapshot.getText(0, this.snapshot.getLength()));
116
+ if (this.astroMeta.diagnostics.length > 0) {
117
+ this.compilerDiagnostics.push(...this.astroMeta.diagnostics);
118
+ }
115
119
  const { htmlDocument, virtualFile: htmlVirtualFile } = (0, parseHTML_1.parseHTML)(this.fileName, this.snapshot, this.astroMeta.frontmatter.status === 'closed'
116
120
  ? this.astroMeta.frontmatter.position.end.offset
117
121
  : 0);
@@ -122,7 +126,7 @@ class AstroFile {
122
126
  this.embeddedFiles = [];
123
127
  this.embeddedFiles.push(htmlVirtualFile);
124
128
  const tsx = (0, astro2tsx_1.astro2tsx)(this.snapshot.getText(0, this.snapshot.getLength()), this.fileName, this.ts, htmlDocument);
125
- this.compilerDiagnostics = tsx.diagnostics;
129
+ this.compilerDiagnostics.push(...tsx.diagnostics);
126
130
  this.embeddedFiles.push(tsx.virtualFile);
127
131
  }
128
132
  }
@@ -1,8 +1,8 @@
1
- import type { ParseResult, Point } from '@astrojs/compiler/types';
1
+ import type { ParseOptions, ParseResult, Point } from '@astrojs/compiler/types';
2
2
  type AstroMetadata = ParseResult & {
3
3
  frontmatter: FrontmatterStatus;
4
4
  };
5
- export declare function getAstroMetadata(input: string, position?: boolean): AstroMetadata;
5
+ export declare function getAstroMetadata(fileName: string, input: string, options?: ParseOptions): AstroMetadata;
6
6
  interface FrontmatterOpen {
7
7
  status: 'open';
8
8
  position: {
@@ -2,14 +2,37 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAstroMetadata = void 0;
4
4
  const sync_1 = require("@astrojs/compiler/sync");
5
- function getAstroMetadata(input, position = true) {
6
- const parseResult = (0, sync_1.parse)(input, { position: position });
5
+ function getAstroMetadata(fileName, input, options = { position: true }) {
6
+ const parseResult = safeParseAst(fileName, input, options);
7
7
  return {
8
8
  ...parseResult,
9
9
  frontmatter: getFrontmatterStatus(parseResult.ast, input),
10
10
  };
11
11
  }
12
12
  exports.getAstroMetadata = getAstroMetadata;
13
+ function safeParseAst(fileName, input, parseOptions) {
14
+ try {
15
+ const parseResult = (0, sync_1.parse)(input, parseOptions);
16
+ return parseResult;
17
+ }
18
+ catch (e) {
19
+ console.error(`There was an error parsing ${fileName}'s AST. An empty AST will be returned instead to avoid breaking the server. Please create an issue: https://github.com/withastro/language-tools/issues\nError: ${e}.`);
20
+ return {
21
+ ast: {
22
+ type: 'root',
23
+ children: [],
24
+ },
25
+ diagnostics: [
26
+ {
27
+ code: 1000,
28
+ location: { file: fileName, line: 1, column: 1, length: input.length },
29
+ severity: 1,
30
+ text: `The Astro compiler encountered an unknown error while parsing this file's AST. Please create an issue with your code and the error shown in the server's logs: https://github.com/withastro/language-tools/issues`,
31
+ },
32
+ ],
33
+ };
34
+ }
35
+ }
13
36
  function getFrontmatterStatus(ast, text) {
14
37
  if (!ast.children || (ast.children && ast.children.length === 0)) {
15
38
  return {
@@ -29,32 +29,35 @@ const language_core_1 = require("@volar/language-core");
29
29
  const SourceMap = __importStar(require("@volar/source-map"));
30
30
  const muggle = __importStar(require("muggle-string"));
31
31
  function extractScriptTags(fileName, snapshot, htmlDocument, ast) {
32
- const embeddedJSFiles = findIsolatedScripts(fileName, snapshot, htmlDocument.roots);
32
+ const embeddedJSFiles = findModuleScripts(fileName, snapshot, htmlDocument.roots);
33
33
  const javascriptContexts = [
34
- ...findInlineScripts(htmlDocument, snapshot),
34
+ ...findClassicScripts(htmlDocument, snapshot),
35
35
  ...findEventAttributes(ast),
36
36
  ].sort((a, b) => a.startOffset - b.startOffset);
37
37
  if (javascriptContexts.length > 0) {
38
+ // classic scripts share the same scope
39
+ // merging them brings about redeclaration errors
38
40
  embeddedJSFiles.push(mergeJSContexts(fileName, javascriptContexts));
39
41
  }
40
42
  return embeddedJSFiles;
41
43
  }
42
44
  exports.extractScriptTags = extractScriptTags;
43
- function isIsolatedScriptTag(scriptTag) {
44
- // Using any kind of attributes on the script tag will disable hoisting
45
- if (!scriptTag.attributes ||
46
- (scriptTag.attributes && Object.entries(scriptTag.attributes).length === 0) ||
47
- scriptTag.attributes['type']?.includes('module')) {
48
- return true;
49
- }
50
- return false;
45
+ function getScriptType(scriptTag) {
46
+ // script tags without attributes are processed and converted into module scripts
47
+ if (!scriptTag.attributes || Object.entries(scriptTag.attributes).length === 0)
48
+ return 'processed module';
49
+ // even when it is not processed by vite, scripts with type=module remain modules
50
+ if (scriptTag.attributes['type']?.includes('module') === true)
51
+ return 'module';
52
+ // whenever there are attributes, is:inline is implied and in the absence of type=module, the script is classic
53
+ return 'classic';
51
54
  }
52
55
  /**
53
56
  * Get all the isolated scripts in the HTML document
54
57
  * Isolated scripts are scripts that are hoisted by Astro and as such, are isolated from the rest of the code because of the implicit `type="module"`
55
58
  * All the isolated scripts are passed to the TypeScript language server as separate `.mts` files.
56
59
  */
57
- function findIsolatedScripts(fileName, snapshot, roots) {
60
+ function findModuleScripts(fileName, snapshot, roots) {
58
61
  const embeddedScripts = [];
59
62
  let scriptIndex = 0;
60
63
  getEmbeddedScriptsInNodes(roots);
@@ -63,10 +66,11 @@ function findIsolatedScripts(fileName, snapshot, roots) {
63
66
  if (node.tag === 'script' &&
64
67
  node.startTagEnd !== undefined &&
65
68
  node.endTagStart !== undefined &&
66
- isIsolatedScriptTag(node)) {
69
+ getScriptType(node) !== 'classic') {
67
70
  const scriptText = snapshot.getText(node.startTagEnd, node.endTagStart);
71
+ const extension = getScriptType(node) === 'processed module' ? 'mts' : 'mjs';
68
72
  embeddedScripts.push({
69
- fileName: fileName + `.${scriptIndex}.mts`,
73
+ fileName: fileName + `.${scriptIndex}.${extension}`,
70
74
  kind: language_core_1.FileKind.TypeScriptHostFile,
71
75
  snapshot: {
72
76
  getText: (start, end) => scriptText.substring(start, end),
@@ -104,7 +108,7 @@ function findIsolatedScripts(fileName, snapshot, roots) {
104
108
  * Inline scripts are scripts that are not hoisted by Astro and as such, are not isolated from the rest of the code.
105
109
  * All the inline scripts are concatenated into a single `.mjs` file and passed to the TypeScript language server.
106
110
  */
107
- function findInlineScripts(htmlDocument, snapshot) {
111
+ function findClassicScripts(htmlDocument, snapshot) {
108
112
  const inlineScripts = [];
109
113
  getInlineScriptsInNodes(htmlDocument.roots);
110
114
  function getInlineScriptsInNodes(nodes) {
@@ -113,7 +117,7 @@ function findInlineScripts(htmlDocument, snapshot) {
113
117
  node.startTagEnd !== undefined &&
114
118
  node.endTagStart !== undefined &&
115
119
  !isJSON(node.attributes?.type) &&
116
- !isIsolatedScriptTag(node)) {
120
+ getScriptType(node) === 'classic') {
117
121
  const scriptText = snapshot.getText(node.startTagEnd, node.endTagStart);
118
122
  inlineScripts.push({
119
123
  startOffset: node.startTagEnd,
@@ -153,7 +157,10 @@ function findEventAttributes(ast) {
153
157
  const eventAttribute = child.attributes.find((attr) => htmlEventAttributes.includes(attr.name) && attr.kind === 'quoted');
154
158
  if (eventAttribute && eventAttribute.position) {
155
159
  eventAttrs.push({
156
- content: eventAttribute.value,
160
+ // Add a semicolon to the end of the event attribute to attempt to prevent errors from spreading to the rest of the document
161
+ // This is not perfect, but it's better than nothing
162
+ // See: https://github.com/microsoft/vscode/blob/e8e04769ec817a3374c3eaa26a08d3ae491820d5/extensions/html-language-features/server/src/modes/embeddedSupport.ts#L192
163
+ content: eventAttribute.value + ';',
157
164
  startOffset: eventAttribute.position.start.offset + `${eventAttribute.name}="`.length,
158
165
  });
159
166
  }
@@ -39,14 +39,18 @@ const plugin = (initOptions, modules) => ({
39
39
  resolveConfig(config, ctx) {
40
40
  config.languages ??= {};
41
41
  if (ctx) {
42
- const astroInstall = (0, utils_js_1.getAstroInstall)([ctx.project.rootUri.fsPath]);
43
- if (!astroInstall) {
42
+ const nearestPackageJson = modules.typescript?.findConfigFile(ctx.project.rootUri.fsPath, modules.typescript.sys.fileExists, 'package.json');
43
+ const astroInstall = (0, utils_js_1.getAstroInstall)([ctx.project.rootUri.fsPath], {
44
+ nearestPackageJson: nearestPackageJson,
45
+ readDirectory: modules.typescript.sys.readDirectory,
46
+ });
47
+ if (astroInstall === 'not-found') {
44
48
  ctx.server.connection.sendNotification(node_1.ShowMessageNotification.type, {
45
49
  message: `Couldn't find Astro in workspace "${ctx.project.rootUri.fsPath}". Experience might be degraded. For the best experience, please make sure Astro is installed into your project and restart the language server.`,
46
50
  type: node_1.MessageType.Warning,
47
51
  });
48
52
  }
49
- config.languages.astro = (0, core_1.getLanguageModule)(astroInstall, modules.typescript);
53
+ config.languages.astro = (0, core_1.getLanguageModule)(typeof astroInstall === 'string' ? undefined : astroInstall, modules.typescript);
50
54
  config.languages.vue = (0, vue_js_1.getVueLanguageModule)();
51
55
  config.languages.svelte = (0, svelte_js_1.getSvelteLanguageModule)();
52
56
  }
@@ -9,4 +9,4 @@ export declare enum DiagnosticCodes {
9
9
  JSX_NO_CLOSING_TAG = 17008,
10
10
  JSX_ELEMENT_NO_CALL = 2604
11
11
  }
12
- export declare function enhancedProvideSemanticDiagnostics(originalDiagnostics: Diagnostic[], documentLineCount: number): Diagnostic[];
12
+ export declare function enhancedProvideSemanticDiagnostics(originalDiagnostics: Diagnostic[], astroLineCount?: number | undefined): Diagnostic[];
@@ -14,20 +14,31 @@ var DiagnosticCodes;
14
14
  DiagnosticCodes[DiagnosticCodes["JSX_NO_CLOSING_TAG"] = 17008] = "JSX_NO_CLOSING_TAG";
15
15
  DiagnosticCodes[DiagnosticCodes["JSX_ELEMENT_NO_CALL"] = 2604] = "JSX_ELEMENT_NO_CALL";
16
16
  })(DiagnosticCodes || (exports.DiagnosticCodes = DiagnosticCodes = {}));
17
- function enhancedProvideSemanticDiagnostics(originalDiagnostics, documentLineCount) {
17
+ function enhancedProvideSemanticDiagnostics(originalDiagnostics, astroLineCount) {
18
18
  const diagnostics = originalDiagnostics
19
- .filter((diagnostic) => diagnostic.range.start.line <= documentLineCount &&
19
+ .filter((diagnostic) => (astroLineCount ? diagnostic.range.start.line <= astroLineCount : true) &&
20
20
  isNoCantReturnOutsideFunction(diagnostic) &&
21
21
  isNoIsolatedModuleError(diagnostic) &&
22
22
  isNoJsxCannotHaveMultipleAttrsError(diagnostic))
23
- .map(enhanceIfNecessary);
23
+ .map((diag) => astroLineCount ? generalEnhancements(astroEnhancements(diag)) : generalEnhancements(diag));
24
24
  return diagnostics;
25
25
  }
26
26
  exports.enhancedProvideSemanticDiagnostics = enhancedProvideSemanticDiagnostics;
27
+ // General enhancements that apply to all files
28
+ function generalEnhancements(diagnostic) {
29
+ if (diagnostic.code === DiagnosticCodes.CANNOT_FIND_MODULE &&
30
+ diagnostic.message.includes('astro:content')) {
31
+ diagnostic.message +=
32
+ "\n\nIf you're using content collections, make sure to run `astro dev`, `astro build` or `astro sync` to first generate the types so you can import from them. If you already ran one of those commands, restarting the language server might be necessary in order for the change to take effect.";
33
+ return diagnostic;
34
+ }
35
+ return diagnostic;
36
+ }
27
37
  /**
28
- * Some diagnostics have JSX-specific nomenclature or unclear description. Enhance them for more clarity.
38
+ * Astro-specific enhancements. For instance, when the user tries to import a component from a framework that is not installed
39
+ * or a difference with JSX needing a different error message
29
40
  */
30
- function enhanceIfNecessary(diagnostic) {
41
+ function astroEnhancements(diagnostic) {
31
42
  // When the language integrations are not installed, the content of the imported snapshot is empty
32
43
  // As such, it triggers the "is not a module error", which we can enhance with a more helpful message for the related framework
33
44
  if (diagnostic.code === DiagnosticCodes.IS_NOT_A_MODULE) {
@@ -41,12 +52,6 @@ function enhanceIfNecessary(diagnostic) {
41
52
  }
42
53
  return diagnostic;
43
54
  }
44
- if (diagnostic.code === DiagnosticCodes.CANNOT_FIND_MODULE &&
45
- diagnostic.message.includes('astro:content')) {
46
- diagnostic.message +=
47
- "\n\nIf you're using content collections, make sure to run `astro dev`, `astro build` or `astro sync` to first generate the types so you can import from them. If you already ran one of those commands, restarting the language server might be necessary in order for the change to take effect.";
48
- return diagnostic;
49
- }
50
55
  // JSX element has no closing tag. JSX -> HTML
51
56
  if (diagnostic.code === DiagnosticCodes.JSX_NO_CLOSING_TAG) {
52
57
  return {
@@ -112,16 +112,17 @@ const create = () => (context, modules) => {
112
112
  async provideSemanticDiagnostics(document, token) {
113
113
  const [_, source] = context.documents.getVirtualFileByUri(document.uri);
114
114
  const file = source?.root;
115
- if (!(file instanceof index_js_1.AstroFile))
116
- return null;
117
- // If we have compiler errors, our TSX isn't valid so don't bother showing TS errors
118
- if (file.hasCompilationErrors)
119
- return null;
115
+ let astroDocument = undefined;
116
+ if (file instanceof index_js_1.AstroFile) {
117
+ // If we have compiler errors, our TSX isn't valid so don't bother showing TS errors
118
+ if (file.hasCompilationErrors)
119
+ return null;
120
+ astroDocument = context.documents.getDocumentByFileName(file.snapshot, file.sourceFileName);
121
+ }
120
122
  const diagnostics = await typeScriptPlugin.provideSemanticDiagnostics(document, token);
121
123
  if (!diagnostics)
122
124
  return null;
123
- const astroDocument = context.documents.getDocumentByFileName(file.snapshot, file.sourceFileName);
124
- return (0, diagnostics_js_1.enhancedProvideSemanticDiagnostics)(diagnostics, astroDocument.lineCount);
125
+ return (0, diagnostics_js_1.enhancedProvideSemanticDiagnostics)(diagnostics, astroDocument?.lineCount);
125
126
  },
126
127
  };
127
128
  };
package/dist/utils.d.ts CHANGED
@@ -7,4 +7,7 @@ export interface AstroInstall {
7
7
  patch: number;
8
8
  };
9
9
  }
10
- export declare function getAstroInstall(basePaths: string[]): AstroInstall | undefined;
10
+ export declare function getAstroInstall(basePaths: string[], checkForAstro?: {
11
+ nearestPackageJson: string | undefined;
12
+ readDirectory: typeof import('typescript/lib/tsserverlibrary.js').sys.readDirectory;
13
+ }): AstroInstall | 'not-an-astro-project' | 'not-found';
package/dist/utils.js CHANGED
@@ -26,9 +26,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.getAstroInstall = void 0;
27
27
  const path = __importStar(require("node:path"));
28
28
  const importPackage_js_1 = require("./importPackage.js");
29
- function getAstroInstall(basePaths) {
29
+ function getAstroInstall(basePaths, checkForAstro) {
30
30
  let astroPath;
31
31
  let version;
32
+ if (checkForAstro && checkForAstro.nearestPackageJson) {
33
+ basePaths.push(path.dirname(checkForAstro.nearestPackageJson));
34
+ let deps = new Set();
35
+ try {
36
+ const packageJSON = require(checkForAstro.nearestPackageJson);
37
+ [
38
+ ...Object.keys(packageJSON.dependencies ?? {}),
39
+ ...Object.keys(packageJSON.devDependencies ?? {}),
40
+ ...Object.keys(packageJSON.peerDependencies ?? {}),
41
+ ].forEach((dep) => deps.add(dep));
42
+ }
43
+ catch { }
44
+ if (!deps.has('astro')) {
45
+ const directoryContent = checkForAstro.readDirectory(path.dirname(checkForAstro.nearestPackageJson), ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'], undefined, undefined, 1);
46
+ if (!directoryContent.some((file) => path.basename(file).startsWith('astro.config'))) {
47
+ return 'not-an-astro-project';
48
+ }
49
+ }
50
+ }
32
51
  try {
33
52
  astroPath = (0, importPackage_js_1.getPackagePath)('astro', basePaths);
34
53
  if (!astroPath) {
@@ -48,9 +67,12 @@ function getAstroInstall(basePaths) {
48
67
  catch (e) {
49
68
  // If we still couldn't find it, it probably just doesn't exist
50
69
  console.error(`${basePaths[0]} seems to be an Astro project, but we couldn't find Astro or Astro is not installed`);
51
- return undefined;
70
+ return 'not-found';
52
71
  }
53
72
  }
73
+ if (!version) {
74
+ return 'not-found';
75
+ }
54
76
  let [major, minor, patch] = version.split('.');
55
77
  if (patch.includes('-')) {
56
78
  const patchParts = patch.split('-');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/language-server",
3
- "version": "2.5.2",
3
+ "version": "2.5.4",
4
4
  "author": "withastro",
5
5
  "license": "MIT",
6
6
  "repository": {