@tsrx/oxc 0.0.0-trusted-publishing-bootstrap → 0.8.0

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/THIRD_PARTY_NOTICES.md +49 -0
  4. package/bin/oxc-tsrx +2 -0
  5. package/bin/oxc-tsrx-fmt +2 -0
  6. package/bin/oxc-tsrx-lint +2 -0
  7. package/bin/oxc-tsrx-lsp +2 -0
  8. package/bin/oxfmt +2 -0
  9. package/bin/oxlint +2 -0
  10. package/dist/bin/oxc-tsrx-fmt.js +13 -0
  11. package/dist/bin/oxc-tsrx-lint.js +13 -0
  12. package/dist/bin/oxc-tsrx-lsp.js +13 -0
  13. package/dist/bin/oxc-tsrx.js +115 -0
  14. package/dist/bin/oxfmt.js +24 -0
  15. package/dist/bin/oxlint.js +33 -0
  16. package/dist/canonical-command.d.ts +50 -0
  17. package/dist/canonical-command.js +196 -0
  18. package/dist/compat.d.ts +149 -0
  19. package/dist/compat.js +1615 -0
  20. package/dist/editor-resolution.js +508 -0
  21. package/dist/format-cli.js +276 -0
  22. package/dist/format-invocation.js +97 -0
  23. package/dist/format.d.ts +1 -0
  24. package/dist/format.js +56 -0
  25. package/dist/index.d.ts +8 -0
  26. package/dist/index.js +16 -0
  27. package/dist/lint-cli.js +487 -0
  28. package/dist/lint-invocation.js +192 -0
  29. package/dist/lint-js-plugins.js +819 -0
  30. package/dist/lint-plugins-dev.d.ts +1 -0
  31. package/dist/lint-plugins-dev.js +2 -0
  32. package/dist/lint-prestart.js +16 -0
  33. package/dist/lint.d.ts +1 -0
  34. package/dist/lint.js +2 -0
  35. package/dist/native-targets.js +76 -0
  36. package/dist/oxlint-lsp-multiplexer.js +622 -0
  37. package/dist/package-binary.js +29 -0
  38. package/dist/parser.d.ts +216 -0
  39. package/dist/parser.js +557 -0
  40. package/dist/process.js +88 -0
  41. package/dist/provider-resolve.d.ts +160 -0
  42. package/dist/provider-resolve.js +471 -0
  43. package/dist/providers-report.js +49 -0
  44. package/dist/runtime.js +323 -0
  45. package/dist/spawn-command.d.ts +20 -0
  46. package/dist/spawn-command.js +87 -0
  47. package/dist/tsrx-core-compat/facade.js +1184 -0
  48. package/dist/tsrx-core-compat/index.d.ts +6 -0
  49. package/dist/tsrx-core-compat/index.js +9 -0
  50. package/dist/tsrx-core-compat/style.js +525 -0
  51. package/dist/tsrx-core-compat/types/estree.d.ts +20 -0
  52. package/dist/tsrx-core-compat/types/index.d.ts +50 -0
  53. package/dist/tsrx-transfer.js +352 -0
  54. package/package.json +144 -5
@@ -0,0 +1,160 @@
1
+ export type OxcProviderCapabilityName = "parse" | "lint" | "format" | "lsp";
2
+
3
+ export type OxcProviderDiagnosticCode =
4
+ | "ancestor-provider"
5
+ | "duplicate-id"
6
+ | "extension-conflict"
7
+ | "invalid-capability"
8
+ | "invalid-provider"
9
+ | "reserved-extension"
10
+ | "unreadable-manifest"
11
+ | "unresolved-capability"
12
+ | "unsupported-protocol";
13
+
14
+ export interface OxcProviderDiagnostic {
15
+ readonly severity: "error" | "warning";
16
+ readonly code: OxcProviderDiagnosticCode;
17
+ readonly message: string;
18
+ readonly packages?: readonly string[];
19
+ readonly extension?: string;
20
+ readonly capability?: OxcProviderCapabilityName;
21
+ readonly protocol?: number;
22
+ readonly id?: string;
23
+ readonly manifest?: string;
24
+ readonly root?: string;
25
+ }
26
+
27
+ export interface OxcProviderBinCapability {
28
+ readonly kind: "bin";
29
+ readonly bin: string;
30
+ readonly path: string;
31
+ }
32
+
33
+ export interface OxcProviderModuleCapability {
34
+ readonly kind: "module";
35
+ readonly subpath: string;
36
+ readonly specifier: string;
37
+ readonly path: string | null;
38
+ }
39
+
40
+ export type OxcProviderCapability = OxcProviderBinCapability | OxcProviderModuleCapability;
41
+
42
+ export type OxcProviderCapabilities = {
43
+ readonly [key in OxcProviderCapabilityName]?: OxcProviderCapability;
44
+ };
45
+
46
+ export interface OxcProviderLanguage {
47
+ readonly id: string;
48
+ readonly extensions: readonly string[];
49
+ readonly capabilities: OxcProviderCapabilities;
50
+ }
51
+
52
+ export interface OxcProvider {
53
+ readonly name: string;
54
+ readonly version: string | null;
55
+ readonly root: string;
56
+ readonly manifest: string;
57
+ readonly protocol: number;
58
+ readonly id: string;
59
+ readonly languages: readonly OxcProviderLanguage[];
60
+ }
61
+
62
+ export interface OxcProviderExtension {
63
+ readonly extension: string;
64
+ readonly package: string;
65
+ readonly providerId: string;
66
+ readonly providerRoot: string;
67
+ readonly language: string;
68
+ readonly capabilities: OxcProviderCapabilities;
69
+ }
70
+
71
+ export interface OxcProviderIndex {
72
+ readonly root: string;
73
+ readonly providers: readonly OxcProvider[];
74
+ readonly extensions: { readonly [extension: string]: OxcProviderExtension };
75
+ readonly diagnostics: readonly OxcProviderDiagnostic[];
76
+ }
77
+
78
+ /**
79
+ * Issuer-aware module resolution. A Plug'n'Play host passes its PnP API's
80
+ * `resolveRequest`, which already has this exact `(request, issuer)` shape.
81
+ */
82
+ export type OxcProviderResolve = (request: string, issuer: string) => string;
83
+
84
+ /**
85
+ * Manifest reader. It must read through the same layer `resolve` answers from.
86
+ *
87
+ * Under Plug'n'Play there is no `node_modules`: packages stay zipped in
88
+ * `.yarn/cache` and `.pnp.cjs` answers with a path *into* the archive, which an
89
+ * ordinary `fs.readFile` cannot open (it fails with `ENOTDIR`). A host that
90
+ * injects the PnP resolver but keeps reading with an ordinary `fs` resolves
91
+ * every manifest and reads none of them.
92
+ */
93
+ export type OxcProviderReadFile = (
94
+ path: string,
95
+ encoding: "utf8",
96
+ ) => string | Promise<string | Uint8Array>;
97
+
98
+ /**
99
+ * Host obligation: `resolve` and `readFile` travel together. Injecting one
100
+ * without the other is a host fault, not a protocol option.
101
+ *
102
+ * A Plug'n'Play host must supply both the PnP `resolveRequest` **and** a reader
103
+ * backed by the PnP filesystem layer, which in practice means running under the
104
+ * PnP runtime (`--require .pnp.cjs`, or a `yarn node` launcher) so `fs` is
105
+ * patched to see inside the zip. An injected resolver alone is not enough.
106
+ *
107
+ * A host that gets this wrong is told so. A dependency manifest that resolves
108
+ * and then cannot be read or parsed produces an `unreadable-manifest` warning
109
+ * naming the package and the manifest path, so a Plug'n'Play host with an
110
+ * unpatched reader sees one warning per direct dependency instead of an empty
111
+ * index and silence. A dependency that does not resolve at all stays quiet,
112
+ * because that only means it is not installed.
113
+ */
114
+ export interface OxcProviderDiscoveryOptions {
115
+ readonly root?: string;
116
+ readonly resolve?: OxcProviderResolve;
117
+ readonly readFile?: OxcProviderReadFile;
118
+ readonly protocols?: readonly number[];
119
+ readonly throwOnError?: boolean;
120
+ readonly inspectAncestors?: boolean;
121
+ }
122
+
123
+ export type OxcResolvedCapability = OxcProviderCapability & {
124
+ readonly package: string;
125
+ readonly providerId: string;
126
+ readonly providerRoot: string;
127
+ readonly language: string;
128
+ readonly extension: string;
129
+ readonly capability: OxcProviderCapabilityName;
130
+ };
131
+
132
+ export declare const PROTOCOL_VERSION: 1;
133
+ export declare const SUPPORTED_PROTOCOLS: readonly number[];
134
+ export declare const CAPABILITY_NAMES: readonly OxcProviderCapabilityName[];
135
+ export declare const DEPENDENCY_FIELDS: readonly string[];
136
+ export declare const RESERVED_EXTENSIONS: readonly string[];
137
+
138
+ export declare class ProviderProtocolError extends Error {
139
+ readonly diagnostics: readonly OxcProviderDiagnostic[];
140
+ constructor(diagnostics: readonly OxcProviderDiagnostic[]);
141
+ }
142
+
143
+ export declare function dependencyNames(manifest: unknown): string[];
144
+ export declare function providerDeclaration(manifest: unknown): Record<string, unknown> | null;
145
+ export declare function extensionOf(filePath: string): string | null;
146
+ export declare function isReservedExtension(extension: string): boolean;
147
+ export declare function findProjectRoot(
148
+ start?: string,
149
+ options?: { readonly readFile?: OxcProviderReadFile },
150
+ ): Promise<string>;
151
+ export declare function discoverProviders(
152
+ options?: OxcProviderDiscoveryOptions,
153
+ ): Promise<OxcProviderIndex>;
154
+ export declare function providerExtensions(index: OxcProviderIndex): string[];
155
+ export declare function hasProviderErrors(index: OxcProviderIndex): boolean;
156
+ export declare function resolveCapability(
157
+ index: OxcProviderIndex,
158
+ filePath: string,
159
+ capability: OxcProviderCapabilityName,
160
+ ): OxcResolvedCapability | null;
@@ -0,0 +1,471 @@
1
+ import { createRequire } from "node:module";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, join, parse, relative, resolve, sep } from "node:path";
4
+ //#region src/provider-resolve.ts
5
+ /**
6
+ * Reference implementation of the static `oxc.provider` discovery protocol.
7
+ *
8
+ * A provider declares one static top-level `oxc.provider` block in its own
9
+ * package.json. A host reads the project root manifest, takes the union of the
10
+ * direct dependency fields, resolves `<name>/package.json` for each, parses the
11
+ * JSON, and builds an extension index. Nothing in this file imports, requires,
12
+ * or spawns a dependency: module resolution and `JSON.parse` are the only
13
+ * operations performed against a candidate package.
14
+ *
15
+ * The module deliberately contains no knowledge of any individual provider, so
16
+ * it can be vendored by an unrelated host unchanged.
17
+ */
18
+ const PROTOCOL_VERSION = 1;
19
+ const SUPPORTED_PROTOCOLS = Object.freeze([1]);
20
+ const CAPABILITY_NAMES = Object.freeze([
21
+ "parse",
22
+ "lint",
23
+ "format",
24
+ "lsp"
25
+ ]);
26
+ const DEPENDENCY_FIELDS = Object.freeze([
27
+ "dependencies",
28
+ "devDependencies",
29
+ "optionalDependencies"
30
+ ]);
31
+ /**
32
+ * Extensions the core toolchain owns. A provider claiming one is a hard error,
33
+ * which is what structurally keeps ordinary source files off provider paths.
34
+ */
35
+ const RESERVED_EXTENSIONS = Object.freeze([
36
+ ".astro",
37
+ ".cjs",
38
+ ".cts",
39
+ ".js",
40
+ ".json",
41
+ ".json5",
42
+ ".jsonc",
43
+ ".jsx",
44
+ ".mjs",
45
+ ".mts",
46
+ ".svelte",
47
+ ".ts",
48
+ ".tsx",
49
+ ".vue"
50
+ ]);
51
+ const RESERVED = new Set(RESERVED_EXTENSIONS);
52
+ const IDENTIFIER = /^[a-z][a-z0-9-]*$/u;
53
+ var ProviderProtocolError = class extends Error {
54
+ diagnostics;
55
+ constructor(diagnostics) {
56
+ const failures = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
57
+ super(`the declared language providers cannot be indexed:\n${failures.map((diagnostic) => `- ${diagnostic.message}`).join("\n")}`);
58
+ this.name = "ProviderProtocolError";
59
+ this.diagnostics = diagnostics;
60
+ }
61
+ };
62
+ function defaultReadFile(path) {
63
+ return readFile(path, "utf8");
64
+ }
65
+ /**
66
+ * Issuer-aware Node resolution. A host with a different module map (Yarn PnP)
67
+ * injects `pnp.resolveRequest`, which has the same `(request, issuer)` shape.
68
+ */
69
+ function createDefaultResolver() {
70
+ const requires = /* @__PURE__ */ new Map();
71
+ return (request, issuer) => {
72
+ let resolver = requires.get(issuer);
73
+ if (resolver === void 0) {
74
+ resolver = createRequire(issuer);
75
+ requires.set(issuer, resolver);
76
+ }
77
+ return resolver.resolve(request);
78
+ };
79
+ }
80
+ function isPlainObject(value) {
81
+ return typeof value === "object" && value !== null && !Array.isArray(value);
82
+ }
83
+ function parseJson(text, path) {
84
+ try {
85
+ return JSON.parse(String(text));
86
+ } catch (error) {
87
+ throw new Error(`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
88
+ }
89
+ }
90
+ function diagnostic(severity, code, message, details = {}) {
91
+ return {
92
+ severity,
93
+ code,
94
+ message,
95
+ ...details
96
+ };
97
+ }
98
+ function isInside(directory, path) {
99
+ const offset = relative(directory, path);
100
+ return offset.length > 0 && !offset.startsWith("..") && !offset.startsWith(`${sep}`);
101
+ }
102
+ /** Union of the direct dependency fields, sorted for deterministic output. */
103
+ function dependencyNames(manifest) {
104
+ const names = /* @__PURE__ */ new Set();
105
+ for (const field of DEPENDENCY_FIELDS) {
106
+ const declared = manifest?.[field];
107
+ if (!isPlainObject(declared)) continue;
108
+ for (const name of Object.keys(declared)) names.add(name);
109
+ }
110
+ return [...names].sort();
111
+ }
112
+ function providerDeclaration(manifest) {
113
+ const declaration = manifest?.oxc?.provider;
114
+ return isPlainObject(declaration) ? declaration : null;
115
+ }
116
+ function extensionOf(filePath) {
117
+ if (typeof filePath !== "string") return null;
118
+ const name = filePath.split(/[/\\]/u).at(-1) ?? "";
119
+ const dot = name.lastIndexOf(".");
120
+ if (dot <= 0) return null;
121
+ return name.slice(dot).toLowerCase();
122
+ }
123
+ function isReservedExtension(extension) {
124
+ return typeof extension === "string" && RESERVED.has(extension.toLowerCase());
125
+ }
126
+ /** The nearest ancestor directory of `start` that contains a package.json. */
127
+ async function findProjectRoot(start = process.cwd(), options = {}) {
128
+ const readFile = options.readFile ?? defaultReadFile;
129
+ const from = resolve(start);
130
+ const filesystemRoot = parse(from).root;
131
+ let directory = from;
132
+ for (;;) try {
133
+ await readFile(join(directory, "package.json"), "utf8");
134
+ return directory;
135
+ } catch {
136
+ if (directory === filesystemRoot) throw new Error(`no package.json was found at or above ${from}`);
137
+ directory = dirname(directory);
138
+ }
139
+ }
140
+ function normalizeBinMap(manifest) {
141
+ if (typeof manifest.bin === "string") return typeof manifest.name === "string" ? { [manifest.name]: manifest.bin } : {};
142
+ return isPlainObject(manifest.bin) ? manifest.bin : {};
143
+ }
144
+ function moduleSpecifier(name, subpath) {
145
+ if (subpath === ".") return name;
146
+ return `${name}/${subpath.replace(/^\.\/?/u, "")}`;
147
+ }
148
+ function readCapabilities(declared, context) {
149
+ const capabilities = {};
150
+ if (!isPlainObject(declared)) return capabilities;
151
+ for (const capability of CAPABILITY_NAMES) {
152
+ const value = declared[capability];
153
+ if (value === void 0) continue;
154
+ const target = readCapabilityTarget(capability, value, context);
155
+ if (target !== null) capabilities[capability] = target;
156
+ }
157
+ return capabilities;
158
+ }
159
+ function readCapabilityTarget(capability, value, context) {
160
+ const { name, providerRoot, manifest, diagnostics, resolve: resolve$1, issuer } = context;
161
+ if (!isPlainObject(value)) {
162
+ diagnostics.push(diagnostic("warning", "invalid-capability", `package ${name} declares a non-object ${capability} capability`, {
163
+ packages: [name],
164
+ capability
165
+ }));
166
+ return null;
167
+ }
168
+ if (typeof value.bin === "string" && value.bin.length > 0) {
169
+ const declared = normalizeBinMap(manifest)[value.bin];
170
+ if (typeof declared !== "string" || declared.length === 0) {
171
+ diagnostics.push(diagnostic("warning", "invalid-capability", `package ${name} declares the ${capability} capability as bin "${value.bin}", which is not a key of its own bin map`, {
172
+ packages: [name],
173
+ capability
174
+ }));
175
+ return null;
176
+ }
177
+ const path = resolve(providerRoot, declared);
178
+ if (!isInside(providerRoot, path)) {
179
+ diagnostics.push(diagnostic("warning", "invalid-capability", `package ${name} declares the ${capability} capability outside its own package directory`, {
180
+ packages: [name],
181
+ capability
182
+ }));
183
+ return null;
184
+ }
185
+ return {
186
+ kind: "bin",
187
+ bin: value.bin,
188
+ path
189
+ };
190
+ }
191
+ if (typeof value.module === "string" && value.module.startsWith(".")) {
192
+ const specifier = moduleSpecifier(name, value.module);
193
+ let path = null;
194
+ try {
195
+ path = resolve$1(specifier, issuer);
196
+ } catch {
197
+ diagnostics.push(diagnostic("warning", "unresolved-capability", `package ${name} declares the ${capability} capability at ${specifier}, which its exports map does not resolve for this host`, {
198
+ packages: [name],
199
+ capability
200
+ }));
201
+ }
202
+ return {
203
+ kind: "module",
204
+ subpath: value.module,
205
+ specifier,
206
+ path
207
+ };
208
+ }
209
+ diagnostics.push(diagnostic("warning", "invalid-capability", `package ${name} declares a ${capability} capability that is neither an export subpath nor an own bin key`, {
210
+ packages: [name],
211
+ capability
212
+ }));
213
+ return null;
214
+ }
215
+ function readLanguages(declared, context) {
216
+ const { name, diagnostics } = context;
217
+ if (!Array.isArray(declared) || declared.length === 0) {
218
+ diagnostics.push(diagnostic("warning", "invalid-provider", `package ${name} declares no provider languages`, { packages: [name] }));
219
+ return [];
220
+ }
221
+ const languages = [];
222
+ for (const entry of declared) {
223
+ if (!isPlainObject(entry) || typeof entry.id !== "string" || !IDENTIFIER.test(entry.id)) {
224
+ diagnostics.push(diagnostic("warning", "invalid-provider", `package ${name} declares a language without a valid id`, { packages: [name] }));
225
+ continue;
226
+ }
227
+ const extensions = [];
228
+ for (const extension of Array.isArray(entry.extensions) ? entry.extensions : []) {
229
+ if (typeof extension !== "string" || extension.length < 2 || !extension.startsWith(".") || extension !== extension.toLowerCase() || /[\s/\\]/u.test(extension)) {
230
+ diagnostics.push(diagnostic("warning", "invalid-provider", `package ${name} declares the malformed extension ${JSON.stringify(extension)} for language ${entry.id}`, { packages: [name] }));
231
+ continue;
232
+ }
233
+ if (RESERVED.has(extension)) {
234
+ diagnostics.push(diagnostic("error", "reserved-extension", `package ${name} claims the reserved ${extension} extension, which protocol 1 keeps on the core toolchain`, {
235
+ packages: [name],
236
+ extension
237
+ }));
238
+ continue;
239
+ }
240
+ if (!extensions.includes(extension)) extensions.push(extension);
241
+ }
242
+ if (extensions.length === 0) continue;
243
+ languages.push({
244
+ id: entry.id,
245
+ extensions,
246
+ capabilities: readCapabilities(entry.capabilities, context)
247
+ });
248
+ }
249
+ return languages;
250
+ }
251
+ function readProvider(name, record, declaration, context) {
252
+ const { diagnostics, protocols } = context;
253
+ const { protocol, id } = declaration;
254
+ if (!Number.isInteger(protocol)) {
255
+ diagnostics.push(diagnostic("warning", "invalid-provider", `package ${name} declares a non-integer provider protocol`, { packages: [name] }));
256
+ return null;
257
+ }
258
+ if (!protocols.has(protocol)) {
259
+ diagnostics.push(diagnostic("warning", "unsupported-protocol", `package ${name} declares provider protocol ${protocol}, which this host does not support; it is ignored`, {
260
+ packages: [name],
261
+ protocol
262
+ }));
263
+ return null;
264
+ }
265
+ if (typeof id !== "string" || !IDENTIFIER.test(id)) {
266
+ diagnostics.push(diagnostic("warning", "invalid-provider", `package ${name} declares an invalid provider id ${JSON.stringify(id ?? null)}`, { packages: [name] }));
267
+ return null;
268
+ }
269
+ const languages = readLanguages(declaration.languages, {
270
+ ...context,
271
+ name,
272
+ manifest: record.manifest,
273
+ providerRoot: record.root
274
+ });
275
+ if (languages.length === 0) return null;
276
+ return {
277
+ name,
278
+ version: typeof record.manifest.version === "string" ? record.manifest.version : null,
279
+ root: record.root,
280
+ manifest: record.manifestPath,
281
+ protocol,
282
+ id,
283
+ languages
284
+ };
285
+ }
286
+ /**
287
+ * Read one candidate dependency's manifest.
288
+ *
289
+ * The two failure modes below look alike and are not alike. Resolution failing
290
+ * means the package is simply not installed, which is ordinary and stays quiet;
291
+ * an uninstalled optional dependency must never warn. A manifest that resolves
292
+ * and then cannot be read or parsed is different: resolution already proved the
293
+ * package is there, so failing to read it is a host or environment fault, and it
294
+ * must never be mistaken for "this package is not a provider".
295
+ */
296
+ async function readDependencyManifest(name, issuer, resolve, readFile, diagnostics) {
297
+ let manifestPath;
298
+ try {
299
+ manifestPath = resolve(`${name}/package.json`, issuer);
300
+ } catch {
301
+ return null;
302
+ }
303
+ let manifest;
304
+ try {
305
+ manifest = JSON.parse(String(await readFile(manifestPath, "utf8")));
306
+ } catch (error) {
307
+ diagnostics?.push(diagnostic("warning", "unreadable-manifest", `package ${name} resolved to ${manifestPath}, which this host could not read or parse: ${error instanceof Error ? error.message : String(error)}; a host with its own module map must supply a readFile that reads through the same layer`, {
308
+ packages: [name],
309
+ manifest: manifestPath
310
+ }));
311
+ return null;
312
+ }
313
+ if (!isPlainObject(manifest)) {
314
+ diagnostics?.push(diagnostic("warning", "unreadable-manifest", `package ${name} resolved to ${manifestPath}, which does not contain a JSON object`, {
315
+ packages: [name],
316
+ manifest: manifestPath
317
+ }));
318
+ return null;
319
+ }
320
+ return {
321
+ manifestPath,
322
+ root: dirname(manifestPath),
323
+ manifest
324
+ };
325
+ }
326
+ async function inspectAncestors(root, directNames, context) {
327
+ const { readFile, resolve, diagnostics } = context;
328
+ const filesystemRoot = parse(root).root;
329
+ let directory = root;
330
+ while (directory !== filesystemRoot) {
331
+ directory = dirname(directory);
332
+ const manifestPath = join(directory, "package.json");
333
+ let manifest;
334
+ try {
335
+ manifest = JSON.parse(String(await readFile(manifestPath, "utf8")));
336
+ } catch {
337
+ continue;
338
+ }
339
+ for (const name of dependencyNames(manifest)) {
340
+ if (directNames.has(name)) continue;
341
+ const record = await readDependencyManifest(name, manifestPath, resolve, readFile, diagnostics);
342
+ if (record === null || providerDeclaration(record.manifest) === null) continue;
343
+ diagnostics.push(diagnostic("warning", "ancestor-provider", `package ${name} declares a language provider and is a dependency of ${manifestPath}, but it is not a direct dependency of ${join(root, "package.json")}; it is not activated`, {
344
+ packages: [name],
345
+ manifest: manifestPath,
346
+ root: join(root, "package.json")
347
+ }));
348
+ }
349
+ }
350
+ }
351
+ /**
352
+ * Build the provider index for one project root.
353
+ *
354
+ * `resolve(request, issuer)` and `readFile(path, encoding)` are injectable so a
355
+ * host with a non-filesystem module map can supply its own. Fatal protocol
356
+ * violations (a reserved extension, two providers claiming one extension, two
357
+ * providers claiming one id) throw unless `throwOnError` is `false`; a report
358
+ * command uses `false` to show every violation at once.
359
+ */
360
+ async function discoverProviders(options = {}) {
361
+ const root = resolve(options.root ?? process.cwd());
362
+ const readFile = options.readFile ?? defaultReadFile;
363
+ const resolve$2 = options.resolve ?? createDefaultResolver();
364
+ const protocols = new Set(options.protocols ?? SUPPORTED_PROTOCOLS);
365
+ const throwOnError = options.throwOnError !== false;
366
+ const manifestPath = join(root, "package.json");
367
+ const diagnostics = [];
368
+ const names = dependencyNames(parseJson(await readFile(manifestPath, "utf8"), manifestPath));
369
+ const context = {
370
+ diagnostics,
371
+ protocols,
372
+ resolve: resolve$2,
373
+ readFile,
374
+ issuer: manifestPath
375
+ };
376
+ const declared = [];
377
+ for (const name of names) {
378
+ const record = await readDependencyManifest(name, manifestPath, resolve$2, readFile, diagnostics);
379
+ if (record === null) continue;
380
+ const declaration = providerDeclaration(record.manifest);
381
+ if (declaration === null) continue;
382
+ const provider = readProvider(name, record, declaration, context);
383
+ if (provider !== null) declared.push(provider);
384
+ }
385
+ const byIdentifier = /* @__PURE__ */ new Map();
386
+ for (const provider of declared) {
387
+ const group = byIdentifier.get(provider.id);
388
+ if (group === void 0) byIdentifier.set(provider.id, [provider]);
389
+ else group.push(provider);
390
+ }
391
+ const rejectedIdentifiers = /* @__PURE__ */ new Set();
392
+ for (const [id, group] of byIdentifier) {
393
+ if (group.length < 2) continue;
394
+ rejectedIdentifiers.add(id);
395
+ const packages = group.map((provider) => provider.name);
396
+ diagnostics.push(diagnostic("error", "duplicate-id", `packages ${packages.join(" and ")} both declare the provider id "${id}"`, {
397
+ packages,
398
+ id
399
+ }));
400
+ }
401
+ const providers = declared.filter((provider) => !rejectedIdentifiers.has(provider.id));
402
+ const claims = /* @__PURE__ */ new Map();
403
+ for (const provider of providers) for (const language of provider.languages) for (const extension of language.extensions) {
404
+ const claim = {
405
+ provider,
406
+ language
407
+ };
408
+ const group = claims.get(extension);
409
+ if (group === void 0) claims.set(extension, [claim]);
410
+ else group.push(claim);
411
+ }
412
+ const extensions = {};
413
+ for (const extension of [...claims.keys()].sort()) {
414
+ const group = claims.get(extension);
415
+ if (group.length > 1) {
416
+ const packages = group.map((claim) => claim.provider.name);
417
+ diagnostics.push(diagnostic("error", "extension-conflict", `packages ${packages.join(" and ")} both claim the ${extension} extension; protocol 1 never picks a winner`, {
418
+ packages,
419
+ extension
420
+ }));
421
+ continue;
422
+ }
423
+ const [{ provider, language }] = group;
424
+ extensions[extension] = {
425
+ extension,
426
+ package: provider.name,
427
+ providerId: provider.id,
428
+ providerRoot: provider.root,
429
+ language: language.id,
430
+ capabilities: language.capabilities
431
+ };
432
+ }
433
+ if (options.inspectAncestors !== false) await inspectAncestors(root, new Set(names), context);
434
+ const index = {
435
+ root,
436
+ providers,
437
+ extensions,
438
+ diagnostics
439
+ };
440
+ if (throwOnError && diagnostics.some((entry) => entry.severity === "error")) throw new ProviderProtocolError(diagnostics);
441
+ return index;
442
+ }
443
+ function providerExtensions(index) {
444
+ return Object.keys(index?.extensions ?? {}).sort();
445
+ }
446
+ function hasProviderErrors(index) {
447
+ return (index?.diagnostics ?? []).some((entry) => entry.severity === "error");
448
+ }
449
+ /**
450
+ * Look up one capability for one file. Returns `null` for every extension the
451
+ * index does not own, which is the fast path for ordinary source files.
452
+ */
453
+ function resolveCapability(index, filePath, capability) {
454
+ const extension = extensionOf(filePath);
455
+ if (extension === null) return null;
456
+ const entry = index?.extensions?.[extension];
457
+ if (entry === void 0) return null;
458
+ const target = entry.capabilities?.[capability];
459
+ if (target === void 0) return null;
460
+ return {
461
+ package: entry.package,
462
+ providerId: entry.providerId,
463
+ providerRoot: entry.providerRoot,
464
+ language: entry.language,
465
+ extension,
466
+ capability,
467
+ ...target
468
+ };
469
+ }
470
+ //#endregion
471
+ export { CAPABILITY_NAMES, DEPENDENCY_FIELDS, PROTOCOL_VERSION, ProviderProtocolError, RESERVED_EXTENSIONS, SUPPORTED_PROTOCOLS, dependencyNames, discoverProviders, extensionOf, findProjectRoot, hasProviderErrors, isReservedExtension, providerDeclaration, providerExtensions, resolveCapability };
@@ -0,0 +1,49 @@
1
+ import { CAPABILITY_NAMES, discoverProviders, findProjectRoot, hasProviderErrors } from "./provider-resolve.js";
2
+ //#region src/providers-report.ts
3
+ /**
4
+ * Read-only audit of the language providers a project root activates.
5
+ *
6
+ * The report never writes a file, never mutates node_modules, and never runs a
7
+ * dependency: it is the auditable counterpart to a host quietly executing a
8
+ * provider it discovered.
9
+ */
10
+ async function collectProviderReport(options = {}) {
11
+ const root = await findProjectRoot(options.projectRoot ?? process.cwd());
12
+ const index = await discoverProviders({
13
+ root,
14
+ throwOnError: false
15
+ });
16
+ return {
17
+ schemaVersion: 1,
18
+ ok: !hasProviderErrors(index),
19
+ root: index.root,
20
+ providers: index.providers,
21
+ extensions: index.extensions,
22
+ diagnostics: index.diagnostics
23
+ };
24
+ }
25
+ function capabilityLabel(capability) {
26
+ if (capability.kind === "bin") return `bin ${capability.bin} -> ${capability.path}`;
27
+ return `module ${capability.specifier}${capability.path === null ? " (unresolved)" : ""}`;
28
+ }
29
+ function formatProviderReport(report) {
30
+ const lines = [`project root: ${report.root}`];
31
+ if (report.providers.length === 0) lines.push("no language providers are declared by the direct dependencies");
32
+ for (const provider of report.providers) {
33
+ lines.push(`${provider.name}${provider.version === null ? "" : `@${provider.version}`} (provider ${provider.id}, protocol ${provider.protocol})`);
34
+ for (const language of provider.languages) {
35
+ lines.push(` language ${language.id}: ${language.extensions.join(" ")}`);
36
+ for (const capability of CAPABILITY_NAMES) {
37
+ const target = language.capabilities[capability];
38
+ if (target === void 0) continue;
39
+ lines.push(` ${capability}: ${capabilityLabel(target)}`);
40
+ }
41
+ }
42
+ }
43
+ const routed = Object.keys(report.extensions).sort();
44
+ if (routed.length > 0) lines.push(`routed extensions: ${routed.map((extension) => `${extension} -> ${report.extensions[extension].package}`).join(", ")}`);
45
+ for (const diagnostic of report.diagnostics) lines.push(`${diagnostic.severity}: ${diagnostic.message}`);
46
+ return `${lines.join("\n")}\n`;
47
+ }
48
+ //#endregion
49
+ export { collectProviderReport, formatProviderReport };