From 651d1f47d49bd9e83b2aaf1ec4ab25c8a46c0b5a Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 11 Nov 2025 23:20:35 +0200 Subject: [PATCH 01/15] feat: Support typeCheckingMode in executionEnvironments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for specifying typeCheckingMode within individual executionEnvironments entries in the configuration file. This allows different folders in a project to use different type checking strictness levels (off, basic, standard, strict, recommended, all). Previously, users had to manually specify dozens of individual diagnostic rules to achieve different strictness levels per folder. This change makes it much cleaner and more maintainable. Features: - typeCheckingMode can be specified in any executionEnvironment - If specified, it overrides the global typeCheckingMode for that folder - Individual diagnostic rule overrides still take precedence - All modes (off, basic, standard, strict, recommended, all) are supported - Invalid modes are validated and logged as errors Changes: - Modified _initExecutionEnvironmentFromJson to check for typeCheckingMode - Added comprehensive test coverage for all scenarios - Updated documentation with examples Fixes: https://github.com/DetachHead/basedpyright/issues/1638 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/configuration/config-files.md | 20 +- .../src/common/configOptions.ts | 26 ++- .../pyright-internal/src/tests/config.test.ts | 186 ++++++++++++++++++ 3 files changed, 223 insertions(+), 9 deletions(-) diff --git a/docs/configuration/config-files.md b/docs/configuration/config-files.md index bcb0a11763..399ac1b23b 100644 --- a/docs/configuration/config-files.md +++ b/docs/configuration/config-files.md @@ -314,19 +314,21 @@ 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. - **root** [string, required]: Root path for the code that will execute within this 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. 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. +- **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: `"off"`, `"basic"`, `"standard"`, `"strict"`, `"recommended"`, or `"all"`. If not specified, the global `typeCheckingMode` is used. This provides a cleaner alternative to specifying individual diagnostic rule overrides when you want to apply a different strictness level to specific folders. + +- **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: @@ -364,20 +366,22 @@ The following is an example of a pyright config file: "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", @@ -411,9 +415,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" ] }, + { 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" } ] ``` diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index fb861ea0d0..a13044de03 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -2030,10 +2030,34 @@ export class ConfigOptions { configExtraPaths: Uri[] ): ExecutionEnvironment | undefined { try { + // If typeCheckingMode is specified for this execution environment, + // use it to generate the base diagnostic rule set. Otherwise, use + // the config-level diagnostic rule set. + let baseDiagnosticRuleSet = configDiagnosticRuleSet; + if (envObj.typeCheckingMode !== undefined) { + if (typeof envObj.typeCheckingMode === 'string') { + if ((allTypeCheckingModes as readonly string[]).includes(envObj.typeCheckingMode)) { + baseDiagnosticRuleSet = this.constructor.getDiagnosticRuleSet( + envObj.typeCheckingMode as TypeCheckingMode + ); + } else { + console.error( + `Config executionEnvironments index ${index}: invalid "typeCheckingMode" value: "${ + envObj.typeCheckingMode + }". Expected: ${userFacingOptionsList(allTypeCheckingModes)}` + ); + } + } else { + console.error( + `Config executionEnvironments index ${index}: typeCheckingMode must be a string.` + ); + } + } + const newExecEnv = new ExecutionEnvironment( this._getEnvironmentName(), configDirUri, - configDiagnosticRuleSet, + baseDiagnosticRuleSet, configPythonVersion, configPythonPlatform, configExtraPaths diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index d7df114a8d..e075e09f43 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -708,6 +708,192 @@ 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 = configOptions.executionEnvironments[0]; + const basicEnv = configOptions.executionEnvironments[1]; + + // 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(console.errors.length > 0); + assert( + console.errors[0].includes('invalid "typeCheckingMode"') && + console.errors[0].includes('invalid_mode') + ); + }); + + test('typeCheckingMode must be a string in executionEnvironment', () => { + const cwd = UriEx.file(normalizePath(process.cwd())); + const configOptions = new ConfigOptions(cwd); + + const json = { + executionEnvironments: [ + { + root: 'src/invalid', + typeCheckingMode: 123, + }, + ], + }; + + 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(console.errors.length > 0); + assert(console.errors[0].includes('typeCheckingMode must be a string')); + }); + + test('executionEnvironment without typeCheckingMode inherits global setting', () => { + const cwd = UriEx.file(normalizePath(process.cwd())); + const configOptions = new ConfigOptions(cwd); + + const json = { + typeCheckingMode: 'strict', + executionEnvironments: [ + { + root: 'src/inherit', + }, + ], + }; + + 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 inheritEnv = configOptions.executionEnvironments[0]; + + // Should have strict settings from global config + assert.strictEqual(inheritEnv.diagnosticRuleSet.strictListInference, true); + assert.strictEqual(inheritEnv.diagnosticRuleSet.reportMissingTypeStubs, 'error'); + }); + + test('all typeCheckingMode values work in executionEnvironment', () => { + const modes: Array<'off' | 'basic' | 'standard' | 'strict' | 'recommended' | 'all'> = [ + 'off', + 'basic', + 'standard', + 'strict', + 'recommended', + 'all', + ]; + + for (const mode of modes) { + 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); From 3d078de1b4300d65f525cbdd83dc86b332b2c3c8 Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 11 Nov 2025 23:21:59 +0200 Subject: [PATCH 02/15] test: Add specific test for recommended mode in executionEnvironments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a test that specifically verifies basedpyright's 'recommended' mode applies the correct diagnostic rules when used in executionEnvironments, including basedpyright-specific rules like reportAny and reportExplicitAny. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../pyright-internal/src/tests/config.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index e075e09f43..338430f059 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -892,6 +892,37 @@ describe(`config test'}`, () => { assert.strictEqual(configOptions.executionEnvironments.length, 1); } }); + + test('recommended mode in executionEnvironment applies basedpyright-specific rules', () => { + const cwd = UriEx.file(normalizePath(process.cwd())); + const configOptions = new ConfigOptions(cwd); + + const json = { + typeCheckingMode: 'standard', + executionEnvironments: [ + { + root: 'src/recommended_folder', + typeCheckingMode: 'recommended', + }, + ], + }; + + 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 recommendedEnv = configOptions.executionEnvironments[0]; + + // Verify basedpyright-specific "recommended" settings are applied + assert.strictEqual(recommendedEnv.diagnosticRuleSet.deprecateTypingAliases, true); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportAny, 'warning'); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportExplicitAny, 'warning'); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportImportCycles, 'error'); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.strictListInference, true); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.failOnWarnings, true); + }); }); function createAnalyzer(console?: ConsoleInterface) { From 0216f47f79d86dae61ef65f6e31f68d402c38ea6 Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 11 Nov 2025 23:23:45 +0200 Subject: [PATCH 03/15] test: Add comprehensive test for multiple execution environments with different modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This test verifies that: - Multiple execution environments can have different typeCheckingModes - Each environment's settings are independent and don't interfere - Environments with 'basic', 'recommended', 'strict', and inherited 'standard' modes all have the correct diagnostic rules simultaneously - Specifically verifies that 'recommended' mode's basedpyright-specific rules (reportAny, reportExplicitAny, failOnWarnings) are ONLY enabled in the 'recommended' environment and NOT in the others This addresses the concern that the previous tests only verified one execution environment at a time and didn't prove isolation between environments. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../pyright-internal/src/tests/config.test.ts | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index 338430f059..925ee99cc1 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -923,6 +923,76 @@ describe(`config test'}`, () => { assert.strictEqual(recommendedEnv.diagnosticRuleSet.strictListInference, true); assert.strictEqual(recommendedEnv.diagnosticRuleSet.failOnWarnings, true); }); + + test('multiple executionEnvironments with different typeCheckingModes work independently', () => { + const cwd = UriEx.file(normalizePath(process.cwd())); + const configOptions = new ConfigOptions(cwd); + + const json = { + typeCheckingMode: 'standard', + executionEnvironments: [ + { + root: 'src/legacy', + typeCheckingMode: 'basic', + }, + { + root: 'src/new', + typeCheckingMode: 'recommended', + }, + { + root: 'src/strict_code', + typeCheckingMode: 'strict', + }, + { + root: 'src/other', + // No typeCheckingMode - should inherit global 'standard' + }, + ], + }; + + 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, 4); + + const basicEnv = configOptions.executionEnvironments[0]; + const recommendedEnv = configOptions.executionEnvironments[1]; + const strictEnv = configOptions.executionEnvironments[2]; + const standardEnv = configOptions.executionEnvironments[3]; + + // Verify basic environment has basic settings + assert.strictEqual(basicEnv.diagnosticRuleSet.strictListInference, false); + assert.strictEqual(basicEnv.diagnosticRuleSet.reportMissingTypeStubs, 'none'); + assert.strictEqual(basicEnv.diagnosticRuleSet.reportUnusedVariable, 'hint'); + assert.strictEqual(basicEnv.diagnosticRuleSet.reportAny, 'none'); + assert.strictEqual(basicEnv.diagnosticRuleSet.failOnWarnings, false); + + // Verify recommended environment has basedpyright-specific settings + assert.strictEqual(recommendedEnv.diagnosticRuleSet.deprecateTypingAliases, true); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportAny, 'warning'); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportExplicitAny, 'warning'); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportImportCycles, 'error'); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.strictListInference, true); + assert.strictEqual(recommendedEnv.diagnosticRuleSet.failOnWarnings, true); + + // Verify strict environment has strict settings + assert.strictEqual(strictEnv.diagnosticRuleSet.strictListInference, true); + assert.strictEqual(strictEnv.diagnosticRuleSet.reportMissingTypeStubs, 'error'); + assert.strictEqual(strictEnv.diagnosticRuleSet.reportUnusedVariable, 'error'); + // Strict mode does NOT enable basedpyright-specific rules like reportAny + assert.strictEqual(strictEnv.diagnosticRuleSet.reportAny, 'none'); + assert.strictEqual(strictEnv.diagnosticRuleSet.failOnWarnings, false); + + // Verify standard environment (inherited from global) has standard settings + assert.strictEqual(standardEnv.diagnosticRuleSet.strictListInference, false); + assert.strictEqual(standardEnv.diagnosticRuleSet.reportMissingTypeStubs, 'none'); + assert.strictEqual(standardEnv.diagnosticRuleSet.reportUnusedVariable, 'hint'); + assert.strictEqual(standardEnv.diagnosticRuleSet.reportAny, 'none'); + assert.strictEqual(standardEnv.diagnosticRuleSet.failOnWarnings, false); + }); }); function createAnalyzer(console?: ConsoleInterface) { From 29b4c8c0122e42a32c271611b5885185d90d20a0 Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 10:06:54 +0200 Subject: [PATCH 04/15] Update packages/pyright-internal/src/tests/config.test.ts Co-authored-by: DetachHead <57028336+DetachHead@users.noreply.github.com> --- packages/pyright-internal/src/tests/config.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index 925ee99cc1..54d4d3008f 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -735,8 +735,7 @@ describe(`config test'}`, () => { assert.strictEqual(configOptions.executionEnvironments.length, 2); - const strictEnv = configOptions.executionEnvironments[0]; - const basicEnv = configOptions.executionEnvironments[1]; + const [strictEnv, strictEnv] = configOptions.executionEnvironments; // Verify strict environment has strict settings assert.strictEqual(strictEnv.diagnosticRuleSet.strictListInference, true); From 260b094a250e7279c3e9bf87c0c2de457d780e4f Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 10:07:16 +0200 Subject: [PATCH 05/15] Update packages/pyright-internal/src/tests/config.test.ts Co-authored-by: DetachHead <57028336+DetachHead@users.noreply.github.com> --- packages/pyright-internal/src/tests/config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index 54d4d3008f..faf444f621 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -797,7 +797,7 @@ describe(`config test'}`, () => { configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost()); configOptions.setupExecutionEnvironments(json, cwd, console); - assert(console.errors.length > 0); + assert(console.errors.length === 1); assert( console.errors[0].includes('invalid "typeCheckingMode"') && console.errors[0].includes('invalid_mode') From 24f2ba6f35ad32f2c9ae10d5d03574f56770323e Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 10:07:24 +0200 Subject: [PATCH 06/15] Update packages/pyright-internal/src/tests/config.test.ts Co-authored-by: DetachHead <57028336+DetachHead@users.noreply.github.com> --- packages/pyright-internal/src/tests/config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index faf444f621..85a3e27796 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -823,7 +823,7 @@ describe(`config test'}`, () => { configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost()); configOptions.setupExecutionEnvironments(json, cwd, console); - assert(console.errors.length > 0); + assert(console.errors.length === 1); assert(console.errors[0].includes('typeCheckingMode must be a string')); }); From acf3b9ce60464b460406864bcd7775ecc3129493 Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 10:08:38 +0200 Subject: [PATCH 07/15] Update docs/configuration/config-files.md Co-authored-by: DetachHead <57028336+DetachHead@users.noreply.github.com> --- docs/configuration/config-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/config-files.md b/docs/configuration/config-files.md index 399ac1b23b..f6c67f3d53 100644 --- a/docs/configuration/config-files.md +++ b/docs/configuration/config-files.md @@ -320,7 +320,7 @@ 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: `"off"`, `"basic"`, `"standard"`, `"strict"`, `"recommended"`, or `"all"`. If not specified, the global `typeCheckingMode` is used. This provides a cleaner alternative to specifying individual diagnostic rule overrides when you want to apply a different strictness level to specific folders. +- **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: `"off"`, `"basic"`, `"standard"`, `"strict"`, `"recommended"`, or `"all"`. 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. From 88a355b7ba7857d98f0fec462478a3238686b67a Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 10:09:34 +0200 Subject: [PATCH 08/15] Update packages/pyright-internal/src/tests/config.test.ts Co-authored-by: DetachHead <57028336+DetachHead@users.noreply.github.com> --- packages/pyright-internal/src/tests/config.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index 85a3e27796..64e068a4a0 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -854,14 +854,14 @@ describe(`config test'}`, () => { }); test('all typeCheckingMode values work in executionEnvironment', () => { - const modes: Array<'off' | 'basic' | 'standard' | 'strict' | 'recommended' | 'all'> = [ + const modes = [ 'off', 'basic', 'standard', 'strict', 'recommended', 'all', - ]; + ] as const; for (const mode of modes) { const cwd = UriEx.file(normalizePath(process.cwd())); From f47d7999ce6830e1550a8bb11d39bb57569d0f1d Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 10:38:24 +0200 Subject: [PATCH 09/15] Address PR review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Link to valid values in docs instead of duplicating - Remove subjective language from typeCheckingMode description - Add reportMissingImports to examples to show diagnostic overrides work with typeCheckingMode - Extract getDiagnosticRuleSetFromString helper function to eliminate code duplication - Add typeCheckingMode property to ExecutionEnvironment class - Fix destructuring typo in test (strictEnv, strictEnv -> strictEnv, basicEnv) - Fix error count assertion formatting - Import allTypeCheckingModes from configOptions.ts instead of hardcoding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/configuration/config-files.md | 9 +- .../src/common/configOptions.ts | 70 +++++++--- .../pyright-internal/src/tests/config.test.ts | 122 ++---------------- 3 files changed, 64 insertions(+), 137 deletions(-) diff --git a/docs/configuration/config-files.md b/docs/configuration/config-files.md index f6c67f3d53..d30e6b661c 100644 --- a/docs/configuration/config-files.md +++ b/docs/configuration/config-files.md @@ -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. - **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: `"off"`, `"basic"`, `"standard"`, `"strict"`, `"recommended"`, or `"all"`. If not specified, the global `typeCheckingMode` is used. +- **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. @@ -369,7 +369,8 @@ The following is an example of a pyright config file: "typeCheckingMode": "basic", "extraPaths": [ "src/service_libs" - ] + ], + "reportMissingImports": "warning" }, { "root": "src/sdk", @@ -415,7 +416,7 @@ pythonVersion = "3.6" pythonPlatform = "Linux" executionEnvironments = [ - { root = "src/web", pythonVersion = "3.5", pythonPlatform = "Windows", typeCheckingMode = "basic", extraPaths = [ "src/service_libs" ] }, + { 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" } diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index a13044de03..89310e7686 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -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). @@ -1572,6 +1575,39 @@ export class ConfigOptions { } } + /** + * 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: any, + console_: ConsoleInterface, + errorContext: string + ): DiagnosticRuleSet | undefined { + if (typeCheckingMode === undefined) { + return undefined; + } + + if (typeof typeCheckingMode !== 'string') { + console_.error(`${errorContext}: typeCheckingMode must be a string.`); + return undefined; + } + + if ((allTypeCheckingModes as readonly string[]).includes(typeCheckingMode)) { + return ConfigOptions.getDiagnosticRuleSet(typeCheckingMode as TypeCheckingMode); + } else { + console_.error( + `${errorContext}: invalid "typeCheckingMode" value: "${typeCheckingMode}". Expected: ${userFacingOptionsList(allTypeCheckingModes)}` + ); + return undefined; + } + } + // Initialize the structure from a JSON object. initializeFromJson(configObj: any, configDirUri: Uri, serviceProvider: ServiceProvider, host: Host) { this.initializedFromJson = true; @@ -2033,26 +2069,12 @@ export class ConfigOptions { // If typeCheckingMode is specified for this execution environment, // use it to generate the base diagnostic rule set. Otherwise, use // the config-level diagnostic rule set. - let baseDiagnosticRuleSet = configDiagnosticRuleSet; - if (envObj.typeCheckingMode !== undefined) { - if (typeof envObj.typeCheckingMode === 'string') { - if ((allTypeCheckingModes as readonly string[]).includes(envObj.typeCheckingMode)) { - baseDiagnosticRuleSet = this.constructor.getDiagnosticRuleSet( - envObj.typeCheckingMode as TypeCheckingMode - ); - } else { - console.error( - `Config executionEnvironments index ${index}: invalid "typeCheckingMode" value: "${ - envObj.typeCheckingMode - }". Expected: ${userFacingOptionsList(allTypeCheckingModes)}` - ); - } - } else { - console.error( - `Config executionEnvironments index ${index}: typeCheckingMode must be a string.` - ); - } - } + const baseDiagnosticRuleSet = + ConfigOptions.getDiagnosticRuleSetFromString( + envObj.typeCheckingMode, + console, + `Config executionEnvironments index ${index}` + ) ?? configDiagnosticRuleSet; const newExecEnv = new ExecutionEnvironment( this._getEnvironmentName(), @@ -2063,6 +2085,14 @@ export class ConfigOptions { configExtraPaths ); + // Store the typeCheckingMode if it was explicitly specified and valid. + if ( + typeof envObj.typeCheckingMode === 'string' && + (allTypeCheckingModes as readonly string[]).includes(envObj.typeCheckingMode) + ) { + newExecEnv.typeCheckingMode = envObj.typeCheckingMode as TypeCheckingMode; + } + // Validate the root. if (envObj.root && typeof envObj.root === 'string') { newExecEnv.root = configDirUri.resolvePaths(envObj.root); diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index 64e068a4a0..aac8415434 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -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'; @@ -735,7 +740,7 @@ describe(`config test'}`, () => { assert.strictEqual(configOptions.executionEnvironments.length, 2); - const [strictEnv, strictEnv] = configOptions.executionEnvironments; + const [strictEnv, basicEnv] = configOptions.executionEnvironments; // Verify strict environment has strict settings assert.strictEqual(strictEnv.diagnosticRuleSet.strictListInference, true); @@ -797,7 +802,7 @@ describe(`config test'}`, () => { configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost()); configOptions.setupExecutionEnvironments(json, cwd, console); - assert(console.errors.length === 1); + assert(console.errors.length === 1); assert( console.errors[0].includes('invalid "typeCheckingMode"') && console.errors[0].includes('invalid_mode') @@ -854,16 +859,7 @@ describe(`config test'}`, () => { }); test('all typeCheckingMode values work in executionEnvironment', () => { - const modes = [ - 'off', - 'basic', - 'standard', - 'strict', - 'recommended', - 'all', - ] as const; - - for (const mode of modes) { + for (const mode of allTypeCheckingModes) { const cwd = UriEx.file(normalizePath(process.cwd())); const configOptions = new ConfigOptions(cwd); @@ -892,106 +888,6 @@ describe(`config test'}`, () => { } }); - test('recommended mode in executionEnvironment applies basedpyright-specific rules', () => { - const cwd = UriEx.file(normalizePath(process.cwd())); - const configOptions = new ConfigOptions(cwd); - - const json = { - typeCheckingMode: 'standard', - executionEnvironments: [ - { - root: 'src/recommended_folder', - typeCheckingMode: 'recommended', - }, - ], - }; - - 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 recommendedEnv = configOptions.executionEnvironments[0]; - - // Verify basedpyright-specific "recommended" settings are applied - assert.strictEqual(recommendedEnv.diagnosticRuleSet.deprecateTypingAliases, true); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportAny, 'warning'); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportExplicitAny, 'warning'); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportImportCycles, 'error'); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.strictListInference, true); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.failOnWarnings, true); - }); - - test('multiple executionEnvironments with different typeCheckingModes work independently', () => { - const cwd = UriEx.file(normalizePath(process.cwd())); - const configOptions = new ConfigOptions(cwd); - - const json = { - typeCheckingMode: 'standard', - executionEnvironments: [ - { - root: 'src/legacy', - typeCheckingMode: 'basic', - }, - { - root: 'src/new', - typeCheckingMode: 'recommended', - }, - { - root: 'src/strict_code', - typeCheckingMode: 'strict', - }, - { - root: 'src/other', - // No typeCheckingMode - should inherit global 'standard' - }, - ], - }; - - 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, 4); - - const basicEnv = configOptions.executionEnvironments[0]; - const recommendedEnv = configOptions.executionEnvironments[1]; - const strictEnv = configOptions.executionEnvironments[2]; - const standardEnv = configOptions.executionEnvironments[3]; - - // Verify basic environment has basic settings - assert.strictEqual(basicEnv.diagnosticRuleSet.strictListInference, false); - assert.strictEqual(basicEnv.diagnosticRuleSet.reportMissingTypeStubs, 'none'); - assert.strictEqual(basicEnv.diagnosticRuleSet.reportUnusedVariable, 'hint'); - assert.strictEqual(basicEnv.diagnosticRuleSet.reportAny, 'none'); - assert.strictEqual(basicEnv.diagnosticRuleSet.failOnWarnings, false); - - // Verify recommended environment has basedpyright-specific settings - assert.strictEqual(recommendedEnv.diagnosticRuleSet.deprecateTypingAliases, true); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportAny, 'warning'); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportExplicitAny, 'warning'); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.reportImportCycles, 'error'); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.strictListInference, true); - assert.strictEqual(recommendedEnv.diagnosticRuleSet.failOnWarnings, true); - - // Verify strict environment has strict settings - assert.strictEqual(strictEnv.diagnosticRuleSet.strictListInference, true); - assert.strictEqual(strictEnv.diagnosticRuleSet.reportMissingTypeStubs, 'error'); - assert.strictEqual(strictEnv.diagnosticRuleSet.reportUnusedVariable, 'error'); - // Strict mode does NOT enable basedpyright-specific rules like reportAny - assert.strictEqual(strictEnv.diagnosticRuleSet.reportAny, 'none'); - assert.strictEqual(strictEnv.diagnosticRuleSet.failOnWarnings, false); - - // Verify standard environment (inherited from global) has standard settings - assert.strictEqual(standardEnv.diagnosticRuleSet.strictListInference, false); - assert.strictEqual(standardEnv.diagnosticRuleSet.reportMissingTypeStubs, 'none'); - assert.strictEqual(standardEnv.diagnosticRuleSet.reportUnusedVariable, 'hint'); - assert.strictEqual(standardEnv.diagnosticRuleSet.reportAny, 'none'); - assert.strictEqual(standardEnv.diagnosticRuleSet.failOnWarnings, false); - }); }); function createAnalyzer(console?: ConsoleInterface) { From cf00bded9c62c64afdf9aea5dbfc73449fe42346 Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 10:59:14 +0200 Subject: [PATCH 10/15] Fix: Restore straight quotes instead of curly quotes in docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverted unintentional quote character change on line 317 that was introduced during merge conflict resolution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/configuration/config-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/config-files.md b/docs/configuration/config-files.md index d30e6b661c..c777081b26 100644 --- a/docs/configuration/config-files.md +++ b/docs/configuration/config-files.md @@ -314,7 +314,7 @@ 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. From ab295b8db72f00e56f2f7af866d33a517a11291e Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 11:07:58 +0200 Subject: [PATCH 11/15] Revert incorrect quote change - restore curly quotes to match main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit incorrectly changed curly quotes to straight quotes. The main branch uses curly quotes, so reverting to match. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/configuration/config-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/config-files.md b/docs/configuration/config-files.md index c777081b26..d30e6b661c 100644 --- a/docs/configuration/config-files.md +++ b/docs/configuration/config-files.md @@ -314,7 +314,7 @@ 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. From 7ba013c5aa3b19cfe2e99afc322653c72e3ef5ba Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Tue, 18 Nov 2025 11:09:48 +0200 Subject: [PATCH 12/15] Fix: Restore curly apostrophes to match main branch formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed straight apostrophe (') to curly apostrophe (') in 'file's' on line 325 to match the formatting style used in main branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/configuration/config-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/config-files.md b/docs/configuration/config-files.md index d30e6b661c..c267f1d013 100644 --- a/docs/configuration/config-files.md +++ b/docs/configuration/config-files.md @@ -322,7 +322,7 @@ The following settings can be specified for each execution environment. Each sou - **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. +- **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. From f6352d7ade451a111b418e5265c95d4f27583a95 Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Fri, 21 Nov 2025 20:31:15 +0200 Subject: [PATCH 13/15] refactor: Extract typeCheckingMode validation logic and address PR feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract validation logic into reusable validateTypeCheckingMode helper - Change parameter type from 'any' to 'unknown' for better type safety - Remove redundant tests (type validation and inheritance tests) - Fix error assertion to use strictEqual - Maintain original error message format for backward compatibility This addresses PR review feedback from DetachHead. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/common/configOptions.ts | 86 +++++++++++-------- .../pyright-internal/src/tests/config.test.ts | 51 +---------- 2 files changed, 50 insertions(+), 87 deletions(-) diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index 89310e7686..4e0c3cf2be 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -1562,50 +1562,63 @@ 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); } } /** - * Validates a typeCheckingMode string and returns the corresponding DiagnosticRuleSet. + * 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") - * @returns DiagnosticRuleSet if valid, undefined if not specified or invalid + * @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 getDiagnosticRuleSetFromString( - typeCheckingMode: any, + private static validateTypeCheckingMode( + typeCheckingMode: unknown, console_: ConsoleInterface, errorContext: string - ): DiagnosticRuleSet | undefined { + ): TypeCheckingMode | undefined { if (typeCheckingMode === undefined) { return undefined; } if (typeof typeCheckingMode !== 'string') { - console_.error(`${errorContext}: typeCheckingMode must be a string.`); + const prefix = errorContext ? `${errorContext}: ` : ''; + console_.error(`${prefix}typeCheckingMode must be a string.`); return undefined; } if ((allTypeCheckingModes as readonly string[]).includes(typeCheckingMode)) { - return ConfigOptions.getDiagnosticRuleSet(typeCheckingMode as TypeCheckingMode); - } else { - console_.error( - `${errorContext}: invalid "typeCheckingMode" value: "${typeCheckingMode}". Expected: ${userFacingOptionsList(allTypeCheckingModes)}` - ); - return undefined; + return typeCheckingMode as TypeCheckingMode; } + + const prefix = errorContext ? `${errorContext}: ` : ''; + 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; } // Initialize the structure from a JSON object. @@ -2067,14 +2080,16 @@ export class ConfigOptions { ): ExecutionEnvironment | undefined { try { // If typeCheckingMode is specified for this execution environment, - // use it to generate the base diagnostic rule set. Otherwise, use - // the config-level diagnostic rule set. - const baseDiagnosticRuleSet = - ConfigOptions.getDiagnosticRuleSetFromString( - envObj.typeCheckingMode, - console, - `Config executionEnvironments index ${index}` - ) ?? configDiagnosticRuleSet; + // 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(), @@ -2085,12 +2100,9 @@ export class ConfigOptions { configExtraPaths ); - // Store the typeCheckingMode if it was explicitly specified and valid. - if ( - typeof envObj.typeCheckingMode === 'string' && - (allTypeCheckingModes as readonly string[]).includes(envObj.typeCheckingMode) - ) { - newExecEnv.typeCheckingMode = envObj.typeCheckingMode as TypeCheckingMode; + // Store the validated typeCheckingMode if it was specified and valid. + if (validatedMode !== undefined) { + newExecEnv.typeCheckingMode = validatedMode; } // Validate the root. diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index aac8415434..fb690e6e49 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -802,62 +802,13 @@ describe(`config test'}`, () => { configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost()); configOptions.setupExecutionEnvironments(json, cwd, console); - assert(console.errors.length === 1); + assert.strictEqual(console.errors.length, 1); assert( console.errors[0].includes('invalid "typeCheckingMode"') && console.errors[0].includes('invalid_mode') ); }); - test('typeCheckingMode must be a string in executionEnvironment', () => { - const cwd = UriEx.file(normalizePath(process.cwd())); - const configOptions = new ConfigOptions(cwd); - - const json = { - executionEnvironments: [ - { - root: 'src/invalid', - typeCheckingMode: 123, - }, - ], - }; - - 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(console.errors.length === 1); - assert(console.errors[0].includes('typeCheckingMode must be a string')); - }); - - test('executionEnvironment without typeCheckingMode inherits global setting', () => { - const cwd = UriEx.file(normalizePath(process.cwd())); - const configOptions = new ConfigOptions(cwd); - - const json = { - typeCheckingMode: 'strict', - executionEnvironments: [ - { - root: 'src/inherit', - }, - ], - }; - - 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 inheritEnv = configOptions.executionEnvironments[0]; - - // Should have strict settings from global config - assert.strictEqual(inheritEnv.diagnosticRuleSet.strictListInference, true); - assert.strictEqual(inheritEnv.diagnosticRuleSet.reportMissingTypeStubs, 'error'); - }); - test('all typeCheckingMode values work in executionEnvironment', () => { for (const mode of allTypeCheckingModes) { const cwd = UriEx.file(normalizePath(process.cwd())); From 3e6f6dea1d494cd7d47536b3f566fed233d3b915 Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Fri, 21 Nov 2025 22:36:12 +0200 Subject: [PATCH 14/15] fix: Address linting issues - naming convention and member ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename validateTypeCheckingMode to _validateTypeCheckingMode (private static method naming) - Rename getDiagnosticRuleSetFromString to _getDiagnosticRuleSetFromString - Move private static methods after public methods to fix member ordering This fixes ESLint errors: - @typescript-eslint/naming-convention - @typescript-eslint/member-ordering 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/common/configOptions.ts | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index 4e0c3cf2be..9293066cd9 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -1562,65 +1562,12 @@ export class ConfigOptions { * @returns any errors that occurred */ initializeTypeCheckingModeFromString(typeCheckingMode: string | undefined, console_: ConsoleInterface) { - const validatedMode = ConfigOptions.validateTypeCheckingMode(typeCheckingMode, console_, ''); + const validatedMode = ConfigOptions._validateTypeCheckingMode(typeCheckingMode, console_, ''); if (validatedMode !== undefined) { this.initializeTypeCheckingMode(validatedMode); } } - /** - * 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; - } - - if (typeof typeCheckingMode !== 'string') { - const prefix = errorContext ? `${errorContext}: ` : ''; - console_.error(`${prefix}typeCheckingMode must be a string.`); - return undefined; - } - - if ((allTypeCheckingModes as readonly string[]).includes(typeCheckingMode)) { - return typeCheckingMode as TypeCheckingMode; - } - - const prefix = errorContext ? `${errorContext}: ` : ''; - 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; - } - // Initialize the structure from a JSON object. initializeFromJson(configObj: any, configDirUri: Uri, serviceProvider: ServiceProvider, host: Host) { this.initializedFromJson = true; @@ -2032,6 +1979,59 @@ 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; + } + + if (typeof typeCheckingMode !== 'string') { + const prefix = errorContext ? `${errorContext}: ` : ''; + console_.error(`${prefix}typeCheckingMode must be a string.`); + return undefined; + } + + if ((allTypeCheckingModes as readonly string[]).includes(typeCheckingMode)) { + return typeCheckingMode as TypeCheckingMode; + } + + const prefix = errorContext ? `${errorContext}: ` : ''; + 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'; } @@ -2082,7 +2082,7 @@ export class ConfigOptions { // 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( + const validatedMode = ConfigOptions._validateTypeCheckingMode( envObj.typeCheckingMode, console, `Config executionEnvironments index ${index}` From c604052c767e3a5f8e458f5aef281a862e4b908c Mon Sep 17 00:00:00 2001 From: Aviad Rozenhek Date: Sun, 30 Nov 2025 13:29:59 +0200 Subject: [PATCH 15/15] Update packages/pyright-internal/src/common/configOptions.ts Co-authored-by: DetachHead <57028336+DetachHead@users.noreply.github.com> --- packages/pyright-internal/src/common/configOptions.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index 9293066cd9..6e3dc257c4 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -1997,8 +1997,8 @@ export class ConfigOptions { return undefined; } + const prefix = errorContext ? `${errorContext}: ` : ''; if (typeof typeCheckingMode !== 'string') { - const prefix = errorContext ? `${errorContext}: ` : ''; console_.error(`${prefix}typeCheckingMode must be a string.`); return undefined; } @@ -2006,8 +2006,6 @@ export class ConfigOptions { if ((allTypeCheckingModes as readonly string[]).includes(typeCheckingMode)) { return typeCheckingMode as TypeCheckingMode; } - - const prefix = errorContext ? `${errorContext}: ` : ''; console_.error( `${prefix}invalid "typeCheckingMode" value: "${typeCheckingMode}". expected: ${userFacingOptionsList(allTypeCheckingModes)}` );