Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,11 +419,17 @@ function createResolver(
}

const isIncludedRelative = getIncluder(
config.include?.map((p) => ensureRelative(configDir, p)),
config.include?.map((p) => ensureRelative(configDir, p)) ??
(config.files ? [] : undefined),
config.exclude?.map((p) => ensureRelative(configDir, p)),
outDir
)

// Explicit files are included independently of include/exclude globs.
const includedFiles = new Set(
config.files?.map((file) => path.resolve(configDir, path.normalize(file)))
)

const isImporterSupported = opts.loose
? () => true
: (opts.importerFilter ??
Expand Down Expand Up @@ -456,7 +462,10 @@ function createResolver(

// Respect the include/exclude properties.
const relativeImporterFile = path.relative(configDir, importerFile)
if (!isIncludedRelative(relativeImporterFile)) {
if (
!includedFiles.has(importerFile) &&
!isIncludedRelative(relativeImporterFile)
) {
logFile?.write('configMismatch', { importer, id, configPath })
return notApplicable
}
Expand Down Expand Up @@ -600,7 +609,7 @@ function getIncluder(
return includers.some(test) && !excluders.some(test)
}
}
return () => true
return () => false
}

function addCompiledGlob(this: RegExp[], glob: string) {
Expand Down
44 changes: 44 additions & 0 deletions test/resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,47 @@ async function expectResolution(
true,
])
}

test('resolves aliases from explicit files even when include is empty and exclude matches', async () => {
const root = createProject(
{},
{
files: ['src/index.ts'],
include: [],
exclude: ['src'],
}
)
const resolvers = createTsconfigResolvers({
projects: ['tsconfig.json'],
projectRoot: root,
workspaceRoot: root,
logger: { error() {}, hasErrorLogged: () => false },
})
resolvers.reset()
const importer = join(root, 'src/index.ts')
const [resolveId] = await collect(resolvers.get(importer))
await expect(resolveId('@/value', importer)).resolves.toEqual([
normalize(realpathSync(join(root, 'src/value.ts'))),
true,
])
})

test('files-only configs do not include unlisted importers', async () => {
const root = createProject({}, { files: ['src/index.ts'], exclude: [] })
const resolvers = createTsconfigResolvers({
projects: ['tsconfig.json'],
projectRoot: root,
workspaceRoot: root,
logger: { error() {}, hasErrorLogged: () => false },
})
resolvers.reset()
const importer = join(root, 'src/index.ts')
const [resolveId] = await collect(resolvers.get(importer))
await expect(resolveId('@/value', importer)).resolves.toEqual([
normalize(realpathSync(join(root, 'src/value.ts'))),
true,
])
await expect(
resolveId('@/value', join(root, 'src/unlisted.ts'))
).resolves.toEqual([undefined, false])
})
30 changes: 30 additions & 0 deletions test/typescript-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,33 @@ function startServer(
optimizeDeps: { noDiscovery: true, include: [] },
})
}

test('explicit files override exclude and empty include like TypeScript', async () => {
const root = makeRoot()
let server: Awaited<ReturnType<typeof createServer>> | undefined
try {
writeConfig(
root,
'tsconfig.json',
{},
{
files: ['src/index.ts'],
include: [],
exclude: ['src'],
}
)
write(root, 'src/index.ts', 'export { value } from "@/value"')
write(root, 'src/value.ts', 'export const value = true')
const importer = join(root, 'src/index.ts')
const config = parseConfig(join(root, 'tsconfig.json'))
expect(config.fileNames.map(normalize)).toContain(normalize(importer))
const expected = normalize(realpathSync(join(root, 'src/value.ts')))
expect(resolveTypescript('@/value', importer, config)).toBe(expected)
server = await startServer(root)
const result = await server.pluginContainer.resolveId('@/value', importer)
expect(result?.id).toBe(expected)
} finally {
await server?.close()
rmSync(root, { recursive: true, force: true })
}
})