@markuplint/language-server 5.4.0 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/const.d.ts CHANGED
@@ -39,3 +39,13 @@ export declare const BUNDLED_PARSER_LOAD_ERROR = "The markuplint bundled with th
39
39
  * `{2*}` the working directory.
40
40
  */
41
41
  export declare const BUNDLED_PARSER_LOAD_COMPAT_ERROR = "The markuplint bundled with the language server (v{0}) cannot load {1*} from {2*} because the markuplint installed there is incompatible with Node.js 22+. Upgrade it to markuplint@4.10.0 or later, then restart the language server.";
42
+ /**
43
+ * Error message shown when the configuration needs `@markuplint/pretenders` (`pretenders.scan`, `pretenders.auto`,
44
+ * or a pretender that has a `filePath`) and the markuplint bundled with the language server is the one in use.
45
+ * markuplint does not install `@markuplint/pretenders` and the extension does not bundle it, and the bundled
46
+ * `@markuplint/file-resolver` imports it relative to itself, so installing it in the workspace is not enough
47
+ * until markuplint itself is installed there. The message of the error that markuplint raises tells to install
48
+ * it in the project, which does not fix this case.
49
+ * Displayed as a popup via `errorToPopup`. `{0}` is the bundled version, `{1*}` the working directory.
50
+ */
51
+ export declare const BUNDLED_PRETENDERS_NOT_FOUND_ERROR = "The markuplint bundled with the language server (v{0}) does not include @markuplint/pretenders, which your configuration needs. Install markuplint and @markuplint/pretenders in {1*}, then restart the language server so that it can use the local installation.";
package/lib/const.js CHANGED
@@ -42,3 +42,13 @@ export const BUNDLED_PARSER_LOAD_ERROR = 'The markuplint bundled with the langua
42
42
  * `{2*}` the working directory.
43
43
  */
44
44
  export const BUNDLED_PARSER_LOAD_COMPAT_ERROR = 'The markuplint bundled with the language server (v{0}) cannot load {1*} from {2*} because the markuplint installed there is incompatible with Node.js 22+. Upgrade it to markuplint@4.10.0 or later, then restart the language server.';
45
+ /**
46
+ * Error message shown when the configuration needs `@markuplint/pretenders` (`pretenders.scan`, `pretenders.auto`,
47
+ * or a pretender that has a `filePath`) and the markuplint bundled with the language server is the one in use.
48
+ * markuplint does not install `@markuplint/pretenders` and the extension does not bundle it, and the bundled
49
+ * `@markuplint/file-resolver` imports it relative to itself, so installing it in the workspace is not enough
50
+ * until markuplint itself is installed there. The message of the error that markuplint raises tells to install
51
+ * it in the project, which does not fix this case.
52
+ * Displayed as a popup via `errorToPopup`. `{0}` is the bundled version, `{1*}` the working directory.
53
+ */
54
+ export const BUNDLED_PRETENDERS_NOT_FOUND_ERROR = 'The markuplint bundled with the language server (v{0}) does not include @markuplint/pretenders, which your configuration needs. Install markuplint and @markuplint/pretenders in {1*}, then restart the language server so that it can use the local installation.';
@@ -1,3 +1,4 @@
1
+ import { searchNodeAtLocation } from '@markuplint/ml-core';
1
2
  export async function getAccessibilityByLocation(
2
3
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
3
4
  engine, line, col, ariaVersion) {
@@ -7,7 +8,10 @@ engine, line, col, ariaVersion) {
7
8
  if (!engine?.document) {
8
9
  return null;
9
10
  }
10
- const node = engine.document.searchNodeByLocation(line, col);
11
+ // The engine is the markuplint the user installed, whose `searchNodeByLocation` may be of an older
12
+ // version (one that compared the line and the column apart and took the end of a range in), so the
13
+ // node list is searched here, by the rule of this version.
14
+ const node = searchNodeAtLocation(engine.document.nodeList, line, col);
11
15
  if (!node || !node.is(node.ELEMENT_NODE)) {
12
16
  return null;
13
17
  }
@@ -18,10 +18,20 @@ export type ParserLoadFailureContext = {
18
18
  * @returns The package name (scoped when applicable), or `null` if the message is not a parser load failure
19
19
  */
20
20
  export declare function extractMissingParserName(message: string): string | null;
21
+ /**
22
+ * Whether an error is what markuplint raises for a configuration that needs `@markuplint/pretenders` when it
23
+ * is not installed: an error that says how to install it, with the `ERR_MODULE_NOT_FOUND` of the import as its
24
+ * `cause`. It is reported as a `config-error` of the file (the `lint-error` event of `MLEngine`) rather than
25
+ * as a rejection of `exec()`, so it is told by its cause.
26
+ *
27
+ * @param error - The error of the `lint-error` event, or of a rejection
28
+ * @returns `true` when the cause is the missing `@markuplint/pretenders`
29
+ */
30
+ export declare function isPretendersNotInstalled(error: unknown): boolean;
21
31
  /**
22
32
  * Builds the user-facing explanation for a parser load failure.
23
33
  *
24
- * @param error - The rejection from `MLEngine#exec()`
34
+ * @param error - The rejection from `MLEngine#exec()`, or the error of its `lint-error` event
25
35
  * @param context - Which markuplint module linted the document
26
36
  * @returns The message to show, or `null` if the error is not a parser load failure
27
37
  */
@@ -1,6 +1,7 @@
1
1
  import { isFatalError } from 'markuplint/suppressions';
2
- import { BUNDLED_PARSER_LOAD_COMPAT_ERROR, BUNDLED_PARSER_LOAD_ERROR, PARSER_NOT_FOUND_ERROR } from '../const.js';
2
+ import { BUNDLED_PARSER_LOAD_COMPAT_ERROR, BUNDLED_PARSER_LOAD_ERROR, BUNDLED_PRETENDERS_NOT_FOUND_ERROR, PARSER_NOT_FOUND_ERROR, } from '../const.js';
3
3
  import { t } from '../i18n.js';
4
+ const PRETENDERS_PACKAGE = '@markuplint/pretenders';
4
5
  // `@markuplint/file-resolver` (v3+) rewrites a bare parser specifier that is not importable into an
5
6
  // absolute path under the config's directory before giving up, so the reported specifier is
6
7
  // usually `<configDir>/@markuplint/xxx-parser`, not the name the user wrote.
@@ -39,10 +40,24 @@ function packageNameOf(spec) {
39
40
  }
40
41
  return segments.length === 1 ? name : null;
41
42
  }
43
+ /**
44
+ * Whether an error is what markuplint raises for a configuration that needs `@markuplint/pretenders` when it
45
+ * is not installed: an error that says how to install it, with the `ERR_MODULE_NOT_FOUND` of the import as its
46
+ * `cause`. It is reported as a `config-error` of the file (the `lint-error` event of `MLEngine`) rather than
47
+ * as a rejection of `exec()`, so it is told by its cause.
48
+ *
49
+ * @param error - The error of the `lint-error` event, or of a rejection
50
+ * @returns `true` when the cause is the missing `@markuplint/pretenders`
51
+ */
52
+ export function isPretendersNotInstalled(error) {
53
+ return (error instanceof Error &&
54
+ error.cause instanceof Error &&
55
+ extractMissingParserName(error.cause.message) === PRETENDERS_PACKAGE);
56
+ }
42
57
  /**
43
58
  * Builds the user-facing explanation for a parser load failure.
44
59
  *
45
- * @param error - The rejection from `MLEngine#exec()`
60
+ * @param error - The rejection from `MLEngine#exec()`, or the error of its `lint-error` event
46
61
  * @param context - Which markuplint module linted the document
47
62
  * @returns The message to show, or `null` if the error is not a parser load failure
48
63
  */
@@ -50,6 +65,16 @@ export function describeParserLoadFailure(error, context) {
50
65
  if (!(error instanceof Error)) {
51
66
  return null;
52
67
  }
68
+ if (isPretendersNotInstalled(error)) {
69
+ if (context.isLocalModule) {
70
+ // The message of the error says how to install it, which is the fix for a local markuplint.
71
+ return error.message;
72
+ }
73
+ if (context.fallbackReason === 'import-assertion-compat') {
74
+ return t(BUNDLED_PARSER_LOAD_COMPAT_ERROR, context.version, PRETENDERS_PACKAGE, context.workspace);
75
+ }
76
+ return t(BUNDLED_PRETENDERS_NOT_FOUND_ERROR, context.version, context.workspace);
77
+ }
53
78
  const parser = extractMissingParserName(error.message);
54
79
  if (!parser) {
55
80
  return null;
package/lib/server/v3.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { searchNodeAtLocation } from '@markuplint/ml-core';
2
3
  import { getFilePath } from '../utils/get-file-path.js';
3
4
  import { convertDiagnostics } from './convert-diagnostics.js';
4
5
  const engines = new Map();
@@ -163,7 +164,10 @@ position, ariaVersion) {
163
164
  if (!engine || !engine.document) {
164
165
  return null;
165
166
  }
166
- const node = engine.document.searchNodeByLocation(position.line + 1, position.character);
167
+ // LSP counts the character from 0, the node list the column from 1. The engine is the user's
168
+ // markuplint v3, whose `searchNodeByLocation` takes the end of a range in and compares the line
169
+ // and the column apart, so the node list is searched by the rule of this version.
170
+ const node = searchNodeAtLocation(engine.document.nodeList, position.line + 1, position.character + 1);
167
171
  if (!node || !node.is(node.ELEMENT_NODE)) {
168
172
  return null;
169
173
  }
package/lib/server/v4.js CHANGED
@@ -224,7 +224,8 @@ position, ariaVersion) {
224
224
  if (!engine) {
225
225
  return null;
226
226
  }
227
- const a11y = await getAccessibilityByLocation(engine, position.line + 1, position.character, ariaVersion);
227
+ // LSP counts the character from 0, the node list the column from 1.
228
+ const a11y = await getAccessibilityByLocation(engine, position.line + 1, position.character + 1, ariaVersion);
228
229
  if (!a11y) {
229
230
  return null;
230
231
  }
package/lib/server/v5.js CHANGED
@@ -5,6 +5,7 @@ import { getFilePath } from '../utils/get-file-path.js';
5
5
  import { createQuickFixActions, createFixAllAction } from './code-actions.js';
6
6
  import { convertDiagnostics } from './convert-diagnostics.js';
7
7
  import { getAccessibilityByLocation } from './get-accessibility-by-location.js';
8
+ import { isPretendersNotInstalled } from './parser-load-failure.js';
8
9
  import { applySuppressionsToViolations, loadSuppressions } from './suppression-support.js';
9
10
  const engines = new Map();
10
11
  const fixStates = new Map();
@@ -78,8 +79,18 @@ config, locale, log, diagnosticsLog, sendDiagnostics, notFoundParserError, works
78
79
  engine.on('log', (phase, message) => {
79
80
  log(`[${phase}]: ${message}`, 'trace');
80
81
  });
82
+ // The lint fails again on every edit of the document, with the same cause until the package is installed
83
+ // and the server restarted (which the popup says), so the popup is shown once per document.
84
+ let pretendersPopupShown = false;
81
85
  engine.on('lint-error', (_filePath, _sourceCode, error) => {
82
86
  diagnosticsLog(error.message, 'error');
87
+ // A configuration that needs `@markuplint/pretenders` is a `config-error` diagnostic, not a rejection of
88
+ // `exec()`, so the popup that a missing parser gets has to be asked for here: the diagnostic tells to
89
+ // install it in the project, which does not fix it for the markuplint bundled with the language server.
90
+ if (!pretendersPopupShown && isPretendersNotInstalled(error)) {
91
+ pretendersPopupShown = true;
92
+ notFoundParserError(error);
93
+ }
83
94
  });
84
95
  engine.on('config-errors', (_filePath, errors) => {
85
96
  for (const error of errors) {
@@ -251,7 +262,8 @@ position, ariaVersion) {
251
262
  if (!engine) {
252
263
  return null;
253
264
  }
254
- const a11y = await getAccessibilityByLocation(engine, position.line + 1, position.character, ariaVersion);
265
+ // LSP counts the character from 0, the node list the column from 1.
266
+ const a11y = await getAccessibilityByLocation(engine, position.line + 1, position.character + 1, ariaVersion);
255
267
  if (!a11y) {
256
268
  return null;
257
269
  }
package/locales/ja.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "keywords": {
3
+ ". ": "。",
3
4
  "computed accessibility properties": "計算済みアクセシビリティプロパティ",
4
5
  "hidden element": "非表示要素",
5
6
  "no corresponding role": "対応するロールなし",
@@ -16,6 +17,7 @@
16
17
  "[suppressed] this warning is suppressed but should be fixed:": "[抑制中] この警告は抑制されていますが、本来は修正すべき問題です:",
17
18
  "parser not found. you probably need to install {0} for documents of language {1}.": "パーサーが見つかりません。言語 {1} の文書には {0} をインストールする必要があります。",
18
19
  "the markuplint bundled with the language server (v{0}) cannot load parsers installed in your workspace. install markuplint and {1} in {2}, then restart the language server so that it can use the local installation.": "言語サーバーにバンドルされたmarkuplint(v{0})はワークスペースにインストールされたパーサーを読み込めません。ローカル版を使えるように、{2} にmarkuplintと{1}をインストールしてから言語サーバーを再起動してください。",
19
- "the markuplint bundled with the language server (v{0}) cannot load {1} from {2} because the markuplint installed there is incompatible with node.js 22+. upgrade it to markuplint@4.10.0 or later, then restart the language server.": "言語サーバーにバンドルされたmarkuplint(v{0})は {2} の{1}を読み込めません。そこにインストールされたmarkuplintがNode.js 22+と互換性がないためです。markuplint@4.10.0以降にアップグレードしてから言語サーバーを再起動してください。"
20
+ "the markuplint bundled with the language server (v{0}) cannot load {1} from {2} because the markuplint installed there is incompatible with node.js 22+. upgrade it to markuplint@4.10.0 or later, then restart the language server.": "言語サーバーにバンドルされたmarkuplint(v{0})は {2} の{1}を読み込めません。そこにインストールされたmarkuplintがNode.js 22+と互換性がないためです。markuplint@4.10.0以降にアップグレードしてから言語サーバーを再起動してください。",
21
+ "the markuplint bundled with the language server (v{0}) does not include @markuplint/pretenders, which your configuration needs. install markuplint and @markuplint/pretenders in {1}, then restart the language server so that it can use the local installation.": "言語サーバーにバンドルされたmarkuplint(v{0})には、設定が必要とする@markuplint/pretendersが含まれていません。ローカル版を使えるように、{1} にmarkuplintと@markuplint/pretendersをインストールしてから言語サーバーを再起動してください。"
20
22
  }
21
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/language-server",
3
- "version": "5.4.0",
3
+ "version": "5.6.0",
4
4
  "description": "Language Server Protocol server for markuplint",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,20 +41,22 @@
41
41
  "clean": "tsc --build --clean tsconfig.build.json"
42
42
  },
43
43
  "dependencies": {
44
- "@markuplint/file-resolver": "5.4.0",
45
- "@markuplint/i18n": "5.3.0",
46
- "@markuplint/ml-config": "5.4.0",
47
- "@markuplint/ml-spec": "5.4.0",
44
+ "@markuplint/file-resolver": "5.6.0",
45
+ "@markuplint/i18n": "5.6.0",
46
+ "@markuplint/ml-config": "5.6.0",
47
+ "@markuplint/ml-core": "5.6.0",
48
+ "@markuplint/ml-spec": "5.6.0",
48
49
  "debug": "^4.4.3",
49
- "markuplint": "5.4.0",
50
- "semver": "^7.8.5",
50
+ "markuplint": "5.6.0",
51
+ "semver": "^7.8.6",
51
52
  "vscode-languageserver": "^9.0.1",
52
- "vscode-languageserver-textdocument": "^1.0.15"
53
+ "vscode-languageserver-textdocument": "^1.0.16"
53
54
  },
54
55
  "devDependencies": {
56
+ "@markuplint/test-tools": "5.6.0",
55
57
  "@types/debug": "4.1.13",
56
58
  "@types/semver": "7.8.0",
57
59
  "vscode-jsonrpc": "8.2.1"
58
60
  },
59
- "gitHead": "647f6fb5d5743f060678599c97e9c84b58970747"
61
+ "gitHead": "eade3d7202682e567f3c1b6985478f1d4b5c5481"
60
62
  }