@bubblebrain-ai/bubble 0.0.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/README.md +70 -0
- package/dist/agent/evidence-tracker.d.ts +15 -0
- package/dist/agent/evidence-tracker.js +93 -0
- package/dist/agent/execution-governor.d.ts +30 -0
- package/dist/agent/execution-governor.js +169 -0
- package/dist/agent/subtask-policy.d.ts +14 -0
- package/dist/agent/subtask-policy.js +60 -0
- package/dist/agent/task-classifier.d.ts +3 -0
- package/dist/agent/task-classifier.js +36 -0
- package/dist/agent/tool-arbiter.d.ts +7 -0
- package/dist/agent/tool-arbiter.js +33 -0
- package/dist/agent/tool-intent.d.ts +20 -0
- package/dist/agent/tool-intent.js +176 -0
- package/dist/agent.d.ts +95 -0
- package/dist/agent.js +672 -0
- package/dist/approval/controller.d.ts +48 -0
- package/dist/approval/controller.js +78 -0
- package/dist/approval/danger.d.ts +13 -0
- package/dist/approval/danger.js +55 -0
- package/dist/approval/diff-hunks.d.ts +12 -0
- package/dist/approval/diff-hunks.js +32 -0
- package/dist/approval/session-cache.d.ts +35 -0
- package/dist/approval/session-cache.js +68 -0
- package/dist/approval/tool-helper.d.ts +14 -0
- package/dist/approval/tool-helper.js +32 -0
- package/dist/approval/types.d.ts +56 -0
- package/dist/approval/types.js +8 -0
- package/dist/bubble-home.d.ts +8 -0
- package/dist/bubble-home.js +19 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +82 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +144 -0
- package/dist/context/budget.d.ts +21 -0
- package/dist/context/budget.js +72 -0
- package/dist/context/compact-llm.d.ts +16 -0
- package/dist/context/compact-llm.js +132 -0
- package/dist/context/compact.d.ts +15 -0
- package/dist/context/compact.js +251 -0
- package/dist/context/overflow.d.ts +9 -0
- package/dist/context/overflow.js +46 -0
- package/dist/context/projector.d.ts +26 -0
- package/dist/context/projector.js +150 -0
- package/dist/context/prune.d.ts +9 -0
- package/dist/context/prune.js +111 -0
- package/dist/lsp/config.d.ts +18 -0
- package/dist/lsp/config.js +58 -0
- package/dist/lsp/diagnostics.d.ts +24 -0
- package/dist/lsp/diagnostics.js +103 -0
- package/dist/lsp/index.d.ts +3 -0
- package/dist/lsp/index.js +3 -0
- package/dist/lsp/service.d.ts +85 -0
- package/dist/lsp/service.js +695 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +352 -0
- package/dist/mcp/client.d.ts +68 -0
- package/dist/mcp/client.js +163 -0
- package/dist/mcp/config.d.ts +26 -0
- package/dist/mcp/config.js +127 -0
- package/dist/mcp/manager.d.ts +55 -0
- package/dist/mcp/manager.js +296 -0
- package/dist/mcp/name.d.ts +26 -0
- package/dist/mcp/name.js +40 -0
- package/dist/mcp/transports.d.ts +53 -0
- package/dist/mcp/transports.js +248 -0
- package/dist/mcp/types.d.ts +111 -0
- package/dist/mcp/types.js +14 -0
- package/dist/memory/db.d.ts +62 -0
- package/dist/memory/db.js +313 -0
- package/dist/memory/index.d.ts +9 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/paths.d.ts +18 -0
- package/dist/memory/paths.js +38 -0
- package/dist/memory/phase1.d.ts +23 -0
- package/dist/memory/phase1.js +172 -0
- package/dist/memory/phase2.d.ts +19 -0
- package/dist/memory/phase2.js +100 -0
- package/dist/memory/prompts.d.ts +19 -0
- package/dist/memory/prompts.js +99 -0
- package/dist/memory/reset.d.ts +1 -0
- package/dist/memory/reset.js +13 -0
- package/dist/memory/start.d.ts +24 -0
- package/dist/memory/start.js +50 -0
- package/dist/memory/storage.d.ts +10 -0
- package/dist/memory/storage.js +82 -0
- package/dist/memory/store.d.ts +43 -0
- package/dist/memory/store.js +193 -0
- package/dist/memory/usage.d.ts +1 -0
- package/dist/memory/usage.js +38 -0
- package/dist/model-catalog.d.ts +20 -0
- package/dist/model-catalog.js +99 -0
- package/dist/model-config.d.ts +32 -0
- package/dist/model-config.js +59 -0
- package/dist/model-pricing.d.ts +23 -0
- package/dist/model-pricing.js +46 -0
- package/dist/oauth/index.d.ts +3 -0
- package/dist/oauth/index.js +2 -0
- package/dist/oauth/openai-codex.d.ts +9 -0
- package/dist/oauth/openai-codex.js +173 -0
- package/dist/oauth/storage.d.ts +18 -0
- package/dist/oauth/storage.js +60 -0
- package/dist/oauth/types.d.ts +15 -0
- package/dist/oauth/types.js +1 -0
- package/dist/orchestrator/default-hooks.d.ts +2 -0
- package/dist/orchestrator/default-hooks.js +96 -0
- package/dist/orchestrator/hooks.d.ts +78 -0
- package/dist/orchestrator/hooks.js +52 -0
- package/dist/orchestrator/workflow.d.ts +10 -0
- package/dist/orchestrator/workflow.js +22 -0
- package/dist/permission/mode.d.ts +23 -0
- package/dist/permission/mode.js +20 -0
- package/dist/permissions/rule.d.ts +39 -0
- package/dist/permissions/rule.js +234 -0
- package/dist/permissions/settings.d.ts +71 -0
- package/dist/permissions/settings.js +202 -0
- package/dist/permissions/types.d.ts +61 -0
- package/dist/permissions/types.js +14 -0
- package/dist/prompt/compose.d.ts +12 -0
- package/dist/prompt/compose.js +67 -0
- package/dist/prompt/environment.d.ts +12 -0
- package/dist/prompt/environment.js +38 -0
- package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
- package/dist/prompt/provider-prompts/anthropic.js +5 -0
- package/dist/prompt/provider-prompts/codex.d.ts +1 -0
- package/dist/prompt/provider-prompts/codex.js +5 -0
- package/dist/prompt/provider-prompts/default.d.ts +1 -0
- package/dist/prompt/provider-prompts/default.js +6 -0
- package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
- package/dist/prompt/provider-prompts/gemini.js +5 -0
- package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
- package/dist/prompt/provider-prompts/gpt.js +5 -0
- package/dist/prompt/reminders.d.ts +30 -0
- package/dist/prompt/reminders.js +164 -0
- package/dist/prompt/runtime.d.ts +12 -0
- package/dist/prompt/runtime.js +31 -0
- package/dist/prompt/skills.d.ts +2 -0
- package/dist/prompt/skills.js +4 -0
- package/dist/provider-openai-codex.d.ts +14 -0
- package/dist/provider-openai-codex.js +409 -0
- package/dist/provider-registry.d.ts +56 -0
- package/dist/provider-registry.js +244 -0
- package/dist/provider-transform.d.ts +10 -0
- package/dist/provider-transform.js +69 -0
- package/dist/provider.d.ts +31 -0
- package/dist/provider.js +269 -0
- package/dist/question/controller.d.ts +22 -0
- package/dist/question/controller.js +97 -0
- package/dist/question/index.d.ts +2 -0
- package/dist/question/index.js +2 -0
- package/dist/question/types.d.ts +42 -0
- package/dist/question/types.js +6 -0
- package/dist/session-log.d.ts +16 -0
- package/dist/session-log.js +267 -0
- package/dist/session-types.d.ts +55 -0
- package/dist/session-types.js +1 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +135 -0
- package/dist/skills/discovery.d.ts +12 -0
- package/dist/skills/discovery.js +148 -0
- package/dist/skills/format.d.ts +2 -0
- package/dist/skills/format.js +47 -0
- package/dist/skills/frontmatter.d.ts +5 -0
- package/dist/skills/frontmatter.js +60 -0
- package/dist/skills/invocation.d.ts +8 -0
- package/dist/skills/invocation.js +51 -0
- package/dist/skills/registry.d.ts +17 -0
- package/dist/skills/registry.js +42 -0
- package/dist/skills/types.d.ts +32 -0
- package/dist/skills/types.js +1 -0
- package/dist/slash-commands/commands.d.ts +7 -0
- package/dist/slash-commands/commands.js +779 -0
- package/dist/slash-commands/index.d.ts +4 -0
- package/dist/slash-commands/index.js +8 -0
- package/dist/slash-commands/registry.d.ts +31 -0
- package/dist/slash-commands/registry.js +70 -0
- package/dist/slash-commands/types.d.ts +44 -0
- package/dist/slash-commands/types.js +1 -0
- package/dist/slash-commands/unified.d.ts +38 -0
- package/dist/slash-commands/unified.js +38 -0
- package/dist/system-prompt.d.ts +34 -0
- package/dist/system-prompt.js +7 -0
- package/dist/tools/bash.d.ts +6 -0
- package/dist/tools/bash.js +135 -0
- package/dist/tools/edit.d.ts +16 -0
- package/dist/tools/edit.js +95 -0
- package/dist/tools/exa-mcp.d.ts +3 -0
- package/dist/tools/exa-mcp.js +74 -0
- package/dist/tools/exit-plan-mode.d.ts +17 -0
- package/dist/tools/exit-plan-mode.js +68 -0
- package/dist/tools/glob.d.ts +5 -0
- package/dist/tools/glob.js +129 -0
- package/dist/tools/grep.d.ts +5 -0
- package/dist/tools/grep.js +111 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +59 -0
- package/dist/tools/lsp.d.ts +4 -0
- package/dist/tools/lsp.js +92 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +90 -0
- package/dist/tools/question.d.ts +3 -0
- package/dist/tools/question.js +174 -0
- package/dist/tools/read.d.ts +7 -0
- package/dist/tools/read.js +83 -0
- package/dist/tools/sensitive-paths.d.ts +3 -0
- package/dist/tools/sensitive-paths.js +24 -0
- package/dist/tools/skill.d.ts +5 -0
- package/dist/tools/skill.js +51 -0
- package/dist/tools/task.d.ts +2 -0
- package/dist/tools/task.js +57 -0
- package/dist/tools/todo.d.ts +12 -0
- package/dist/tools/todo.js +151 -0
- package/dist/tools/tool-search.d.ts +23 -0
- package/dist/tools/tool-search.js +124 -0
- package/dist/tools/web-fetch.d.ts +6 -0
- package/dist/tools/web-fetch.js +75 -0
- package/dist/tools/web-search.d.ts +5 -0
- package/dist/tools/web-search.js +49 -0
- package/dist/tools/write.d.ts +11 -0
- package/dist/tools/write.js +77 -0
- package/dist/tui/display-history.d.ts +35 -0
- package/dist/tui/display-history.js +243 -0
- package/dist/tui/file-mentions.d.ts +29 -0
- package/dist/tui/file-mentions.js +174 -0
- package/dist/tui/image-paste.d.ts +54 -0
- package/dist/tui/image-paste.js +288 -0
- package/dist/tui/markdown-theme-rules.d.ts +23 -0
- package/dist/tui/markdown-theme-rules.js +164 -0
- package/dist/tui/markdown-theme.d.ts +5 -0
- package/dist/tui/markdown-theme.js +27 -0
- package/dist/tui/opencode-spinner.d.ts +21 -0
- package/dist/tui/opencode-spinner.js +216 -0
- package/dist/tui/prompt-keybindings.d.ts +41 -0
- package/dist/tui/prompt-keybindings.js +28 -0
- package/dist/tui/recent-activity.d.ts +8 -0
- package/dist/tui/recent-activity.js +71 -0
- package/dist/tui/run.d.ts +39 -0
- package/dist/tui/run.js +5696 -0
- package/dist/tui/sidebar-mcp.d.ts +31 -0
- package/dist/tui/sidebar-mcp.js +62 -0
- package/dist/tui/sidebar-state.d.ts +12 -0
- package/dist/tui/sidebar-state.js +69 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.js +4 -0
- package/dist/variant/thinking-level.d.ts +5 -0
- package/dist/variant/thinking-level.js +25 -0
- package/dist/variant/variant-resolver.d.ts +4 -0
- package/dist/variant/variant-resolver.js +12 -0
- package/package.json +47 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
import { execFile as execFileCallback, spawn } from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { access, readFile } from "node:fs/promises";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
8
|
+
import { createMessageConnection, StreamMessageReader, StreamMessageWriter, } from "vscode-jsonrpc/node.js";
|
|
9
|
+
import { customLspServerEntries, isLspEnabled, isLspServerEnabled } from "./config.js";
|
|
10
|
+
const execFile = promisify(execFileCallback);
|
|
11
|
+
const services = new Map();
|
|
12
|
+
const TS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
13
|
+
const NODE_ROOT_MARKERS = ["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock", "package.json"];
|
|
14
|
+
const DENO_ROOT_MARKERS = ["deno.json", "deno.jsonc"];
|
|
15
|
+
export class ProjectLspService {
|
|
16
|
+
cwd;
|
|
17
|
+
emitter = new EventEmitter();
|
|
18
|
+
clients = new Map();
|
|
19
|
+
spawning = new Map();
|
|
20
|
+
starting = new Map();
|
|
21
|
+
broken = new Map();
|
|
22
|
+
unavailable = new Set();
|
|
23
|
+
diagnosticsByFile = new Map();
|
|
24
|
+
waiters = new Set();
|
|
25
|
+
disposed = false;
|
|
26
|
+
config;
|
|
27
|
+
constructor(cwd, config) {
|
|
28
|
+
this.cwd = cwd;
|
|
29
|
+
this.config = config;
|
|
30
|
+
}
|
|
31
|
+
updateConfig(config) {
|
|
32
|
+
const wasDisabled = this.isDisabled();
|
|
33
|
+
this.config = config;
|
|
34
|
+
this.unavailable.clear();
|
|
35
|
+
this.broken.clear();
|
|
36
|
+
if (!wasDisabled && this.isDisabled()) {
|
|
37
|
+
void this.shutdownClients();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
void this.shutdownDisabledClients();
|
|
41
|
+
}
|
|
42
|
+
this.emitStatus();
|
|
43
|
+
}
|
|
44
|
+
async restart() {
|
|
45
|
+
this.broken.clear();
|
|
46
|
+
this.unavailable.clear();
|
|
47
|
+
await this.shutdownClients();
|
|
48
|
+
this.emitStatus();
|
|
49
|
+
}
|
|
50
|
+
onStatusChange(listener) {
|
|
51
|
+
this.emitter.on("status", listener);
|
|
52
|
+
return () => this.emitter.off("status", listener);
|
|
53
|
+
}
|
|
54
|
+
isDisabled() {
|
|
55
|
+
return !isLspEnabled(this.config);
|
|
56
|
+
}
|
|
57
|
+
status() {
|
|
58
|
+
const connected = [...this.clients.values()].map((client) => ({
|
|
59
|
+
id: client.id,
|
|
60
|
+
name: client.name,
|
|
61
|
+
root: relative(this.cwd, client.root) || ".",
|
|
62
|
+
status: "connected",
|
|
63
|
+
}));
|
|
64
|
+
const starting = [...this.starting.entries()]
|
|
65
|
+
.filter(([key]) => !this.clients.has(key) && !this.broken.has(key))
|
|
66
|
+
.map(([, status]) => status);
|
|
67
|
+
return [...connected, ...starting, ...this.broken.values()];
|
|
68
|
+
}
|
|
69
|
+
async hasClients(filePath) {
|
|
70
|
+
const file = this.resolveInsideCwd(filePath);
|
|
71
|
+
if (!file)
|
|
72
|
+
return false;
|
|
73
|
+
return (await this.matchingServers(file)).length > 0;
|
|
74
|
+
}
|
|
75
|
+
async touchFile(filePath, diagnostics) {
|
|
76
|
+
const file = this.resolveInsideCwd(filePath);
|
|
77
|
+
if (!file)
|
|
78
|
+
return;
|
|
79
|
+
const clients = await this.getClients(file);
|
|
80
|
+
if (!clients.length)
|
|
81
|
+
return;
|
|
82
|
+
await Promise.all(clients.map((client) => this.openOrChange(client, file)));
|
|
83
|
+
if (diagnostics) {
|
|
84
|
+
await this.waitForDiagnostics(file, diagnostics === "full" ? 8000 : 3500);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
diagnostics() {
|
|
88
|
+
const result = {};
|
|
89
|
+
for (const [file, byServer] of this.diagnosticsByFile.entries()) {
|
|
90
|
+
result[file] = [...byServer.values()].flat();
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
async hover(input) {
|
|
95
|
+
return this.runRequest(input, "textDocument/hover", {
|
|
96
|
+
textDocument: { uri: pathToFileURL(input.file).href },
|
|
97
|
+
position: { line: input.line, character: input.character },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async definition(input) {
|
|
101
|
+
return this.runRequest(input, "textDocument/definition", {
|
|
102
|
+
textDocument: { uri: pathToFileURL(input.file).href },
|
|
103
|
+
position: { line: input.line, character: input.character },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async references(input) {
|
|
107
|
+
return this.runRequest(input, "textDocument/references", {
|
|
108
|
+
textDocument: { uri: pathToFileURL(input.file).href },
|
|
109
|
+
position: { line: input.line, character: input.character },
|
|
110
|
+
context: { includeDeclaration: true },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async implementation(input) {
|
|
114
|
+
return this.runRequest(input, "textDocument/implementation", {
|
|
115
|
+
textDocument: { uri: pathToFileURL(input.file).href },
|
|
116
|
+
position: { line: input.line, character: input.character },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async documentSymbol(filePath) {
|
|
120
|
+
const file = this.resolveInsideCwd(filePath);
|
|
121
|
+
if (!file)
|
|
122
|
+
return [];
|
|
123
|
+
const clients = await this.getClients(file);
|
|
124
|
+
await Promise.all(clients.map((client) => this.openOrChange(client, file)));
|
|
125
|
+
const results = await Promise.all(clients.map((client) => client.connection
|
|
126
|
+
.sendRequest("textDocument/documentSymbol", { textDocument: { uri: pathToFileURL(file).href } })
|
|
127
|
+
.catch(() => [])));
|
|
128
|
+
return results.flatMap(normalizeLspResult);
|
|
129
|
+
}
|
|
130
|
+
async workspaceSymbol(query) {
|
|
131
|
+
const results = await Promise.all([...this.clients.values()].map((client) => client.connection.sendRequest("workspace/symbol", { query }).catch(() => [])));
|
|
132
|
+
return results.flatMap(normalizeLspResult).slice(0, 50);
|
|
133
|
+
}
|
|
134
|
+
async prepareCallHierarchy(input) {
|
|
135
|
+
return this.runRequest(input, "textDocument/prepareCallHierarchy", {
|
|
136
|
+
textDocument: { uri: pathToFileURL(input.file).href },
|
|
137
|
+
position: { line: input.line, character: input.character },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async incomingCalls(input) {
|
|
141
|
+
return this.callHierarchyRequest(input, "callHierarchy/incomingCalls");
|
|
142
|
+
}
|
|
143
|
+
async outgoingCalls(input) {
|
|
144
|
+
return this.callHierarchyRequest(input, "callHierarchy/outgoingCalls");
|
|
145
|
+
}
|
|
146
|
+
async shutdown() {
|
|
147
|
+
if (this.disposed)
|
|
148
|
+
return;
|
|
149
|
+
this.disposed = true;
|
|
150
|
+
for (const waiter of this.waiters) {
|
|
151
|
+
waiter.resolve();
|
|
152
|
+
}
|
|
153
|
+
this.waiters.clear();
|
|
154
|
+
await this.shutdownClients();
|
|
155
|
+
}
|
|
156
|
+
shutdownNow() {
|
|
157
|
+
for (const waiter of this.waiters)
|
|
158
|
+
waiter.resolve();
|
|
159
|
+
this.waiters.clear();
|
|
160
|
+
for (const client of this.clients.values()) {
|
|
161
|
+
client.stopping = true;
|
|
162
|
+
client.connection.dispose();
|
|
163
|
+
client.process.kill();
|
|
164
|
+
}
|
|
165
|
+
this.clients.clear();
|
|
166
|
+
}
|
|
167
|
+
async runRequest(input, method, params) {
|
|
168
|
+
const file = this.resolveInsideCwd(input.file);
|
|
169
|
+
if (!file)
|
|
170
|
+
return [];
|
|
171
|
+
const clients = await this.getClients(file);
|
|
172
|
+
await Promise.all(clients.map((client) => this.openOrChange(client, file)));
|
|
173
|
+
const results = await Promise.all(clients.map((client) => client.connection.sendRequest(method, params).catch(() => [])));
|
|
174
|
+
return results.flatMap(normalizeLspResult);
|
|
175
|
+
}
|
|
176
|
+
async callHierarchyRequest(input, method) {
|
|
177
|
+
const file = this.resolveInsideCwd(input.file);
|
|
178
|
+
if (!file)
|
|
179
|
+
return [];
|
|
180
|
+
const clients = await this.getClients(file);
|
|
181
|
+
const results = await Promise.all(clients.map(async (client) => {
|
|
182
|
+
await this.openOrChange(client, file);
|
|
183
|
+
const items = await client.connection
|
|
184
|
+
.sendRequest("textDocument/prepareCallHierarchy", {
|
|
185
|
+
textDocument: { uri: pathToFileURL(file).href },
|
|
186
|
+
position: { line: input.line, character: input.character },
|
|
187
|
+
})
|
|
188
|
+
.catch(() => []);
|
|
189
|
+
const first = normalizeLspResult(items)[0];
|
|
190
|
+
if (!first)
|
|
191
|
+
return [];
|
|
192
|
+
return client.connection.sendRequest(method, { item: first }).catch(() => []);
|
|
193
|
+
}));
|
|
194
|
+
return results.flatMap(normalizeLspResult);
|
|
195
|
+
}
|
|
196
|
+
async getClients(file) {
|
|
197
|
+
const matches = await this.matchingServers(file);
|
|
198
|
+
const clients = await Promise.all(matches.map(({ server, root }) => this.getClient(server, root)));
|
|
199
|
+
return clients.filter((client) => !!client);
|
|
200
|
+
}
|
|
201
|
+
async getClient(server, root) {
|
|
202
|
+
const key = `${root}:${server.id}`;
|
|
203
|
+
const existing = this.clients.get(key);
|
|
204
|
+
if (existing)
|
|
205
|
+
return existing;
|
|
206
|
+
if (this.broken.has(key))
|
|
207
|
+
return undefined;
|
|
208
|
+
if (this.unavailable.has(key))
|
|
209
|
+
return undefined;
|
|
210
|
+
const inflight = this.spawning.get(key);
|
|
211
|
+
if (inflight)
|
|
212
|
+
return inflight;
|
|
213
|
+
this.starting.set(key, {
|
|
214
|
+
id: server.id,
|
|
215
|
+
name: server.name,
|
|
216
|
+
root: relative(this.cwd, root) || ".",
|
|
217
|
+
status: "starting",
|
|
218
|
+
message: "starting",
|
|
219
|
+
});
|
|
220
|
+
this.emitStatus();
|
|
221
|
+
const task = this.spawnClient(server, root, key);
|
|
222
|
+
this.spawning.set(key, task);
|
|
223
|
+
task.finally(() => {
|
|
224
|
+
if (this.spawning.get(key) === task)
|
|
225
|
+
this.spawning.delete(key);
|
|
226
|
+
if (this.starting.delete(key))
|
|
227
|
+
this.emitStatus();
|
|
228
|
+
});
|
|
229
|
+
return task;
|
|
230
|
+
}
|
|
231
|
+
async spawnClient(server, root, key) {
|
|
232
|
+
try {
|
|
233
|
+
const handle = await server.spawn(root, { cwd: this.cwd });
|
|
234
|
+
if (!handle) {
|
|
235
|
+
this.unavailable.add(key);
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
const connection = createMessageConnection(new StreamMessageReader(handle.process.stdout), new StreamMessageWriter(handle.process.stdin), {
|
|
239
|
+
error: () => { },
|
|
240
|
+
warn: () => { },
|
|
241
|
+
info: () => { },
|
|
242
|
+
log: () => { },
|
|
243
|
+
});
|
|
244
|
+
const client = {
|
|
245
|
+
key,
|
|
246
|
+
id: server.id,
|
|
247
|
+
name: server.name,
|
|
248
|
+
root,
|
|
249
|
+
languageId: server.languageId,
|
|
250
|
+
process: handle.process,
|
|
251
|
+
connection,
|
|
252
|
+
documents: new Map(),
|
|
253
|
+
stopping: false,
|
|
254
|
+
};
|
|
255
|
+
connection.onNotification("textDocument/publishDiagnostics", (params) => {
|
|
256
|
+
if (!params?.uri)
|
|
257
|
+
return;
|
|
258
|
+
const file = normalize(fileURLToPath(params.uri));
|
|
259
|
+
const byServer = this.diagnosticsByFile.get(file) ?? new Map();
|
|
260
|
+
byServer.set(server.id, params.diagnostics ?? []);
|
|
261
|
+
this.diagnosticsByFile.set(file, byServer);
|
|
262
|
+
this.resolveDiagnosticWaiters(file);
|
|
263
|
+
});
|
|
264
|
+
connection.onRequest("workspace/configuration", (params) => server.configuration?.(root, Array.isArray(params?.items) ? params.items : []) ?? []);
|
|
265
|
+
connection.onRequest("workspace/workspaceFolders", () => [{ uri: pathToFileURL(root).href, name: basename(root) }]);
|
|
266
|
+
connection.onRequest("client/registerCapability", () => null);
|
|
267
|
+
connection.onRequest("client/unregisterCapability", () => null);
|
|
268
|
+
handle.process.once("exit", (code, signal) => {
|
|
269
|
+
this.clients.delete(key);
|
|
270
|
+
if (!this.disposed && !client.stopping) {
|
|
271
|
+
this.markBroken(key, server, root, `server exited${code === null ? "" : ` with code ${code}`}${signal ? ` (${signal})` : ""}`);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
connection.listen();
|
|
275
|
+
await connection.sendRequest("initialize", {
|
|
276
|
+
processId: process.pid,
|
|
277
|
+
rootPath: root,
|
|
278
|
+
rootUri: pathToFileURL(root).href,
|
|
279
|
+
workspaceFolders: [{ uri: pathToFileURL(root).href, name: basename(root) }],
|
|
280
|
+
initializationOptions: handle.initializationOptions ?? {},
|
|
281
|
+
capabilities: {
|
|
282
|
+
textDocument: {
|
|
283
|
+
synchronization: { dynamicRegistration: true, didSave: true },
|
|
284
|
+
hover: { dynamicRegistration: true, contentFormat: ["markdown", "plaintext"] },
|
|
285
|
+
definition: { dynamicRegistration: true },
|
|
286
|
+
references: { dynamicRegistration: true },
|
|
287
|
+
implementation: { dynamicRegistration: true },
|
|
288
|
+
documentSymbol: { dynamicRegistration: true, hierarchicalDocumentSymbolSupport: true },
|
|
289
|
+
callHierarchy: { dynamicRegistration: true },
|
|
290
|
+
publishDiagnostics: { relatedInformation: true, versionSupport: true },
|
|
291
|
+
},
|
|
292
|
+
workspace: {
|
|
293
|
+
configuration: true,
|
|
294
|
+
workspaceFolders: true,
|
|
295
|
+
symbol: { dynamicRegistration: true },
|
|
296
|
+
},
|
|
297
|
+
window: { workDoneProgress: false },
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
connection.sendNotification("initialized", {});
|
|
301
|
+
this.clients.set(key, client);
|
|
302
|
+
this.emitStatus();
|
|
303
|
+
return client;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
this.markBroken(key, server, root, error instanceof Error ? error.message : String(error));
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async openOrChange(client, file) {
|
|
311
|
+
const text = await readFile(file, "utf-8");
|
|
312
|
+
const languageId = client.languageId(file);
|
|
313
|
+
const uri = pathToFileURL(file).href;
|
|
314
|
+
const existing = client.documents.get(file);
|
|
315
|
+
if (!existing) {
|
|
316
|
+
client.documents.set(file, { languageId, version: 1 });
|
|
317
|
+
client.connection.sendNotification("textDocument/didOpen", {
|
|
318
|
+
textDocument: { uri, languageId, version: 1, text },
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
existing.version += 1;
|
|
323
|
+
client.connection.sendNotification("textDocument/didChange", {
|
|
324
|
+
textDocument: { uri, version: existing.version },
|
|
325
|
+
contentChanges: [{ text }],
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
waitForDiagnostics(file, timeoutMs) {
|
|
329
|
+
return new Promise((resolve) => {
|
|
330
|
+
let settled = false;
|
|
331
|
+
const timeout = setTimeout(() => done(), timeoutMs);
|
|
332
|
+
const debounce = setTimeout(() => {
|
|
333
|
+
if (this.diagnosticsByFile.has(file))
|
|
334
|
+
done();
|
|
335
|
+
}, 150);
|
|
336
|
+
const done = () => {
|
|
337
|
+
if (settled)
|
|
338
|
+
return;
|
|
339
|
+
settled = true;
|
|
340
|
+
clearTimeout(timeout);
|
|
341
|
+
clearTimeout(debounce);
|
|
342
|
+
this.waiters.delete(waiter);
|
|
343
|
+
resolve();
|
|
344
|
+
};
|
|
345
|
+
const waiter = { file, resolve: done, timeout };
|
|
346
|
+
this.waiters.add(waiter);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
resolveDiagnosticWaiters(file) {
|
|
350
|
+
for (const waiter of [...this.waiters]) {
|
|
351
|
+
if (waiter.file !== file)
|
|
352
|
+
continue;
|
|
353
|
+
waiter.resolve();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async matchingServers(file) {
|
|
357
|
+
if (!isLspEnabled(this.config))
|
|
358
|
+
return [];
|
|
359
|
+
const extension = extname(file) || file;
|
|
360
|
+
const result = [];
|
|
361
|
+
for (const server of this.servers()) {
|
|
362
|
+
if (!isLspServerEnabled(this.config, server.id))
|
|
363
|
+
continue;
|
|
364
|
+
if (server.extensions.length && !server.extensions.includes(extension))
|
|
365
|
+
continue;
|
|
366
|
+
const root = await server.root(file, { cwd: this.cwd });
|
|
367
|
+
if (!root)
|
|
368
|
+
continue;
|
|
369
|
+
if (this.broken.has(`${root}:${server.id}`))
|
|
370
|
+
continue;
|
|
371
|
+
result.push({ server, root });
|
|
372
|
+
}
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
servers() {
|
|
376
|
+
return [...BUILTIN_SERVERS, ...customServers(this.config)];
|
|
377
|
+
}
|
|
378
|
+
markBroken(key, server, root, message) {
|
|
379
|
+
this.starting.delete(key);
|
|
380
|
+
this.broken.set(key, {
|
|
381
|
+
id: server.id,
|
|
382
|
+
name: server.name,
|
|
383
|
+
root: relative(this.cwd, root) || ".",
|
|
384
|
+
status: "error",
|
|
385
|
+
message,
|
|
386
|
+
});
|
|
387
|
+
this.emitStatus();
|
|
388
|
+
}
|
|
389
|
+
emitStatus() {
|
|
390
|
+
this.emitter.emit("status");
|
|
391
|
+
}
|
|
392
|
+
resolveInsideCwd(filePath) {
|
|
393
|
+
const file = normalize(isAbsolute(filePath) ? filePath : resolve(this.cwd, filePath));
|
|
394
|
+
const rel = relative(this.cwd, file);
|
|
395
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
396
|
+
return undefined;
|
|
397
|
+
return file;
|
|
398
|
+
}
|
|
399
|
+
async shutdownClient(client) {
|
|
400
|
+
client.stopping = true;
|
|
401
|
+
await client.connection.sendRequest("shutdown").catch(() => undefined);
|
|
402
|
+
client.connection.sendNotification("exit");
|
|
403
|
+
client.connection.dispose();
|
|
404
|
+
client.process.kill();
|
|
405
|
+
}
|
|
406
|
+
async shutdownClients() {
|
|
407
|
+
await Promise.all([...this.clients.values()].map((client) => this.shutdownClient(client)));
|
|
408
|
+
this.clients.clear();
|
|
409
|
+
this.starting.clear();
|
|
410
|
+
this.diagnosticsByFile.clear();
|
|
411
|
+
}
|
|
412
|
+
async shutdownDisabledClients() {
|
|
413
|
+
const disabled = [...this.clients.values()].filter((client) => !isLspServerEnabled(this.config, client.id));
|
|
414
|
+
await Promise.all(disabled.map((client) => this.shutdownClient(client)));
|
|
415
|
+
for (const client of disabled) {
|
|
416
|
+
this.clients.delete(client.key);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
export const TypeScriptLspService = ProjectLspService;
|
|
421
|
+
export function getLspService(cwd, config) {
|
|
422
|
+
const key = resolve(cwd);
|
|
423
|
+
const existing = services.get(key);
|
|
424
|
+
if (existing) {
|
|
425
|
+
existing.updateConfig(config);
|
|
426
|
+
return existing;
|
|
427
|
+
}
|
|
428
|
+
const service = new ProjectLspService(key, config);
|
|
429
|
+
services.set(key, service);
|
|
430
|
+
process.once("exit", () => service.shutdownNow());
|
|
431
|
+
return service;
|
|
432
|
+
}
|
|
433
|
+
const TypeScriptServer = {
|
|
434
|
+
id: "typescript",
|
|
435
|
+
name: "typescript",
|
|
436
|
+
extensions: TS_EXTENSIONS,
|
|
437
|
+
root: nearestRoot(NODE_ROOT_MARKERS, DENO_ROOT_MARKERS),
|
|
438
|
+
languageId: languageIdFor,
|
|
439
|
+
async spawn(root, ctx) {
|
|
440
|
+
const requireFromRoot = createRequire(join(root, "package.json"));
|
|
441
|
+
const requireFromSelf = createRequire(import.meta.url);
|
|
442
|
+
const tsserverPath = resolveModule(requireFromRoot, "typescript/lib/tsserver.js")
|
|
443
|
+
?? resolveModule(requireFromSelf, "typescript/lib/tsserver.js");
|
|
444
|
+
const serverPath = resolveModule(requireFromRoot, "typescript-language-server/lib/cli.mjs")
|
|
445
|
+
?? resolveModule(requireFromSelf, "typescript-language-server/lib/cli.mjs");
|
|
446
|
+
if (!tsserverPath || !serverPath)
|
|
447
|
+
return undefined;
|
|
448
|
+
return {
|
|
449
|
+
process: spawn(process.execPath, [serverPath, "--stdio"], { cwd: root, env: process.env, stdio: "pipe" }),
|
|
450
|
+
initializationOptions: { tsserver: { path: tsserverPath } },
|
|
451
|
+
};
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
const DenoServer = {
|
|
455
|
+
id: "deno",
|
|
456
|
+
name: "deno",
|
|
457
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"],
|
|
458
|
+
root: denoRoot,
|
|
459
|
+
languageId: languageIdFor,
|
|
460
|
+
async spawn(root) {
|
|
461
|
+
const deno = await findExecutable("deno", [root]);
|
|
462
|
+
if (!deno)
|
|
463
|
+
return undefined;
|
|
464
|
+
return {
|
|
465
|
+
process: spawn(deno, ["lsp"], { cwd: root, env: process.env, stdio: "pipe" }),
|
|
466
|
+
initializationOptions: { enable: true, lint: true },
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
const VueServer = {
|
|
471
|
+
id: "vue",
|
|
472
|
+
name: "vue",
|
|
473
|
+
extensions: [".vue"],
|
|
474
|
+
root: nearestRoot(NODE_ROOT_MARKERS),
|
|
475
|
+
languageId: () => "vue",
|
|
476
|
+
async spawn(root) {
|
|
477
|
+
const requireFromRoot = createRequire(join(root, "package.json"));
|
|
478
|
+
const requireFromSelf = createRequire(import.meta.url);
|
|
479
|
+
const serverPath = resolveModule(requireFromRoot, "@vue/language-server/bin/vue-language-server.js")
|
|
480
|
+
?? resolveModule(requireFromSelf, "@vue/language-server/bin/vue-language-server.js");
|
|
481
|
+
const tsdk = dirname(resolveModule(requireFromRoot, "typescript/lib/tsserverlibrary.js")
|
|
482
|
+
?? resolveModule(requireFromSelf, "typescript/lib/tsserverlibrary.js")
|
|
483
|
+
?? "");
|
|
484
|
+
if (!serverPath)
|
|
485
|
+
return undefined;
|
|
486
|
+
const args = tsdk ? [serverPath, "--stdio", "--tsdk", tsdk] : [serverPath, "--stdio"];
|
|
487
|
+
return {
|
|
488
|
+
process: spawn(process.execPath, args, { cwd: root, env: process.env, stdio: "pipe" }),
|
|
489
|
+
};
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
const ESLintServer = {
|
|
493
|
+
id: "eslint",
|
|
494
|
+
name: "eslint",
|
|
495
|
+
extensions: [...TS_EXTENSIONS, ".vue"],
|
|
496
|
+
root: eslintRoot,
|
|
497
|
+
languageId: languageIdFor,
|
|
498
|
+
async spawn(root, ctx) {
|
|
499
|
+
const requireFromRoot = createRequire(join(root, "package.json"));
|
|
500
|
+
const requireFromSelf = createRequire(import.meta.url);
|
|
501
|
+
const eslint = resolveModule(requireFromRoot, "eslint");
|
|
502
|
+
if (!eslint)
|
|
503
|
+
return undefined;
|
|
504
|
+
const serverPath = resolveModule(requireFromRoot, "vscode-langservers-extracted/bin/vscode-eslint-language-server")
|
|
505
|
+
?? resolveModule(requireFromSelf, "vscode-langservers-extracted/bin/vscode-eslint-language-server");
|
|
506
|
+
if (!serverPath)
|
|
507
|
+
return undefined;
|
|
508
|
+
return {
|
|
509
|
+
process: spawn(process.execPath, [serverPath, "--stdio"], { cwd: root, env: process.env, stdio: "pipe" }),
|
|
510
|
+
};
|
|
511
|
+
},
|
|
512
|
+
configuration: (root, items) => items.map(() => eslintConfiguration(root)),
|
|
513
|
+
};
|
|
514
|
+
const OxlintServer = {
|
|
515
|
+
id: "oxlint",
|
|
516
|
+
name: "oxlint",
|
|
517
|
+
extensions: [...TS_EXTENSIONS, ".vue", ".astro", ".svelte"],
|
|
518
|
+
root: nearestRoot([".oxlintrc.json", ...NODE_ROOT_MARKERS]),
|
|
519
|
+
languageId: languageIdFor,
|
|
520
|
+
async spawn(root) {
|
|
521
|
+
const oxlint = await findExecutable("oxlint", [root]);
|
|
522
|
+
if (!oxlint || !(await supportsArg(oxlint, "--lsp")))
|
|
523
|
+
return undefined;
|
|
524
|
+
return {
|
|
525
|
+
process: spawn(oxlint, ["--lsp"], { cwd: root, env: process.env, stdio: "pipe" }),
|
|
526
|
+
};
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
const BUILTIN_SERVERS = [DenoServer, TypeScriptServer, VueServer, ESLintServer, OxlintServer];
|
|
530
|
+
function customServers(config) {
|
|
531
|
+
return customLspServerEntries(config).map(([id, server]) => ({
|
|
532
|
+
id,
|
|
533
|
+
name: id,
|
|
534
|
+
extensions: server.extensions ?? [],
|
|
535
|
+
root: customRoot(server.rootMarkers),
|
|
536
|
+
languageId: () => server.languageId ?? id,
|
|
537
|
+
async spawn(root) {
|
|
538
|
+
const command = server.command;
|
|
539
|
+
if (!command?.length)
|
|
540
|
+
return undefined;
|
|
541
|
+
return {
|
|
542
|
+
process: spawn(command[0], command.slice(1), {
|
|
543
|
+
cwd: root,
|
|
544
|
+
env: { ...process.env, ...(server.env ?? {}) },
|
|
545
|
+
stdio: "pipe",
|
|
546
|
+
}),
|
|
547
|
+
initializationOptions: server.initializationOptions,
|
|
548
|
+
};
|
|
549
|
+
},
|
|
550
|
+
}));
|
|
551
|
+
}
|
|
552
|
+
function customRoot(rootMarkers) {
|
|
553
|
+
return async (file, ctx) => {
|
|
554
|
+
if (!rootMarkers?.length)
|
|
555
|
+
return ctx.cwd;
|
|
556
|
+
const marker = await findUp(dirname(file), ctx.cwd, rootMarkers);
|
|
557
|
+
return marker ? dirname(marker) : undefined;
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function nearestRoot(includeMarkers, excludeMarkers = []) {
|
|
561
|
+
return async (file, ctx) => {
|
|
562
|
+
if (excludeMarkers.length && await findUp(dirname(file), ctx.cwd, excludeMarkers))
|
|
563
|
+
return undefined;
|
|
564
|
+
const marker = await findUp(dirname(file), ctx.cwd, includeMarkers);
|
|
565
|
+
return marker ? dirname(marker) : ctx.cwd;
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
async function denoRoot(file, ctx) {
|
|
569
|
+
const marker = await findUp(dirname(file), ctx.cwd, DENO_ROOT_MARKERS);
|
|
570
|
+
return marker ? dirname(marker) : undefined;
|
|
571
|
+
}
|
|
572
|
+
async function eslintRoot(file, ctx) {
|
|
573
|
+
const root = await nearestRoot(NODE_ROOT_MARKERS)(file, ctx);
|
|
574
|
+
if (!root)
|
|
575
|
+
return undefined;
|
|
576
|
+
const config = await findUp(dirname(file), root, [
|
|
577
|
+
"eslint.config.js",
|
|
578
|
+
"eslint.config.mjs",
|
|
579
|
+
"eslint.config.cjs",
|
|
580
|
+
".eslintrc",
|
|
581
|
+
".eslintrc.json",
|
|
582
|
+
".eslintrc.js",
|
|
583
|
+
".eslintrc.cjs",
|
|
584
|
+
".eslintrc.yaml",
|
|
585
|
+
".eslintrc.yml",
|
|
586
|
+
]);
|
|
587
|
+
return config ? root : undefined;
|
|
588
|
+
}
|
|
589
|
+
async function findUp(start, stop, markers) {
|
|
590
|
+
let dir = start;
|
|
591
|
+
while (true) {
|
|
592
|
+
for (const marker of markers) {
|
|
593
|
+
const target = join(dir, marker);
|
|
594
|
+
try {
|
|
595
|
+
await access(target);
|
|
596
|
+
return target;
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
// keep searching
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (dir === stop || dir === dirname(dir))
|
|
603
|
+
return undefined;
|
|
604
|
+
dir = dirname(dir);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function resolveModule(requireFn, modulePath) {
|
|
608
|
+
try {
|
|
609
|
+
return requireFn.resolve(modulePath);
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async function findExecutable(command, roots) {
|
|
616
|
+
const ext = process.platform === "win32" ? ".cmd" : "";
|
|
617
|
+
for (const root of roots) {
|
|
618
|
+
const local = join(root, "node_modules", ".bin", command + ext);
|
|
619
|
+
try {
|
|
620
|
+
await access(local);
|
|
621
|
+
return local;
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
// try next location
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
for (const dir of (process.env.PATH ?? "").split(":")) {
|
|
628
|
+
if (!dir)
|
|
629
|
+
continue;
|
|
630
|
+
const target = join(dir, command + ext);
|
|
631
|
+
try {
|
|
632
|
+
await access(target);
|
|
633
|
+
return target;
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
// try next PATH entry
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return undefined;
|
|
640
|
+
}
|
|
641
|
+
async function supportsArg(binary, arg) {
|
|
642
|
+
try {
|
|
643
|
+
const { stdout, stderr } = await execFile(binary, ["--help"], { timeout: 3000 });
|
|
644
|
+
return `${stdout}\n${stderr}`.includes(arg);
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function languageIdFor(file) {
|
|
651
|
+
switch (extname(file)) {
|
|
652
|
+
case ".ts":
|
|
653
|
+
case ".mts":
|
|
654
|
+
case ".cts":
|
|
655
|
+
return "typescript";
|
|
656
|
+
case ".tsx":
|
|
657
|
+
return "typescriptreact";
|
|
658
|
+
case ".jsx":
|
|
659
|
+
return "javascriptreact";
|
|
660
|
+
case ".vue":
|
|
661
|
+
return "vue";
|
|
662
|
+
default:
|
|
663
|
+
return "javascript";
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function normalizeLspResult(result) {
|
|
667
|
+
if (!result)
|
|
668
|
+
return [];
|
|
669
|
+
return Array.isArray(result) ? result.filter(Boolean) : [result];
|
|
670
|
+
}
|
|
671
|
+
function eslintConfiguration(root) {
|
|
672
|
+
return {
|
|
673
|
+
validate: "on",
|
|
674
|
+
packageManager: packageManagerFor(root),
|
|
675
|
+
useESLintClass: false,
|
|
676
|
+
experimental: { useFlatConfig: false },
|
|
677
|
+
codeAction: {
|
|
678
|
+
disableRuleComment: { enable: true, location: "separateLine" },
|
|
679
|
+
showDocumentation: { enable: true },
|
|
680
|
+
},
|
|
681
|
+
codeActionOnSave: { enable: false, mode: "all" },
|
|
682
|
+
format: false,
|
|
683
|
+
nodePath: null,
|
|
684
|
+
onIgnoredFiles: "off",
|
|
685
|
+
options: {},
|
|
686
|
+
problems: { shortenToSingleLine: false },
|
|
687
|
+
quiet: false,
|
|
688
|
+
rulesCustomizations: [],
|
|
689
|
+
run: "onType",
|
|
690
|
+
workingDirectory: { mode: "location" },
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function packageManagerFor(root) {
|
|
694
|
+
return root.includes("pnpm") ? "pnpm" : "npm";
|
|
695
|
+
}
|