@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.
- package/LICENSE +21 -0
- package/README.md +74 -2
- package/bin/markuplint-language-server.mjs +3 -0
- package/lib/const.d.ts +41 -0
- package/lib/const.js +44 -0
- package/lib/i18n.d.ts +19 -0
- package/lib/i18n.js +63 -0
- package/lib/index.d.ts +41 -0
- package/lib/index.js +40 -0
- package/lib/lsp.d.ts +8 -0
- package/lib/lsp.js +8 -0
- package/lib/protocol.d.ts +10 -0
- package/lib/protocol.js +9 -0
- package/lib/server/client-channel.d.ts +38 -0
- package/lib/server/client-channel.js +89 -0
- package/lib/server/code-actions.d.ts +36 -0
- package/lib/server/code-actions.js +101 -0
- package/lib/server/convert-diagnostics.d.ts +28 -0
- package/lib/server/convert-diagnostics.js +82 -0
- package/lib/server/debug.d.ts +3 -0
- package/lib/server/debug.js +7 -0
- package/lib/server/document-events.d.ts +78 -0
- package/lib/server/document-events.js +299 -0
- package/lib/server/get-accessibility-by-location.d.ts +6 -0
- package/lib/server/get-accessibility-by-location.js +19 -0
- package/lib/server/get-module.d.ts +127 -0
- package/lib/server/get-module.js +257 -0
- package/lib/server/index.d.ts +1 -0
- package/lib/server/index.js +5 -0
- package/lib/server/initialize-params.d.ts +32 -0
- package/lib/server/initialize-params.js +28 -0
- package/lib/server/module-notices.d.ts +28 -0
- package/lib/server/module-notices.js +41 -0
- package/lib/server/parser-load-failure.d.ts +36 -0
- package/lib/server/parser-load-failure.js +85 -0
- package/lib/server/server.d.ts +20 -0
- package/lib/server/server.js +96 -0
- package/lib/server/suppression-support.d.ts +57 -0
- package/lib/server/suppression-support.js +233 -0
- package/lib/server/v2.d.ts +12 -0
- package/lib/server/v2.js +109 -0
- package/lib/server/v3.d.ts +19 -0
- package/lib/server/v3.js +208 -0
- package/lib/server/v4.d.ts +85 -0
- package/lib/server/v4.js +292 -0
- package/lib/server/v5.d.ts +89 -0
- package/lib/server/v5.js +339 -0
- package/lib/types.d.ts +88 -0
- package/lib/types.js +1 -0
- package/lib/utils/get-file-path.d.ts +15 -0
- package/lib/utils/get-file-path.js +45 -0
- package/lib/utils/resolve-working-directory.d.ts +44 -0
- package/lib/utils/resolve-working-directory.js +266 -0
- package/locales/en.json +7 -0
- package/locales/ja.json +21 -0
- 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
|
-
#
|
|
1
|
+
# @markuplint/language-server
|
|
2
2
|
|
|
3
|
-
|
|
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`. |
|
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';
|
package/lib/protocol.js
ADDED
|
@@ -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;
|