Skip to content

fix(@angular/build): forward tsconfig paths as Vite aliases for Vitest coverage#33024

Open
tomeelog wants to merge 1 commit intoangular:mainfrom
tomeelog:fix/vitest-coverage-tsconfig-paths
Open

fix(@angular/build): forward tsconfig paths as Vite aliases for Vitest coverage#33024
tomeelog wants to merge 1 commit intoangular:mainfrom
tomeelog:fix/vitest-coverage-tsconfig-paths

Conversation

@tomeelog
Copy link
Copy Markdown

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

When a project configures compilerOptions.paths in tsconfig.json (e.g.
"#/*": ["./src/app/*"]) and runs ng test with coverage: true, Vitest's
vite:import-analysis plugin fails during the coverage phase with:

Pre-transform error: Failed to resolve import "#/util" from "src/app/app.ts".
Does the file exist?

This happens because the Angular CLI's Vitest integration does not forward
tsconfig path aliases to Vite's resolve.alias configuration. They are
resolved during compilation (by esbuild) but not during Vitest's own
Vite-based coverage instrumentation pass.

Issue Number: #32891

What is the new behavior?

The tsconfig file is read at Vitest initialisation time. Its
compilerOptions.paths entries are converted to Vite-compatible
resolve.alias objects (supporting both exact and wildcard patterns) and
injected into the projectDefaults.resolve.alias config that the Angular
CLI passes to the Vitest project workspace.

Path aliases are now resolved correctly during both test execution and
coverage instrumentation.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

The fix is intentionally narrow — it only reads paths and baseUrl from
the tsconfig, leaving all other resolution behaviour unchanged. Comment
syntax in tsconfig files (C-style /* */ and //) is stripped before
JSON parsing to handle real-world configs.

A new behavior spec (vitest-coverage-tsconfig-paths_spec.ts) verifies the
fix end-to-end using the builder test harness.

…t coverage

When using tsconfig.json 'paths' (e.g. "#/util": ["./src/util"]) with
coverage enabled, Vitest's vite:import-analysis plugin fails to resolve
path-alias imports from original source files during coverage processing
because the Angular CLI's Vitest integration did not expose those aliases
to the Vite resolve configuration.

The fix reads the tsconfig file, converts every paths entry to a Vite
resolve.alias entry (supporting both exact and wildcard patterns), and
injects them into the projectDefaults resolve config used by the project
workspace. This makes path aliases available during both test execution
and coverage instrumentation.

Fixes angular#32891
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for resolving tsconfig path aliases during Vitest unit testing, specifically addressing issues where coverage instrumentation failed to resolve these aliases. It includes logic to parse compilerOptions.paths from the tsconfig file and map them to Vite-compatible resolve aliases. Feedback was provided regarding the robustness of the JSON comment-stripping regex to avoid breaking valid strings and the need to normalize resolved paths to POSIX format for better cross-platform compatibility.

try {
const raw = await readFile(tsConfigPath, 'utf-8');
// tsconfig files may contain C-style comments – strip them before parsing.
const json = JSON.parse(raw.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, ''));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current regex for stripping comments is prone to breaking valid JSON if it contains strings with // or /* (for example, URLs like https://angular.io or path patterns). A safer approach is to use a regex that accounts for strings to avoid accidental matches within them.

Suggested change
const json = JSON.parse(raw.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, ''));
const json = JSON.parse(
raw.replace(/("(?:\\.|[^\\"])*")|\/\*[\s\S]*?\*\/|\/\/.*/g, (m, g1) => g1 ?? ''),
);

Comment on lines +98 to +105
const targetDir = path.join(baseDir, target.replace(/\/\*$/, ''));
return [{
find: new RegExp(`^${escapeRegExp(prefix)}\/(.*)$`),
replacement: `${targetDir}/$1`,
}];
}
// Exact alias: "#/util" -> "./src/util"
return [{ find: pattern, replacement: path.join(baseDir, target) }];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure consistent behavior across different operating systems, especially on Windows, it is recommended to normalize the resolved alias paths to POSIX format using toPosixPath. Vite and Vitest generally prefer POSIX-style paths or correctly formatted absolute paths for aliases, and mixing slashes (e.g., C:\path/to/file) can lead to resolution issues.

Suggested change
const targetDir = path.join(baseDir, target.replace(/\/\*$/, ''));
return [{
find: new RegExp(`^${escapeRegExp(prefix)}\/(.*)$`),
replacement: `${targetDir}/$1`,
}];
}
// Exact alias: "#/util" -> "./src/util"
return [{ find: pattern, replacement: path.join(baseDir, target) }];
const targetDir = toPosixPath(path.join(baseDir, target.replace(/\/\*$/, '')));
return [{
find: new RegExp('^' + escapeRegExp(prefix) + '\\/(.*)$'),
replacement: targetDir + '/$1',
}];
}
// Exact alias: "#/util" -> "./src/util"
return [{ find: pattern, replacement: toPosixPath(path.join(baseDir, target)) }];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant