@markuplint/language-server 0.0.0-stage → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -2
  3. package/bin/markuplint-language-server.mjs +3 -0
  4. package/lib/const.d.ts +41 -0
  5. package/lib/const.js +44 -0
  6. package/lib/i18n.d.ts +19 -0
  7. package/lib/i18n.js +63 -0
  8. package/lib/index.d.ts +41 -0
  9. package/lib/index.js +40 -0
  10. package/lib/lsp.d.ts +8 -0
  11. package/lib/lsp.js +8 -0
  12. package/lib/protocol.d.ts +10 -0
  13. package/lib/protocol.js +9 -0
  14. package/lib/server/client-channel.d.ts +38 -0
  15. package/lib/server/client-channel.js +89 -0
  16. package/lib/server/code-actions.d.ts +36 -0
  17. package/lib/server/code-actions.js +101 -0
  18. package/lib/server/convert-diagnostics.d.ts +28 -0
  19. package/lib/server/convert-diagnostics.js +82 -0
  20. package/lib/server/debug.d.ts +3 -0
  21. package/lib/server/debug.js +7 -0
  22. package/lib/server/document-events.d.ts +78 -0
  23. package/lib/server/document-events.js +299 -0
  24. package/lib/server/get-accessibility-by-location.d.ts +6 -0
  25. package/lib/server/get-accessibility-by-location.js +19 -0
  26. package/lib/server/get-module.d.ts +127 -0
  27. package/lib/server/get-module.js +257 -0
  28. package/lib/server/index.d.ts +1 -0
  29. package/lib/server/index.js +5 -0
  30. package/lib/server/initialize-params.d.ts +32 -0
  31. package/lib/server/initialize-params.js +28 -0
  32. package/lib/server/module-notices.d.ts +28 -0
  33. package/lib/server/module-notices.js +41 -0
  34. package/lib/server/parser-load-failure.d.ts +36 -0
  35. package/lib/server/parser-load-failure.js +85 -0
  36. package/lib/server/server.d.ts +20 -0
  37. package/lib/server/server.js +96 -0
  38. package/lib/server/suppression-support.d.ts +57 -0
  39. package/lib/server/suppression-support.js +233 -0
  40. package/lib/server/v2.d.ts +12 -0
  41. package/lib/server/v2.js +109 -0
  42. package/lib/server/v3.d.ts +19 -0
  43. package/lib/server/v3.js +208 -0
  44. package/lib/server/v4.d.ts +85 -0
  45. package/lib/server/v4.js +292 -0
  46. package/lib/server/v5.d.ts +89 -0
  47. package/lib/server/v5.js +339 -0
  48. package/lib/types.d.ts +88 -0
  49. package/lib/types.js +1 -0
  50. package/lib/utils/get-file-path.d.ts +15 -0
  51. package/lib/utils/get-file-path.js +45 -0
  52. package/lib/utils/resolve-working-directory.d.ts +44 -0
  53. package/lib/utils/resolve-working-directory.js +266 -0
  54. package/locales/en.json +7 -0
  55. package/locales/ja.json +21 -0
  56. package/package.json +59 -5
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017-2024 Yusuke Hirao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,75 @@
1
- # Temporary Holding Version
1
+ # @markuplint/language-server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) server for [markuplint](https://markuplint.dev). It reports markuplint violations as diagnostics, offers quick fixes, and shows the accessibility properties of an element on hover. It is the server behind the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=markuplint.markuplint-vscode), and runs with any other LSP client: AI coding agents such as Claude Code and OpenCode, Neovim, Helix, and so on.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install --save-dev @markuplint/language-server
9
+ ```
10
+
11
+ The server starts with `markuplint-language-server --stdio`. It needs no settings: it lints with the project's markuplint config file (`.markuplintrc` and its siblings), falls back to `markuplint:recommended` as the CLI does, and loads the markuplint installed in the project. The markuplint that ships with this package is used when the project has none.
12
+
13
+ Which files the server receives is up to the client, so list the extensions you want linted in its configuration.
14
+
15
+ ## Claude Code
16
+
17
+ Claude Code registers language servers through plugins. Create a directory with a `.lsp.json`, and load it with `claude --plugin-dir <directory>`:
18
+
19
+ ```json
20
+ {
21
+ "markuplint": {
22
+ "command": "npx",
23
+ "args": ["markuplint-language-server", "--stdio"],
24
+ "extensionToLanguage": {
25
+ ".html": "html"
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ After Claude edits a matching file, the violations appear as diagnostics in its context. Claude Code uses one server per extension, so leave out extensions that another language server already handles (such as `.tsx`).
32
+
33
+ ## OpenCode
34
+
35
+ Add the server to `opencode.json`. OpenCode passes only diagnostics of the `error` severity to the model, so `severityMap` re-reports markuplint's warnings as errors:
36
+
37
+ ```json
38
+ {
39
+ "lsp": {
40
+ "markuplint": {
41
+ "command": ["npx", "markuplint-language-server", "--stdio"],
42
+ "extensions": [".html"],
43
+ "initialization": {
44
+ "severityMap": { "warning": "error" }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ ## Neovim
52
+
53
+ Neovim 0.11 or later:
54
+
55
+ ```lua
56
+ vim.lsp.config['markuplint'] = {
57
+ cmd = { 'npx', 'markuplint-language-server', '--stdio' },
58
+ filetypes = { 'html' },
59
+ root_markers = { '.markuplintrc', 'package.json', '.git' },
60
+ }
61
+ vim.lsp.enable('markuplint')
62
+ ```
63
+
64
+ ## Options
65
+
66
+ A client passes them as `initializationOptions`. All of them are optional.
67
+
68
+ | Option | Description |
69
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `severityMap` | Re-reports diagnostics with another severity: `{ "warning": "error" }`. Keys are `error`, `warning`, `info`; values add `hint`. |
71
+ | `workingDirectories` | Working directories for monorepos, as in the VS Code extension's `markuplint.workingDirectories` setting. |
72
+ | `workspaceFolders` | Absolute paths of the workspace folders. The `workspaceFolders` and `rootUri` of the `initialize` request are used when this is not set. |
73
+ | `gitPath` | Path to the `git` binary that bulk suppression uses. |
74
+ | `langConfigs` | Per-language settings. When set, only the languages listed with `enable: true` are linted. |
75
+ | `extendedProtocol` | For the VS Code extension: sends the `markuplint/*` messages instead of `window/logMessage` and `window/showMessage`. |
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ await import('../lib/server/index.js');
package/lib/const.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ export declare const ID = "markuplint";
2
+ export declare const NAME = "Markuplint";
3
+ export declare const WEBSITE_URL = "https://markuplint.dev";
4
+ export declare const WEBSITE_URL_RULE_PAGE: "https://markuplint.dev/docs/rules/";
5
+ /**
6
+ * Warning message shown when markuplint is not found in the workspace's node_modules.
7
+ * Displayed in the status bar tooltip. Placeholder `{0}` is replaced with the bundled version number.
8
+ */
9
+ export declare const NO_INSTALL_WARNING = "since markuplint could not be found in the node_modules of the workspace, this use the version (v{0}) installed with the language server";
10
+ /**
11
+ * Warning message shown when the local markuplint (v4.0.0–4.9.x) fails to load on Node.js 22+
12
+ * due to the removed `assert { type: 'json' }` syntax (import assertions).
13
+ * Displayed as a popup via `warningToPopup`. Placeholder `{0}` is replaced with the bundled version number.
14
+ *
15
+ * @see https://github.com/markuplint/markuplint/issues/2837
16
+ */
17
+ export declare const IMPORT_ASSERTION_COMPAT_WARNING = "your local markuplint is incompatible with Node.js 22+ due to import assertion syntax. The bundled version (v{0}) is used instead. To use your local version, upgrade to markuplint@4.10.0 or later. See: https://github.com/markuplint/markuplint/issues/2837";
18
+ /**
19
+ * Error message shown when the parser a config requires cannot be loaded by a local markuplint.
20
+ * Displayed as a popup via `errorToPopup`. `{0*}` is the parser package name, `{1*}` the language identifier.
21
+ */
22
+ export declare const PARSER_NOT_FOUND_ERROR = "Parser not found. You probably need to install {0*} for documents of language {1*}.";
23
+ /**
24
+ * Error message shown when the bundled markuplint cannot load a parser that lives in the user's workspace.
25
+ * The bundled `@markuplint/file-resolver` imports parsers relative to itself, so a parser installed only in
26
+ * the working directory is unreachable until markuplint itself is installed there.
27
+ * The module cache is only cleared by a server restart, hence the restart instruction.
28
+ * Displayed as a popup via `errorToPopup`. `{0}` is the bundled version, `{1*}` the parser package name,
29
+ * `{2*}` the working directory.
30
+ *
31
+ * @see https://github.com/markuplint/markuplint/issues/4048
32
+ */
33
+ export declare const BUNDLED_PARSER_LOAD_ERROR = "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.";
34
+ /**
35
+ * Variant of {@link BUNDLED_PARSER_LOAD_ERROR} for when the local markuplint exists but was skipped
36
+ * for the import assertion incompatibility ({@link IMPORT_ASSERTION_COMPAT_WARNING}): installing is
37
+ * not the fix there, upgrading is.
38
+ * Displayed as a popup via `errorToPopup`. `{0}` is the bundled version, `{1*}` the parser package name,
39
+ * `{2*}` the working directory.
40
+ */
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.";
package/lib/const.js ADDED
@@ -0,0 +1,44 @@
1
+ // Identifiers
2
+ export const ID = 'markuplint';
3
+ export const NAME = 'Markuplint';
4
+ // URI
5
+ export const WEBSITE_URL = 'https://markuplint.dev';
6
+ export const WEBSITE_URL_RULE_PAGE = `${WEBSITE_URL}/docs/rules/`;
7
+ // Messages
8
+ /**
9
+ * Warning message shown when markuplint is not found in the workspace's node_modules.
10
+ * Displayed in the status bar tooltip. Placeholder `{0}` is replaced with the bundled version number.
11
+ */
12
+ export const NO_INSTALL_WARNING = 'since markuplint could not be found in the node_modules of the workspace, this use the version (v{0}) installed with the language server';
13
+ /**
14
+ * Warning message shown when the local markuplint (v4.0.0–4.9.x) fails to load on Node.js 22+
15
+ * due to the removed `assert { type: 'json' }` syntax (import assertions).
16
+ * Displayed as a popup via `warningToPopup`. Placeholder `{0}` is replaced with the bundled version number.
17
+ *
18
+ * @see https://github.com/markuplint/markuplint/issues/2837
19
+ */
20
+ export const IMPORT_ASSERTION_COMPAT_WARNING = 'your local markuplint is incompatible with Node.js 22+ due to import assertion syntax. The bundled version (v{0}) is used instead. To use your local version, upgrade to markuplint@4.10.0 or later. See: https://github.com/markuplint/markuplint/issues/2837';
21
+ /**
22
+ * Error message shown when the parser a config requires cannot be loaded by a local markuplint.
23
+ * Displayed as a popup via `errorToPopup`. `{0*}` is the parser package name, `{1*}` the language identifier.
24
+ */
25
+ export const PARSER_NOT_FOUND_ERROR = 'Parser not found. You probably need to install {0*} for documents of language {1*}.';
26
+ /**
27
+ * Error message shown when the bundled markuplint cannot load a parser that lives in the user's workspace.
28
+ * The bundled `@markuplint/file-resolver` imports parsers relative to itself, so a parser installed only in
29
+ * the working directory is unreachable until markuplint itself is installed there.
30
+ * The module cache is only cleared by a server restart, hence the restart instruction.
31
+ * Displayed as a popup via `errorToPopup`. `{0}` is the bundled version, `{1*}` the parser package name,
32
+ * `{2*}` the working directory.
33
+ *
34
+ * @see https://github.com/markuplint/markuplint/issues/4048
35
+ */
36
+ export const BUNDLED_PARSER_LOAD_ERROR = '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.';
37
+ /**
38
+ * Variant of {@link BUNDLED_PARSER_LOAD_ERROR} for when the local markuplint exists but was skipped
39
+ * for the import assertion incompatibility ({@link IMPORT_ASSERTION_COMPAT_WARNING}): installing is
40
+ * not the fix there, upgrading is.
41
+ * Displayed as a popup via `errorToPopup`. `{0}` is the bundled version, `{1*}` the parser package name,
42
+ * `{2*}` the working directory.
43
+ */
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.';
package/lib/i18n.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { Translator } from '@markuplint/i18n';
2
+ /**
3
+ * Sets the locale of the client, as sent in the `initialize` request.
4
+ *
5
+ * @param locale - A locale such as `en` or `ja-JP`
6
+ */
7
+ export declare function setLocale(locale: string): void;
8
+ /**
9
+ * Translates a message into the locale of the client.
10
+ *
11
+ * @param args - The arguments of the `@markuplint/i18n` translator: the message and its placeholder values
12
+ * @returns The translated message, or the message itself without a translation
13
+ */
14
+ export declare function t(...args: Parameters<Translator>): string;
15
+ /**
16
+ * The locale of the client. The `VSCODE_NLS_CONFIG` environment variable of the VS Code extension
17
+ * host is kept as the fallback for a client that does not send a locale.
18
+ */
19
+ export declare function getLocale(): string;
package/lib/i18n.js ADDED
@@ -0,0 +1,63 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { translator } from '@markuplint/i18n';
3
+ let clientLocale;
4
+ /**
5
+ * Sets the locale of the client, as sent in the `initialize` request.
6
+ *
7
+ * @param locale - A locale such as `en` or `ja-JP`
8
+ */
9
+ export function setLocale(locale) {
10
+ clientLocale = locale;
11
+ }
12
+ /**
13
+ * Translates a message into the locale of the client.
14
+ *
15
+ * @param args - The arguments of the `@markuplint/i18n` translator: the message and its placeholder values
16
+ * @returns The translated message, or the message itself without a translation
17
+ */
18
+ export function t(
19
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
20
+ ...args) {
21
+ const locale = getLocale();
22
+ const localeSet = i18n(locale);
23
+ return translator(localeSet)(...args);
24
+ }
25
+ /**
26
+ * The locale of the client. The `VSCODE_NLS_CONFIG` environment variable of the VS Code extension
27
+ * host is kept as the fallback for a client that does not send a locale.
28
+ */
29
+ export function getLocale() {
30
+ const locale = clientLocale ?? JSON.parse(process.env.VSCODE_NLS_CONFIG || '{}').locale ?? 'en';
31
+ return locale;
32
+ }
33
+ /**
34
+ * The language of a locale (`ja` for `ja-JP` or `ja_JP`).
35
+ *
36
+ * The locale arrives from the client, and the language names a file under `locales/`. Anything
37
+ * but a two or three letter language falls back to English so the name can never leave that
38
+ * directory.
39
+ */
40
+ function toLanguage(locale) {
41
+ const language = (locale.split(/[-_]/)[0] ?? '').toLowerCase();
42
+ return /^[a-z]{2,3}$/.test(language) ? language : 'en';
43
+ }
44
+ function i18n(locale) {
45
+ const langCode = toLanguage(locale);
46
+ const localeSet = getLocaleSet(langCode);
47
+ return {
48
+ ...localeSet,
49
+ locale: langCode,
50
+ };
51
+ }
52
+ function readLocaleFile(langCode) {
53
+ return JSON.parse(readFileSync(new URL(`../locales/${langCode}.json`, import.meta.url), 'utf8'));
54
+ }
55
+ function getLocaleSet(langCode) {
56
+ try {
57
+ return readLocaleFile(langCode);
58
+ }
59
+ catch {
60
+ // Avoid
61
+ }
62
+ return readLocaleFile('en');
63
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Language Server Protocol server for markuplint.
3
+ *
4
+ * Run `markuplint-language-server --stdio` to start it. Any LSP client can use it: a client
5
+ * needs no markuplint-specific configuration, because the server lints with the project's own
6
+ * config file (`.markuplintrc` and its siblings), falls back to `markuplint:recommended` like the
7
+ * CLI, and loads the markuplint installed in the project, falling back to the one that installs
8
+ * with this package.
9
+ *
10
+ * The VS Code extension is a client of this package. What it sends beyond the LSP is in
11
+ * {@link InitializationOptions}, and the custom messages are in `@markuplint/language-server/protocol`.
12
+ *
13
+ * Contracts that the clients impose, and that the code keeps because of them:
14
+ *
15
+ * - **stdout carries the protocol only.** Claude Code treats any other output on stdout as a
16
+ * crash of the server. `--stdio` makes `vscode-languageserver` redirect `console.*` away from
17
+ * stdout, and `debug` writes to stderr; do not write to `process.stdout` directly.
18
+ * - **The `markuplint/*` messages are opt-in (`extendedProtocol`).** A client that has not
19
+ * registered `markuplint/ready` answers the request with `MethodNotFound`, and the others
20
+ * have no output channel or popup to show. Only the VS Code extension handles them; every
21
+ * other client receives `window/logMessage` and `window/showMessage`.
22
+ * - **`window/showMessage` is sent as a notification.** `connection.window.showErrorMessage`
23
+ * sends `window/showMessageRequest`, which a client must answer and many do not.
24
+ * - **`severityMap` exists because clients surface different severities.** OpenCode hands only
25
+ * `error` diagnostics to the model, Claude Code hands errors and warnings. Re-reporting
26
+ * markuplint's warnings as errors is the client's choice, not a server default.
27
+ * - **No `langConfigs` means no per-language gate.** Which documents reach the server is the
28
+ * client's setting (`extensionToLanguage` of Claude Code, `extensions` of OpenCode,
29
+ * `filetypes` of Neovim), so the server lints every document it is given.
30
+ *
31
+ * What the clients accept is theirs to change; check them when this package misbehaves in one:
32
+ *
33
+ * - Claude Code: https://code.claude.com/docs/en/plugins-reference#lspservers (a server is
34
+ * registered only through a plugin, and one server serves an extension)
35
+ * - OpenCode: https://opencode.ai/docs/lsp/ (`lsp` of `opencode.json`)
36
+ * - Neovim: `:help vim.lsp.config()`
37
+ *
38
+ * @module
39
+ */
40
+ export { bootServer } from './server/server.js';
41
+ export type { InitializationOptions, SeverityMap } from './types.js';
package/lib/index.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Language Server Protocol server for markuplint.
3
+ *
4
+ * Run `markuplint-language-server --stdio` to start it. Any LSP client can use it: a client
5
+ * needs no markuplint-specific configuration, because the server lints with the project's own
6
+ * config file (`.markuplintrc` and its siblings), falls back to `markuplint:recommended` like the
7
+ * CLI, and loads the markuplint installed in the project, falling back to the one that installs
8
+ * with this package.
9
+ *
10
+ * The VS Code extension is a client of this package. What it sends beyond the LSP is in
11
+ * {@link InitializationOptions}, and the custom messages are in `@markuplint/language-server/protocol`.
12
+ *
13
+ * Contracts that the clients impose, and that the code keeps because of them:
14
+ *
15
+ * - **stdout carries the protocol only.** Claude Code treats any other output on stdout as a
16
+ * crash of the server. `--stdio` makes `vscode-languageserver` redirect `console.*` away from
17
+ * stdout, and `debug` writes to stderr; do not write to `process.stdout` directly.
18
+ * - **The `markuplint/*` messages are opt-in (`extendedProtocol`).** A client that has not
19
+ * registered `markuplint/ready` answers the request with `MethodNotFound`, and the others
20
+ * have no output channel or popup to show. Only the VS Code extension handles them; every
21
+ * other client receives `window/logMessage` and `window/showMessage`.
22
+ * - **`window/showMessage` is sent as a notification.** `connection.window.showErrorMessage`
23
+ * sends `window/showMessageRequest`, which a client must answer and many do not.
24
+ * - **`severityMap` exists because clients surface different severities.** OpenCode hands only
25
+ * `error` diagnostics to the model, Claude Code hands errors and warnings. Re-reporting
26
+ * markuplint's warnings as errors is the client's choice, not a server default.
27
+ * - **No `langConfigs` means no per-language gate.** Which documents reach the server is the
28
+ * client's setting (`extensionToLanguage` of Claude Code, `extensions` of OpenCode,
29
+ * `filetypes` of Neovim), so the server lints every document it is given.
30
+ *
31
+ * What the clients accept is theirs to change; check them when this package misbehaves in one:
32
+ *
33
+ * - Claude Code: https://code.claude.com/docs/en/plugins-reference#lspservers (a server is
34
+ * registered only through a plugin, and one server serves an extension)
35
+ * - OpenCode: https://opencode.ai/docs/lsp/ (`lsp` of `opencode.json`)
36
+ * - Neovim: `:help vim.lsp.config()`
37
+ *
38
+ * @module
39
+ */
40
+ export { bootServer } from './server/server.js';
package/lib/lsp.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { LogArg, Status } from './types.js';
2
+ import { NotificationType, RequestType } from 'vscode-languageserver';
3
+ export declare const status: RequestType<Status, void, void>;
4
+ export declare const logToPrimaryChannel: NotificationType<LogArg>;
5
+ export declare const logToDiagnosticsChannel: NotificationType<LogArg>;
6
+ export declare const errorToPopup: NotificationType<string>;
7
+ export declare const warningToPopup: NotificationType<string>;
8
+ export declare const infoToPopup: NotificationType<string>;
package/lib/lsp.js ADDED
@@ -0,0 +1,8 @@
1
+ import { NotificationType, RequestType } from 'vscode-languageserver';
2
+ import { ID } from './const.js';
3
+ export const status = new RequestType(`${ID}/ready`);
4
+ export const logToPrimaryChannel = new NotificationType(`${ID}/log-primary-channel`);
5
+ export const logToDiagnosticsChannel = new NotificationType(`${ID}/log-diagnostics-channel`);
6
+ export const errorToPopup = new NotificationType(`${ID}/error-popup`);
7
+ export const warningToPopup = new NotificationType(`${ID}/warning-popup`);
8
+ export const infoToPopup = new NotificationType(`${ID}/info-popup`);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * What a client that opts in to `extendedProtocol` shares with the server: the identifiers, the
3
+ * custom `markuplint/*` messages, and the types of what they carry. The VS Code extension
4
+ * imports this to handle the messages; the server imports the same definitions to send them.
5
+ *
6
+ * @module
7
+ */
8
+ export { ID, NAME } from './const.js';
9
+ export * from './lsp.js';
10
+ export type { Config, InitializationOptions, LangConfigs, Log, LogArg, LogType, ReportedSeverity, SeverityMap, Status, } from './types.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * What a client that opts in to `extendedProtocol` shares with the server: the identifiers, the
3
+ * custom `markuplint/*` messages, and the types of what they carry. The VS Code extension
4
+ * imports this to handle the messages; the server imports the same definitions to send them.
5
+ *
6
+ * @module
7
+ */
8
+ export { ID, NAME } from './const.js';
9
+ export * from './lsp.js';
@@ -0,0 +1,38 @@
1
+ import type { Log, Status } from '../types.js';
2
+ import type { Connection } from 'vscode-languageserver/node.js';
3
+ /**
4
+ * How the server talks to its client apart from diagnostics.
5
+ */
6
+ export type ClientChannel = {
7
+ /**
8
+ * Selects the protocol. Called once, from the `initialize` request.
9
+ *
10
+ * @param extendedProtocol - `true` when the client understands the `markuplint/*` messages
11
+ */
12
+ configure(extendedProtocol: boolean): void;
13
+ /** Primary log: the extension's output channel, otherwise `window/logMessage` */
14
+ readonly log: Log;
15
+ /** Diagnostics log: the extension's second output channel, otherwise `window/logMessage` */
16
+ readonly diagnosticsLog: Log;
17
+ /** An error the user should see: the extension's popup, otherwise `window/showMessage` */
18
+ readonly errorLog: Log;
19
+ /** A warning the user should see: the extension's popup, otherwise `window/showMessage` */
20
+ warningPopup(message: string): void;
21
+ /** Tells the client which markuplint lints the documents. Only the extension has a status bar for it. */
22
+ reportStatus(current: Status): void;
23
+ };
24
+ /**
25
+ * Creates the channel between the server and its client.
26
+ *
27
+ * The `markuplint/*` messages are custom: a client that has not registered them rejects the
28
+ * `markuplint/ready` request with `MethodNotFound`, and it has no output channel or popup to
29
+ * show the notifications in. Only the VS Code extension, which handles all of them, opts in.
30
+ * Every other client gets `window/logMessage` and `window/showMessage`, which the LSP requires
31
+ * a client to support. `window/showMessage` is the notification, never the
32
+ * `window/showMessageRequest` that `connection.window.showErrorMessage` sends and a client must
33
+ * answer.
34
+ *
35
+ * @param connection - The connection to the client; only what it sends is used
36
+ * @returns The channel, which uses the standard messages until `configure(true)` is called
37
+ */
38
+ export declare function createClientChannel(connection: Pick<Connection, 'sendNotification' | 'sendRequest'>): ClientChannel;
@@ -0,0 +1,89 @@
1
+ import { isFatalError } from 'markuplint/suppressions';
2
+ import { LogMessageNotification, MessageType, ShowMessageNotification } from 'vscode-languageserver/node.js';
3
+ import { errorToPopup, logToDiagnosticsChannel, logToPrimaryChannel, status, warningToPopup } from '../lsp.js';
4
+ const MESSAGE_TYPES = {
5
+ error: MessageType.Error,
6
+ warn: MessageType.Warning,
7
+ info: MessageType.Info,
8
+ debug: MessageType.Log,
9
+ };
10
+ /**
11
+ * Creates the channel between the server and its client.
12
+ *
13
+ * The `markuplint/*` messages are custom: a client that has not registered them rejects the
14
+ * `markuplint/ready` request with `MethodNotFound`, and it has no output channel or popup to
15
+ * show the notifications in. Only the VS Code extension, which handles all of them, opts in.
16
+ * Every other client gets `window/logMessage` and `window/showMessage`, which the LSP requires
17
+ * a client to support. `window/showMessage` is the notification, never the
18
+ * `window/showMessageRequest` that `connection.window.showErrorMessage` sends and a client must
19
+ * answer.
20
+ *
21
+ * @param connection - The connection to the client; only what it sends is used
22
+ * @returns The channel, which uses the standard messages until `configure(true)` is called
23
+ */
24
+ export function createClientChannel(connection) {
25
+ let extended = false;
26
+ // `trace` is one line per engine phase for every document, which is what the extension's
27
+ // output channel is for. A client that does not opt in has no such channel to put it in.
28
+ function standardLog(message, type) {
29
+ if (type === 'clear' || type === 'trace') {
30
+ return;
31
+ }
32
+ void connection.sendNotification(LogMessageNotification.type, {
33
+ type: MESSAGE_TYPES[type ?? 'debug'],
34
+ message,
35
+ });
36
+ }
37
+ function standardShow(message, type) {
38
+ void connection.sendNotification(ShowMessageNotification.type, { type, message });
39
+ }
40
+ const log = (message, type) => {
41
+ if (extended) {
42
+ void connection.sendNotification(logToPrimaryChannel, [message, type]);
43
+ }
44
+ else {
45
+ standardLog(message, type);
46
+ }
47
+ };
48
+ return {
49
+ configure(extendedProtocol) {
50
+ extended = extendedProtocol;
51
+ },
52
+ log,
53
+ diagnosticsLog(message, type) {
54
+ if (extended) {
55
+ void connection.sendNotification(logToDiagnosticsChannel, [message, type]);
56
+ }
57
+ else {
58
+ standardLog(message, type ?? 'info');
59
+ }
60
+ },
61
+ errorLog(message) {
62
+ if (extended) {
63
+ void connection.sendNotification(errorToPopup, message);
64
+ }
65
+ else {
66
+ standardShow(message, MessageType.Error);
67
+ }
68
+ },
69
+ warningPopup(message) {
70
+ if (extended) {
71
+ void connection.sendNotification(warningToPopup, message);
72
+ }
73
+ else {
74
+ standardShow(message, MessageType.Warning);
75
+ }
76
+ },
77
+ reportStatus(current) {
78
+ if (!extended) {
79
+ return;
80
+ }
81
+ connection.sendRequest(status, current).catch((error) => {
82
+ if (isFatalError(error)) {
83
+ throw error;
84
+ }
85
+ log(`The client rejected the status: ${error}`, 'warn');
86
+ });
87
+ },
88
+ };
89
+ }
@@ -0,0 +1,36 @@
1
+ import type { FixState } from './v4.js';
2
+ import type { CodeAction, Diagnostic, Position } from 'vscode-languageserver';
3
+ /**
4
+ * Code Action kind for "Fix all markuplint issues".
5
+ * Follows the `source.fixAll.<provider>` convention used by ESLint and others.
6
+ */
7
+ export declare const SOURCE_FIX_ALL_MARKUPLINT: string;
8
+ /**
9
+ * Converts a 0-based character offset to an LSP Position using the source code.
10
+ *
11
+ * @param sourceCode - The full raw source text
12
+ * @param offset - A zero-based character offset into the source
13
+ * @returns A 0-based LSP Position (line and character)
14
+ */
15
+ export declare function offsetToPosition(sourceCode: string, offset: number): Position;
16
+ /**
17
+ * Creates per-violation QuickFix Code Actions.
18
+ *
19
+ * @param uri - The document URI to associate edits with
20
+ * @param diagnostics - The diagnostics from the Code Action request context
21
+ * @param fixState - The latest lint result containing violations and fix data
22
+ * @returns An array of QuickFix Code Actions, one per fixable violation
23
+ */
24
+ export declare function createQuickFixActions(uri: string, diagnostics: readonly Diagnostic[], fixState: FixState): CodeAction[];
25
+ /**
26
+ * Creates a SourceFixAll Code Action (`source.fixAll.markuplint`).
27
+ *
28
+ * Uses firstPassEdits from FixSummary for cursor-safe individual edits.
29
+ * Falls back to full-document replacement when firstPassEdits is unavailable.
30
+ *
31
+ * @param uri - The document URI to associate edits with
32
+ * @param diagnostics - The diagnostics to attach to the Code Action
33
+ * @param fixState - The latest lint result containing violations and fix data
34
+ * @returns A SourceFixAll Code Action, or `null` if nothing to fix
35
+ */
36
+ export declare function createFixAllAction(uri: string, diagnostics: readonly Diagnostic[], fixState: FixState): CodeAction | null;