Skip to content
Open
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
13 changes: 9 additions & 4 deletions docs/configuration/config-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,15 @@ The following settings can be specified for each execution environment. Each sou

- **root** [string, required]: Root path for the code that will execute within this execution environment.

- **typeCheckingMode** [string, optional]: Specifies the type checking mode to use for this execution environment. This overrides the global `typeCheckingMode` setting. Valid values are the same as [the global setting](#diagnostic-settings-defaults). If not specified, the global `typeCheckingMode` is used.

- **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 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.

- **pythonPlatform** [string, optional]: Specifies the target platform that will be used for this execution environment. If not specified, the global `pythonPlatform` setting is used instead.

In addition, any of the [type check diagnostics settings](config-files.md#type-check-diagnostics-settings) listed above can be specified. These settings act as overrides for the files in this execution environment.
In addition, any of the [type check diagnostics settings](config-files.md#type-check-diagnostics-settings) listed above can be specified. These settings act as overrides for the files in this execution environment. Individual diagnostic rule overrides take precedence over the `typeCheckingMode` setting.

## Sample Config File
The following is an example of a pyright config file:
Expand Down Expand Up @@ -364,6 +366,7 @@ The following is an example of a pyright config file:
"root": "src/web",
"pythonVersion": "3.5",
"pythonPlatform": "Windows",
"typeCheckingMode": "basic",
"extraPaths": [
"src/service_libs"
],
Expand All @@ -372,12 +375,14 @@ The following is an example of a pyright config file:
{
"root": "src/sdk",
"pythonVersion": "3.0",
"typeCheckingMode": "strict",
"extraPaths": [
"src/backend"
]
},
{
"root": "src/tests",
"typeCheckingMode": "standard",
"reportPrivateUsage": false,
"extraPaths": [
"src/tests/e2e",
Expand Down Expand Up @@ -411,9 +416,9 @@ pythonVersion = "3.6"
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 = "src/web", pythonVersion = "3.5", pythonPlatform = "Windows", typeCheckingMode = "basic", extraPaths = [ "src/service_libs" ], reportMissingImports = "warning" },
{ root = "src/sdk", pythonVersion = "3.0", typeCheckingMode = "strict", extraPaths = [ "src/backend" ] },
{ root = "src/tests", typeCheckingMode = "standard", reportPrivateUsage = false, extraPaths = ["src/tests/e2e", "src/sdk" ]},
{ root = "src" }
]
```
Expand Down
86 changes: 75 additions & 11 deletions packages/pyright-internal/src/common/configOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export class ExecutionEnvironment {
// Diagnostic rules with overrides.
diagnosticRuleSet: DiagnosticRuleSet;

// The type checking mode specified for this execution environment, if any.
typeCheckingMode?: TypeCheckingMode;

// Skip import resolution attempts for native libraries. These can
// be expensive and are not needed for some use cases (e.g. web-based
// tools or playgrounds).
Expand Down Expand Up @@ -1559,16 +1562,9 @@ export class ConfigOptions {
* @returns any errors that occurred
*/
initializeTypeCheckingModeFromString(typeCheckingMode: string | undefined, console_: ConsoleInterface) {
if (typeCheckingMode !== undefined) {
if ((allTypeCheckingModes as readonly string[]).includes(typeCheckingMode)) {
this.initializeTypeCheckingMode(typeCheckingMode as TypeCheckingMode);
} else {
console_.error(
`invalid "typeCheckingMode" value: "${typeCheckingMode}". expected: ${userFacingOptionsList(
allTypeCheckingModes
)}`
);
}
const validatedMode = ConfigOptions._validateTypeCheckingMode(typeCheckingMode, console_, '');
if (validatedMode !== undefined) {
this.initializeTypeCheckingMode(validatedMode);
}
}

Expand Down Expand Up @@ -1983,6 +1979,57 @@ export class ConfigOptions {
}
}

/**
* Validates a typeCheckingMode value and returns the corresponding TypeCheckingMode enum.
* Logs an error to the console if the value is invalid.
*
* @param typeCheckingMode - The typeCheckingMode value to validate (can be undefined)
* @param console_ - Console interface for logging errors
* @param errorContext - Context string for error messages (e.g., "Config executionEnvironments index 0"). Pass empty string for global config.
* @returns TypeCheckingMode if valid, undefined if not specified or invalid
*/
private static _validateTypeCheckingMode(
typeCheckingMode: unknown,
console_: ConsoleInterface,
errorContext: string
): TypeCheckingMode | undefined {
if (typeCheckingMode === undefined) {
return undefined;
}

const prefix = errorContext ? `${errorContext}: ` : '';
if (typeof typeCheckingMode !== 'string') {
console_.error(`${prefix}typeCheckingMode must be a string.`);
return undefined;
}

if ((allTypeCheckingModes as readonly string[]).includes(typeCheckingMode)) {
return typeCheckingMode as TypeCheckingMode;
}
console_.error(
`${prefix}invalid "typeCheckingMode" value: "${typeCheckingMode}". expected: ${userFacingOptionsList(allTypeCheckingModes)}`
);
return undefined;
}

/**
* Validates a typeCheckingMode string and returns the corresponding DiagnosticRuleSet.
* Logs an error to the console if the value is invalid.
*
* @param typeCheckingMode - The typeCheckingMode value to validate (can be undefined)
* @param console_ - Console interface for logging errors
* @param errorContext - Context string for error messages (e.g., "Config executionEnvironments index 0")
* @returns DiagnosticRuleSet if valid, undefined if not specified or invalid
*/
private static _getDiagnosticRuleSetFromString(
typeCheckingMode: unknown,
console_: ConsoleInterface,
errorContext: string
): DiagnosticRuleSet | undefined {
const validatedMode = ConfigOptions._validateTypeCheckingMode(typeCheckingMode, console_, errorContext);
return validatedMode !== undefined ? ConfigOptions.getDiagnosticRuleSet(validatedMode) : undefined;
}

private _getEnvironmentName(): string {
return this.pythonEnvironmentName || this.pythonPath?.toString() || 'python';
}
Expand Down Expand Up @@ -2030,15 +2077,32 @@ export class ConfigOptions {
configExtraPaths: Uri[]
): ExecutionEnvironment | undefined {
try {
// If typeCheckingMode is specified for this execution environment,
// validate it once and use it to generate the base diagnostic rule set.
// Otherwise, use the config-level diagnostic rule set.
const validatedMode = ConfigOptions._validateTypeCheckingMode(
envObj.typeCheckingMode,
console,
`Config executionEnvironments index ${index}`
);
const baseDiagnosticRuleSet = validatedMode !== undefined
? ConfigOptions.getDiagnosticRuleSet(validatedMode)
: configDiagnosticRuleSet;

const newExecEnv = new ExecutionEnvironment(
this._getEnvironmentName(),
configDirUri,
configDiagnosticRuleSet,
baseDiagnosticRuleSet,
configPythonVersion,
configPythonPlatform,
configExtraPaths
);

// Store the validated typeCheckingMode if it was specified and valid.
if (validatedMode !== undefined) {
newExecEnv.typeCheckingMode = validatedMode;
}

// Validate the root.
if (envObj.root && typeof envObj.root === 'string') {
newExecEnv.root = configDirUri.resolvePaths(envObj.root);
Expand Down
135 changes: 134 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,12 @@ 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,
allTypeCheckingModes,
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 @@ -708,6 +713,134 @@ describe(`config test'}`, () => {
assert.equal(configOptions.diagnosticRuleSet.reportAny, 'warning');
});

describe('executionEnvironments typeCheckingMode', () => {
test('typeCheckingMode in executionEnvironment sets diagnostic rules correctly', () => {
const cwd = UriEx.file(normalizePath(process.cwd()));
const configOptions = new ConfigOptions(cwd);

const json = {
typeCheckingMode: 'standard',
executionEnvironments: [
{
root: 'src/strict_folder',
typeCheckingMode: 'strict',
},
{
root: 'src/basic_folder',
typeCheckingMode: 'basic',
},
],
};

const fs = new TestFileSystem(/* ignoreCase */ false);
const console = new NullConsole();
const sp = createServiceProvider(fs, console);
configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost());
configOptions.setupExecutionEnvironments(json, cwd, console);

assert.strictEqual(configOptions.executionEnvironments.length, 2);

const [strictEnv, basicEnv] = configOptions.executionEnvironments;

// Verify strict environment has strict settings
assert.strictEqual(strictEnv.diagnosticRuleSet.strictListInference, true);
assert.strictEqual(strictEnv.diagnosticRuleSet.reportMissingTypeStubs, 'error');
assert.strictEqual(strictEnv.diagnosticRuleSet.reportUnusedVariable, 'error');

// Verify basic environment has basic settings
assert.strictEqual(basicEnv.diagnosticRuleSet.strictListInference, false);
assert.strictEqual(basicEnv.diagnosticRuleSet.reportMissingTypeStubs, 'none');
assert.strictEqual(basicEnv.diagnosticRuleSet.reportUnusedVariable, 'hint');
});

test('typeCheckingMode in executionEnvironment with individual rule overrides', () => {
const cwd = UriEx.file(normalizePath(process.cwd()));
const configOptions = new ConfigOptions(cwd);

const json = {
typeCheckingMode: 'standard',
executionEnvironments: [
{
root: 'src/custom',
typeCheckingMode: 'strict',
reportUnusedVariable: 'warning',
},
],
};

const fs = new TestFileSystem(/* ignoreCase */ false);
const console = new NullConsole();
const sp = createServiceProvider(fs, console);
configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost());
configOptions.setupExecutionEnvironments(json, cwd, console);

const customEnv = configOptions.executionEnvironments[0];

// Verify strict mode is applied as base
assert.strictEqual(customEnv.diagnosticRuleSet.strictListInference, true);

// Verify individual override takes precedence over typeCheckingMode
assert.strictEqual(customEnv.diagnosticRuleSet.reportUnusedVariable, 'warning');
});

test('invalid typeCheckingMode in executionEnvironment logs error', () => {
const cwd = UriEx.file(normalizePath(process.cwd()));
const configOptions = new ConfigOptions(cwd);

const json = {
executionEnvironments: [
{
root: 'src/invalid',
typeCheckingMode: 'invalid_mode',
},
],
};

const fs = new TestFileSystem(/* ignoreCase */ false);
const console = new ErrorTrackingNullConsole();
const sp = createServiceProvider(fs, console);
configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost());
configOptions.setupExecutionEnvironments(json, cwd, console);

assert.strictEqual(console.errors.length, 1);
assert(
console.errors[0].includes('invalid "typeCheckingMode"') &&
console.errors[0].includes('invalid_mode')
);
});

test('all typeCheckingMode values work in executionEnvironment', () => {
for (const mode of allTypeCheckingModes) {
const cwd = UriEx.file(normalizePath(process.cwd()));
const configOptions = new ConfigOptions(cwd);

const json = {
executionEnvironments: [
{
root: `src/${mode}`,
typeCheckingMode: mode,
},
],
};

const fs = new TestFileSystem(/* ignoreCase */ false);
const console = new ErrorTrackingNullConsole();
const sp = createServiceProvider(fs, console);
configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost());
configOptions.setupExecutionEnvironments(json, cwd, console);

// Should not log any errors for valid modes
assert.strictEqual(
console.errors.length,
0,
`typeCheckingMode "${mode}" should be valid but got errors: ${console.errors.join(', ')}`
);
assert.strictEqual(configOptions.executionEnvironments.length, 1);
}
});

});

function createAnalyzer(console?: ConsoleInterface) {
const cons = console ?? new ErrorTrackingNullConsole();
const fs = createFromRealFileSystem(tempFile, cons);
Expand Down
Loading