Skip to content

transpileModule/transpileDeclaration silently canonicalize fileName and echo the canonical form in diagnostics — undocumented #64090

Description

🔎 Search Terms

transpileModule fileName normalized, transpileDeclaration diagnostic fileName, unstable/sync API canonicalize path, TranspileOptions fileName

🕗 Version & Regression Information

  • This is the behavior in every version I tried: typescript@7.1.0-dev.20260828.1 (nightly), via the typescript/unstable/sync API class. Not a regression — undocumented existing behavior, as far as we can tell.

⏯ Playground Link

N/A — reproducible only via the typescript/unstable/sync Node API, not the Playground.

💻 Code

const { API } = require('typescript/unstable/sync');
const api = new API({});

const result = api.transpileModule('var a\n== 0;\n', {
  compilerOptions: {},
  fileName: 'C:\\Users\\me\\project\\app.ts',
  reportDiagnostics: true,
});

console.log(result.diagnostics[0].fileName);
// "C:/Users/me/project/app.ts" - not what was passed in

🙁 Actual behavior

fileName is silently canonicalized before use (separators to /, ./.. segments resolved — real path.normalize, not a slash swap), and every diagnostic echoes the canonical form instead of the caller's input:

fileName in diagnostics[0].fileName out
C:\Users\me\project\app.ts C:/Users/me/project/app.ts
C:/Users/me/../me/project/app.ts C:/Users/me/project/app.ts
/tmp/foo/app.ts (already canonical) /tmp/foo/app.ts (unchanged)

transpileDeclaration does the same. Neither behavior is documented on TranspileOptions.fileName or Diagnostic.fileName.

Not Windows-specific, though the backslash example is: POSIX paths never contain \, so that row won't reproduce on macOS/Linux, but the ./.. resolution will — pass a fileName like /tmp/foo/../foo/app.ts to see the same canonicalization on any OS.

This is a pain if you're trying to match up a diagnostic to the source file that it relates to. ts-loader's workaround is to canonicalize fileName itself before calling transpileModule, then use that same canonicalized string as the key: https://github.com/TypeStrong/ts-loader/blob/38d364e7d702138fa74672a9ef15c4447e6322ed/src/typeScriptApi.ts#L339

We found this reworking TypeStrong/ts-loader#1704: db8b27c added the canonicalization workaround to fix exactly this mismatch.

🙂 Expected behavior

It feels curious that the fileName that comes out differs from what is passed in. It would be marvellous if that didn't happen, but perhaps there are reasons it is necessary.

Here are some ideas that might improve things for consumers:

  1. Document on TranspileOptions.fileName/Diagnostic.fileName that fileName is canonicalized and diagnostics echo the canonical form, not the input.
  2. Include the original filename as well as the processed one in the diagnostics for consumers to use. originalFileName perhaps
  3. Specify the exact rule (or export it as a utility) so callers building their own location lookup can replicate it, rather than reverse-engineering it via a silently-wrong test result.

Additional information about the issue

Full writeup with more probe data: see the linked ts-loader PR/commits above. Andrew Branch (@andrewbranch) this may be interesting to you - but I wouldn't want to presume!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions