mirror of
https://github.com/microsoft/pyright.git
synced 2024-08-17 03:40:44 +03:00
Added support for pyproject.toml files.
This commit is contained in:
parent
d659009c72
commit
69a0c5594a
@ -2,6 +2,8 @@
|
||||
|
||||
Pyright offers flexible configuration options specified in a JSON-formatted text configuration. By default, the file is called “pyrightconfig.json” and is located within the root directory of your project. Multi-root workspaces (“Add Folder to Workspace…”) are supported, and each workspace root can have its own “pyrightconfig.json” file.
|
||||
|
||||
Pyright settings can also be specified in a `[tool.pyright]` section of a “pyproject.toml” file. A “pyrightconfig.json” file always takes precedent over “pyproject.toml” if both are present.
|
||||
|
||||
Relative paths specified within the config file are relative to the config file’s location. Paths with shell variables (including `~`) are not supported.
|
||||
|
||||
## Main Pyright Config Options
|
||||
@ -223,6 +225,32 @@ The following is an example of a pyright config file:
|
||||
}
|
||||
```
|
||||
|
||||
## Sample Pyproject.toml File
|
||||
```toml
|
||||
[tool.pyright]
|
||||
include = ["src"]
|
||||
exclude = ["**/node_modules",
|
||||
"**/__pycache__",
|
||||
"src/experimental",
|
||||
"src/typestubs"
|
||||
]
|
||||
ignore = ["src/oldstuff"]
|
||||
stubPath = "src/stubs"
|
||||
venv = "env367"
|
||||
|
||||
reportMissingImports = true
|
||||
reportMissingTypeStubs = false
|
||||
|
||||
pythonVersion = "3.6"
|
||||
pythonPlatform = "Linux"
|
||||
|
||||
executionEnvironments = [
|
||||
{ root = "src/web", pythonVersion = "3.5", pythonPlatform = "Windows", extraPaths = [ "src/service_libs" ] },
|
||||
{ root = "src/sdk", pythonVersion = "3.0", extraPaths = [ "src/backend" ] },
|
||||
{ root = "src/tests", extraPaths = ["src/tests/e2e", "src/sdk" ]},
|
||||
{ root = "src" }
|
||||
]
|
||||
```
|
||||
|
||||
## Diagnostic Rule Defaults
|
||||
|
||||
|
5
packages/pyright-internal/package-lock.json
generated
5
packages/pyright-internal/package-lock.json
generated
@ -437,6 +437,11 @@
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@iarna/toml": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
|
||||
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
|
||||
},
|
||||
"@istanbuljs/load-nyc-config": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"test:coverage": "jest --forceExit --reporters=jest-junit --reporters=default --coverage --coverageReporters=cobertura --coverageReporters=html --coverageReporters=json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iarna/toml": "2.2.5",
|
||||
"assert": "^2.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"chokidar": "^3.5.1",
|
||||
|
@ -8,6 +8,7 @@
|
||||
* Python files.
|
||||
*/
|
||||
|
||||
import * as TOML from '@iarna/toml';
|
||||
import * as JSONC from 'jsonc-parser';
|
||||
import {
|
||||
AbstractCancellationTokenSource,
|
||||
@ -63,6 +64,7 @@ import { findPythonSearchPaths } from './pythonPathUtils';
|
||||
import { TypeEvaluator } from './typeEvaluator';
|
||||
|
||||
export const configFileNames = ['pyrightconfig.json', 'mspythonconfig.json'];
|
||||
export const pyprojectTomlName = 'pyproject.toml';
|
||||
|
||||
// How long since the last user activity should we wait until running
|
||||
// the analyzer on any files that have not yet been analyzed?
|
||||
@ -430,6 +432,7 @@ export class AnalyzerService {
|
||||
private _getConfigOptions(commandLineOptions: CommandLineOptions): ConfigOptions {
|
||||
let projectRoot = commandLineOptions.executionRoot;
|
||||
let configFilePath: string | undefined;
|
||||
let pyprojectFilePath: string | undefined;
|
||||
|
||||
if (commandLineOptions.configFilePath) {
|
||||
// If the config file path was specified, determine whether it's
|
||||
@ -455,13 +458,13 @@ export class AnalyzerService {
|
||||
}
|
||||
} else if (projectRoot) {
|
||||
// In a project-based IDE like VS Code, we should assume that the
|
||||
// project root directory contains the config file. If pyright is
|
||||
// being executed from the command line, the working directory
|
||||
// may be deep within a project, and we need to walk up the directory
|
||||
// hierarchy to find the project root.
|
||||
if (commandLineOptions.fromVsCodeExtension) {
|
||||
configFilePath = this._findConfigFile(projectRoot);
|
||||
} else {
|
||||
// project root directory contains the config file.
|
||||
configFilePath = this._findConfigFile(projectRoot);
|
||||
|
||||
// If pyright is being executed from the command line, the working
|
||||
// directory may be deep within a project, and we need to walk up the
|
||||
// directory hierarchy to find the project root.
|
||||
if (!configFilePath && !commandLineOptions.fromVsCodeExtension) {
|
||||
configFilePath = this._findConfigFileHereOrUp(projectRoot);
|
||||
}
|
||||
|
||||
@ -473,6 +476,22 @@ export class AnalyzerService {
|
||||
}
|
||||
}
|
||||
|
||||
if (!configFilePath) {
|
||||
// See if we can find a pyproject.toml file in this directory.
|
||||
pyprojectFilePath = this._findPyprojectTomlFile(projectRoot);
|
||||
|
||||
if (!pyprojectFilePath && !commandLineOptions.fromVsCodeExtension) {
|
||||
pyprojectFilePath = this._findPyprojectTomlFileHereOrUp(projectRoot);
|
||||
}
|
||||
|
||||
if (pyprojectFilePath) {
|
||||
projectRoot = getDirectoryPath(pyprojectFilePath);
|
||||
this._console.info(`pyproject.toml file found at ${projectRoot}.`);
|
||||
} else {
|
||||
this._console.info(`No pyproject.toml file found.`);
|
||||
}
|
||||
}
|
||||
|
||||
const configOptions = new ConfigOptions(projectRoot, this._typeCheckingMode);
|
||||
const defaultExcludes = ['**/node_modules', '**/__pycache__', '.git'];
|
||||
|
||||
@ -504,41 +523,46 @@ export class AnalyzerService {
|
||||
}
|
||||
}
|
||||
|
||||
this._configFilePath = configFilePath;
|
||||
this._configFilePath = configFilePath || pyprojectFilePath;
|
||||
|
||||
// If we found a config file, parse it to compute the effective options.
|
||||
let configJsonObj: object | undefined;
|
||||
if (configFilePath) {
|
||||
this._console.info(`Loading configuration file at ${configFilePath}`);
|
||||
const configJsonObj = this._parseConfigFile(configFilePath);
|
||||
if (configJsonObj) {
|
||||
configOptions.initializeFromJson(
|
||||
configJsonObj,
|
||||
this._typeCheckingMode,
|
||||
this._console,
|
||||
commandLineOptions.diagnosticSeverityOverrides,
|
||||
commandLineOptions.pythonPath,
|
||||
commandLineOptions.fileSpecs.length > 0
|
||||
);
|
||||
configJsonObj = this._parseJsonConfigFile(configFilePath);
|
||||
} else if (pyprojectFilePath) {
|
||||
this._console.info(`Loading pyproject.toml file at ${pyprojectFilePath}`);
|
||||
configJsonObj = this._parsePyprojectTomlFile(pyprojectFilePath);
|
||||
}
|
||||
|
||||
const configFileDir = getDirectoryPath(configFilePath);
|
||||
if (configJsonObj) {
|
||||
configOptions.initializeFromJson(
|
||||
configJsonObj,
|
||||
this._typeCheckingMode,
|
||||
this._console,
|
||||
commandLineOptions.diagnosticSeverityOverrides,
|
||||
commandLineOptions.pythonPath,
|
||||
commandLineOptions.fileSpecs.length > 0
|
||||
);
|
||||
|
||||
// If no include paths were provided, assume that all files within
|
||||
// the project should be included.
|
||||
if (configOptions.include.length === 0) {
|
||||
this._console.info(`No include entries specified; assuming ${configFileDir}`);
|
||||
configOptions.include.push(getFileSpec(configFileDir, '.'));
|
||||
}
|
||||
const configFileDir = getDirectoryPath(this._configFilePath!);
|
||||
|
||||
// If there was no explicit set of excludes, add a few common ones to avoid long scan times.
|
||||
if (configOptions.exclude.length === 0) {
|
||||
defaultExcludes.forEach((exclude) => {
|
||||
this._console.info(`Auto-excluding ${exclude}`);
|
||||
configOptions.exclude.push(getFileSpec(configFileDir, exclude));
|
||||
});
|
||||
// If no include paths were provided, assume that all files within
|
||||
// the project should be included.
|
||||
if (configOptions.include.length === 0) {
|
||||
this._console.info(`No include entries specified; assuming ${configFileDir}`);
|
||||
configOptions.include.push(getFileSpec(configFileDir, '.'));
|
||||
}
|
||||
|
||||
if (configOptions.autoExcludeVenv === undefined) {
|
||||
configOptions.autoExcludeVenv = true;
|
||||
}
|
||||
// If there was no explicit set of excludes, add a few common ones to avoid long scan times.
|
||||
if (configOptions.exclude.length === 0) {
|
||||
defaultExcludes.forEach((exclude) => {
|
||||
this._console.info(`Auto-excluding ${exclude}`);
|
||||
configOptions.exclude.push(getFileSpec(configFileDir, exclude));
|
||||
});
|
||||
|
||||
if (configOptions.autoExcludeVenv === undefined) {
|
||||
configOptions.autoExcludeVenv = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -803,27 +827,63 @@ export class AnalyzerService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _parseConfigFile(configPath: string): any | undefined {
|
||||
let configContents = '';
|
||||
private _findPyprojectTomlFileHereOrUp(searchPath: string): string | undefined {
|
||||
return forEachAncestorDirectory(searchPath, (ancestor) => this._findPyprojectTomlFile(ancestor));
|
||||
}
|
||||
|
||||
private _findPyprojectTomlFile(searchPath: string) {
|
||||
const fileName = combinePaths(searchPath, pyprojectTomlName);
|
||||
if (this._fs.existsSync(fileName)) {
|
||||
return fileName;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _parseJsonConfigFile(configPath: string): object | undefined {
|
||||
return this._attemptParseFile(configPath, (fileContents) => {
|
||||
return JSONC.parse(fileContents);
|
||||
});
|
||||
}
|
||||
|
||||
private _parsePyprojectTomlFile(pyprojectPath: string): object | undefined {
|
||||
return this._attemptParseFile(pyprojectPath, (fileContents, attemptCount) => {
|
||||
try {
|
||||
const configObj = TOML.parse(fileContents);
|
||||
if (configObj && configObj.tool && (configObj.tool as TOML.JsonMap).pyright) {
|
||||
return (configObj.tool as TOML.JsonMap).pyright as object;
|
||||
}
|
||||
} catch (e) {
|
||||
this._console.error(`Pyproject file parse attempt ${attemptCount} error: ${JSON.stringify(e)}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
this._console.error(`Pyproject file "${pyprojectPath}" is missing "[tool.pyright] section.`);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private _attemptParseFile(
|
||||
filePath: string,
|
||||
parseCallback: (contents: string, attempt: number) => object | undefined
|
||||
): object | undefined {
|
||||
let fileContents = '';
|
||||
let parseAttemptCount = 0;
|
||||
|
||||
while (true) {
|
||||
// Attempt to read the config file contents.
|
||||
// Attempt to read the file contents.
|
||||
try {
|
||||
configContents = this._fs.readFileSync(configPath, 'utf8');
|
||||
fileContents = this._fs.readFileSync(filePath, 'utf8');
|
||||
} catch {
|
||||
this._console.error(`Config file "${configPath}" could not be read.`);
|
||||
this._console.error(`Config file "${filePath}" could not be read.`);
|
||||
this._reportConfigParseError();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Attempt to parse the config file.
|
||||
let configObj: any;
|
||||
// Attempt to parse the file.
|
||||
let parseFailed = false;
|
||||
try {
|
||||
configObj = JSONC.parse(configContents);
|
||||
return configObj;
|
||||
} catch {
|
||||
return parseCallback(fileContents, parseAttemptCount + 1);
|
||||
} catch (e) {
|
||||
parseFailed = true;
|
||||
}
|
||||
|
||||
@ -831,12 +891,11 @@ export class AnalyzerService {
|
||||
break;
|
||||
}
|
||||
|
||||
// If we attempt to read the config file immediately after it
|
||||
// was saved, it may have been partially written when we read it,
|
||||
// resulting in parse errors. We'll give it a little more time and
|
||||
// try again.
|
||||
// If we attempt to read the file immediately after it was saved, it
|
||||
// may have been partially written when we read it, resulting in parse
|
||||
// errors. We'll give it a little more time and try again.
|
||||
if (parseAttemptCount++ >= 5) {
|
||||
this._console.error(`Config file "${configPath}" could not be parsed. Verify that JSON is correct.`);
|
||||
this._console.error(`Config file "${filePath}" could not be parsed. Verify that format is correct.`);
|
||||
this._reportConfigParseError();
|
||||
return undefined;
|
||||
}
|
||||
|
5
packages/vscode-pyright/package-lock.json
generated
5
packages/vscode-pyright/package-lock.json
generated
@ -10,6 +10,11 @@
|
||||
"integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==",
|
||||
"dev": true
|
||||
},
|
||||
"@iarna/toml": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
|
||||
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
|
||||
|
@ -750,6 +750,7 @@
|
||||
"webpack-dev": "npm run clean && webpack --mode development --watch --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iarna/toml": "2.2.5",
|
||||
"vscode-jsonrpc": "6.0.0",
|
||||
"vscode-languageclient": "7.0.0",
|
||||
"vscode-languageserver": "7.0.0",
|
||||
|
Loading…
Reference in New Issue
Block a user