From e30dd9cf692ede7bc62d1cbdb5732f513a85e001 Mon Sep 17 00:00:00 2001 From: innoprej <273227832+innoprej@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:24:13 +0900 Subject: [PATCH] fix(coverage): avoid matching sibling project roots (#10311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Shin JaeHee Co-authored-by: Ari Perkkiƶ --- packages/vitest/src/node/coverage.ts | 5 +++-- .../test/include-exclude.unit.test.ts | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/vitest/src/node/coverage.ts b/packages/vitest/src/node/coverage.ts index f17a15f8d..c8f8fdf40 100644 --- a/packages/vitest/src/node/coverage.ts +++ b/packages/vitest/src/node/coverage.ts @@ -156,14 +156,15 @@ export class BaseCoverageProvider { return cacheHit } + const matchingRoot = roots.find(root => filename.startsWith(`${slash(root)}/`) || filename === slash(root)) + // File outside project root with default allowExternal - if (this.options.allowExternal === false && roots.every(root => !filename.startsWith(root))) { + if (this.options.allowExternal === false && !matchingRoot) { this.globCache.set(filename, false) return false } - const matchingRoot = roots.find(root => filename.startsWith(`${slash(root)}/`) || filename === slash(root)) const relativeFilename = matchingRoot ? relative(matchingRoot, filename) : filename if (pm.isMatch(relativeFilename, this.options.exclude, { dot: true })) { diff --git a/test/coverage-test/test/include-exclude.unit.test.ts b/test/coverage-test/test/include-exclude.unit.test.ts index 333aa850a..67d4411ef 100644 --- a/test/coverage-test/test/include-exclude.unit.test.ts +++ b/test/coverage-test/test/include-exclude.unit.test.ts @@ -144,10 +144,23 @@ test('files outside project when allowExternal: true', async () => { expect(isIncluded(resolve(process.cwd(), '../../package-b/src/two.ts'))).toBe(false) }) -async function init(options: Partial & { testInclude?: string[] }) { +test('files with almost matching name, outside project when allowExternal: false', async () => { + const isIncluded = await init({ + include: ['**/*.ts'], + root: './something/', + allowExternal: false, + }) + + expect(isIncluded(resolve(process.cwd(), './something/src/one.ts'))).toBe(true) + expect(isIncluded(resolve(process.cwd(), './not-something/src/two.ts'))).toBe(false) + expect(isIncluded(resolve(process.cwd(), './something-else/src/three.ts'))).toBe(false) +}) + +async function init(options: Partial & { testInclude?: string[]; root?: string }) { const vitest = await createVitest('test', { config: false, include: ['dont-match-anything', ...(options.testInclude || [])], + root: options.root, coverage: { ...options, enabled: true, -- 2.51.2