@markup-carve/carve-lsp 0.1.1 → 0.1.2

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 (63) hide show
  1. package/README.md +55 -0
  2. package/dist/analyze.d.ts +17 -1
  3. package/dist/analyze.js +53 -1
  4. package/dist/analyze.js.map +1 -1
  5. package/dist/analyze.test.js +84 -0
  6. package/dist/analyze.test.js.map +1 -1
  7. package/dist/definition.js +22 -2
  8. package/dist/definition.js.map +1 -1
  9. package/dist/hover.js +23 -8
  10. package/dist/hover.js.map +1 -1
  11. package/dist/include-directive.d.ts +61 -0
  12. package/dist/include-directive.js +158 -0
  13. package/dist/include-directive.js.map +1 -0
  14. package/dist/include-directive.test.d.ts +1 -0
  15. package/dist/include-directive.test.js +83 -0
  16. package/dist/include-directive.test.js.map +1 -0
  17. package/dist/include-path.d.ts +63 -0
  18. package/dist/include-path.js +135 -0
  19. package/dist/include-path.js.map +1 -0
  20. package/dist/include-path.test.d.ts +1 -0
  21. package/dist/include-path.test.js +189 -0
  22. package/dist/include-path.test.js.map +1 -0
  23. package/dist/include-security-conformance.test.d.ts +1 -0
  24. package/dist/include-security-conformance.test.js +100 -0
  25. package/dist/include-security-conformance.test.js.map +1 -0
  26. package/dist/include-settings.d.ts +50 -0
  27. package/dist/include-settings.js +135 -0
  28. package/dist/include-settings.js.map +1 -0
  29. package/dist/include-settings.test.d.ts +1 -0
  30. package/dist/include-settings.test.js +191 -0
  31. package/dist/include-settings.test.js.map +1 -0
  32. package/dist/includes.d.ts +78 -0
  33. package/dist/includes.js +158 -0
  34. package/dist/includes.js.map +1 -0
  35. package/dist/includes.test.d.ts +1 -0
  36. package/dist/includes.test.js +202 -0
  37. package/dist/includes.test.js.map +1 -0
  38. package/dist/inline-text.d.ts +24 -0
  39. package/dist/inline-text.js +34 -0
  40. package/dist/inline-text.js.map +1 -0
  41. package/dist/inline-text.test.d.ts +1 -0
  42. package/dist/inline-text.test.js +32 -0
  43. package/dist/inline-text.test.js.map +1 -0
  44. package/dist/position-encoding.test.d.ts +1 -0
  45. package/dist/position-encoding.test.js +143 -0
  46. package/dist/position-encoding.test.js.map +1 -0
  47. package/dist/position.d.ts +82 -0
  48. package/dist/position.js +137 -0
  49. package/dist/position.js.map +1 -0
  50. package/dist/position.test.d.ts +1 -0
  51. package/dist/position.test.js +61 -0
  52. package/dist/position.test.js.map +1 -0
  53. package/dist/references.js +3 -0
  54. package/dist/references.js.map +1 -1
  55. package/dist/semantic.d.ts +14 -0
  56. package/dist/semantic.js +98 -13
  57. package/dist/semantic.js.map +1 -1
  58. package/dist/semantic.test.d.ts +1 -0
  59. package/dist/semantic.test.js +93 -0
  60. package/dist/semantic.test.js.map +1 -0
  61. package/dist/server.js +50 -23
  62. package/dist/server.js.map +1 -1
  63. package/package.json +2 -2
@@ -0,0 +1,189 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileSystemResolver } from './include-path.js';
7
+ // PART 9 §19 puts four MUSTs on path resolution. Each has a test here, and
8
+ // each was proven load-bearing by deleting its guard in `include-path.ts` and
9
+ // watching THIS test go red (see the pull request for the transcript).
10
+ //
11
+ // Expectations follow the cross-engine include-conformance vectors
12
+ // `i10-fs-*` in markup-carve/carve, which currently live only on the unmerged
13
+ // spec branch for markup-carve/carve#291. They are cited, not vendored.
14
+ const CTX = { stack: [], depth: 0 };
15
+ function fixture(tree) {
16
+ const dir = realpathSync(mkdtempSync(path.join(tmpdir(), 'carve-lsp-inc-')));
17
+ for (const [name, value] of Object.entries(tree)) {
18
+ const full = path.join(dir, name);
19
+ mkdirSync(path.dirname(full), { recursive: true });
20
+ if (typeof value === 'string')
21
+ writeFileSync(full, value);
22
+ else
23
+ symlinkSync(path.join(dir, value.symlink), full);
24
+ }
25
+ test.after(() => rmSync(dir, { recursive: true, force: true }));
26
+ return dir;
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // MUST 1: resolve only under the root AFTER symlink resolution
30
+ // ---------------------------------------------------------------------------
31
+ test('a symlinked DIRECTORY escaping the root is denied (i10-fs-symlink-dir-escape-denied)', () => {
32
+ const dir = fixture({
33
+ 'root/main.crv': '{{ linkdir/secret.crv }}\n',
34
+ 'outside/secret.crv': 'TOP SECRET\n',
35
+ 'root/linkdir': { symlink: 'outside' },
36
+ });
37
+ const resolve = fileSystemResolver(path.join(dir, 'root'));
38
+ const result = resolve('linkdir/secret.crv', CTX);
39
+ assert.equal(result.ok, false);
40
+ assert.equal(result.ok === false && result.denial, 'outside-root');
41
+ // The dependency id is the path as written, never the real path behind it.
42
+ assert.equal(result.id, 'linkdir/secret.crv');
43
+ });
44
+ test('a symlinked FILE whose real target escapes the root is denied (i10-fs-symlink-file-and-dotdot-denied)', () => {
45
+ const dir = fixture({
46
+ 'root/main.crv': '{{ link.crv }}\n',
47
+ 'secret.crv': 'TOP SECRET\n',
48
+ 'root/link.crv': { symlink: 'secret.crv' },
49
+ });
50
+ const resolve = fileSystemResolver(path.join(dir, 'root'));
51
+ const result = resolve('link.crv', CTX);
52
+ assert.equal(result.ok, false);
53
+ assert.equal(result.ok === false && result.denial, 'outside-root');
54
+ });
55
+ // ---------------------------------------------------------------------------
56
+ // MUST 2: reject `..`-traversal that leaves the root
57
+ // ---------------------------------------------------------------------------
58
+ test('a ../ escape out of the root is denied (i10-fs-dotdot-escape-denied)', () => {
59
+ const dir = fixture({
60
+ 'root/main.crv': '{{ ../secret.crv }}\n',
61
+ 'secret.crv': 'TOP SECRET\n',
62
+ });
63
+ const resolve = fileSystemResolver(path.join(dir, 'root'));
64
+ const result = resolve('../secret.crv', CTX);
65
+ assert.equal(result.ok, false);
66
+ assert.equal(result.ok === false && result.denial, 'outside-root');
67
+ });
68
+ test('a ../ path whose canonical target stays INSIDE the root resolves (i10-fs-dotdot-inside-root-allowed)', () => {
69
+ // Control for the test above: the guard is canonical containment, not a
70
+ // lexical ban on `..`. A book layout that reaches a sibling directory is
71
+ // legal and must keep working. No mutation of the containment check breaks
72
+ // this case - deleting the check makes it pass just as well - so it is
73
+ // stated here as the boundary the guard must NOT overreach, not as proof.
74
+ const dir = fixture({
75
+ 'chapters/ch1.crv': 'Chapter one.\n',
76
+ 'shared/glossary.crv': 'Glossary body.\n',
77
+ });
78
+ const resolve = fileSystemResolver(dir);
79
+ const result = resolve('../shared/glossary.crv', {
80
+ stack: [path.join(dir, 'chapters/ch1.crv')],
81
+ depth: 1,
82
+ });
83
+ assert.equal(result.ok, true);
84
+ assert.equal(result.ok === true && result.source, 'Glossary body.\n');
85
+ });
86
+ // ---------------------------------------------------------------------------
87
+ // MUST 3: reject absolute paths that escape the root
88
+ // ---------------------------------------------------------------------------
89
+ test('an absolute path outside the root is denied even with allowAbsolute (i10-fs-absolute-path-denied)', () => {
90
+ // With absolute paths explicitly enabled, containment is the ONLY thing
91
+ // standing between the directive and the file, which is what makes this the
92
+ // test for the MUST rather than for the convenience default below.
93
+ const dir = fixture({
94
+ 'root/main.crv': 'x\n',
95
+ 'secret.crv': 'TOP SECRET\n',
96
+ });
97
+ const resolve = fileSystemResolver(path.join(dir, 'root'), { allowAbsolute: true });
98
+ const result = resolve(path.join(dir, 'secret.crv'), CTX);
99
+ assert.equal(result.ok, false);
100
+ assert.equal(result.ok === false && result.denial, 'outside-root');
101
+ });
102
+ test('an absolute path is denied by default, before containment is consulted', () => {
103
+ const dir = fixture({ 'root/main.crv': 'x\n', 'root/inside.crv': 'Inside.\n' });
104
+ const resolve = fileSystemResolver(path.join(dir, 'root'));
105
+ // Absolute AND inside the root: only the default-deny can reject this one,
106
+ // so it isolates that guard from containment.
107
+ const result = resolve(path.join(dir, 'root/inside.crv'), CTX);
108
+ assert.equal(result.ok, false);
109
+ assert.equal(result.ok === false && result.denial, 'absolute-denied');
110
+ });
111
+ test('an absolute path inside the root resolves once allowAbsolute is set', () => {
112
+ const dir = fixture({ 'root/inside.crv': 'Inside.\n' });
113
+ const resolve = fileSystemResolver(path.join(dir, 'root'), { allowAbsolute: true });
114
+ const result = resolve(path.join(dir, 'root/inside.crv'), CTX);
115
+ assert.equal(result.ok, true);
116
+ });
117
+ // ---------------------------------------------------------------------------
118
+ // MUST 4: never fetch a remote URL without an explicit allowlist
119
+ // ---------------------------------------------------------------------------
120
+ test('a remote URL is denied with no allowlist configured, and never touches the disk', () => {
121
+ const dir = fixture({ 'main.crv': 'x\n' });
122
+ const resolve = fileSystemResolver(dir);
123
+ for (const url of [
124
+ 'https://example.com/evil.crv',
125
+ 'http://example.com/evil.crv',
126
+ 'file://localhost/etc/passwd',
127
+ '//example.com/evil.crv',
128
+ ]) {
129
+ const result = resolve(url, CTX);
130
+ assert.equal(result.ok, false, url);
131
+ assert.equal(result.ok === false && result.denial, 'remote-not-allowed', url);
132
+ }
133
+ });
134
+ test('a remote URL is denied even when its host IS allowlisted, because nothing fetches', () => {
135
+ const dir = fixture({ 'main.crv': 'x\n' });
136
+ const resolve = fileSystemResolver(dir, { allowedRemoteHosts: ['example.com'] });
137
+ const result = resolve('https://example.com/ok.crv', CTX);
138
+ assert.equal(result.ok, false);
139
+ assert.equal(result.ok === false && result.denial, 'remote-not-allowed');
140
+ });
141
+ test('a URL-shaped path is not read off disk as a relative path', () => {
142
+ // Without the scheme check a URL falls through to path joining, and
143
+ // `<root>/https:/example.com/evil.crv` is a file an attacker can plant.
144
+ const dir = fixture({
145
+ 'https:/example.com/evil.crv': 'PLANTED\n',
146
+ });
147
+ const resolve = fileSystemResolver(dir);
148
+ const result = resolve('https://example.com/evil.crv', CTX);
149
+ assert.equal(result.ok, false);
150
+ assert.equal(result.ok === false && result.denial, 'remote-not-allowed');
151
+ });
152
+ // ---------------------------------------------------------------------------
153
+ // Ordinary resolution and non-file targets
154
+ // ---------------------------------------------------------------------------
155
+ test('a missing target is reported as not-found, not as a containment denial', () => {
156
+ const dir = fixture({ 'main.crv': 'x\n' });
157
+ const resolve = fileSystemResolver(dir);
158
+ const result = resolve('absent.crv', CTX);
159
+ assert.equal(result.ok, false);
160
+ assert.equal(result.ok === false && result.denial, 'not-found');
161
+ });
162
+ test('a directory is not a document', () => {
163
+ const dir = fixture({ 'sub/child.crv': 'x\n' });
164
+ const resolve = fileSystemResolver(dir);
165
+ const result = resolve('sub', CTX);
166
+ assert.equal(result.ok, false);
167
+ assert.equal(result.ok === false && result.denial, 'not-a-file');
168
+ });
169
+ test('bytes are counted as encoded UTF-8, not as JavaScript characters', () => {
170
+ const dir = fixture({ 'child.crv': 'éé\u{1f600}' });
171
+ const resolve = fileSystemResolver(dir);
172
+ const result = resolve('child.crv', CTX);
173
+ assert.equal(result.ok, true);
174
+ assert.equal(result.ok === true && result.bytes, Buffer.byteLength('éé\u{1f600}', 'utf8'));
175
+ });
176
+ test('a nested relative include resolves against its parent directory, not the root', () => {
177
+ const dir = fixture({
178
+ 'chapters/ch1.crv': 'Chapter one.\n',
179
+ 'chapters/notes.crv': 'Notes.\n',
180
+ });
181
+ const resolve = fileSystemResolver(dir);
182
+ const result = resolve('notes.crv', {
183
+ stack: [path.join(dir, 'chapters/ch1.crv')],
184
+ depth: 1,
185
+ });
186
+ assert.equal(result.ok, true);
187
+ assert.equal(result.ok === true && result.source, 'Notes.\n');
188
+ });
189
+ //# sourceMappingURL=include-path.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"include-path.test.js","sourceRoot":"","sources":["../src/include-path.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,100 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { fileSystemResolver } from './include-path.js';
8
+ import { resolveIncludes } from './includes.js';
9
+ const sourceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
10
+ const corpusPath = path.join(sourceRoot, 'tests/spec/tests/include-security-conformance/vectors.json');
11
+ const corpus = JSON.parse(readFileSync(corpusPath, 'utf8'));
12
+ function fixture(tree) {
13
+ const dir = realpathSync(mkdtempSync(path.join(tmpdir(), 'carve-lsp-security-')));
14
+ const links = [];
15
+ for (const [name, value] of Object.entries(tree ?? {})) {
16
+ const full = path.join(dir, name);
17
+ mkdirSync(path.dirname(full), { recursive: true });
18
+ if (typeof value === 'string')
19
+ writeFileSync(full, value);
20
+ else
21
+ links.push([full, value.symlink]);
22
+ }
23
+ for (const [full, target] of links)
24
+ symlinkSync(path.join(dir, target), full);
25
+ test.after(() => rmSync(dir, { recursive: true, force: true }));
26
+ return dir;
27
+ }
28
+ function graph(vector) {
29
+ const calls = [];
30
+ let maxVisitedDepth = 0;
31
+ const resolver = (request, context) => {
32
+ calls.push(request);
33
+ maxVisitedDepth = Math.max(maxVisitedDepth, context.depth + 1);
34
+ const source = vector.files?.[request];
35
+ return source === undefined
36
+ ? { ok: false, id: request, denial: 'not-found' }
37
+ : { ok: true, id: request, source, bytes: Buffer.byteLength(source, 'utf8') };
38
+ };
39
+ const result = resolveIncludes(vector.entry ?? '', {
40
+ resolver,
41
+ maxDepth: vector.maxDepth,
42
+ maxBytes: vector.maxBytes,
43
+ });
44
+ const warning = result.warnings.find((item) => item.rule === 'include-depth' || item.rule === 'include-budget');
45
+ return {
46
+ resolverCalls: calls,
47
+ maxVisitedDepth,
48
+ chargedBytes: result.bytes,
49
+ status: warning ? 'denied' : 'allowed',
50
+ denial: warning?.rule === 'include-depth' ? 'depth' : warning ? 'budget' : undefined,
51
+ };
52
+ }
53
+ function run(vector) {
54
+ if (vector.kind === 'activation') {
55
+ const calls = [];
56
+ const resolver = (request) => {
57
+ calls.push(request);
58
+ return { ok: false, id: request, denial: 'not-found' };
59
+ };
60
+ resolveIncludes(vector.entry ?? '', vector.enabled ? { resolver } : {});
61
+ return { resolverCalls: calls };
62
+ }
63
+ if (vector.kind === 'graph')
64
+ return graph(vector);
65
+ const dir = fixture(vector.tree ?? { 'root/main.crv': '' });
66
+ const root = realpathSync(path.join(dir, vector.root ?? 'root'));
67
+ const resolver = fileSystemResolver(root, {
68
+ allowAbsolute: vector.allowAbsolute,
69
+ allowedRemoteHosts: vector.allowedRemoteHosts,
70
+ });
71
+ const rawRequest = vector.request ?? '';
72
+ const request = rawRequest.replace(/^<ABS:([^>]+)>$/, (_match, rel) => path.join(dir, rel));
73
+ const context = vector.from
74
+ ? { stack: [realpathSync(path.join(dir, vector.from))], depth: 0 }
75
+ : { stack: [], depth: 0 };
76
+ const result = resolver(request, context);
77
+ if (vector.kind === 'remote') {
78
+ return {
79
+ status: result.ok ? 'allowed' : vector.allowedRemoteHosts?.length ? 'unsupported' : 'denied',
80
+ denial: result.ok ? undefined : result.denial,
81
+ remoteFetches: [],
82
+ };
83
+ }
84
+ return result.ok
85
+ ? { status: 'allowed', canonicalId: result.id.replace(root, '<ROOT>') }
86
+ : { status: 'denied', denial: result.denial };
87
+ }
88
+ test('executes every shared PART 9 section 19 security vector', async (t) => {
89
+ assert.equal(corpus.version, 1);
90
+ assert.equal(corpus.vectors.length, 12);
91
+ for (const vector of corpus.vectors) {
92
+ await t.test(vector.name, () => {
93
+ const actual = run(vector);
94
+ for (const [field, expected] of Object.entries(vector.expected)) {
95
+ assert.deepEqual(actual[field], expected, `${vector.name}: ${field}`);
96
+ }
97
+ });
98
+ }
99
+ });
100
+ //# sourceMappingURL=include-security-conformance.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"include-security-conformance.test.js","sourceRoot":"","sources":["../src/include-security-conformance.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { IncludeOptions } from './includes.js';
2
+ export interface IncludeSettings {
3
+ /**
4
+ * `auto` (the default) means ON ONLY for a workspace the client has reported
5
+ * as trusted. A client that says nothing about trust therefore gets includes
6
+ * OFF: silence is not consent.
7
+ */
8
+ enabled: 'auto' | 'on' | 'off';
9
+ /** Containment root override. Otherwise the workspace root, then the document's directory. */
10
+ includeRoot?: string;
11
+ /** Allow absolute include paths, still subject to root containment. */
12
+ allowAbsolute?: boolean;
13
+ /** Hosts a remote include may name. Empty means none; this server never fetches. */
14
+ allowedRemoteHosts?: string[];
15
+ maxDepth?: number;
16
+ maxBytes?: number;
17
+ }
18
+ export declare const DEFAULT_INCLUDE_SETTINGS: IncludeSettings;
19
+ /**
20
+ * Read `carve.includes` out of `initializationOptions` or a
21
+ * `workspace/didChangeConfiguration` payload. Anything unrecognized falls back
22
+ * to the default rather than being trusted as given.
23
+ */
24
+ export declare function readIncludeSettings(raw: unknown): IncludeSettings;
25
+ /** Client-reported workspace trust. Absent means untrusted. */
26
+ export declare function readWorkspaceTrusted(raw: unknown): boolean;
27
+ export declare function fsPath(uri: string): string | undefined;
28
+ export interface IncludeGateInput {
29
+ uri: string;
30
+ settings: IncludeSettings;
31
+ workspaceTrusted: boolean;
32
+ /**
33
+ * Every workspace folder the client reported, in the order it reported them.
34
+ * A multi-root session gets one root per folder, not the first folder for
35
+ * everything: rooting a document from the second folder at the first would
36
+ * reject its ordinary relative includes as escapes.
37
+ */
38
+ workspaceRoots?: string[];
39
+ }
40
+ /**
41
+ * Build the include options for one document, or undefined to leave its
42
+ * directives literal.
43
+ *
44
+ * The containment root is the explicit override, else the workspace root, else
45
+ * the document's own directory for a file opened outside any workspace. It is
46
+ * NEVER the process working directory: a language server is commonly spawned
47
+ * from the user's home or from `/`, and rooting there would make containment
48
+ * meaningless.
49
+ */
50
+ export declare function includeOptionsFor(input: IncludeGateInput): IncludeOptions | undefined;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Client-facing include settings and the decision of whether a document gets
3
+ * include resolution at all (PART 9 §19: "MUST treat includes as opt-in (off
4
+ * for untrusted input)").
5
+ *
6
+ * Kept out of `server.ts` so the opt-in decision is testable on its own. It is
7
+ * the gate in front of a file-read capability, which is not something to leave
8
+ * exercised only by hand.
9
+ */
10
+ import { realpathSync } from 'node:fs';
11
+ import path, { dirname } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { fileSystemResolver } from './include-path.js';
14
+ export const DEFAULT_INCLUDE_SETTINGS = { enabled: 'auto' };
15
+ /**
16
+ * Read `carve.includes` out of `initializationOptions` or a
17
+ * `workspace/didChangeConfiguration` payload. Anything unrecognized falls back
18
+ * to the default rather than being trusted as given.
19
+ */
20
+ export function readIncludeSettings(raw) {
21
+ const source = raw?.carve
22
+ ?.includes;
23
+ if (!source || typeof source !== 'object')
24
+ return DEFAULT_INCLUDE_SETTINGS;
25
+ const enabled = source['enabled'];
26
+ const settings = {
27
+ enabled: enabled === 'on' || enabled === 'off' ? enabled : 'auto',
28
+ };
29
+ if (typeof source['includeRoot'] === 'string')
30
+ settings.includeRoot = source['includeRoot'];
31
+ if (typeof source['allowAbsolute'] === 'boolean')
32
+ settings.allowAbsolute = source['allowAbsolute'];
33
+ if (Array.isArray(source['allowedRemoteHosts'])) {
34
+ settings.allowedRemoteHosts = source['allowedRemoteHosts'].filter((host) => typeof host === 'string');
35
+ }
36
+ if (typeof source['maxDepth'] === 'number')
37
+ settings.maxDepth = source['maxDepth'];
38
+ if (typeof source['maxBytes'] === 'number')
39
+ settings.maxBytes = source['maxBytes'];
40
+ return settings;
41
+ }
42
+ /** Client-reported workspace trust. Absent means untrusted. */
43
+ export function readWorkspaceTrusted(raw) {
44
+ return raw?.workspaceTrusted === true;
45
+ }
46
+ export function fsPath(uri) {
47
+ if (!uri.startsWith('file:'))
48
+ return undefined;
49
+ try {
50
+ return fileURLToPath(uri);
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ /** True when `candidate` is `root` or sits underneath it, segment-wise. */
57
+ function contains(root, candidate) {
58
+ const rel = path.relative(root, candidate);
59
+ if (rel === '')
60
+ return true;
61
+ if (!rel || path.isAbsolute(rel))
62
+ return false;
63
+ return rel.split(path.sep)[0] !== '..';
64
+ }
65
+ /**
66
+ * The workspace folder this document belongs to: the deepest one containing
67
+ * it. A document in no folder at all has no workspace root, and falls back to
68
+ * its own directory rather than to somebody else's folder.
69
+ */
70
+ function workspaceRootFor(documentPath, roots) {
71
+ let best;
72
+ for (const root of roots ?? []) {
73
+ if (!contains(root, documentPath))
74
+ continue;
75
+ if (best === undefined || root.length > best.length)
76
+ best = root;
77
+ }
78
+ return best;
79
+ }
80
+ /**
81
+ * Build the include options for one document, or undefined to leave its
82
+ * directives literal.
83
+ *
84
+ * The containment root is the explicit override, else the workspace root, else
85
+ * the document's own directory for a file opened outside any workspace. It is
86
+ * NEVER the process working directory: a language server is commonly spawned
87
+ * from the user's home or from `/`, and rooting there would make containment
88
+ * meaningless.
89
+ */
90
+ export function includeOptionsFor(input) {
91
+ // GUARD 0 (§19: MUST treat includes as opt-in, off for untrusted input).
92
+ if (input.settings.enabled === 'off')
93
+ return undefined;
94
+ if (input.settings.enabled === 'auto' && !input.workspaceTrusted)
95
+ return undefined;
96
+ const documentPath = fsPath(input.uri);
97
+ // A document with no filesystem identity (untitled:, a remote scheme) has no
98
+ // directory to resolve against and gets no capability.
99
+ if (documentPath === undefined)
100
+ return undefined;
101
+ const configured = input.settings.includeRoot ??
102
+ workspaceRootFor(documentPath, input.workspaceRoots) ??
103
+ dirname(documentPath);
104
+ // Canonicalize the root here as well as inside the resolver, so that the
105
+ // root a caller sees on `includeRoot` is in the same coordinate system as
106
+ // the ids the resolver returns. Without it, a root reached through a symlink
107
+ // (or given relatively) makes every child id look like it sits outside the
108
+ // root, and a diagnostic naming that child would print an absolute path.
109
+ let root;
110
+ try {
111
+ root = realpathSync(configured);
112
+ }
113
+ catch {
114
+ return undefined;
115
+ }
116
+ let resolver;
117
+ try {
118
+ resolver = fileSystemResolver(root, {
119
+ allowAbsolute: input.settings.allowAbsolute ?? false,
120
+ allowedRemoteHosts: input.settings.allowedRemoteHosts ?? [],
121
+ });
122
+ }
123
+ catch {
124
+ // A root that is not a real directory yields no capability at all, rather
125
+ // than a resolver that silently falls back to somewhere wider.
126
+ return undefined;
127
+ }
128
+ const options = { resolver, sourcePath: documentPath, includeRoot: root };
129
+ if (input.settings.maxDepth !== undefined)
130
+ options.maxDepth = input.settings.maxDepth;
131
+ if (input.settings.maxBytes !== undefined)
132
+ options.maxBytes = input.settings.maxBytes;
133
+ return options;
134
+ }
135
+ //# sourceMappingURL=include-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"include-settings.js","sourceRoot":"","sources":["../src/include-settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};