@markuplint/language-server 0.0.0-stage → 5.1.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.
Files changed (55) hide show
  1. package/README.md +74 -2
  2. package/bin/markuplint-language-server.mjs +3 -0
  3. package/lib/const.d.ts +41 -0
  4. package/lib/const.js +44 -0
  5. package/lib/i18n.d.ts +19 -0
  6. package/lib/i18n.js +63 -0
  7. package/lib/index.d.ts +41 -0
  8. package/lib/index.js +40 -0
  9. package/lib/lsp.d.ts +8 -0
  10. package/lib/lsp.js +8 -0
  11. package/lib/protocol.d.ts +10 -0
  12. package/lib/protocol.js +9 -0
  13. package/lib/server/client-channel.d.ts +38 -0
  14. package/lib/server/client-channel.js +89 -0
  15. package/lib/server/code-actions.d.ts +36 -0
  16. package/lib/server/code-actions.js +101 -0
  17. package/lib/server/convert-diagnostics.d.ts +28 -0
  18. package/lib/server/convert-diagnostics.js +82 -0
  19. package/lib/server/debug.d.ts +3 -0
  20. package/lib/server/debug.js +7 -0
  21. package/lib/server/document-events.d.ts +78 -0
  22. package/lib/server/document-events.js +299 -0
  23. package/lib/server/get-accessibility-by-location.d.ts +6 -0
  24. package/lib/server/get-accessibility-by-location.js +19 -0
  25. package/lib/server/get-module.d.ts +127 -0
  26. package/lib/server/get-module.js +257 -0
  27. package/lib/server/index.d.ts +1 -0
  28. package/lib/server/index.js +5 -0
  29. package/lib/server/initialize-params.d.ts +32 -0
  30. package/lib/server/initialize-params.js +28 -0
  31. package/lib/server/module-notices.d.ts +28 -0
  32. package/lib/server/module-notices.js +41 -0
  33. package/lib/server/parser-load-failure.d.ts +36 -0
  34. package/lib/server/parser-load-failure.js +85 -0
  35. package/lib/server/server.d.ts +20 -0
  36. package/lib/server/server.js +96 -0
  37. package/lib/server/suppression-support.d.ts +57 -0
  38. package/lib/server/suppression-support.js +233 -0
  39. package/lib/server/v2.d.ts +12 -0
  40. package/lib/server/v2.js +109 -0
  41. package/lib/server/v3.d.ts +19 -0
  42. package/lib/server/v3.js +208 -0
  43. package/lib/server/v4.d.ts +85 -0
  44. package/lib/server/v4.js +292 -0
  45. package/lib/server/v5.d.ts +89 -0
  46. package/lib/server/v5.js +339 -0
  47. package/lib/types.d.ts +88 -0
  48. package/lib/types.js +1 -0
  49. package/lib/utils/get-file-path.d.ts +15 -0
  50. package/lib/utils/get-file-path.js +45 -0
  51. package/lib/utils/resolve-working-directory.d.ts +44 -0
  52. package/lib/utils/resolve-working-directory.js +266 -0
  53. package/locales/en.json +7 -0
  54. package/locales/ja.json +21 -0
  55. package/package.json +58 -5
@@ -0,0 +1,266 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * Markuplint configuration file names used for auto-detection.
5
+ */
6
+ const MARKUPLINT_CONFIG_FILES = [
7
+ '.markuplintrc',
8
+ '.markuplintrc.json',
9
+ '.markuplintrc.yaml',
10
+ '.markuplintrc.yml',
11
+ '.markuplintrc.js',
12
+ '.markuplintrc.cjs',
13
+ '.markuplintrc.mjs',
14
+ '.markuplintrc.ts',
15
+ 'markuplint.config.js',
16
+ 'markuplint.config.cjs',
17
+ 'markuplint.config.mjs',
18
+ 'markuplint.config.ts',
19
+ ];
20
+ /**
21
+ * Files that indicate a project root boundary.
22
+ */
23
+ const ROOT_INDICATOR_FILES = ['package.json', ...MARKUPLINT_CONFIG_FILES];
24
+ /**
25
+ * Converts a simple glob pattern (supporting `*` and `**`) into a RegExp.
26
+ *
27
+ * This is a simplified implementation for matching directory paths against
28
+ * patterns like `./packages/* /` or `./apps/** /`.
29
+ *
30
+ * @param pattern - A glob pattern string (e.g. `"./packages/* /"`)
31
+ * @returns A RegExp anchored at the start that matches directory paths
32
+ */
33
+ export function convertGlobToRegex(pattern) {
34
+ // Normalize and remove trailing slash
35
+ let normalized = pattern.replaceAll('\\', '/');
36
+ if (normalized.endsWith('/')) {
37
+ normalized = normalized.slice(0, -1);
38
+ }
39
+ // Escape special regex chars except * and **
40
+ let regexStr = '';
41
+ let i = 0;
42
+ while (i < normalized.length) {
43
+ const char = normalized[i];
44
+ if (char === '*') {
45
+ if (normalized[i + 1] === '*') {
46
+ regexStr += '.*';
47
+ i += 2;
48
+ // Skip optional trailing /
49
+ if (normalized[i] === '/') {
50
+ i++;
51
+ }
52
+ }
53
+ else {
54
+ regexStr += '[^/]*';
55
+ i++;
56
+ }
57
+ }
58
+ else if ('.+^${}()|[]\\'.includes(char)) {
59
+ regexStr += '\\' + char;
60
+ i++;
61
+ }
62
+ else {
63
+ regexStr += char;
64
+ i++;
65
+ }
66
+ }
67
+ return new RegExp('^' + regexStr + '(?:/|$)');
68
+ }
69
+ /**
70
+ * Resolves the working directory for a given file based on workingDirectories configuration.
71
+ *
72
+ * @param filePath - Absolute file path of the document being linted
73
+ * @param workspaceFolders - Workspace folder paths (absolute)
74
+ * @param workingDirectories - The user-configured working directories
75
+ * @returns The resolved working directory, or `undefined` to use the default behavior
76
+ */
77
+ export function resolveWorkingDirectory(filePath, workspaceFolders, workingDirectories) {
78
+ if (!workingDirectories || workingDirectories.length === 0) {
79
+ return undefined;
80
+ }
81
+ const normalizedFilePath = filePath.replaceAll('\\', '/');
82
+ let bestMatch;
83
+ let bestMatchLength = -1;
84
+ for (const entry of workingDirectories) {
85
+ if (typeof entry === 'string') {
86
+ const resolved = resolveDirectoryEntry(entry, workspaceFolders);
87
+ if (resolved) {
88
+ const match = matchDirectory(normalizedFilePath, resolved);
89
+ if (match > bestMatchLength) {
90
+ bestMatchLength = match;
91
+ bestMatch = { directory: resolved };
92
+ }
93
+ }
94
+ continue;
95
+ }
96
+ if ('mode' in entry) {
97
+ const result = resolveByMode(filePath, workspaceFolders, entry.mode);
98
+ if (result) {
99
+ return result;
100
+ }
101
+ continue;
102
+ }
103
+ if ('directory' in entry) {
104
+ const resolved = resolveDirectoryEntry(entry.directory, workspaceFolders);
105
+ if (resolved) {
106
+ const match = matchDirectory(normalizedFilePath, resolved);
107
+ if (match > bestMatchLength) {
108
+ bestMatchLength = match;
109
+ bestMatch = { directory: resolved };
110
+ }
111
+ }
112
+ continue;
113
+ }
114
+ if ('pattern' in entry) {
115
+ const result = resolveByPattern(normalizedFilePath, workspaceFolders, entry.pattern);
116
+ if (result && result.directory.length > bestMatchLength) {
117
+ bestMatchLength = result.directory.length;
118
+ bestMatch = result;
119
+ }
120
+ }
121
+ }
122
+ return bestMatch;
123
+ }
124
+ /**
125
+ * Resolves a directory path relative to workspace folders.
126
+ * Returns the first existing absolute path found.
127
+ */
128
+ function resolveDirectoryEntry(directory, workspaceFolders) {
129
+ // If already absolute and exists, use it directly
130
+ if (path.isAbsolute(directory)) {
131
+ return directory;
132
+ }
133
+ // Resolve relative to each workspace folder
134
+ for (const folder of workspaceFolders) {
135
+ const resolved = path.resolve(folder, directory);
136
+ return resolved;
137
+ }
138
+ return undefined;
139
+ }
140
+ /**
141
+ * Checks if a file path is inside a directory.
142
+ * Returns the directory path length if matched, -1 otherwise.
143
+ */
144
+ function matchDirectory(normalizedFilePath, directory) {
145
+ const normalizedDir = directory.replaceAll('\\', '/');
146
+ const dirWithSlash = normalizedDir.endsWith('/') ? normalizedDir : normalizedDir + '/';
147
+ if (normalizedFilePath.startsWith(dirWithSlash)) {
148
+ return dirWithSlash.length;
149
+ }
150
+ return -1;
151
+ }
152
+ /**
153
+ * Resolves working directory by glob pattern matching against workspace folders.
154
+ */
155
+ function resolveByPattern(normalizedFilePath, workspaceFolders, pattern) {
156
+ for (const folder of workspaceFolders) {
157
+ const normalizedFolder = folder.replaceAll('\\', '/');
158
+ const fullPattern = pattern.startsWith('./')
159
+ ? normalizedFolder + pattern.slice(1)
160
+ : path.posix.join(normalizedFolder, pattern);
161
+ const regex = convertGlobToRegex(fullPattern);
162
+ if (regex.test(normalizedFilePath)) {
163
+ // Extract the matched directory portion
164
+ const match = regex.exec(normalizedFilePath);
165
+ if (match) {
166
+ let matchedDir = match[0];
167
+ if (matchedDir.endsWith('/')) {
168
+ matchedDir = matchedDir.slice(0, -1);
169
+ }
170
+ return { directory: matchedDir };
171
+ }
172
+ }
173
+ }
174
+ return undefined;
175
+ }
176
+ /**
177
+ * Resolves working directory by mode ("auto" or "location").
178
+ */
179
+ function resolveByMode(filePath, workspaceFolders, mode) {
180
+ if (mode === 'location') {
181
+ // Use workspace folder path. If a markuplint config is found closer to the file,
182
+ // use that directory instead.
183
+ const configDir = findClosestConfigDir(filePath, workspaceFolders);
184
+ if (configDir) {
185
+ return { directory: configDir };
186
+ }
187
+ // Fall back to workspace folder containing the file
188
+ for (const folder of workspaceFolders) {
189
+ const normalizedFolder = folder.replaceAll('\\', '/');
190
+ const normalizedFilePath = filePath.replaceAll('\\', '/');
191
+ if (normalizedFilePath.startsWith(normalizedFolder + '/') ||
192
+ normalizedFilePath.startsWith(normalizedFolder + '\\')) {
193
+ return { directory: folder };
194
+ }
195
+ }
196
+ return undefined;
197
+ }
198
+ // mode === 'auto'
199
+ // Walk up from the file's directory to find the closest project root
200
+ // (indicated by package.json or markuplint config files)
201
+ const autoDir = findAutoWorkingDirectory(filePath, workspaceFolders);
202
+ if (autoDir) {
203
+ return { directory: autoDir };
204
+ }
205
+ return undefined;
206
+ }
207
+ /**
208
+ * Finds the closest directory containing a markuplint config file,
209
+ * walking up from the file's parent directory to the workspace root.
210
+ */
211
+ function findClosestConfigDir(filePath, workspaceFolders) {
212
+ const fileDir = path.dirname(filePath);
213
+ const workspaceRoot = findContainingWorkspace(filePath, workspaceFolders);
214
+ const stopDir = workspaceRoot ?? path.parse(fileDir).root;
215
+ let current = fileDir;
216
+ while (current.length >= stopDir.length) {
217
+ for (const configFile of MARKUPLINT_CONFIG_FILES) {
218
+ if (fs.existsSync(path.join(current, configFile))) {
219
+ return current;
220
+ }
221
+ }
222
+ const parent = path.dirname(current);
223
+ if (parent === current) {
224
+ break;
225
+ }
226
+ current = parent;
227
+ }
228
+ return undefined;
229
+ }
230
+ /**
231
+ * Finds the closest directory containing a root indicator file (package.json or markuplint config),
232
+ * walking up from the file's parent directory to the workspace root.
233
+ * Used by "auto" mode.
234
+ */
235
+ function findAutoWorkingDirectory(filePath, workspaceFolders) {
236
+ const fileDir = path.dirname(filePath);
237
+ const workspaceRoot = findContainingWorkspace(filePath, workspaceFolders);
238
+ const stopDir = workspaceRoot ?? path.parse(fileDir).root;
239
+ let current = fileDir;
240
+ while (current.length >= stopDir.length) {
241
+ for (const rootFile of ROOT_INDICATOR_FILES) {
242
+ if (fs.existsSync(path.join(current, rootFile))) {
243
+ return current;
244
+ }
245
+ }
246
+ const parent = path.dirname(current);
247
+ if (parent === current) {
248
+ break;
249
+ }
250
+ current = parent;
251
+ }
252
+ return undefined;
253
+ }
254
+ /**
255
+ * Finds the workspace folder that contains the given file path.
256
+ */
257
+ function findContainingWorkspace(filePath, workspaceFolders) {
258
+ const normalizedFilePath = filePath.replaceAll('\\', '/');
259
+ for (const folder of workspaceFolders) {
260
+ const normalizedFolder = folder.replaceAll('\\', '/');
261
+ if (normalizedFilePath.startsWith(normalizedFolder + '/')) {
262
+ return folder;
263
+ }
264
+ }
265
+ return undefined;
266
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "sentences": {
3
+ "since markuplint could not be found in the node_modules of the workspace, this use the version (v{0}) installed in vs code extension": "Since markuplint could not be found in the node_modules of the workspace, this use the version (v{0}) installed in VS Code Extension",
4
+ "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": "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",
5
+ "[suppressed] this warning is suppressed but should be fixed:": "[Suppressed] This warning is suppressed but should be fixed:"
6
+ }
7
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "keywords": {
3
+ "computed accessibility properties": "計算済みアクセシビリティプロパティ",
4
+ "hidden element": "非表示要素",
5
+ "no corresponding role": "対応するロールなし",
6
+ "no exposed to accessibility tree": "アクセシビリティツリーに公開されません",
7
+ "none": "なし",
8
+ "prohibited": "禁止",
9
+ "required": "必須",
10
+ "unknown": "不明",
11
+ "undefined": "未定義"
12
+ },
13
+ "sentences": {
14
+ "since markuplint could not be found in the node_modules of the workspace, this use the version (v{0}) installed with the language server": "ワークスペースのnode_modulesにmarkuplintが発見できなかったためこの言語サーバーに同梱されているバージョン(v{0})を利用します",
15
+ "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": "ローカルのmarkuplintはimport assertion構文のためNode.js 22+と互換性がありません。代わりにバンドル版(v{0})を使用しています。ローカル版を使用するにはmarkuplint@4.10.0以降にアップグレードしてください。詳細: https://github.com/markuplint/markuplint/issues/2837",
16
+ "[suppressed] this warning is suppressed but should be fixed:": "[抑制中] この警告は抑制されていますが、本来は修正すべき問題です:",
17
+ "parser not found. you probably need to install {0} for documents of language {1}.": "パーサーが見つかりません。言語 {1} の文書には {0} をインストールする必要があります。",
18
+ "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
+ }
21
+ }
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
- "name": "@markuplint/language-server",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@markuplint/language-server",
3
+ "version": "5.1.0",
4
+ "description": "Language Server Protocol server for markuplint",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/markuplint/markuplint.git",
8
+ "directory": "packages/@markuplint/language-server"
9
+ },
10
+ "author": "Yusuke Hirao <yusukehirao@me.com>",
11
+ "license": "MIT",
12
+ "engines": {
13
+ "node": ">=24"
14
+ },
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./lib/index.js",
19
+ "types": "./lib/index.d.ts"
20
+ },
21
+ "./protocol": {
22
+ "import": "./lib/protocol.js",
23
+ "types": "./lib/protocol.d.ts"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "bin": {
28
+ "markuplint-language-server": "./bin/markuplint-language-server.mjs"
29
+ },
30
+ "files": [
31
+ "bin",
32
+ "lib",
33
+ "locales"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc --project tsconfig.build.json",
40
+ "dev": "tsc --watch --project tsconfig.build.json",
41
+ "clean": "tsc --build --clean tsconfig.build.json"
42
+ },
43
+ "dependencies": {
44
+ "@markuplint/file-resolver": "5.1.0",
45
+ "@markuplint/i18n": "5.1.0",
46
+ "@markuplint/ml-config": "5.1.0",
47
+ "@markuplint/ml-spec": "5.1.0",
48
+ "debug": "4.4.3",
49
+ "markuplint": "5.1.0",
50
+ "semver": "7.7.4",
51
+ "vscode-languageserver": "9.0.1",
52
+ "vscode-languageserver-textdocument": "1.0.12"
53
+ },
54
+ "devDependencies": {
55
+ "@types/debug": "4.1.13",
56
+ "@types/semver": "7.7.1",
57
+ "vscode-jsonrpc": "8.2.0"
58
+ }
59
+ }