Skip to content
Merged
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
51 changes: 36 additions & 15 deletions packages/extension/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
import type { WorkspaceConfiguration, WorkspaceFolder } from 'vscode'
import { homedir } from 'node:os'
import { dirname, isAbsolute, resolve } from 'node:path'
import { dirname, isAbsolute, resolve, sep } from 'node:path'
import * as vscode from 'vscode'
import { configGlob } from './constants'

export const extensionId = 'vitest.explorer'
export const testControllerId = 'vitest'

export function substituteVariables(value: string, workspaceFolder?: WorkspaceFolder): string {
const folder = workspaceFolder ?? vscode.workspace.workspaceFolders?.[0]
return (
value
// eslint-disable-next-line no-template-curly-in-string
.replace(/\$\{workspaceFolder\}/g, folder?.uri.fsPath ?? '')
// eslint-disable-next-line no-template-curly-in-string
.replace(/\$\{workspaceFolderBasename\}/g, folder?.name ?? '')
// eslint-disable-next-line no-template-curly-in-string
.replace(/\$\{userHome\}/g, homedir())
// eslint-disable-next-line no-template-curly-in-string
.replace(/\$\{env:([^}]+)\}/g, (_, name) => process.env[name] ?? '')
// eslint-disable-next-line no-template-curly-in-string
.replace(/\$\{pathSeparator\}/g, sep)
)
}

function resolvePathWithSubstitution(path: string | undefined, workspaceFolder?: WorkspaceFolder) {
return resolveConfigPath(
path ? substituteVariables(path, workspaceFolder) : path,
workspaceFolder,
)
}

export function getConfigValue<T>(
rootConfig: WorkspaceConfiguration,
folderConfig: WorkspaceConfiguration,
Expand Down Expand Up @@ -42,14 +66,9 @@ export function getConfig(workspaceFolder?: WorkspaceFolder) {
get<string>('configSearchPatternInclude', configGlob) || configGlob

const vitestPackagePath = get<string | undefined>('vitestPackagePath')
const resolvedVitestPackagePath =
workspaceFolder && vitestPackagePath
? resolve(
workspaceFolder.uri.fsPath,
// eslint-disable-next-line no-template-curly-in-string
vitestPackagePath.replace('${workspaceFolder}', workspaceFolder.uri.fsPath),
)
: vitestPackagePath
const resolvedVitestPackagePath = vitestPackagePath
? resolvePathWithSubstitution(vitestPackagePath, workspaceFolder)
: vitestPackagePath

const logLevel = get<string>('logLevel', 'info')

Expand All @@ -74,24 +93,24 @@ export function getConfig(workspaceFolder?: WorkspaceFolder) {
env: get<null | Record<string, string>>('nodeEnv', null),
debugEnv: get<null | Record<string, string>>('debugNodeEnv', null),
debugExclude: get<string[]>('debugExclude'),
debugOutFiles,
debugOutFiles: debugOutFiles?.map((f) => substituteVariables(f, workspaceFolder)),
filesWatcherInclude,
runtime,
forceCancelTimeout,
watchOnStartup,
terminalShellArgs,
terminalShellPath,
terminalShellPath: resolvePathWithSubstitution(terminalShellPath, workspaceFolder),
shellType,
applyDiagnostic,
cliArguments,
nodeExecArgs,
vitestPackagePath: resolvedVitestPackagePath,
workspaceConfig: resolveConfigPath(workspaceConfig),
rootConfig: resolveConfigPath(rootConfigFile),
workspaceConfig: resolvePathWithSubstitution(workspaceConfig, workspaceFolder),
rootConfig: resolvePathWithSubstitution(rootConfigFile, workspaceFolder),
configSearchPatternInclude,
configSearchPatternExclude,
ignoreWorkspace,
nodeExecutable: resolveConfigPath(nodeExecutable),
nodeExecutable: resolvePathWithSubstitution(nodeExecutable, workspaceFolder),
disableWorkspaceWarning: get<boolean>('disableWorkspaceWarning', false),
debuggerPort: get<number>('debuggerPort') || undefined,
debuggerAddress: get<string>('debuggerAddress', undefined) || undefined,
Expand All @@ -101,11 +120,13 @@ export function getConfig(workspaceFolder?: WorkspaceFolder) {
}
}

export function resolveConfigPath(path: string | undefined) {
export function resolveConfigPath(path: string | undefined, workspaceFolder?: WorkspaceFolder) {
if (!path || isAbsolute(path)) return path
if (path.startsWith('~/')) {
return resolve(homedir(), path.slice(2))
}
// if a workspaceFolder was provided, resolve relative to it
if (workspaceFolder) return resolve(workspaceFolder.uri.fsPath, path)
// if there is a workspace file, then it should be relative to it because
// this option cannot be configured on a workspace folder level
if (vscode.workspace.workspaceFile)
Expand Down
57 changes: 55 additions & 2 deletions test/unit/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,61 @@
import { resolve } from 'node:path'
import { resolve, sep } from 'node:path'
import { homedir } from 'node:os'
import { expect } from 'chai'
import { resolveConfigPath } from '../../packages/extension/src/config'
import type { Uri, WorkspaceFolder } from 'vscode'
import { resolveConfigPath, substituteVariables } from '../../packages/extension/src/config'

function mockWorkspaceFolder(fsPath: string, name: string): WorkspaceFolder {
return {
uri: { fsPath } as Uri,
name,
index: 0,
}
}

it('correctly resolves ~', () => {
expect(resolveConfigPath('~/test')).to.equal(resolve(homedir(), 'test'))
})

describe('substituteVariables', () => {
it('substitutes ${workspaceFolder}', () => {
const folder = mockWorkspaceFolder('/my/workspace', 'workspace')
// eslint-disable-next-line no-template-curly-in-string
expect(substituteVariables('${workspaceFolder}/src', folder)).to.equal('/my/workspace/src')
})

it('substitutes ${workspaceFolderBasename}', () => {
const folder = mockWorkspaceFolder('/my/workspace', 'myproject')
// eslint-disable-next-line no-template-curly-in-string
expect(substituteVariables('${workspaceFolderBasename}', folder)).to.equal('myproject')
})

it('substitutes ${userHome}', () => {
// eslint-disable-next-line no-template-curly-in-string
expect(substituteVariables('${userHome}/projects')).to.equal(`${homedir()}/projects`)
})

it('substitutes ${env:NAME}', () => {
process.env.TEST_VAR_VITEST_EXT = 'hello'
// eslint-disable-next-line no-template-curly-in-string
expect(substituteVariables('${env:TEST_VAR_VITEST_EXT}/path')).to.equal('hello/path')
delete process.env.TEST_VAR_VITEST_EXT
})

it('substitutes ${pathSeparator}', () => {
// eslint-disable-next-line no-template-curly-in-string
expect(substituteVariables('foo${pathSeparator}bar')).to.equal(`foo${sep}bar`)
})

it('substitutes multiple occurrences', () => {
const folder = mockWorkspaceFolder('/ws', 'myws')
// eslint-disable-next-line no-template-curly-in-string
expect(
substituteVariables('${workspaceFolder}/a/${workspaceFolderBasename}/b', folder),
).to.equal('/ws/a/myws/b')
})

it('leaves unrecognized variables unchanged', () => {
// eslint-disable-next-line no-template-curly-in-string
expect(substituteVariables('${unknown}/path')).to.equal('${unknown}/path')
})
})
Loading