@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
@@ -0,0 +1,339 @@
1
+ import path from 'node:path';
2
+ import { isFatalError } from 'markuplint/suppressions';
3
+ import { t } from '../i18n.js';
4
+ import { getFilePath } from '../utils/get-file-path.js';
5
+ import { createQuickFixActions, createFixAllAction } from './code-actions.js';
6
+ import { convertDiagnostics } from './convert-diagnostics.js';
7
+ import { getAccessibilityByLocation } from './get-accessibility-by-location.js';
8
+ import { applySuppressionsToViolations, loadSuppressions } from './suppression-support.js';
9
+ const engines = new Map();
10
+ const fixStates = new Map();
11
+ let moduleLog;
12
+ /**
13
+ * Returns the latest fix state for a document.
14
+ *
15
+ * @param uri - The document URI
16
+ * @returns The fix state, or `undefined` if the document has not been linted yet
17
+ */
18
+ export function getFixState(uri) {
19
+ return fixStates.get(uri);
20
+ }
21
+ /**
22
+ * Handles the `textDocument/didOpen` event by creating an MLEngine for the document.
23
+ *
24
+ * Sets up linting, diagnostics, fix-state tracking, and bulk suppression
25
+ * severity downgrade for the opened document.
26
+ *
27
+ * @param document - The opened text document
28
+ * @param MLEngine - The MLEngine constructor
29
+ * @param config - The language configuration
30
+ * @param locale - The locale for diagnostic messages
31
+ * @param log - Logger for general messages
32
+ * @param diagnosticsLog - Logger for diagnostic-specific messages
33
+ * @param sendDiagnostics - Callback to publish diagnostics to the client
34
+ * @param notFoundParserError - Callback invoked when a parser is not found
35
+ * @param workspace - The working directory the document belongs to; the base for config discovery
36
+ */
37
+ export async function onDidOpen(
38
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
39
+ document,
40
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
41
+ MLEngine,
42
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
43
+ config, locale, log, diagnosticsLog, sendDiagnostics, notFoundParserError, workspace) {
44
+ moduleLog = log;
45
+ const key = document.uri;
46
+ log(`Opened: ${key}`, 'debug');
47
+ const currentEngine = engines.get(key);
48
+ if (currentEngine) {
49
+ return;
50
+ }
51
+ const filePath = getFilePath(document.uri, document.languageId);
52
+ log(`${filePath.dirname}/${filePath.basename}`, 'debug');
53
+ const absoluteFilePath = `${filePath.dirname}/${filePath.basename}`;
54
+ const sourceCode = document.getText();
55
+ // `name` must be the workspace-relative path (not just the basename), or
56
+ // MLFile.path collapses subdirectory files to workspace-root + basename —
57
+ // which breaks pretender import resolution, nested config discovery, and
58
+ // excludeFiles/overrides glob matching for anything outside the workspace root.
59
+ const relativeFilePath = path.relative(workspace, absoluteFilePath);
60
+ const file = await MLEngine.toMLFile({ sourceCode, name: relativeFilePath, workspace });
61
+ if (!file) {
62
+ log(`File not found: ${filePath.basename}`, 'warn');
63
+ return;
64
+ }
65
+ const engine = new MLEngine(file, {
66
+ locale,
67
+ debug: config.debug,
68
+ defaultConfig: config.defaultConfig,
69
+ watch: true,
70
+ fix: true,
71
+ });
72
+ log(`Engine created: ${key}`);
73
+ let configSet = null;
74
+ engines.set(key, engine);
75
+ engine.on('config', (_, _configSet) => {
76
+ configSet = _configSet;
77
+ });
78
+ engine.on('log', (phase, message) => {
79
+ log(`[${phase}]: ${message}`, 'trace');
80
+ });
81
+ engine.on('lint-error', (_filePath, _sourceCode, error) => {
82
+ diagnosticsLog(error.message, 'error');
83
+ });
84
+ engine.on('config-errors', (_filePath, errors) => {
85
+ for (const error of errors) {
86
+ diagnosticsLog('ConfigError: ' + error.message, 'error');
87
+ log('ConfigError: ' + error.message, 'warn');
88
+ }
89
+ });
90
+ engine.on('lint', (lintFilePath, lintSourceCode, violations, fixedCode, debug, fixSummary) => {
91
+ clearTimeout(debounceTimers.get(key));
92
+ diagnosticsLog('', 'clear');
93
+ // Execute after 300ms from the last change.
94
+ debounceTimers.set(key, setTimeout(() => {
95
+ lint().catch((error) => {
96
+ log(String(error), 'error');
97
+ });
98
+ }, 300));
99
+ async function lint() {
100
+ diagnosticsLog(`Lint: ${document.uri}`);
101
+ // Apply bulk suppression severity downgrade and prefix suppressed messages
102
+ const suppressedPrefix = t('[suppressed] this warning is suppressed but should be fixed:');
103
+ const downgraded = await applyBulkSuppressions(absoluteFilePath, violations, workspace, log);
104
+ // The document may have been closed while the suppressions were loading; publishing
105
+ // now would resurrect the diagnostics `onDidClose` cleared and re-add the fix state.
106
+ if (engines.get(key) !== engine) {
107
+ return;
108
+ }
109
+ const effectiveViolations = downgraded.map(v => 'originalSeverity' in v && v.originalSeverity
110
+ ? { ...v, message: `${suppressedPrefix} ${v.message}` }
111
+ : v);
112
+ const errors = effectiveViolations.filter(v => v.severity === 'error');
113
+ const warns = effectiveViolations.filter(v => v.severity === 'warning');
114
+ const suppressed = effectiveViolations.filter(v => v.severity === 'info');
115
+ log(`Errors: ${errors.length}`, 'debug');
116
+ log(`Warnings: ${warns.length}`, 'debug');
117
+ if (suppressed.length > 0) {
118
+ log(`Suppressed (info): ${suppressed.length}`, 'debug');
119
+ }
120
+ if (debug) {
121
+ diagnosticsLog(' Tracing AST Mapping:\n' + debug.map(line => ` ${line}`).join('\n'), 'trace');
122
+ }
123
+ if (configSet) {
124
+ if (configSet.files.size > 0) {
125
+ diagnosticsLog(' Used configs:');
126
+ for (const files of configSet.files.values()) {
127
+ diagnosticsLog(` ${files}`);
128
+ }
129
+ }
130
+ else {
131
+ diagnosticsLog(' No use configs');
132
+ }
133
+ if (configSet.plugins.length > 0) {
134
+ diagnosticsLog(' Used plugins:');
135
+ for (const plugin of configSet.plugins.values()) {
136
+ diagnosticsLog(` ${plugin.name}`);
137
+ }
138
+ }
139
+ else {
140
+ diagnosticsLog(' No use plugins');
141
+ }
142
+ }
143
+ const diagnostics = convertDiagnostics({
144
+ filePath: lintFilePath,
145
+ sourceCode: lintSourceCode,
146
+ violations: effectiveViolations,
147
+ fixedCode,
148
+ status: 'processed',
149
+ });
150
+ fixStates.set(key, {
151
+ sourceCode: lintSourceCode,
152
+ violations: effectiveViolations,
153
+ fixedCode,
154
+ fixSummary,
155
+ });
156
+ if (diagnostics.length > 0) {
157
+ diagnosticsLog(` Violations(${diagnostics.length}):`);
158
+ for (const d of diagnostics) {
159
+ diagnosticsLog(` [${d.line}:${d.col}] ${d.code}`);
160
+ }
161
+ }
162
+ else {
163
+ diagnosticsLog(' ✔ No violations');
164
+ }
165
+ sendDiagnostics({
166
+ uri: document.uri,
167
+ diagnostics,
168
+ });
169
+ }
170
+ });
171
+ log('Run `engine.exec()` in `onDidOpen`', 'debug');
172
+ engine.exec().catch((error) => {
173
+ log(String(error), 'error');
174
+ notFoundParserError(error);
175
+ throw error;
176
+ });
177
+ }
178
+ const debounceTimers = new Map();
179
+ /**
180
+ * Handles the `textDocument/didChangeContent` event by re-running the lint engine.
181
+ *
182
+ * Debounces execution so that rapid successive changes do not trigger
183
+ * multiple lint passes.
184
+ *
185
+ * @param document - The changed text document
186
+ * @param log - Logger for messages
187
+ * @param notFoundParserError - Callback invoked when a parser is not found
188
+ */
189
+ export function onDidChangeContent(
190
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
191
+ document, log, notFoundParserError) {
192
+ const key = document.uri;
193
+ clearTimeout(debounceTimers.get(key));
194
+ const engine = engines.get(key);
195
+ debounceTimers.set(key, setTimeout(async () => {
196
+ if (!engine) {
197
+ return;
198
+ }
199
+ const code = document.getText();
200
+ try {
201
+ await engine.setCode(code);
202
+ log('Run `engine.exec()` in `onDidChangeContent`', 'debug');
203
+ engine.exec().catch((error) => notFoundParserError(error));
204
+ }
205
+ catch (error) {
206
+ if (error instanceof Error) {
207
+ log(error.message, 'error');
208
+ return;
209
+ }
210
+ log(`UnknownError: ${error}`, 'error');
211
+ }
212
+ }, 300));
213
+ }
214
+ /**
215
+ * Handles the `textDocument/didClose` event by releasing everything kept for the document.
216
+ *
217
+ * Without it every document opened during a session would keep its engine, its DOM and
218
+ * its config watcher (`watch: true`) until the server exits.
219
+ *
220
+ * @param uri - The URI of the closed document
221
+ */
222
+ export async function onDidClose(uri) {
223
+ clearTimeout(debounceTimers.get(uri));
224
+ debounceTimers.delete(uri);
225
+ fixStates.delete(uri);
226
+ const engine = engines.get(uri);
227
+ if (!engine) {
228
+ return;
229
+ }
230
+ engines.delete(uri);
231
+ await engine.close();
232
+ }
233
+ /**
234
+ * Retrieves the ARIA accessibility properties of the element at the given position.
235
+ *
236
+ * Used by the hover provider to display accessibility information such as
237
+ * role, exposed state, and ARIA property labels.
238
+ *
239
+ * @param textDocument - The text document identifier
240
+ * @param position - The cursor position within the document
241
+ * @param ariaVersion - The ARIA specification version to use
242
+ * @returns The node's accessibility properties, or null if unavailable
243
+ */
244
+ export async function getNodeWithAccessibilityProps(
245
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
246
+ textDocument,
247
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
248
+ position, ariaVersion) {
249
+ const key = textDocument.uri;
250
+ const engine = engines.get(key);
251
+ if (!engine) {
252
+ return null;
253
+ }
254
+ const a11y = await getAccessibilityByLocation(engine, position.line + 1, position.character, ariaVersion);
255
+ if (!a11y) {
256
+ return null;
257
+ }
258
+ const { node, aria } = a11y;
259
+ if (!aria) {
260
+ return null;
261
+ }
262
+ if (aria.unknown) {
263
+ return {
264
+ nodeName: node,
265
+ unknown: true,
266
+ };
267
+ }
268
+ if (!aria.exposedToTree) {
269
+ return {
270
+ nodeName: node,
271
+ exposed: false,
272
+ labels: {},
273
+ };
274
+ }
275
+ const labels = {};
276
+ const requiredLabel = `\u26A0\uFE0F**${t('Required')}**`;
277
+ labels.role = aria.role ? `\`${aria.role}\`` : t('No corresponding role');
278
+ labels.name = aria.nameProhibited
279
+ ? `**${t('Prohibited')}**`
280
+ : // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
281
+ aria.name
282
+ ? typeof aria.name === 'string'
283
+ ? `\`"${aria.name}"\``
284
+ : `**${t('Unknown')}**`
285
+ : `${t('None')}${aria.nameRequired ? ` ${requiredLabel}` : ''}`;
286
+ labels.focusable = `\`${aria.focusable}\``;
287
+ for (const [propName, { value, required }] of Object.entries(aria.props ?? {})) {
288
+ labels[propName] =
289
+ value === undefined ? t('Undefined') + (required ? ` ${requiredLabel}` : '') : `\`${value}\``;
290
+ }
291
+ return {
292
+ nodeName: node,
293
+ exposed: true,
294
+ labels,
295
+ };
296
+ }
297
+ /**
298
+ * Handles `textDocument/codeAction` requests for markuplint v5.
299
+ *
300
+ * Returns per-violation QuickFix actions and a SourceFixAll action
301
+ * (`source.fixAll.markuplint`) when fixable violations exist.
302
+ *
303
+ * @param params - The LSP Code Action request parameters
304
+ * @returns An array of Code Actions (QuickFix and/or SourceFixAll)
305
+ */
306
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
307
+ export function onCodeAction(params) {
308
+ const uri = params.textDocument.uri;
309
+ const fixState = getFixState(uri);
310
+ if (!fixState) {
311
+ moduleLog?.(`onCodeAction: no fixState for ${uri}`, 'debug');
312
+ return [];
313
+ }
314
+ const fixableCount = fixState.violations.filter(v => v.fix).length;
315
+ moduleLog?.(`onCodeAction: ${fixState.violations.length} violations (${fixableCount} fixable), ${params.context.diagnostics.length} diagnostics`, 'debug');
316
+ const quickFixes = createQuickFixActions(uri, params.context.diagnostics, fixState);
317
+ const fixAll = createFixAllAction(uri, params.context.diagnostics, fixState);
318
+ return fixAll ? [...quickFixes, fixAll] : quickFixes;
319
+ }
320
+ /**
321
+ * Applies bulk suppression to violations, downgrading severity to 'info'
322
+ * for suppressed violations. Uses git blame for count-exceeded cases.
323
+ */
324
+ async function applyBulkSuppressions(absoluteFilePath, violations, workspace, log) {
325
+ try {
326
+ const cache = await loadSuppressions(workspace, log);
327
+ if (!cache) {
328
+ return violations;
329
+ }
330
+ return await applySuppressionsToViolations(absoluteFilePath, violations, cache, log);
331
+ }
332
+ catch (error) {
333
+ if (isFatalError(error)) {
334
+ throw error;
335
+ }
336
+ log(`Bulk suppression failed: ${error instanceof Error ? error.message : String(error)}`, 'warn');
337
+ return violations;
338
+ }
339
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ import type { Config as MLConfig } from '@markuplint/ml-config';
2
+ import type { ARIAVersion } from '@markuplint/ml-spec';
3
+ import type { WorkingDirectoryEntry } from './utils/resolve-working-directory.js';
4
+ /**
5
+ * Per-language configuration sent by clients that manage settings themselves (the VS Code extension).
6
+ */
7
+ export type Config = {
8
+ enable: boolean;
9
+ debug: boolean;
10
+ /**
11
+ * Applied only when no config file is found. Left out, markuplint falls back to
12
+ * `markuplint:recommended`, exactly as the CLI does.
13
+ */
14
+ defaultConfig?: MLConfig;
15
+ hover: {
16
+ accessibility: {
17
+ enable: boolean;
18
+ ariaVersion?: ARIAVersion;
19
+ };
20
+ };
21
+ };
22
+ /**
23
+ * Status information sent from the language server to the client for the status bar.
24
+ */
25
+ export type Status = {
26
+ readonly version: string;
27
+ readonly isLocalModule: boolean;
28
+ readonly message: string | null;
29
+ };
30
+ /**
31
+ * A map of language IDs to their markuplint configuration.
32
+ */
33
+ export type LangConfigs = Record<string, Config>;
34
+ /**
35
+ * The severities a diagnostic can be reported with.
36
+ */
37
+ export type ReportedSeverity = 'error' | 'warning' | 'info' | 'hint';
38
+ /**
39
+ * Maps the severity markuplint assigns to a violation onto the severity reported to the client.
40
+ * Severities missing from the map are reported unchanged.
41
+ */
42
+ export type SeverityMap = Partial<Record<'error' | 'warning' | 'info', ReportedSeverity>>;
43
+ /**
44
+ * Options a client may pass in `initializationOptions`. Every field is optional so that
45
+ * clients which know nothing about markuplint (Claude Code, OpenCode, Neovim) can start the
46
+ * server without configuring it.
47
+ */
48
+ export type InitializationOptions = {
49
+ /**
50
+ * Per-language configuration. When present, only languages listed here with `enable: true`
51
+ * are linted. When absent, every document the client opens is linted.
52
+ */
53
+ readonly langConfigs?: LangConfigs;
54
+ /** User-configured working directories for monorepo support */
55
+ readonly workingDirectories?: readonly WorkingDirectoryEntry[];
56
+ /**
57
+ * Absolute paths of the workspace folders. Falls back to the LSP-standard
58
+ * `workspaceFolders` and `rootUri` of the `initialize` request.
59
+ */
60
+ readonly workspaceFolders?: readonly string[];
61
+ /**
62
+ * Path to the git binary. Falls back to `'git'` (PATH lookup) when unset.
63
+ */
64
+ readonly gitPath?: string;
65
+ /**
66
+ * Opts in to the `markuplint/*` custom requests and notifications (status bar, output
67
+ * channels, popups). Clients that do not set it receive `window/logMessage` and
68
+ * `window/showMessage` instead, which every LSP client understands.
69
+ */
70
+ readonly extendedProtocol?: boolean;
71
+ /**
72
+ * Re-reports diagnostics with another severity, for clients that surface only some
73
+ * severities to their user (OpenCode hands only errors to the model).
74
+ */
75
+ readonly severityMap?: SeverityMap;
76
+ };
77
+ /**
78
+ * A logging function that writes to an output channel.
79
+ */
80
+ export type Log = (...args: LogArg) => void;
81
+ /**
82
+ * Log severity levels for the output channels.
83
+ */
84
+ export type LogType = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'clear';
85
+ /**
86
+ * Arguments for a log call: a message string and an optional log type.
87
+ */
88
+ export type LogArg = readonly [message: string, type?: LogType];
package/lib/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Extracts a directory name and base file name from a document URI.
3
+ *
4
+ * Handles `file:` and `untitled:` URI schemes. For untitled documents,
5
+ * appends the language ID as a file extension so markuplint can detect
6
+ * the correct parser.
7
+ *
8
+ * @param uri - The document URI (e.g. `"file:///path/to/file.html"`)
9
+ * @param langId - The language identifier (e.g. `"html"`, `"vue"`)
10
+ * @returns An object with `dirname` (absolute directory path) and `basename` (file name)
11
+ */
12
+ export declare function getFilePath(uri: string, langId: string): {
13
+ dirname: string;
14
+ basename: string;
15
+ };
@@ -0,0 +1,45 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ /**
4
+ * Extracts a directory name and base file name from a document URI.
5
+ *
6
+ * Handles `file:` and `untitled:` URI schemes. For untitled documents,
7
+ * appends the language ID as a file extension so markuplint can detect
8
+ * the correct parser.
9
+ *
10
+ * @param uri - The document URI (e.g. `"file:///path/to/file.html"`)
11
+ * @param langId - The language identifier (e.g. `"html"`, `"vue"`)
12
+ * @returns An object with `dirname` (absolute directory path) and `basename` (file name)
13
+ */
14
+ export function getFilePath(uri, langId) {
15
+ if (/^untitled:/i.test(uri)) {
16
+ const name = uri.replace(/^untitled:/i, '');
17
+ const basename = `${name}.${langId}`;
18
+ return {
19
+ dirname: path.resolve(),
20
+ basename,
21
+ };
22
+ }
23
+ const decodePath = fileURLToPath(decodeURIComponent(uri));
24
+ let filePath;
25
+ let untitled = false;
26
+ if (decodePath.startsWith('file:')) {
27
+ filePath = decodePath.replace(/^file:\/+/i, '/');
28
+ }
29
+ else if (decodePath.startsWith('untitled:')) {
30
+ filePath = decodePath.replace(/^untitled:/i, '');
31
+ untitled = true;
32
+ }
33
+ else {
34
+ filePath = decodePath;
35
+ }
36
+ const dirname = path.resolve(path.dirname(filePath));
37
+ let basename = path.basename(filePath);
38
+ if (untitled) {
39
+ basename += `.${langId}`;
40
+ }
41
+ return {
42
+ dirname,
43
+ basename,
44
+ };
45
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A working directory entry configuration, modeled after the ESLint VS Code extension.
3
+ *
4
+ * Supports:
5
+ * - Plain string: a directory path relative to the workspace folder
6
+ * - `{ directory, "!cwd"? }`: explicit directory with optional process.cwd control
7
+ * - `{ pattern, "!cwd"? }`: glob pattern for dynamic directory detection
8
+ * - `{ mode: "auto" | "location" }`: automatic working directory detection
9
+ */
10
+ export type WorkingDirectoryEntry = string | {
11
+ readonly directory: string;
12
+ readonly '!cwd'?: boolean;
13
+ } | {
14
+ readonly pattern: string;
15
+ readonly '!cwd'?: boolean;
16
+ } | {
17
+ readonly mode: 'auto' | 'location';
18
+ };
19
+ /**
20
+ * The result of resolving a working directory for a file.
21
+ */
22
+ export type ResolvedWorkingDirectory = {
23
+ /** The resolved working directory path */
24
+ readonly directory: string;
25
+ };
26
+ /**
27
+ * Converts a simple glob pattern (supporting `*` and `**`) into a RegExp.
28
+ *
29
+ * This is a simplified implementation for matching directory paths against
30
+ * patterns like `./packages/* /` or `./apps/** /`.
31
+ *
32
+ * @param pattern - A glob pattern string (e.g. `"./packages/* /"`)
33
+ * @returns A RegExp anchored at the start that matches directory paths
34
+ */
35
+ export declare function convertGlobToRegex(pattern: string): RegExp;
36
+ /**
37
+ * Resolves the working directory for a given file based on workingDirectories configuration.
38
+ *
39
+ * @param filePath - Absolute file path of the document being linted
40
+ * @param workspaceFolders - Workspace folder paths (absolute)
41
+ * @param workingDirectories - The user-configured working directories
42
+ * @returns The resolved working directory, or `undefined` to use the default behavior
43
+ */
44
+ export declare function resolveWorkingDirectory(filePath: string, workspaceFolders: readonly string[], workingDirectories?: readonly WorkingDirectoryEntry[]): ResolvedWorkingDirectory | undefined;