@myagentroam/node 0.9.97 → 0.9.99

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.
@@ -1,3 +1,4 @@
1
+ import { chmod } from 'node:fs/promises';
1
2
  import { dirname, resolve } from 'node:path';
2
3
  import { fileURLToPath } from 'node:url';
3
4
  export const BUNDLED_CTAGS_VERSION = '6.2.1';
@@ -16,3 +17,12 @@ export function bundledCtagsPath(platform = process.platform, architecture = pro
16
17
  throw new Error('CTAGS_PLATFORM_UNSUPPORTED');
17
18
  return resolve(bundledCtagsDirectory(packageRoot), ...target);
18
19
  }
20
+ /**
21
+ * Restores the execute bit when an installation mechanism lost the bundled
22
+ * Linux binary's archive mode. Explicit Ctags overrides are never modified.
23
+ */
24
+ export async function repairBundledCtagsExecutable(platform = process.platform, architecture = process.arch, packageRoot) {
25
+ if (platform !== 'linux')
26
+ return;
27
+ await chmod(bundledCtagsPath(platform, architecture, packageRoot), 0o755);
28
+ }
@@ -0,0 +1,40 @@
1
+ import { posix } from 'node:path';
2
+ import { matchesPathSuffix } from './path-specifier-resolver.js';
3
+ function accepts(value) {
4
+ return /^(?:\.\.?\/)?(?:[A-Za-z0-9_$+.-]+\/)*[A-Za-z0-9_$+.-]+$/u.test(value);
5
+ }
6
+ function matches(path, currentPath, specifier) {
7
+ return candidates(currentPath, specifier).some((candidate) => path === candidate || matchesPathSuffix(path, candidate));
8
+ }
9
+ function score(path, currentPath, specifier) {
10
+ const resolvedCandidates = candidates(currentPath, specifier);
11
+ const exact = resolvedCandidates.indexOf(path);
12
+ if (exact >= 0)
13
+ return exact;
14
+ const suffix = resolvedCandidates.findIndex((candidate) => matchesPathSuffix(path, candidate));
15
+ return suffix >= 0 ? resolvedCandidates.length + suffix + path.length : Number.MAX_SAFE_INTEGER;
16
+ }
17
+ function candidates(currentPath, specifier) {
18
+ const relative = posix.normalize(posix.join(posix.dirname(currentPath), specifier));
19
+ if (relative.startsWith('../') || relative === '..' || relative === specifier)
20
+ return [specifier];
21
+ return [relative.replace(/^\.\//u, ''), specifier];
22
+ }
23
+ export const cFamilyPathSpecifierResolver = {
24
+ id: 'c-family.path-specifier',
25
+ accepts,
26
+ directCandidates: candidates,
27
+ matches,
28
+ score
29
+ };
30
+ function createCFamilyNavigationResolutionDefinition(languageId) {
31
+ return {
32
+ languageId,
33
+ resolver: { ...cFamilyPathSpecifierResolver, id: `${languageId}.path-specifier` }
34
+ };
35
+ }
36
+ export const cNavigationResolutionDefinition = createCFamilyNavigationResolutionDefinition('c');
37
+ export const cppNavigationResolutionDefinition = createCFamilyNavigationResolutionDefinition('cpp');
38
+ export const objectiveCNavigationResolutionDefinition = createCFamilyNavigationResolutionDefinition('objective-c');
39
+ export const objectiveCppNavigationResolutionDefinition = createCFamilyNavigationResolutionDefinition('objective-cpp');
40
+ export const cFamilyNavigationResolutionDefinition = createCFamilyNavigationResolutionDefinition('c-family');
@@ -0,0 +1,32 @@
1
+ import { matchesPathSuffix } from './path-specifier-resolver.js';
2
+ function accepts(value) {
3
+ return /^[\p{L}_\p{Nl}][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}]*(?:\.[\p{L}_\p{Nl}][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}]*)*$/u.test(value);
4
+ }
5
+ function matches(path, _currentPath, specifier) {
6
+ if (!path.endsWith('.cs'))
7
+ return false;
8
+ return namespaceSuffixes(specifier).some((suffix) => matchesPathSuffix(path, `${suffix}.cs`) ||
9
+ path.startsWith(`${suffix}/`) ||
10
+ path.includes(`/${suffix}/`));
11
+ }
12
+ function score(path, _currentPath, specifier) {
13
+ const suffixes = namespaceSuffixes(specifier);
14
+ const index = suffixes.findIndex((suffix) => matchesPathSuffix(path, `${suffix}.cs`) ||
15
+ path.startsWith(`${suffix}/`) ||
16
+ path.includes(`/${suffix}/`));
17
+ return index < 0 ? Number.MAX_SAFE_INTEGER : index * 10_000 + path.length;
18
+ }
19
+ function namespaceSuffixes(specifier) {
20
+ const segments = specifier.split('.');
21
+ return segments.map((_, index) => segments.slice(index).join('/'));
22
+ }
23
+ export const csharpPathSpecifierResolver = {
24
+ id: 'csharp.path-specifier',
25
+ accepts,
26
+ matches,
27
+ score
28
+ };
29
+ export const csharpNavigationResolutionDefinition = {
30
+ languageId: 'csharp',
31
+ resolver: csharpPathSpecifierResolver
32
+ };
@@ -0,0 +1,290 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { createReadStream, createWriteStream } from 'node:fs';
4
+ import { lstat, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
5
+ import { relative, resolve, sep } from 'node:path';
6
+ import { createInterface } from 'node:readline';
7
+ import { compareTagNames } from './ctags-query.js';
8
+ import { sourceFileFingerprint } from './navigation-target-validator.js';
9
+ const BUILD_TIMEOUT_MS = 120_000;
10
+ const BUILD_QUEUE_LIMIT = 4;
11
+ const BUILD_QUEUE_TIMEOUT_MS = 120_000;
12
+ const MAX_TAGS_BYTES = 512 * 1024 * 1024;
13
+ let activeBuilds = 0;
14
+ const buildWaiters = [];
15
+ export function sourceDelta(previous, current) {
16
+ const changed = Object.entries(current)
17
+ .filter(([path, fingerprint]) => previous[path] !== fingerprint)
18
+ .map(([path]) => path);
19
+ const removed = Object.keys(previous).filter((path) => current[path] === undefined);
20
+ return { changed, removed };
21
+ }
22
+ export function manifestEntry(path, fingerprint) {
23
+ const match = /^([0-9]+):(-1|[0-9]+(?:\.[0-9]+)?)$/u.exec(fingerprint);
24
+ if (match === null)
25
+ return undefined;
26
+ const size = Number(match[1]);
27
+ const modifiedAt = match[2] === '-1' ? null : Number(match[2]);
28
+ if (!Number.isSafeInteger(size) ||
29
+ size < 0 ||
30
+ (modifiedAt !== null && !Number.isFinite(modifiedAt)))
31
+ return undefined;
32
+ return {
33
+ path,
34
+ name: path.slice(path.lastIndexOf('/') + 1),
35
+ kind: 'FILE',
36
+ size,
37
+ modifiedAt
38
+ };
39
+ }
40
+ export function sourceFingerprint(entries) {
41
+ const hash = createHash('sha256');
42
+ for (const entry of entries)
43
+ hash.update(`${entry.path}\0${sourceFileFingerprint(entry.size ?? -1, entry.modifiedAt)}\0`, 'utf8');
44
+ return hash.digest('hex');
45
+ }
46
+ export async function buildIncrementalTags(executable, root, previousTags, delta, staging, destination) {
47
+ const changedTags = resolve(staging, 'changed.tags');
48
+ if (delta.changed.length > 0) {
49
+ const list = resolve(staging, 'changed.files.list');
50
+ await writeFile(list, delta.changed.join('\n'), { encoding: 'utf8', mode: 0o600 });
51
+ const release = await acquireBuild();
52
+ try {
53
+ const ctagsMs = await runCtags(executable, root, list, changedTags);
54
+ await mergeTags(previousTags, changedTags, new Set([...delta.changed, ...delta.removed]), destination);
55
+ return ctagsMs;
56
+ }
57
+ finally {
58
+ release();
59
+ }
60
+ }
61
+ else
62
+ await writeFile(changedTags, '', { encoding: 'utf8', mode: 0o600 });
63
+ await mergeTags(previousTags, changedTags, new Set([...delta.changed, ...delta.removed]), destination);
64
+ return 0;
65
+ }
66
+ async function mergeTags(previous, changed, replacedPaths, destination) {
67
+ const output = createWriteStream(destination, { encoding: 'utf8', mode: 0o600 });
68
+ const oldLines = tagLines(previous)[Symbol.asyncIterator]();
69
+ const changedLines = tagLines(changed)[Symbol.asyncIterator]();
70
+ const write = async (line) => {
71
+ if (!output.write(`${line}\n`))
72
+ await new Promise((resolvePromise) => output.once('drain', resolvePromise));
73
+ };
74
+ let old = await oldLines.next();
75
+ while (!old.done && old.value.startsWith('!_TAG_')) {
76
+ await write(old.value);
77
+ old = await oldLines.next();
78
+ }
79
+ let nextChanged = await nextDataTag(changedLines);
80
+ while (!old.done || nextChanged !== undefined) {
81
+ while (!old.done && tagPath(old.value) !== undefined && replacedPaths.has(tagPath(old.value)))
82
+ old = await oldLines.next();
83
+ if (old.done) {
84
+ await write(nextChanged);
85
+ nextChanged = await nextDataTag(changedLines);
86
+ continue;
87
+ }
88
+ if (nextChanged === undefined || compareTagLines(old.value, nextChanged) <= 0) {
89
+ await write(old.value);
90
+ old = await oldLines.next();
91
+ }
92
+ else {
93
+ await write(nextChanged);
94
+ nextChanged = await nextDataTag(changedLines);
95
+ }
96
+ }
97
+ await new Promise((resolvePromise, reject) => {
98
+ output.once('error', reject);
99
+ output.end(resolvePromise);
100
+ });
101
+ }
102
+ async function* tagLines(path) {
103
+ const input = createInterface({
104
+ input: createReadStream(path, { encoding: 'utf8' }),
105
+ crlfDelay: Infinity
106
+ });
107
+ for await (const line of input)
108
+ yield line;
109
+ }
110
+ async function nextDataTag(iterator) {
111
+ for (;;) {
112
+ const next = await iterator.next();
113
+ if (next.done)
114
+ return undefined;
115
+ if (!next.value.startsWith('!_TAG_'))
116
+ return next.value;
117
+ }
118
+ }
119
+ function tagPath(line) {
120
+ const second = line.indexOf('\t');
121
+ if (second < 0)
122
+ return undefined;
123
+ const third = line.indexOf('\t', second + 1);
124
+ return third < 0 ? undefined : line.slice(second + 1, third);
125
+ }
126
+ function compareTagLines(left, right) {
127
+ const leftName = left.slice(0, left.indexOf('\t'));
128
+ const rightName = right.slice(0, right.indexOf('\t'));
129
+ return (compareTagNames(leftName, rightName) || Buffer.compare(Buffer.from(left), Buffer.from(right)));
130
+ }
131
+ export async function acquireBuild() {
132
+ if (activeBuilds >= 1) {
133
+ if (buildWaiters.length >= BUILD_QUEUE_LIMIT)
134
+ throw new Error('SYMBOL_INDEX_BUILD_FAILED');
135
+ await new Promise((resolvePromise, reject) => {
136
+ const resume = () => {
137
+ clearTimeout(timer);
138
+ resolvePromise();
139
+ };
140
+ const timer = setTimeout(() => {
141
+ const index = buildWaiters.indexOf(resume);
142
+ if (index >= 0)
143
+ buildWaiters.splice(index, 1);
144
+ reject(new Error('SYMBOL_INDEX_BUILD_FAILED'));
145
+ }, BUILD_QUEUE_TIMEOUT_MS);
146
+ timer.unref();
147
+ buildWaiters.push(resume);
148
+ });
149
+ }
150
+ activeBuilds += 1;
151
+ return () => {
152
+ activeBuilds -= 1;
153
+ buildWaiters.shift()?.();
154
+ };
155
+ }
156
+ export async function runCtags(executable, cwd, fileList, destination) {
157
+ const startedAt = Date.now();
158
+ await new Promise((resolvePromise, reject) => {
159
+ const child = spawn(executable, [
160
+ '--options=NONE',
161
+ '--output-format=u-ctags',
162
+ '--excmd=number',
163
+ '--sort=yes',
164
+ '--tag-relative=no',
165
+ '--pseudo-tags=-TAG_PROC_CWD',
166
+ '--fields=+nKS',
167
+ '--extras=+q',
168
+ '-L',
169
+ fileList,
170
+ '-f',
171
+ destination
172
+ ], {
173
+ cwd,
174
+ detached: process.platform !== 'win32',
175
+ windowsHide: true,
176
+ stdio: ['ignore', 'ignore', 'pipe']
177
+ });
178
+ let settled = false;
179
+ const settle = (callback) => {
180
+ if (settled)
181
+ return;
182
+ settled = true;
183
+ clearTimeout(timer);
184
+ callback();
185
+ };
186
+ const terminate = () => {
187
+ if (child.pid !== undefined && process.platform !== 'win32') {
188
+ try {
189
+ process.kill(-child.pid, 'SIGKILL');
190
+ return;
191
+ }
192
+ catch {
193
+ // Fall through to the direct process kill.
194
+ }
195
+ }
196
+ child.kill('SIGKILL');
197
+ };
198
+ const timer = setTimeout(() => {
199
+ terminate();
200
+ settle(() => reject(new Error('SYMBOL_INDEX_BUILD_FAILED')));
201
+ }, BUILD_TIMEOUT_MS);
202
+ timer.unref();
203
+ child.stderr.on('data', () => undefined);
204
+ child.once('error', () => settle(() => reject(new Error('SYMBOL_INDEX_BUILD_FAILED'))));
205
+ child.once('exit', (code) => settle(() => (code === 0 ? resolvePromise() : reject(new Error('SYMBOL_INDEX_BUILD_FAILED')))));
206
+ });
207
+ return Date.now() - startedAt;
208
+ }
209
+ export async function ensurePrivateDirectory(path) {
210
+ await mkdir(path, { recursive: true, mode: 0o700 });
211
+ const metadata = await lstat(path);
212
+ if (!metadata.isDirectory() || metadata.isSymbolicLink())
213
+ throw new Error('SYMBOL_INDEX_UNAVAILABLE');
214
+ }
215
+ export async function removeInactiveGenerations(directory, activeGeneration) {
216
+ const generations = await readdir(directory, { withFileTypes: true }).catch(() => []);
217
+ await Promise.all(generations
218
+ .filter((entry) => entry.isDirectory() &&
219
+ entry.name !== activeGeneration &&
220
+ /^[0-9a-f-]{36}(?:\.staging)?$/u.test(entry.name))
221
+ .map((entry) => rm(resolve(directory, entry.name), { recursive: true, force: true })));
222
+ }
223
+ export async function readManifest(path) {
224
+ try {
225
+ const value = JSON.parse(await readFile(path, 'utf8'));
226
+ const sourceFiles = validManifestSourceFiles(value.sourceFiles) ? value.sourceFiles : undefined;
227
+ const schemaVersion = value.schemaVersion;
228
+ const optionsVersion = value.optionsVersion;
229
+ return typeof value === 'object' &&
230
+ value !== null &&
231
+ typeof schemaVersion === 'number' &&
232
+ Number.isInteger(schemaVersion) &&
233
+ schemaVersion >= 0 &&
234
+ typeof optionsVersion === 'number' &&
235
+ Number.isInteger(optionsVersion) &&
236
+ optionsVersion >= 0 &&
237
+ typeof value.ctagsVersion === 'string' &&
238
+ typeof value.sourceFingerprint === 'string' &&
239
+ /^[a-f0-9]{64}$/u.test(value.sourceFingerprint) &&
240
+ typeof value.truncated === 'boolean' &&
241
+ typeof value.generation === 'string' &&
242
+ /^[0-9a-f-]{36}$/u.test(value.generation)
243
+ ? {
244
+ schemaVersion,
245
+ optionsVersion,
246
+ ctagsVersion: value.ctagsVersion,
247
+ sourceFingerprint: value.sourceFingerprint,
248
+ ...(sourceFiles === undefined ? {} : { sourceFiles }),
249
+ truncated: value.truncated,
250
+ generation: value.generation
251
+ }
252
+ : undefined;
253
+ }
254
+ catch {
255
+ return undefined;
256
+ }
257
+ }
258
+ function validManifestSourceFiles(value) {
259
+ if (value === undefined)
260
+ return false;
261
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
262
+ return false;
263
+ const entries = Object.entries(value);
264
+ return (entries.length <= 100_000 &&
265
+ entries.every(([path, fingerprint]) => path.length > 0 &&
266
+ path.length <= 4096 &&
267
+ !path.includes('\0') &&
268
+ !path.includes('\\') &&
269
+ !path.startsWith('/') &&
270
+ !path.split('/').some((part) => part.length === 0 || part === '.' || part === '..') &&
271
+ typeof fingerprint === 'string' &&
272
+ /^[0-9]+:(?:-1|[0-9]+(?:\.[0-9]+)?)$/u.test(fingerprint)));
273
+ }
274
+ export async function tagsPath(location, manifest) {
275
+ const path = resolve(location.generations, manifest.generation, 'tags');
276
+ if (relative(location.generations, path)
277
+ .split(sep)
278
+ .some((part) => part === '..'))
279
+ throw new Error('SYMBOL_INDEX_UNAVAILABLE');
280
+ return path;
281
+ }
282
+ export async function tagsAvailable(path) {
283
+ try {
284
+ const metadata = await stat(path);
285
+ return metadata.isFile() && metadata.size <= MAX_TAGS_BYTES;
286
+ }
287
+ catch {
288
+ return false;
289
+ }
290
+ }
@@ -0,0 +1,140 @@
1
+ const MAX_TAG_LINE_BYTES = 16 * 1024;
2
+ const READ_BLOCK_BYTES = 4096;
3
+ export async function firstTagOffset(handle, size, symbol) {
4
+ let low = 0;
5
+ let high = size;
6
+ while (low < high) {
7
+ const middle = Math.floor((low + high) / 2);
8
+ const start = await findTagLineStart(handle, middle);
9
+ if (start === undefined) {
10
+ high = middle;
11
+ continue;
12
+ }
13
+ const line = await readTagLine(handle, start, size);
14
+ if (line === undefined) {
15
+ high = middle;
16
+ continue;
17
+ }
18
+ const tag = parseTag(line.text);
19
+ if (tag === undefined || compareTagNames(tag.name, symbol) < 0)
20
+ low = line.nextOffset;
21
+ else
22
+ high = line.start;
23
+ }
24
+ return low;
25
+ }
26
+ async function findTagLineStart(handle, offset) {
27
+ let cursor = offset;
28
+ let scanned = 0;
29
+ while (cursor > 0 && scanned < MAX_TAG_LINE_BYTES) {
30
+ const start = Math.max(0, cursor - Math.min(READ_BLOCK_BYTES, MAX_TAG_LINE_BYTES - scanned));
31
+ const chunk = Buffer.alloc(cursor - start);
32
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, start);
33
+ if (bytesRead === 0)
34
+ return undefined;
35
+ const newline = chunk.subarray(0, bytesRead).lastIndexOf(0x0a);
36
+ if (newline >= 0)
37
+ return start + newline + 1;
38
+ cursor = start;
39
+ scanned += bytesRead;
40
+ }
41
+ return cursor === 0 ? 0 : undefined;
42
+ }
43
+ export async function readTagLine(handle, offset, size) {
44
+ if (offset >= size)
45
+ return undefined;
46
+ let start = offset;
47
+ if (start > 0) {
48
+ const before = Buffer.alloc(1);
49
+ await handle.read(before, 0, 1, start - 1);
50
+ if (before[0] !== 0x0a) {
51
+ const aligned = await findNewline(handle, start, size, MAX_TAG_LINE_BYTES);
52
+ if (aligned === undefined)
53
+ return undefined;
54
+ start = aligned + 1;
55
+ }
56
+ }
57
+ if (start >= size)
58
+ return undefined;
59
+ const bytes = [];
60
+ let total = 0;
61
+ let cursor = start;
62
+ while (cursor < size && total <= MAX_TAG_LINE_BYTES) {
63
+ const chunk = Buffer.alloc(Math.min(READ_BLOCK_BYTES, size - cursor));
64
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, cursor);
65
+ if (bytesRead === 0)
66
+ break;
67
+ const content = chunk.subarray(0, bytesRead);
68
+ const newline = content.indexOf(0x0a);
69
+ const part = newline >= 0 ? content.subarray(0, newline) : content;
70
+ total += part.length;
71
+ if (total > MAX_TAG_LINE_BYTES)
72
+ return undefined;
73
+ bytes.push(part);
74
+ if (newline >= 0)
75
+ return {
76
+ start,
77
+ nextOffset: cursor + newline + 1,
78
+ text: Buffer.concat(bytes, total).toString('utf8')
79
+ };
80
+ cursor += bytesRead;
81
+ }
82
+ return undefined;
83
+ }
84
+ async function findNewline(handle, offset, size, limit) {
85
+ let cursor = offset;
86
+ let remaining = limit;
87
+ while (cursor < size && remaining > 0) {
88
+ const chunk = Buffer.alloc(Math.min(READ_BLOCK_BYTES, size - cursor, remaining));
89
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, cursor);
90
+ if (bytesRead === 0)
91
+ return undefined;
92
+ const newline = chunk.subarray(0, bytesRead).indexOf(0x0a);
93
+ if (newline >= 0)
94
+ return cursor + newline;
95
+ cursor += bytesRead;
96
+ remaining -= bytesRead;
97
+ }
98
+ return undefined;
99
+ }
100
+ export function parseTag(text) {
101
+ if (text.length === 0 ||
102
+ text.startsWith('!_TAG_') ||
103
+ Buffer.byteLength(text) > MAX_TAG_LINE_BYTES)
104
+ return undefined;
105
+ const fields = text.split('\t');
106
+ if (fields.length < 3)
107
+ return undefined;
108
+ const [name, path, excmd, ...extensions] = fields;
109
+ if (name === undefined ||
110
+ path === undefined ||
111
+ excmd === undefined ||
112
+ name.length === 0 ||
113
+ path.length === 0 ||
114
+ path.startsWith('/') ||
115
+ path.includes('\\') ||
116
+ path.split('/').some((part) => part === '' || part === '.' || part === '..'))
117
+ return undefined;
118
+ const match = /^([1-9][0-9]*);?"?$/u.exec(excmd);
119
+ if (match === null)
120
+ return undefined;
121
+ const line = Number(match[1]);
122
+ if (!Number.isSafeInteger(line) || line < 1)
123
+ return undefined;
124
+ let kind;
125
+ let scope;
126
+ for (const extension of extensions) {
127
+ if (extension.length === 1)
128
+ kind ??= extension;
129
+ else if (extension.startsWith('kind:'))
130
+ kind ??= extension.slice('kind:'.length);
131
+ else if (extension.startsWith('scope:') ||
132
+ extension.startsWith('class:') ||
133
+ extension.startsWith('namespace:'))
134
+ scope ??= extension.slice(extension.indexOf(':') + 1);
135
+ }
136
+ return { name, path, line, kind, scope };
137
+ }
138
+ export function compareTagNames(left, right) {
139
+ return Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'));
140
+ }
@@ -0,0 +1,100 @@
1
+ import { posix } from 'node:path';
2
+ import { trailingPathCandidateScore } from './path-specifier-resolver.js';
3
+ const importExtensionMappings = [
4
+ { importExtension: '.js', sourceExtensions: ['.ts', '.tsx'] },
5
+ { importExtension: '.jsx', sourceExtensions: ['.tsx'] },
6
+ { importExtension: '.mjs', sourceExtensions: ['.mts'] },
7
+ { importExtension: '.cjs', sourceExtensions: ['.cts'] }
8
+ ];
9
+ const extensionlessSourceExtensions = ['.ts', '.tsx', '.js', '.jsx'];
10
+ function accepts(value) {
11
+ return (/^(?:\.\.?\/)[A-Za-z0-9_./$-]+$/u.test(value) ||
12
+ /^@\/[A-Za-z0-9_./$-]+$/u.test(value) ||
13
+ /^(?:@?[A-Za-z0-9_$-]+)(?:\/[A-Za-z0-9_.$-]+)+$/u.test(value));
14
+ }
15
+ function matches(path, currentPath, specifier) {
16
+ if (!specifier.startsWith('.'))
17
+ return unresolvedSpecifierScore(path, specifier) !== undefined;
18
+ const resolved = resolveRelative(currentPath, specifier);
19
+ return (resolved !== undefined &&
20
+ candidates(resolved).some((candidate) => path === candidate ||
21
+ path === `${candidate}/index.ts` ||
22
+ path === `${candidate}/index.tsx` ||
23
+ path === `${candidate}/index.js` ||
24
+ path === `${candidate}/index.jsx`));
25
+ }
26
+ function score(path, currentPath, specifier) {
27
+ if (!specifier.startsWith('.'))
28
+ return unresolvedSpecifierScore(path, specifier) ?? Number.MAX_SAFE_INTEGER;
29
+ const resolved = resolveRelative(currentPath, specifier);
30
+ if (resolved === undefined)
31
+ return Number.MAX_SAFE_INTEGER;
32
+ const resolvedCandidates = candidates(resolved);
33
+ const exact = resolvedCandidates.indexOf(path);
34
+ if (exact >= 0)
35
+ return exact;
36
+ const index = resolvedCandidates.findIndex((candidate) => path === `${candidate}/index.ts` ||
37
+ path === `${candidate}/index.tsx` ||
38
+ path === `${candidate}/index.js` ||
39
+ path === `${candidate}/index.jsx`);
40
+ return index >= 0 ? resolvedCandidates.length + index : Number.MAX_SAFE_INTEGER;
41
+ }
42
+ function directCandidates(currentPath, specifier) {
43
+ if (!specifier.startsWith('.'))
44
+ return [];
45
+ const resolved = resolveRelative(currentPath, specifier);
46
+ if (resolved === undefined)
47
+ return [];
48
+ const resolvedCandidates = candidates(resolved);
49
+ return resolvedCandidates.flatMap((candidate) => [
50
+ candidate,
51
+ `${candidate}/index.ts`,
52
+ `${candidate}/index.tsx`,
53
+ `${candidate}/index.js`,
54
+ `${candidate}/index.jsx`
55
+ ]);
56
+ }
57
+ function resolveRelative(currentPath, specifier) {
58
+ const resolved = posix.normalize(posix.join(posix.dirname(currentPath), specifier));
59
+ if (resolved.startsWith('../') || resolved === '..')
60
+ return undefined;
61
+ return resolved.replace(/^\.\//u, '');
62
+ }
63
+ function candidates(path) {
64
+ const mapping = importExtensionMappings.find(({ importExtension }) => path.toLowerCase().endsWith(importExtension));
65
+ if (mapping !== undefined) {
66
+ const sourceBase = path.slice(0, -mapping.importExtension.length);
67
+ return [path, ...mapping.sourceExtensions.map((extension) => `${sourceBase}${extension}`)];
68
+ }
69
+ return path.slice(path.lastIndexOf('/') + 1).includes('.')
70
+ ? [path]
71
+ : [path, ...extensionlessSourceExtensions.map((extension) => `${path}${extension}`)];
72
+ }
73
+ function sourceFileStem(path) {
74
+ const name = path.slice(path.lastIndexOf('/') + 1);
75
+ return name.replace(/(?:\.d)?\.[^.]+$/u, '');
76
+ }
77
+ function unresolvedSpecifierScore(path, specifier) {
78
+ const normalizedSpecifier = specifier.startsWith('@/') ? specifier.slice(2) : specifier;
79
+ return trailingPathCandidateScore(path, unresolvedCandidates(normalizedSpecifier));
80
+ }
81
+ function unresolvedCandidates(specifier) {
82
+ return candidates(specifier).flatMap((candidate) => [
83
+ candidate,
84
+ `${candidate}/index.ts`,
85
+ `${candidate}/index.tsx`,
86
+ `${candidate}/index.js`,
87
+ `${candidate}/index.jsx`
88
+ ]);
89
+ }
90
+ export const ecmascriptPathSpecifierResolver = {
91
+ id: 'ecmascript.path-specifier',
92
+ accepts,
93
+ directCandidates,
94
+ matches,
95
+ score
96
+ };
97
+ export const ecmascriptNavigationResolutionDefinition = {
98
+ languageId: 'ecmascript',
99
+ resolver: ecmascriptPathSpecifierResolver
100
+ };
@@ -0,0 +1,28 @@
1
+ import { fileSpecifierParser } from '@myagentroam/code-navigation';
2
+ const MAX_REFERENCE_REPARSE_POSITIONS = 8;
3
+ export function reparseFileReference(path, requested) {
4
+ const parser = fileSpecifierParser(path, requested.sourceContext.text);
5
+ if (parser === undefined)
6
+ return undefined;
7
+ for (const position of referenceCandidatePositions(requested)) {
8
+ const parsed = parser.find(requested.sourceContext.text, position);
9
+ if (parsed?.value === requested.value && parsed.anchor === requested.anchor)
10
+ return parsed;
11
+ }
12
+ return undefined;
13
+ }
14
+ export function referenceCandidatePositions(requested) {
15
+ const source = requested.sourceContext.text;
16
+ const requestedPosition = requested.sourceContext.position;
17
+ const positions = new Set([requestedPosition, requested.from, requested.to - 1]);
18
+ const matches = [];
19
+ for (let offset = source.indexOf(requested.value); offset >= 0;) {
20
+ matches.push(offset);
21
+ offset = source.indexOf(requested.value, offset + Math.max(1, requested.value.length));
22
+ }
23
+ matches
24
+ .sort((left, right) => Math.abs(left - requestedPosition) - Math.abs(right - requestedPosition) || left - right)
25
+ .slice(0, MAX_REFERENCE_REPARSE_POSITIONS - positions.size)
26
+ .forEach((position) => positions.add(position));
27
+ return [...positions].filter((position) => position >= 0 && position < source.length);
28
+ }
@@ -0,0 +1,31 @@
1
+ import { matchesPathSuffix } from './path-specifier-resolver.js';
2
+ function accepts(value) {
3
+ return /^(?:[A-Za-z0-9][A-Za-z0-9._-]*)(?:\/[A-Za-z0-9][A-Za-z0-9._-]*)+$/u.test(value);
4
+ }
5
+ function matches(path, _currentPath, specifier) {
6
+ if (!path.endsWith('.go'))
7
+ return false;
8
+ const directory = path.slice(0, path.lastIndexOf('/'));
9
+ return packageSuffixes(specifier).some((suffix) => matchesPathSuffix(directory, suffix));
10
+ }
11
+ function score(path, _currentPath, specifier) {
12
+ const directory = path.slice(0, path.lastIndexOf('/'));
13
+ const index = packageSuffixes(specifier).findIndex((suffix) => matchesPathSuffix(directory, suffix));
14
+ return index < 0 ? Number.MAX_SAFE_INTEGER : index * 10_000 + path.length;
15
+ }
16
+ function packageSuffixes(specifier) {
17
+ const segments = specifier.split('/');
18
+ return segments.length < 2
19
+ ? []
20
+ : segments.slice(0, -1).map((_, index) => segments.slice(index).join('/'));
21
+ }
22
+ export const goPathSpecifierResolver = {
23
+ id: 'go.path-specifier',
24
+ accepts,
25
+ matches,
26
+ score
27
+ };
28
+ export const goNavigationResolutionDefinition = {
29
+ languageId: 'go',
30
+ resolver: goPathSpecifierResolver
31
+ };