@adecore/lsp 0.0.1 → 0.17.0-beta.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 (48) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +33 -0
  3. package/dist/connection.d.ts +50 -0
  4. package/dist/connection.js +290 -0
  5. package/dist/document.d.ts +60 -0
  6. package/dist/document.js +282 -0
  7. package/dist/edits.d.ts +18 -0
  8. package/dist/edits.js +161 -0
  9. package/dist/file-operations.d.ts +5 -0
  10. package/dist/file-operations.js +23 -0
  11. package/dist/glob.d.ts +1 -0
  12. package/dist/glob.js +67 -0
  13. package/dist/index.d.ts +12 -0
  14. package/dist/index.js +10 -0
  15. package/dist/protocol.d.ts +470 -0
  16. package/dist/protocol.js +1 -0
  17. package/dist/service.d.ts +53 -0
  18. package/dist/service.js +1 -0
  19. package/dist/session.d.ts +78 -0
  20. package/dist/session.js +528 -0
  21. package/dist/test-transport.d.ts +33 -0
  22. package/dist/test-transport.js +72 -0
  23. package/dist/testing.d.ts +33 -0
  24. package/dist/testing.js +72 -0
  25. package/dist/transport.d.ts +27 -0
  26. package/dist/transport.js +258 -0
  27. package/dist/uris.d.ts +2 -0
  28. package/dist/uris.js +28 -0
  29. package/dist/vue.d.ts +6 -0
  30. package/dist/vue.js +86 -0
  31. package/dist/watched-files.d.ts +3 -0
  32. package/dist/watched-files.js +41 -0
  33. package/package.json +36 -3
  34. package/src/connection.ts +310 -0
  35. package/src/document.ts +357 -0
  36. package/src/edits.ts +182 -0
  37. package/src/file-operations.ts +30 -0
  38. package/src/glob.ts +60 -0
  39. package/src/index.ts +30 -0
  40. package/src/protocol.ts +479 -0
  41. package/src/service.ts +109 -0
  42. package/src/session.ts +613 -0
  43. package/src/test-transport.ts +92 -0
  44. package/src/testing.ts +105 -0
  45. package/src/transport.ts +290 -0
  46. package/src/uris.ts +29 -0
  47. package/src/vue.ts +93 -0
  48. package/src/watched-files.ts +49 -0
@@ -0,0 +1,72 @@
1
+ import { JsonRpcConnection } from './connection.js';
2
+ import { applyContentChanges } from './edits.js';
3
+ export { createMemoryTransportPair } from './transport.js';
4
+ /*
5
+ * A language server for tests, on the far end of a transport. It answers the handshake, keeps the
6
+ * text of every document it was told about the way a real server does, and answers whatever a test
7
+ * registers with `handle`; any other request is refused as unsupported.
8
+ */
9
+ export class FakeLanguageServer {
10
+ connection;
11
+ received = [];
12
+ documents = new Map();
13
+ shutdownRequested = false;
14
+ exited = false;
15
+ constructor(transport, options = {}) {
16
+ this.connection = new JsonRpcConnection(transport, 0);
17
+ transport.onMessage((message) => {
18
+ const { method, params, id } = message;
19
+ if (typeof method === 'string') {
20
+ this.received.push({ method, params, id });
21
+ }
22
+ });
23
+ this.connection.onRequest('initialize', () => options.silent
24
+ ? new Promise(() => undefined)
25
+ : { capabilities: options.capabilities ?? { textDocumentSync: 2 }, serverInfo: options.serverInfo });
26
+ this.connection.onRequest('shutdown', () => {
27
+ this.shutdownRequested = true;
28
+ return null;
29
+ });
30
+ this.connection.onNotification('exit', () => {
31
+ this.exited = true;
32
+ });
33
+ this.connection.onNotification('textDocument/didOpen', (params) => {
34
+ const { textDocument } = params;
35
+ this.documents.set(textDocument.uri, { languageId: textDocument.languageId, text: textDocument.text, version: textDocument.version });
36
+ });
37
+ this.connection.onNotification('textDocument/didChange', (params) => {
38
+ const { textDocument, contentChanges } = params;
39
+ const document = this.documents.get(textDocument.uri);
40
+ if (document) {
41
+ document.text = applyContentChanges(document.text, contentChanges);
42
+ document.version = textDocument.version;
43
+ }
44
+ });
45
+ this.connection.onNotification('textDocument/didClose', (params) => {
46
+ this.documents.delete(params.textDocument.uri);
47
+ });
48
+ for (const [method, handler] of Object.entries(options.handlers ?? {})) {
49
+ this.connection.onRequest(method, handler);
50
+ }
51
+ }
52
+ handle(method, handler) {
53
+ this.connection.onRequest(method, handler);
54
+ }
55
+ /* The params of every request or notification of `method`, in the order they came. */
56
+ paramsOf(method) {
57
+ return this.received.filter((message) => message.method === method).map((message) => message.params);
58
+ }
59
+ publishDiagnostics(uri, diagnostics, version) {
60
+ return this.connection.notify('textDocument/publishDiagnostics', { uri, version, diagnostics });
61
+ }
62
+ notify(method, params) {
63
+ return this.connection.notify(method, params);
64
+ }
65
+ request(method, params) {
66
+ return this.connection.request(method, params);
67
+ }
68
+ /* The process went away: the transport closes under the client. */
69
+ crash() {
70
+ return this.connection.close();
71
+ }
72
+ }
@@ -0,0 +1,27 @@
1
+ import type { Disposable, LspTransport, RpcMessage } from './protocol.ts';
2
+ export declare class ContentLengthDecoder {
3
+ private readonly maxMessageBytes;
4
+ private chunks;
5
+ private size;
6
+ private length;
7
+ constructor(maxMessageBytes?: number);
8
+ accept(chunk: Uint8Array): unknown[];
9
+ }
10
+ export declare function encodeMessage(message: RpcMessage): Uint8Array;
11
+ export interface ByteStream {
12
+ write(chunk: Uint8Array): void | Promise<void>;
13
+ onData(listener: (chunk: Uint8Array) => void): Disposable;
14
+ onClose(listener: (error?: Error) => void): Disposable;
15
+ close(): void | Promise<void>;
16
+ }
17
+ export declare function createStreamTransport(stream: ByteStream, options?: {
18
+ maxMessageBytes?: number;
19
+ }): LspTransport;
20
+ export declare function createMemoryTransportPair(): [LspTransport, LspTransport];
21
+ export interface WebSocketTransportOptions {
22
+ protocols?: string[];
23
+ signal?: AbortSignal;
24
+ timeoutMs?: number;
25
+ createWebSocket?: (url: string, protocols?: string[]) => WebSocket;
26
+ }
27
+ export declare function connectWebSocket(url: string, options?: WebSocketTransportOptions): Promise<LspTransport>;
@@ -0,0 +1,258 @@
1
+ const DEFAULT_MAX_MESSAGE_BYTES = 32 * 1024 * 1024;
2
+ const MAX_HEADER_BYTES = 8192;
3
+ function asError(error) {
4
+ return error instanceof Error ? error : new Error(String(error));
5
+ }
6
+ /*
7
+ * The listeners of a transport. A message that arrives before anyone listens is held and handed to
8
+ * the first listener, since a server may speak the moment its pipe opens.
9
+ */
10
+ class TransportEvents {
11
+ closed = false;
12
+ messages = new Set();
13
+ closes = new Set();
14
+ backlog = [];
15
+ error;
16
+ onMessage = (listener) => {
17
+ this.messages.add(listener);
18
+ for (const message of this.backlog.splice(0)) {
19
+ listener(message);
20
+ }
21
+ return {
22
+ dispose: () => {
23
+ this.messages.delete(listener);
24
+ }
25
+ };
26
+ };
27
+ onClose = (listener) => {
28
+ this.closes.add(listener);
29
+ if (this.closed) {
30
+ listener(this.error);
31
+ }
32
+ return {
33
+ dispose: () => {
34
+ this.closes.delete(listener);
35
+ }
36
+ };
37
+ };
38
+ receive(message) {
39
+ if (this.closed) {
40
+ return;
41
+ }
42
+ if (this.messages.size === 0) {
43
+ this.backlog.push(message);
44
+ return;
45
+ }
46
+ for (const listener of this.messages) {
47
+ listener(message);
48
+ }
49
+ }
50
+ finish(error) {
51
+ if (this.closed) {
52
+ return;
53
+ }
54
+ this.closed = true;
55
+ this.error = error;
56
+ this.backlog = [];
57
+ for (const listener of this.closes) {
58
+ listener(error);
59
+ }
60
+ this.closes.clear();
61
+ this.messages.clear();
62
+ }
63
+ }
64
+ function concat(chunks, size) {
65
+ if (chunks.length === 1) {
66
+ return chunks[0];
67
+ }
68
+ const joined = new Uint8Array(size);
69
+ let offset = 0;
70
+ for (const chunk of chunks) {
71
+ joined.set(chunk, offset);
72
+ offset += chunk.length;
73
+ }
74
+ return joined;
75
+ }
76
+ function indexOfHeaderEnd(bytes) {
77
+ for (let i = 0; i + 3 < bytes.length; i++) {
78
+ if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) {
79
+ return i;
80
+ }
81
+ }
82
+ return -1;
83
+ }
84
+ /* Reads `Content-Length` framed messages, the way a language server speaks over stdio. */
85
+ export class ContentLengthDecoder {
86
+ maxMessageBytes;
87
+ chunks = [];
88
+ size = 0;
89
+ length = null;
90
+ constructor(maxMessageBytes = DEFAULT_MAX_MESSAGE_BYTES) {
91
+ this.maxMessageBytes = maxMessageBytes;
92
+ }
93
+ /* Every complete message in what has arrived so far. Throws on a frame it cannot read, after which the stream is no use. */
94
+ accept(chunk) {
95
+ this.chunks.push(chunk);
96
+ this.size += chunk.length;
97
+ const messages = [];
98
+ while (true) {
99
+ if (this.length === null) {
100
+ const buffer = concat(this.chunks, this.size);
101
+ this.chunks = [buffer];
102
+ const boundary = indexOfHeaderEnd(buffer);
103
+ if (boundary < 0) {
104
+ if (buffer.length > MAX_HEADER_BYTES) {
105
+ throw new Error('LSP header is too large');
106
+ }
107
+ break;
108
+ }
109
+ const header = new TextDecoder().decode(buffer.subarray(0, boundary));
110
+ const match = /^Content-Length:\s*(\d+)\s*$/im.exec(header);
111
+ if (!match || boundary > MAX_HEADER_BYTES) {
112
+ throw new Error('Invalid LSP Content-Length header');
113
+ }
114
+ const length = Number(match[1]);
115
+ if (!length || length > this.maxMessageBytes) {
116
+ throw new Error('LSP message is too large');
117
+ }
118
+ this.length = length;
119
+ this.chunks = [buffer.subarray(boundary + 4)];
120
+ this.size = buffer.length - boundary - 4;
121
+ }
122
+ if (this.size < this.length) {
123
+ break;
124
+ }
125
+ const buffer = concat(this.chunks, this.size);
126
+ messages.push(JSON.parse(new TextDecoder().decode(buffer.subarray(0, this.length))));
127
+ this.chunks = [buffer.subarray(this.length)];
128
+ this.size = buffer.length - this.length;
129
+ this.length = null;
130
+ }
131
+ return messages;
132
+ }
133
+ }
134
+ export function encodeMessage(message) {
135
+ const body = new TextEncoder().encode(JSON.stringify(message));
136
+ const header = new TextEncoder().encode(`Content-Length: ${body.length}\r\n\r\n`);
137
+ const framed = new Uint8Array(header.length + body.length);
138
+ framed.set(header, 0);
139
+ framed.set(body, header.length);
140
+ return framed;
141
+ }
142
+ export function createStreamTransport(stream, options = {}) {
143
+ const events = new TransportEvents();
144
+ const decoder = new ContentLengthDecoder(options.maxMessageBytes);
145
+ stream.onData((chunk) => {
146
+ try {
147
+ for (const message of decoder.accept(chunk)) {
148
+ events.receive(message);
149
+ }
150
+ }
151
+ catch (error) {
152
+ events.finish(asError(error));
153
+ void Promise.resolve(stream.close()).catch(() => undefined);
154
+ }
155
+ });
156
+ stream.onClose((error) => events.finish(error));
157
+ return {
158
+ onMessage: events.onMessage,
159
+ onClose: events.onClose,
160
+ async send(message) {
161
+ if (events.closed) {
162
+ throw new Error('LSP stream is closed');
163
+ }
164
+ await stream.write(encodeMessage(message));
165
+ },
166
+ async close() {
167
+ events.finish();
168
+ await stream.close();
169
+ }
170
+ };
171
+ }
172
+ /* Two ends of one connection in memory: what one sends the other receives, as JSON and one tick later. Closing either ends both. */
173
+ export function createMemoryTransportPair() {
174
+ const left = new TransportEvents();
175
+ const right = new TransportEvents();
176
+ const end = (inbox, outbox) => ({
177
+ onMessage: inbox.onMessage,
178
+ onClose: inbox.onClose,
179
+ send(message) {
180
+ if (inbox.closed || outbox.closed) {
181
+ throw new Error('LSP transport is closed');
182
+ }
183
+ const copy = JSON.parse(JSON.stringify(message));
184
+ queueMicrotask(() => outbox.receive(copy));
185
+ },
186
+ close() {
187
+ inbox.finish();
188
+ outbox.finish();
189
+ }
190
+ });
191
+ return [end(left, right), end(right, left)];
192
+ }
193
+ const MAX_SOCKET_BUFFER_BYTES = 8 * 1024 * 1024;
194
+ /* JSON text frames over a WebSocket, which a browser can open and a daemon can serve. */
195
+ export async function connectWebSocket(url, options = {}) {
196
+ if (options.signal?.aborted) {
197
+ throw new Error('WebSocket connection cancelled');
198
+ }
199
+ const socket = options.createWebSocket ? options.createWebSocket(url, options.protocols) : new WebSocket(url, options.protocols);
200
+ const events = new TransportEvents();
201
+ socket.addEventListener('message', (event) => {
202
+ try {
203
+ if (typeof event.data !== 'string') {
204
+ throw new Error('LSP WebSocket expects JSON text frames');
205
+ }
206
+ events.receive(JSON.parse(event.data));
207
+ }
208
+ catch (error) {
209
+ events.finish(asError(error));
210
+ socket.close(1003, 'Invalid JSON message');
211
+ }
212
+ });
213
+ socket.addEventListener('close', (event) => events.finish(event.code === 1000 ? undefined : new Error(`LSP WebSocket closed (${event.code})`)));
214
+ socket.addEventListener('error', () => events.finish(new Error('LSP WebSocket failed')));
215
+ await new Promise((resolve, reject) => {
216
+ const timer = setTimeout(() => fail(new Error('LSP WebSocket connection timed out')), options.timeoutMs ?? 10_000);
217
+ const cleanup = () => {
218
+ clearTimeout(timer);
219
+ socket.removeEventListener('open', open);
220
+ socket.removeEventListener('error', failed);
221
+ socket.removeEventListener('close', closed);
222
+ options.signal?.removeEventListener('abort', abort);
223
+ };
224
+ const fail = (reason) => {
225
+ cleanup();
226
+ socket.close();
227
+ reject(reason);
228
+ };
229
+ const open = () => {
230
+ cleanup();
231
+ resolve();
232
+ };
233
+ const failed = () => fail(new Error('LSP WebSocket connection failed'));
234
+ const closed = () => fail(new Error('LSP WebSocket closed before opening'));
235
+ const abort = () => fail(new Error('LSP WebSocket connection cancelled'));
236
+ socket.addEventListener('open', open, { once: true });
237
+ socket.addEventListener('error', failed, { once: true });
238
+ socket.addEventListener('close', closed, { once: true });
239
+ options.signal?.addEventListener('abort', abort, { once: true });
240
+ });
241
+ return {
242
+ onMessage: events.onMessage,
243
+ onClose: events.onClose,
244
+ send(message) {
245
+ if (events.closed || socket.readyState !== 1) {
246
+ throw new Error('LSP WebSocket is closed');
247
+ }
248
+ if (socket.bufferedAmount > MAX_SOCKET_BUFFER_BYTES) {
249
+ throw new Error('LSP WebSocket send buffer is full');
250
+ }
251
+ socket.send(JSON.stringify(message));
252
+ },
253
+ close() {
254
+ events.finish();
255
+ socket.close(1000);
256
+ }
257
+ };
258
+ }
package/dist/uris.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function pathToFileUri(path: string): string;
2
+ export declare function fileUriToPath(uri: string): string | null;
package/dist/uris.js ADDED
@@ -0,0 +1,28 @@
1
+ function isWindowsDrive(segment) {
2
+ return /^[A-Za-z]:$/.test(segment);
3
+ }
4
+ /* An absolute path, POSIX or with a Windows drive, as the `file:` URI a language server names it by. */
5
+ export function pathToFileUri(path) {
6
+ const normalized = path.replaceAll('\\', '/');
7
+ const rooted = normalized.startsWith('/') ? normalized : `/${normalized}`;
8
+ const segments = rooted.split('/').map((segment, index) => (index === 1 && isWindowsDrive(segment) ? segment : encodeURIComponent(segment)));
9
+ return `file://${segments.join('/')}`;
10
+ }
11
+ /* The path behind a `file:` URI, in the form of the machine it names, or null for any other kind of URI. */
12
+ export function fileUriToPath(uri) {
13
+ let url;
14
+ try {
15
+ url = new URL(uri);
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ if (url.protocol !== 'file:') {
21
+ return null;
22
+ }
23
+ const path = decodeURIComponent(url.pathname);
24
+ if (/^\/[A-Za-z]:/.test(path)) {
25
+ return path.slice(1).replaceAll('/', '\\');
26
+ }
27
+ return path;
28
+ }
package/dist/vue.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { Disposable } from './protocol.ts';
2
+ import type { LspSession } from './session.ts';
3
+ export declare function bridgeVueTypeScript(vue: LspSession, typescript: LspSession): Disposable;
4
+ export type VueServer = 'vue' | 'typescript';
5
+ export declare function isVueExpression(text: string, offset: number): boolean;
6
+ export declare function vueServerOrder(method: string, text: string, params: unknown): [VueServer, VueServer];
package/dist/vue.js ADDED
@@ -0,0 +1,86 @@
1
+ import { offsetAt } from './edits.js';
2
+ /*
3
+ * Vue's server leaves TypeScript to a tsserver of its own project and asks for it with
4
+ * `tsserver/request` notifications, which this relays to the TypeScript server's
5
+ * `typescript.tsserverRequest` command. Attach it before the Vue session initializes, and open each
6
+ * `.vue` document in both sessions, TypeScript first.
7
+ */
8
+ export function bridgeVueTypeScript(vue, typescript) {
9
+ let disposed = false;
10
+ const subscription = vue.onNotification('tsserver/request', async (params) => {
11
+ // vscode-jsonrpc encodes this single positional argument as an outer array.
12
+ const positional = Array.isArray(params) && params.length === 1 && Array.isArray(params[0]);
13
+ const tuple = positional ? params[0] : params;
14
+ if (!Array.isArray(tuple) || tuple.length !== 3) {
15
+ throw new Error('Invalid Vue tsserver/request notification');
16
+ }
17
+ const [id, command, args] = tuple;
18
+ let body = null;
19
+ try {
20
+ const response = await typescript.executeCommand('typescript.tsserverRequest', [
21
+ command,
22
+ args,
23
+ { isAsync: false, lowPriority: true }
24
+ ]);
25
+ body = response?.body ?? null;
26
+ }
27
+ catch (error) {
28
+ vue.connection.reportError(error);
29
+ }
30
+ // Vue keeps the request pending until it gets an answer, a null one included.
31
+ if (!disposed && !vue.connection.isClosed) {
32
+ await vue.connection.notify('tsserver/response', positional ? [[id, body]] : [id, body]);
33
+ }
34
+ });
35
+ return {
36
+ dispose() {
37
+ disposed = true;
38
+ subscription.dispose();
39
+ }
40
+ };
41
+ }
42
+ /* A `<script>` body, or the expression of an interpolation or a `v-` / `:` / `@` attribute: the places TypeScript knows more than Vue does. */
43
+ export function isVueExpression(text, offset) {
44
+ const script = /<script\b[^>]*>[\s\S]*?<\/script\s*>/gi;
45
+ for (let match = script.exec(text); match; match = script.exec(text)) {
46
+ if (offset > match.index + match[0].indexOf('>') && offset < match.index + match[0].lastIndexOf('</')) {
47
+ return true;
48
+ }
49
+ }
50
+ const expressions = /\{\{[\s\S]*?\}\}|(?:[:@][\w.:-]+|v-[\w.:-]+)\s*=\s*(?:"[^"]*"|'[^']*')/g;
51
+ for (let match = expressions.exec(text); match; match = expressions.exec(text)) {
52
+ if (offset >= match.index && offset <= match.index + match[0].length) {
53
+ return true;
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+ function paramsOffset(text, params) {
59
+ if (!params || typeof params !== 'object') {
60
+ return null;
61
+ }
62
+ const position = 'position' in params
63
+ ? params.position
64
+ : 'range' in params && params.range && typeof params.range === 'object' && 'start' in params.range
65
+ ? params.range.start
66
+ : null;
67
+ if (!position || typeof position !== 'object') {
68
+ return null;
69
+ }
70
+ try {
71
+ return offsetAt(text, position);
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ /*
78
+ * Which of the two servers of a Vue document to ask first, the other being the fallback when the
79
+ * first does not support the method. Vue answers template markup, HTML and CSS; TypeScript answers
80
+ * a script and the expressions of a template, and the inlay hints of all of it.
81
+ */
82
+ export function vueServerOrder(method, text, params) {
83
+ const offset = paramsOffset(text, params);
84
+ const typescriptFirst = method === 'textDocument/inlayHint' || (offset !== null && isVueExpression(text, offset));
85
+ return typescriptFirst ? ['typescript', 'vue'] : ['vue', 'typescript'];
86
+ }
@@ -0,0 +1,3 @@
1
+ import type { FileSystemWatcher } from './protocol.ts';
2
+ export type FileChangeType = 1 | 2 | 3;
3
+ export declare function watchesFile(watchers: readonly FileSystemWatcher[], root: string, path: string, type: FileChangeType): boolean;
@@ -0,0 +1,41 @@
1
+ import { globMatch } from './glob.js';
2
+ import { fileUriToPath } from './uris.js';
3
+ /* Folders whose files a server hears about only when a pattern of its own names the folder. */
4
+ const SPARING_FOLDERS = ['node_modules', 'vendor', '.git'];
5
+ function relativeTo(root, path) {
6
+ const prefix = root.endsWith('/') ? root : `${root}/`;
7
+ return path.startsWith(prefix) ? path.slice(prefix.length) : null;
8
+ }
9
+ /* A pattern with the folder it is relative to, or null for a relative pattern whose base is no folder of this machine. `base` is undefined for a string pattern. */
10
+ function patternOf(watcher) {
11
+ const { globPattern } = watcher;
12
+ if (typeof globPattern === 'string') {
13
+ return { base: undefined, pattern: globPattern };
14
+ }
15
+ const base = fileUriToPath(typeof globPattern.baseUri === 'string' ? globPattern.baseUri : globPattern.baseUri.uri);
16
+ return base === null ? null : { base, pattern: globPattern.pattern };
17
+ }
18
+ /*
19
+ * Whether a server asked to hear about this change. A string pattern is relative to the project folder
20
+ * (or absolute), a relative pattern to its own base. A path inside `node_modules`, `vendor` or `.git` only
21
+ * matches a pattern that names that folder, since `**` over a dependency tree is what a server never means.
22
+ */
23
+ export function watchesFile(watchers, root, path, type) {
24
+ const inProject = relativeTo(root, path);
25
+ const segments = (inProject ?? path).split('/');
26
+ return watchers.some((watcher) => {
27
+ if (((watcher.kind ?? 7) & (1 << (type - 1))) === 0) {
28
+ return false;
29
+ }
30
+ const parts = patternOf(watcher);
31
+ if (parts === null) {
32
+ return false;
33
+ }
34
+ const { base, pattern } = parts;
35
+ const subject = pattern.startsWith('/') ? path : base === undefined ? inProject : relativeTo(base, path);
36
+ if (subject === null || !globMatch(subject, pattern)) {
37
+ return false;
38
+ }
39
+ return SPARING_FOLDERS.every((folder) => !segments.includes(folder) || pattern.includes(folder));
40
+ });
41
+ }
package/package.json CHANGED
@@ -1,11 +1,44 @@
1
1
  {
2
2
  "name": "@adecore/lsp",
3
- "version": "0.0.1",
4
- "description": "Placeholder for the first release.",
3
+ "version": "0.17.0-beta.1",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "source": "./src/index.ts",
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ },
11
+ "./testing": {
12
+ "source": "./src/testing.ts",
13
+ "types": "./dist/testing.d.ts",
14
+ "default": "./dist/testing.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "typecheck": "tsc --noEmit",
19
+ "build": "bun scripts/build.ts",
20
+ "test": "bun test --conditions=source"
21
+ },
22
+ "devDependencies": {
23
+ "@types/bun": "^1.4.2",
24
+ "typescript": "^7.0.2"
25
+ },
5
26
  "license": "FSL-1.1-MIT",
27
+ "author": "Bas Milius",
6
28
  "repository": {
7
29
  "type": "git",
8
30
  "url": "git+https://github.com/basmilius/adecore.git",
9
31
  "directory": "packages/lsp"
10
- }
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/basmilius/adecore/issues"
35
+ },
36
+ "homepage": "https://adecore.dev/lsp/",
37
+ "sideEffects": false,
38
+ "files": [
39
+ "dist",
40
+ "src",
41
+ "!src/**/*.test.ts",
42
+ "LICENSE"
43
+ ]
11
44
  }