Skip to content

Commit 8832b38

Browse files
authored
Fallback to root pyproject.toml if project pyproject.toml doesnt contain [tool.pyright] (#11323)
* Fallback to root pyproject.toml if project pyproject.toml doesnt contain [tool.pyright] * revert package change * another revert * more reverts, wheee * I always forget to add new files * recurse harder
1 parent f6bdd93 commit 8832b38

File tree

7 files changed

+75
-4
lines changed

7 files changed

+75
-4
lines changed

packages/pyright-internal/src/analyzer/service.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,10 @@ export class AnalyzerService {
843843
const configOptions = new ConfigOptions(projectRoot);
844844

845845
// If we found a config file, load it and apply its settings.
846-
const configs = this._getExtendedConfigurations(configFilePath ?? pyprojectFilePath);
846+
const configs = this._getExtendedConfigurations(
847+
configFilePath ?? pyprojectFilePath,
848+
!configFilePath ? pyprojectFilePath?.getDirectory() : undefined
849+
);
847850
if (configs && configs.length > 0) {
848851
// With a pyrightconfig.json set, we want the typeCheckingMode to always be standard
849852
// as that's what the Pyright CLI will expect. Command line options (if not a language server) and
@@ -861,8 +864,8 @@ export class AnalyzerService {
861864
);
862865
}
863866

864-
// Set the configFileSource since we have a config file.
865-
configOptions.configFileSource = configFilePath ?? pyprojectFilePath;
867+
// Set the configFileSource since we have a config file (set by `_getExtendedConfigurations`)
868+
configOptions.configFileSource = this._primaryConfigFileUri;
866869

867870
// When not in language server mode, command line options override config file options.
868871
if (!commandLineOptions.fromLanguageServer) {
@@ -1202,7 +1205,12 @@ export class AnalyzerService {
12021205

12031206
// Loads the config JSON object from the specified config file along with any
12041207
// chained config files specified in the "extends" property (recursively).
1205-
private _getExtendedConfigurations(primaryConfigFileUri: Uri | undefined): ConfigFileContents[] | undefined {
1208+
// If pyprojectSearchDir is provided and the primary file is a pyproject.toml with no
1209+
// [tool.pyright] section, falls back to searching ancestor dirs from pyprojectSearchDir.
1210+
private _getExtendedConfigurations(
1211+
primaryConfigFileUri: Uri | undefined,
1212+
pyprojectSearchDir?: Uri
1213+
): ConfigFileContents[] | undefined {
12061214
this._primaryConfigFileUri = primaryConfigFileUri;
12071215
this._extendedConfigFileUris = [];
12081216

@@ -1252,6 +1260,24 @@ export class AnalyzerService {
12521260
curConfigFileUri = baseConfigUri;
12531261
}
12541262

1263+
// If a pyproject.toml was found but had no [tool.pyright] section, fall back to
1264+
// searching ancestor directories as if that pyproject.toml didn't exist.
1265+
if (configJsonObjs.length === 0 && pyprojectSearchDir) {
1266+
const parentDir = pyprojectSearchDir.getDirectory();
1267+
if (!parentDir.equals(pyprojectSearchDir)) {
1268+
const fallback =
1269+
findConfigFileHereOrUp(this.fs, parentDir) ?? findPyprojectTomlFileHereOrUp(this.fs, parentDir);
1270+
if (fallback) {
1271+
return this._getExtendedConfigurations(
1272+
fallback,
1273+
// Provide the next pyprojectSearchDir, so we can continue
1274+
// searching upward
1275+
fallback.lastExtension.endsWith('.toml') ? fallback.getDirectory() : undefined
1276+
);
1277+
}
1278+
}
1279+
}
1280+
12551281
return configJsonObjs;
12561282
}
12571283

packages/pyright-internal/src/tests/config.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,41 @@ describe(`config test'}`, () => {
606606
assert.deepStrictEqual(config.defaultPythonVersion, pythonVersion3_13);
607607
});
608608

609+
test('PyprojectTomlWithoutPyrightSectionFallsBackToAncestorConfig', () => {
610+
const cwd = normalizePath(process.cwd());
611+
const service = createAnalyzer();
612+
const commandLineOptions = new CommandLineOptions(cwd, /* fromLanguageServer */ false);
613+
commandLineOptions.configFilePath = 'src/tests/samples/project_with_empty_pyproject_toml/subproject';
614+
615+
const configOptions = service.test_getConfigOptions(commandLineOptions);
616+
617+
// Should fall back to the ancestor pyproject.toml which sets pythonVersion to 3.9.
618+
assert.strictEqual(configOptions.defaultPythonVersion!.toString(), pythonVersion3_9.toString());
619+
620+
// configFileSource should point to the ancestor pyproject.toml, not the subproject one.
621+
assert.ok(
622+
configOptions.configFileSource?.toString().endsWith('project_with_empty_pyproject_toml/pyproject.toml')
623+
);
624+
});
625+
626+
test('PyprojectTomlWithoutPyrightSectionFallsBackThroughMultipleAncestors', () => {
627+
const cwd = normalizePath(process.cwd());
628+
const service = createAnalyzer();
629+
const commandLineOptions = new CommandLineOptions(cwd, /* fromLanguageServer */ false);
630+
commandLineOptions.configFilePath =
631+
'src/tests/samples/project_with_nested_empty_pyproject_toml/middle/subproject';
632+
633+
const configOptions = service.test_getConfigOptions(commandLineOptions);
634+
635+
// Should skip the empty pyproject.tomls and fall back to the root's [tool.pyright].
636+
assert.strictEqual(configOptions.defaultPythonVersion!.toString(), pythonVersion3_9.toString());
637+
assert.ok(
638+
configOptions.configFileSource
639+
?.toString()
640+
.endsWith('project_with_nested_empty_pyproject_toml/pyproject.toml')
641+
);
642+
});
643+
609644
test('Diagnostic rule overrides are preserved when positional args override include', () => {
610645
const cwd = normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_with_diag_overrides'));
611646
const service = createAnalyzer();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[tool.pyright]
2+
pythonVersion = "3.9"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[tool.other-tool]
2+
setting = "value"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[tool.other-tool]
2+
setting = "value"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[tool.other-tool]
2+
setting = "value"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[tool.pyright]
2+
pythonVersion = "3.9"

0 commit comments

Comments
 (0)