oxc-project/oxc · error · anyhow::Error

TS5081

TS5081

Error message

Cannot find a tsconfig.json file at the current directory: {}.

What it means

Diagnostic from oxlint's vue/valid-define-emits rule (crates/oxc_linter/src/rules/vue/valid_define_emits.rs). It fires when custom events are declared twice: via `defineEmits(...)` that actually defines events in `<script setup>`, and again via an `emits` option on the `export default {}` in the sibling plain `<script>` block. The two lists diverge easily and the options-block emits duplicate or override the macro's, so the rule asks you to remove the `export default` declaration.

Source

Thrown at crates/oxc_type_checker/src/execute/tsc.rs:86

/// - `Ok(Some(path))` — a resolved config file to load,
/// - `Ok(None)` — source files were given with no config file (compile them directly),
/// - `Err(_)` — one of `tsc`'s command-line errors, ready to print.
fn resolve_config_file(command: &TypeCheckCommand, cwd: &Path) -> Result<Option<PathBuf>> {
    if let Some(project) = &command.project {
        // TS5042
        if !command.files.is_empty() {
            bail!("Option 'project' cannot be mixed with source files on a command line.");
        }

        let file_or_directory = to_path(cwd, project);
        if file_or_directory.is_dir() {
            // A directory: look for `tsconfig.json` inside it.
            let config_file = file_or_directory.join("tsconfig.json");
            if config_file.is_file() {
                Ok(Some(config_file))
            } else {
                // TS5081
                bail!(
                    "Cannot find a tsconfig.json file at the current directory: {}.",
                    config_file.display()
                );
            }
        } else if file_or_directory.exists() {
            // An explicit config file (need not be named `tsconfig.json`).
            Ok(Some(file_or_directory))
        } else {
            // TS5058
            bail!("The specified path does not exist: '{}'.", file_or_directory.display());
        }
    } else if let Some(config_file) = find_config_file(cwd) {
        if command.files.is_empty() {
            Ok(Some(config_file))
        } else {
            // TS5112: a tsconfig.json is present but source files were also specified.
            bail!(
                "tsconfig.json is present but will not be loaded if files are specified on \

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Delete `emits` (or the whole `export default {}`) from the plain `<script>` block and keep events solely in `defineEmits`.
  2. Merge any options-declared events into the defineEmits call first so none are lost.
  3. Keep exactly one source of truth for events per component.

Example fix

// before
<script>
export default { emits: ['notify'] }
</script>
<script setup>
defineEmits({ submit: null })
</script>

// after
<script setup>
defineEmits({ notify: null, submit: null })
</script>
Defensive patterns

Strategy: validation

Validate before calling

// events must not be declared in both defineEmits and options emits
const m = setupSource.match(/defineEmits\s*(<[^>]*>)?\s*\(([^)]*)\)/);
const definesEvents = Boolean(m) && (Boolean(m[1]) || m[2].trim().length > 0);
if (definesEvents && optionsBlockHasEmits) {
  throw new Error('events defined in both defineEmits and export default {}; remove one');
}

Prevention

When it happens

Trigger: An SFC with both `<script>export default { emits: [...] }</script>` (non-empty emits) and `<script setup>defineEmits({ submit: null })</script>`; type-only `defineEmits<{...}>()` plus options emits is also flagged. A bare `defineEmits()` next to options `emits` is allowed (single source of truth).

Common situations: Partial migration from Options API to `<script setup>` leaving the old options block behind; codemods adding script setup without stripping emits; mixed-style SFCs during gradual upgrades.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/0cd671cff0a56ecb. Report an issue: GitHub.