microsoft / microsoft/typespec

[Bug]: Emitter options not resolved for subpath export emitters

Open
#10,200 0 comments 0 reactions 0 assignees View on GitHub
bug compiler:core triaged:core
Dominant language
Java
Stars
5.9k
Forks
394
Avg merge
1d 23h
Merged PRs (30d)
104

Description

### Describe the bug

# Emitter options not resolved for subpath export emitters

## Bug Report

### Describe the bug

When an emitter is exposed as a [subpath export](https://nodejs.org/api/packages.html#subpath-exports) of a package (e.g. `@my-org/my-emitter/typescript`), the compiler fails to match `tspconfig.yaml` options to the emitter. The emitter receives an empty options object `{}` instead of the configured values.

### Reproduction

**Package structure** (`@my-org/my-emitter`):

```json
{
"name": "@my-org/my-emitter",
"exports": {
".": { "default": "./dist/src/index.js" },
"./typescript": { "default": "./dist/src/typescript/index.js" },
"./json-schema": { "default": "./dist/src/json-schema/index.js" }
}
}
```

Each subpath export has its own `$lib` and `$onEmit`:

```typescript
// src/typescript/index.ts
export const $lib = createTypeSpecLibrary({
name: "@my-org/my-emitter/typescript",
diagnostics: {},
emitter: { options: myOptionsSchema },
});

export { $onEmit } from "./emitter.js";
```

**tspconfig.yaml:**

```yaml
emit:
- "@my-org/my-emitter/typescript"
options:
"@my-org/my-emitter/typescript":
emitter-output-dir: "{project-root}/generated"
my-custom-option: "hello"
```

**Result:** `context.options` in `$onEmit` is `{}`. The `my-custom-option` is never passed through.

### Root cause

In [`program.ts` `loadEmitter()`](https://github.com/microsoft/typespec/blob/main/packages/compiler/src/core/program.ts), the options lookup is:

```typescript
emittersOptions[metadata.name ?? emitterNameOrPath] ?? {}
```

For subpath exports, `metadata.name` comes from `computeModuleMetadata()`, which reads `module.manifest.name` — the **parent package.json `name` field** (`"@my-org/my-emitter"`), not the subpath specifier (`"@my-org/my-emitter/typescript"`).

Since `metadata.name` is always defined for npm packages, the fallback to `emitterNameOrPath` is never reached. The lookup becomes:

```
emittersOptions["@my-org/my-emitter"] // ← wrong key, undefined
```

instead of:

```
emittersOptions["@my-org/my-emitter/typescript"] // ← what the user configured
```

Additionally, `computeLibraryMetadata()` ignores `libDefinition?.name` (the `$lib.name`) for module-type packages:

```typescript
function computeLibraryMetadata(module, libDefinition) {
if (module.type === "file") {
return { type: "file", name: libDefinition?.name }; // ← uses $lib.name
}
return computeModuleMetadata(module); // ← ignores $lib.name, uses manifest.name
}
```

This means there's **no way** for a subpath emitter to receive its own options, and when multiple subpath emitters exist in the same package, they can't have distinct options (they'd all resolve to the same parent package name key).

### Suggested fix

Either:

1. **Prefer `emitterNameOrPath`** (the specifier from tspconfig) over `metadata.name` for the options lookup:
```typescript
emittersOptions[emitterNameOrPath] ?? emittersOptions[metadata.name] ?? {}
```

2. **Prefer `libDefinition?.name`** over `module.manifest.name` in `computeLibraryMetadata` for module types:
```typescript
function computeLibraryMetadata(module, libDefinition) {
if (module.type === "file") {
return { type: "file", name: libDefinition?.name };
}
const moduleMetadata = computeModuleMetadata(module);
if (libDefinition?.name) {
moduleMetadata.name = libDefinition.name;
}
return moduleMetadata;
}
```

Option 1 is simpler and backwards-compatible — it matches user intent (the key they wrote in tspconfig.yaml) while falling back to the current behavior.

### Workaround

Hardcode emitter-specific values in the `$onEmit` function instead of reading from `context.options`.

### Checklist

- [x] Verified in `@typespec/compiler` v1.10.0
- [x] Traced through compiled `program.js` and `module-resolver.js`
- [x] Both `emitter-output-dir` and custom options are affected

### Reproduction

[typespec-subpath-emitter-options-repro.tar.gz](https://github.com/user-attachments/files/26356298/typespec-subpath-emitter-options-repro.tar.gz)

### Checklist

- [x] Follow our [Code of Conduct](https://github.com/microsoft/typespec/blob/main/CODE_OF_CONDUCT.md)
- [x] Check that there isn't already an issue that request the same bug to avoid creating a duplicate.
- [x] Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/Microsoft/typespec/discussions).
- [x] The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.