Skip to content
Draft
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
16 changes: 8 additions & 8 deletions docs/configuration/config-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The following settings control the *environment* in which basedpyright will chec

- **verboseOutput** [boolean]: Specifies whether output logs should be verbose. This is useful when diagnosing certain problems like import resolution issues.

- **extraPaths** [array of strings, optional]: Additional search paths that will be used when searching for modules imported by files.
- **extraPaths** [array of strings, optional]: Additional search paths that will be used when searching for modules imported by files. Glob patterns (`**`, `*`, `?`) are supported *(basedpyright exclusive)* — matching directories are expanded at configuration time.

- **pythonVersion** [string, optional]: Specifies the version of Python that will be used to execute the source code. The version should be specified as a string in the format "M.m" where M is the major version and m is the minor (e.g. `"3.0"` or `"3.6"`). If a version is provided, pyright will generate errors if the source code makes use of language features that are not supported in that version. It will also tailor its use of type stub files, which conditionalizes type definitions based on the version. If no version is specified, pyright will use the version of the current python interpreter, if one is present.

Expand Down Expand Up @@ -314,13 +314,13 @@ The following settings allow more fine grained control over the **typeCheckingMo


## Execution Environment Options
Pyright allows multiple execution environments to be defined for different portions of your source tree. For example, a subtree may be designed to run with different import search paths or a different version of the python interpreter than the rest of the source base.
Pyright allows multiple "execution environments" to be defined for different portions of your source tree. For example, a subtree may be designed to run with different import search paths or a different version of the python interpreter than the rest of the source base.

The following settings can be specified for each execution environment. Each source file within a project is associated with at most one execution environment -- the first one whose root directory contains that file.
The following settings can be specified for each execution environment. Each source file within a project is associated with at most one execution environment -- the first one whose root matches that file. Environments are searched in array order; the first match wins.

- **root** [string, required]: Root path for the code that will execute within this execution environment.
- **root** [string, required]: Root path for the code that will execute within this execution environment. Glob patterns (`**`, `*`, `?`) are supported *(basedpyright exclusive)* — when used, import resolution falls back to the project root.

- **extraPaths** [array of strings, optional]: Additional search paths (in addition to the root path) that will be used when searching for modules imported by files within this execution environment. If specified, this overrides the default extraPaths setting when resolving imports for files within this execution environment. Note that each files execution environment mapping is independent, so if file A is in one execution environment and imports a second file B within a second execution environment, any imports from B will use the extraPaths in the second execution environment.
- **extraPaths** [array of strings, optional]: Additional search paths (in addition to the root path) that will be used when searching for modules imported by files within this execution environment. Glob patterns (`**`, `*`, `?`) are supported *(basedpyright exclusive)* — matching directories are expanded at configuration time. If specified, this overrides the default extraPaths setting when resolving imports for files within this execution environment. Note that each file's execution environment mapping is independent, so if file A is in one execution environment and imports a second file B within a second execution environment, any imports from B will use the extraPaths in the second execution environment.

- **pythonVersion** [string, optional]: The version of Python used for this execution environment. If not specified, the global `pythonVersion` setting is used instead.

Expand Down Expand Up @@ -377,10 +377,10 @@ The following is an example of a pyright config file:
]
},
{
"root": "src/tests",
"root": "**/tests",
"reportPrivateUsage": false,
"extraPaths": [
"src/tests/e2e",
"**/fixtures",
"src/sdk"
]
},
Expand Down Expand Up @@ -413,7 +413,7 @@ pythonPlatform = "Linux"
executionEnvironments = [
{ root = "src/web", pythonVersion = "3.5", pythonPlatform = "Windows", extraPaths = [ "src/service_libs" ], reportMissingImports = "warning" },
{ root = "src/sdk", pythonVersion = "3.0", extraPaths = [ "src/backend" ] },
{ root = "src/tests", reportPrivateUsage = false, extraPaths = ["src/tests/e2e", "src/sdk" ]},
{ root = "**/tests", reportPrivateUsage = false, extraPaths = ["**/fixtures", "src/sdk"] },
{ root = "src" }
]
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ export class BackgroundAnalysisProgram {
}

private _ensurePartialStubPackages(execEnv: ExecutionEnvironment) {
this._backgroundAnalysis?.ensurePartialStubPackages(execEnv.root?.toString());
const execEnvIndex = this.configOptions.getExecutionEnvironments().indexOf(execEnv);
this._backgroundAnalysis?.ensurePartialStubPackages(execEnvIndex);
Comment on lines +280 to +281
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The change from using execEnv.root?.toString() as a lookup key to using array indices introduces a subtle correctness issue. With glob roots, multiple execution environments could have the same root URI (the project root), making the old approach unreliable. However, the new index-based approach assumes the execution environments array order and contents remain stable between when ensurePartialStubPackages is called and when it's handled in the background thread. If the execution environments are modified or reordered between these points, this could lead to the wrong environment being used. Consider adding validation or documentation about this assumption.

Suggested change
const execEnvIndex = this.configOptions.getExecutionEnvironments().indexOf(execEnv);
this._backgroundAnalysis?.ensurePartialStubPackages(execEnvIndex);
const execEnvs = this.configOptions.getExecutionEnvironments();
const execEnvIndex = execEnvs.indexOf(execEnv);
if (execEnvIndex >= 0) {
// Note: This relies on the execution environments array remaining stable between
// the time this request is enqueued and when it is processed in the background
// analysis. If that assumption changes, this index-based lookup must be revisited.
this._backgroundAnalysis?.ensurePartialStubPackages(execEnvIndex);
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

By design — execution environments are set once during config loading and are immutable during analysis. The old root.toString() key approach was broken for glob roots since multiple envs can share the same wildcard root URI. Index-based lookup is the correct fix for this.

return this._importResolver.ensurePartialStubPackages(execEnv);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/pyright-internal/src/analyzer/importResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ export class ImportResolver {
return false;
}

if (this.partialStubs.isPartialStubPackagesScanned(execEnv)) {
if (execEnv.root && this.partialStubs.isPathScanned(execEnv.root)) {
return false;
}

Expand Down
16 changes: 7 additions & 9 deletions packages/pyright-internal/src/backgroundAnalysisBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface IBackgroundAnalysis extends Disposable {
setConfigOptions(configOptions: ConfigOptions): void;
setTrackedFiles(fileUris: Uri[]): void;
setAllowedThirdPartyImports(importNames: string[]): void;
ensurePartialStubPackages(executionRoot: string | undefined): void;
ensurePartialStubPackages(execEnvIndex: number): void;
setFileOpened(fileUri: Uri, version: number | null, contents: string, options: OpenFileOptions): void;
updateChainedUri(fileUri: Uri, chainedUri: Uri | undefined): void;
setFileClosed(fileUri: Uri, isTracked?: boolean): void;
Expand Down Expand Up @@ -158,8 +158,8 @@ export class BackgroundAnalysisBase implements IBackgroundAnalysis {
this.enqueueRequest({ requestType: 'setAllowedThirdPartyImports', data: serialize(importNames) });
}

ensurePartialStubPackages(executionRoot: string | undefined) {
this.enqueueRequest({ requestType: 'ensurePartialStubPackages', data: serialize({ executionRoot }) });
ensurePartialStubPackages(execEnvIndex: number) {
this.enqueueRequest({ requestType: 'ensurePartialStubPackages', data: serialize({ execEnvIndex }) });
}

setFileOpened(fileUri: Uri, version: number | null, contents: string, options: OpenFileOptions) {
Expand Down Expand Up @@ -589,8 +589,8 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
}

case 'ensurePartialStubPackages': {
const { executionRoot } = deserialize(msg.data);
this.handleEnsurePartialStubPackages(executionRoot);
const { execEnvIndex } = deserialize(msg.data);
this.handleEnsurePartialStubPackages(execEnvIndex);
break;
}

Expand Down Expand Up @@ -775,10 +775,8 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
this.program.setAllowedThirdPartyImports(importNames);
}

protected handleEnsurePartialStubPackages(executionRoot: string | undefined) {
const execEnv = this._configOptions
.getExecutionEnvironments()
.find((e) => e.root?.toString() === executionRoot);
protected handleEnsurePartialStubPackages(execEnvIndex: number) {
const execEnv = this._configOptions.getExecutionEnvironments()[execEnvIndex];
if (execEnv) {
this.importResolver.ensurePartialStubPackages(execEnv);
}
Expand Down
44 changes: 36 additions & 8 deletions packages/pyright-internal/src/common/configOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
* Class that holds the configuration options for the analyzer.
*/

import { globSync } from 'node:fs';
import { matchesGlob } from 'node:path/posix';

import { ImportLogger } from '../analyzer/importLogger';
import { getPathsFromPthFiles } from '../analyzer/pythonPathUtils';
import * as pathConsts from '../common/pathConsts';
Expand All @@ -27,7 +30,7 @@ import { PythonVersion, latestStablePythonVersion } from './pythonVersion';
import { ServiceKeys } from './serviceKeys';
import { ServiceProvider } from './serviceProvider';
import { Uri } from './uri/uri';
import { FileSpec, getFileSpec, isDirectory } from './uri/uriUtils';
import { FileSpec, getFileSpec, getWildcardRoot, isDirectory } from './uri/uriUtils';
import { userFacingOptionsList } from './stringUtils';

// prevent upstream changes from sneaking in and adding errors using console.error,
Expand Down Expand Up @@ -66,6 +69,8 @@ export class ExecutionEnvironment {
// tools or playgrounds).
skipNativeLibraries: boolean;

rootGlob?: string;

// Default to "." which indicates every file in the project.
constructor(
name: string,
Expand Down Expand Up @@ -1526,13 +1531,21 @@ export class ConfigOptions {
// execution environment is used.
findExecEnvironment(file: Uri): ExecutionEnvironment {
return (
this.executionEnvironments.find((env) => {
const envRoot = Uri.is(env.root) ? env.root : this.projectRoot.resolvePaths(env.root || '');
return file.startsWith(envRoot);
}) ?? this.getDefaultExecEnvironment()
this.executionEnvironments.find((env) => this._fileMatchesEnvironment(file, env)) ??
this.getDefaultExecEnvironment()
);
}

private _fileMatchesEnvironment(file: Uri, env: ExecutionEnvironment): boolean {
if (env.rootGlob !== undefined) {
const relative = this.projectRoot.getRelativePath(file)?.slice(2);
if (relative === undefined) return false;
Comment on lines +1541 to +1542
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The logic relative.slice(2) removes the first two characters from the relative path (likely "./" prefix), but this assumes getRelativePath always returns a path starting with "./". If the file is at the project root itself, or if the implementation changes, this could cause incorrect behavior. Consider using a more robust approach such as checking if the path starts with "./" before slicing, or using path manipulation utilities to ensure correctness.

Suggested change
const relative = this.projectRoot.getRelativePath(file)?.slice(2);
if (relative === undefined) return false;
const relativePath = this.projectRoot.getRelativePath(file);
if (relativePath === undefined) return false;
const relative = relativePath.startsWith('./') ? relativePath.slice(2) : relativePath;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

getRelativePath always starts with ./ — the implementation is ['.', ...components].join('/'), so the prefix is hardcoded. When the file is not a child, it returns undefined (handled by the ?. chain). The .slice(2) pattern is used throughout the codebase.

return matchesGlob(relative, env.rootGlob + '/**');
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The path normalization logic assumes that getRelativePath always returns a path in POSIX format (with forward slashes). However, on Windows systems, if the internal implementation uses backslashes, this could cause matching failures. Verify that the paths are consistently normalized to use forward slashes before passing to matchesGlob, which expects POSIX-style paths.

Suggested change
return matchesGlob(relative, env.rootGlob + '/**');
const posixRelative = relative.replace(/\\/g, '/');
return matchesGlob(posixRelative, env.rootGlob + '/**');

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

getRelativePath always returns a ./-prefixed path — the implementation joins with ['.', ...components].join('/'), so the ./ prefix is hardcoded. Confirmed by unit tests (uri.test.ts:865) which assert ./d/e/f. No backslash path is possible here.

Comment on lines +1539 to +1543
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The findExecEnvironment method is called frequently during analysis (on every file access), and with this change it will perform glob pattern matching via matchesGlob and relative path computation for every file when glob patterns are used. This could impact performance in large projects. Consider implementing caching of the file-to-environment mapping, or at least caching the relative path computation since projectRoot and file paths don't change during analysis.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The previous code ran file.matchesRegex(env.rootFileSpec.regExp) per file access — same per-call cost. matchesGlob is a native Node.js function with no measurable performance difference.

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The pattern concatenation env.rootGlob + '/**' assumes the glob pattern should match all descendant files. However, this changes the semantics of the original glob pattern. For example, if a user specifies 'src/*/utils' to match only immediate children, this will be transformed to 'src/*/utils/**', which would match all descendants. Consider whether the pattern should be matched as-is, or document this behavior clearly if the transformation is intentional.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

By design — root in execution environments means "all files under directories matching this pattern". Without /**, a file like src/foo/utils/helper.py would not match root src/*/utils. The /** suffix is necessary to match descendants.

}
const envRoot = Uri.is(env.root) ? env.root : this.projectRoot.resolvePaths(env.root || '');
return file.startsWith(envRoot);
}

getExecutionEnvironments(): ExecutionEnvironment[] {
if (this.executionEnvironments.length > 0) {
return this.executionEnvironments;
Expand Down Expand Up @@ -1691,7 +1704,7 @@ export class ConfigOptions {
if (typeof path !== 'string') {
console.error(`Config "extraPaths" field ${pathIndex} must be a string.`);
} else {
configExtraPaths!.push(configDirUri.resolvePaths(path));
this._resolveExtraPath(path, configDirUri, configExtraPaths, console);
}
});
this.defaultExtraPaths = [...configExtraPaths];
Expand Down Expand Up @@ -1989,6 +2002,20 @@ export class ConfigOptions {
return this.pythonEnvironmentName || this.pythonPath?.toString() || 'python';
}

private _resolveExtraPath(path: string, configDirUri: Uri, out: Uri[], console: ConsoleInterface) {
if (/[*?]/.test(path)) {
try {
for (const match of globSync(path, { cwd: configDirUri.getFilePath() })) {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

When a glob pattern in extraPaths matches no directories, the result is that no paths are added to the extraPaths array. While this might be intentional, it could lead to silent failures where the user expects paths to be added but none are. Consider adding a warning when a glob pattern matches zero paths to help users debug their configuration.

Suggested change
for (const match of globSync(path, { cwd: configDirUri.getFilePath() })) {
const matches = globSync(path, { cwd: configDirUri.getFilePath() });
if (!matches || matches.length === 0) {
console.error(
`Config "extraPaths" glob pattern "${path}" did not match any directories (cwd="${configDirUri.getFilePath()}").`
);
return;
}
for (const match of matches) {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Glob patterns legitimately match nothing in some environments (e.g. **/fixtures in a project without fixture directories). Warning on zero matches would be noisy. Silently skipping is the correct behaviour.

out.push(configDirUri.resolvePaths(match));
}
} catch (e) {
console.error(`Failed to expand glob pattern "${path}": ${e}`);
}
} else {
out.push(configDirUri.resolvePaths(path));
}
}

private _convertBoolean(value: any, fieldName: string, defaultValue: boolean): boolean {
if (value === undefined) {
return defaultValue;
Expand Down Expand Up @@ -2043,7 +2070,8 @@ export class ConfigOptions {

// Validate the root.
if (envObj.root && typeof envObj.root === 'string') {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

When the root pattern is just "**", getWildcardRoot will return the project root, and the glob pattern matching will attempt to match all files in the project. However, this pattern might not be meaningful for execution environments and could lead to confusion. Consider documenting this behavior or validating against patterns that are too broad.

Suggested change
if (envObj.root && typeof envObj.root === 'string') {
if (envObj.root && typeof envObj.root === 'string') {
// When the root pattern is just "**", getWildcardRoot will return the project root,
// and the glob pattern matching will attempt to match all files in the project.
// This can be broader than intended for an execution environment, so document this
// behavior for users configuring executionEnvironments.
if (envObj.root.trim() === '**') {
console.error(
`Config executionEnvironments index ${index}: root value "**" will match all files in the project; ` +
'consider using a more specific root.'
);
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

By design — ** matching all files is standard glob semantics. If a user writes root: "**", they intend to match everything. Warning on valid input would be over-engineering.

newExecEnv.root = configDirUri.resolvePaths(envObj.root);
newExecEnv.rootGlob = envObj.root;
newExecEnv.root = getWildcardRoot(configDirUri, envObj.root);
} else {
console.error(`Config executionEnvironments index ${index}: missing root value.`);
}
Expand All @@ -2067,7 +2095,7 @@ export class ConfigOptions {
` extraPaths field ${pathIndex} must be a string.`
);
} else {
newExecEnv.extraPaths.push(configDirUri.resolvePaths(path));
this._resolveExtraPath(path, configDirUri, newExecEnv.extraPaths, console);
}
});
}
Expand Down
78 changes: 77 additions & 1 deletion packages/pyright-internal/src/tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import assert from 'assert';
import { AnalyzerService } from '../analyzer/service';
import { deserialize, serialize } from '../backgroundThreadBase';
import { CommandLineOptions, DiagnosticSeverityOverrides } from '../common/commandLineOptions';
import { ConfigOptions, ExecutionEnvironment, getStandardDiagnosticRuleSet } from '../common/configOptions';
import {
ConfigOptions,
ExecutionEnvironment,
getStandardDiagnosticRuleSet,
} from '../common/configOptions';
import { ConsoleInterface, NullConsole } from '../common/console';
import { TaskListPriority } from '../common/diagnostic';
import { combinePaths, normalizePath, normalizeSlashes } from '../common/pathUtils';
Expand Down Expand Up @@ -740,4 +744,76 @@ describe(`config test'}`, () => {
shouldRunAnalysis: () => true,
});
}

describe('glob root support', () => {
function setupExecEnvConfig(roots: ({ root: string } & Record<string, unknown>)[]) {
const cwd = UriEx.file(normalizePath(process.cwd()));
const configOptions = new ConfigOptions(cwd);
const json = { executionEnvironments: roots };
const fs = new TestFileSystem(false);
const console = new ErrorTrackingNullConsole();
const sp = createServiceProvider(fs, console);
configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost());
configOptions.setupExecutionEnvironments(json, cwd, console);
return { cwd, configOptions, console };
}

test.each([
'src', '**/tests', 'src/*/utils', 'src/test?', '***/tests', ' ',
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The test includes a whitespace-only string (' ') as a valid root pattern. This doesn't seem like a meaningful pattern and could indicate a validation issue. Consider adding validation to reject empty or whitespace-only root patterns with a helpful error message.

Suggested change
'src', '**/tests', 'src/*/utils', 'src/test?', '***/tests', ' ',
'src', '**/tests', 'src/*/utils', 'src/test?', '***/tests',

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Intentional edge case coverage — the test documents that truthy whitespace strings pass through the if (envObj.root && typeof envObj.root === 'string') gate. Whether to add trim/validation is a separate discussion.

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The test includes '***/tests' which is not a valid glob pattern (three asterisks). Consider either removing this test case if it's meant to test invalid patterns, or replacing it with a valid pattern if it's meant to test valid patterns.

Suggested change
'src', '**/tests', 'src/*/utils', 'src/test?', '***/tests', ' ',
'src', '**/tests', 'src/*/utils', 'src/test?', '**/*/tests', ' ',

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Intentional — the test proves matchesGlob handles unusual input gracefully rather than crashing. The *** pattern is processed without error (confirmed by passing tests). This is robustness coverage, not an endorsement of the pattern.

])('root "%s" sets rootGlob', (root) => {
const { configOptions, console } = setupExecEnvConfig([{ root }]);
assert.deepStrictEqual(console.errors, []);
const env = configOptions.executionEnvironments[0];
assert.ok(env);
assert.strictEqual(env.rootGlob, root);
assert.ok(env.root);
});

test('serialization round-trip preserves rootGlob', () => {
const { configOptions } = setupExecEnvConfig([{ root: '**/tests' }]);
const cloned = deserialize<ConfigOptions>(serialize(configOptions));
assert.strictEqual(
cloned.executionEnvironments[0].rootGlob,
configOptions.executionEnvironments[0].rootGlob
);
});

test.each([
{ roots: ['**/tests'], file: 'tests/test_foo.py', envIndex: 0 },
{ roots: ['**/tests'], file: 'src/tests/test_foo.py', envIndex: 0 },
{ roots: ['**/tests'], file: 'src/lib/deep/tests/test_bar.py', envIndex: 0 },
{ roots: ['**/tests'], file: 'src/testing/foo.py', envIndex: -1 },
{ roots: ['src/*/utils'], file: 'src/foo/utils/helper.py', envIndex: 0 },
{ roots: ['src/*/utils'], file: 'src/foo/bar/utils/helper.py', envIndex: -1 },
{ roots: ['src/v?/lib'], file: 'src/v1/lib/foo.py', envIndex: 0 },
{ roots: ['src/v?/lib'], file: 'src/v10/lib/foo.py', envIndex: -1 },
{ roots: ['src/core', '**/tests'], file: 'src/core/tests/test_foo.py', envIndex: 0 },
{ roots: ['src/core', '**/tests'], file: 'lib/tests/test_bar.py', envIndex: 1 },
{ roots: ['src/core', '**/tests'], file: 'other/module.py', envIndex: -1 },
{ roots: ['**/tests', 'src'], file: 'src/tests/test_foo.py', envIndex: 0 },
{ roots: ['**/tests', 'src'], file: 'src/module.py', envIndex: 1 },
{ roots: ['**/tests', '**/test'], file: 'src/tests/foo.py', envIndex: 0 },
{ roots: ['**/tests', '**/test'], file: 'src/test/foo.py', envIndex: 1 },
{ roots: ['**/tests', '**/test'], file: 'src/tests/test/foo.py', envIndex: 0 },
])('roots $roots: "$file" -> env $envIndex', ({ roots, file, envIndex }) => {
const { cwd, configOptions } = setupExecEnvConfig(roots.map((root) => ({ root })));
const result = configOptions.findExecEnvironment(cwd.resolvePaths(file));
if (envIndex >= 0) {
assert.strictEqual(result, configOptions.executionEnvironments[envIndex]);
} else {
for (const env of configOptions.executionEnvironments) {
assert.notStrictEqual(result, env);
}
}
});

test('glob root with diagnostic overrides', () => {
const { configOptions } = setupExecEnvConfig([
{ root: '**/tests', reportPrivateUsage: false },
]);
const env = configOptions.executionEnvironments[0];
assert.strictEqual(env.root!.key, configOptions.projectRoot.key);
assert.strictEqual(env.diagnosticRuleSet.reportPrivateUsage, 'none');
});
});
});
Loading
Loading