mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 16:06:39 +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 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.
|
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
|
## 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
|
## 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"
|
"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": {
|
"@istanbuljs/load-nyc-config": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
"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"
|
"test:coverage": "jest --forceExit --reporters=jest-junit --reporters=default --coverage --coverageReporters=cobertura --coverageReporters=html --coverageReporters=json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iarna/toml": "2.2.5",
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
* Python files.
|
* Python files.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as TOML from '@iarna/toml';
|
||||||
import * as JSONC from 'jsonc-parser';
|
import * as JSONC from 'jsonc-parser';
|
||||||
import {
|
import {
|
||||||
AbstractCancellationTokenSource,
|
AbstractCancellationTokenSource,
|
||||||
@ -63,6 +64,7 @@ import { findPythonSearchPaths } from './pythonPathUtils';
|
|||||||
import { TypeEvaluator } from './typeEvaluator';
|
import { TypeEvaluator } from './typeEvaluator';
|
||||||
|
|
||||||
export const configFileNames = ['pyrightconfig.json', 'mspythonconfig.json'];
|
export const configFileNames = ['pyrightconfig.json', 'mspythonconfig.json'];
|
||||||
|
export const pyprojectTomlName = 'pyproject.toml';
|
||||||
|
|
||||||
// How long since the last user activity should we wait until running
|
// How long since the last user activity should we wait until running
|
||||||
// the analyzer on any files that have not yet been analyzed?
|
// the analyzer on any files that have not yet been analyzed?
|
||||||
@ -430,6 +432,7 @@ export class AnalyzerService {
|
|||||||
private _getConfigOptions(commandLineOptions: CommandLineOptions): ConfigOptions {
|
private _getConfigOptions(commandLineOptions: CommandLineOptions): ConfigOptions {
|
||||||
let projectRoot = commandLineOptions.executionRoot;
|
let projectRoot = commandLineOptions.executionRoot;
|
||||||
let configFilePath: string | undefined;
|
let configFilePath: string | undefined;
|
||||||
|
let pyprojectFilePath: string | undefined;
|
||||||
|
|
||||||
if (commandLineOptions.configFilePath) {
|
if (commandLineOptions.configFilePath) {
|
||||||
// If the config file path was specified, determine whether it's
|
// If the config file path was specified, determine whether it's
|
||||||
@ -455,13 +458,13 @@ export class AnalyzerService {
|
|||||||
}
|
}
|
||||||
} else if (projectRoot) {
|
} else if (projectRoot) {
|
||||||
// In a project-based IDE like VS Code, we should assume that the
|
// In a project-based IDE like VS Code, we should assume that the
|
||||||
// project root directory contains the config file. If pyright is
|
// project root directory contains the config file.
|
||||||
// being executed from the command line, the working directory
|
configFilePath = this._findConfigFile(projectRoot);
|
||||||
// may be deep within a project, and we need to walk up the directory
|
|
||||||
// hierarchy to find the project root.
|
// If pyright is being executed from the command line, the working
|
||||||
if (commandLineOptions.fromVsCodeExtension) {
|
// directory may be deep within a project, and we need to walk up the
|
||||||
configFilePath = this._findConfigFile(projectRoot);
|
// directory hierarchy to find the project root.
|
||||||
} else {
|
if (!configFilePath && !commandLineOptions.fromVsCodeExtension) {
|
||||||
configFilePath = this._findConfigFileHereOrUp(projectRoot);
|
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 configOptions = new ConfigOptions(projectRoot, this._typeCheckingMode);
|
||||||
const defaultExcludes = ['**/node_modules', '**/__pycache__', '.git'];
|
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.
|
// If we found a config file, parse it to compute the effective options.
|
||||||
|
let configJsonObj: object | undefined;
|
||||||
if (configFilePath) {
|
if (configFilePath) {
|
||||||
this._console.info(`Loading configuration file at ${configFilePath}`);
|
this._console.info(`Loading configuration file at ${configFilePath}`);
|
||||||
const configJsonObj = this._parseConfigFile(configFilePath);
|
configJsonObj = this._parseJsonConfigFile(configFilePath);
|
||||||
if (configJsonObj) {
|
} else if (pyprojectFilePath) {
|
||||||
configOptions.initializeFromJson(
|
this._console.info(`Loading pyproject.toml file at ${pyprojectFilePath}`);
|
||||||
configJsonObj,
|
configJsonObj = this._parsePyprojectTomlFile(pyprojectFilePath);
|
||||||
this._typeCheckingMode,
|
}
|
||||||
this._console,
|
|
||||||
commandLineOptions.diagnosticSeverityOverrides,
|
|
||||||
commandLineOptions.pythonPath,
|
|
||||||
commandLineOptions.fileSpecs.length > 0
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
const configFileDir = getDirectoryPath(this._configFilePath!);
|
||||||
// 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 there was no explicit set of excludes, add a few common ones to avoid long scan times.
|
// If no include paths were provided, assume that all files within
|
||||||
if (configOptions.exclude.length === 0) {
|
// the project should be included.
|
||||||
defaultExcludes.forEach((exclude) => {
|
if (configOptions.include.length === 0) {
|
||||||
this._console.info(`Auto-excluding ${exclude}`);
|
this._console.info(`No include entries specified; assuming ${configFileDir}`);
|
||||||
configOptions.exclude.push(getFileSpec(configFileDir, exclude));
|
configOptions.include.push(getFileSpec(configFileDir, '.'));
|
||||||
});
|
}
|
||||||
|
|
||||||
if (configOptions.autoExcludeVenv === undefined) {
|
// If there was no explicit set of excludes, add a few common ones to avoid long scan times.
|
||||||
configOptions.autoExcludeVenv = true;
|
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 {
|
} else {
|
||||||
@ -803,27 +827,63 @@ export class AnalyzerService {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _parseConfigFile(configPath: string): any | undefined {
|
private _findPyprojectTomlFileHereOrUp(searchPath: string): string | undefined {
|
||||||
let configContents = '';
|
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;
|
let parseAttemptCount = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Attempt to read the config file contents.
|
// Attempt to read the file contents.
|
||||||
try {
|
try {
|
||||||
configContents = this._fs.readFileSync(configPath, 'utf8');
|
fileContents = this._fs.readFileSync(filePath, 'utf8');
|
||||||
} catch {
|
} catch {
|
||||||
this._console.error(`Config file "${configPath}" could not be read.`);
|
this._console.error(`Config file "${filePath}" could not be read.`);
|
||||||
this._reportConfigParseError();
|
this._reportConfigParseError();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to parse the config file.
|
// Attempt to parse the file.
|
||||||
let configObj: any;
|
|
||||||
let parseFailed = false;
|
let parseFailed = false;
|
||||||
try {
|
try {
|
||||||
configObj = JSONC.parse(configContents);
|
return parseCallback(fileContents, parseAttemptCount + 1);
|
||||||
return configObj;
|
} catch (e) {
|
||||||
} catch {
|
|
||||||
parseFailed = true;
|
parseFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,12 +891,11 @@ export class AnalyzerService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we attempt to read the config file immediately after it
|
// If we attempt to read the file immediately after it was saved, it
|
||||||
// was saved, it may have been partially written when we read it,
|
// may have been partially written when we read it, resulting in parse
|
||||||
// resulting in parse errors. We'll give it a little more time and
|
// errors. We'll give it a little more time and try again.
|
||||||
// try again.
|
|
||||||
if (parseAttemptCount++ >= 5) {
|
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();
|
this._reportConfigParseError();
|
||||||
return undefined;
|
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==",
|
"integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==",
|
||||||
"dev": true
|
"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": {
|
"@nodelib/fs.scandir": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
|
"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"
|
"webpack-dev": "npm run clean && webpack --mode development --watch --progress"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iarna/toml": "2.2.5",
|
||||||
"vscode-jsonrpc": "6.0.0",
|
"vscode-jsonrpc": "6.0.0",
|
||||||
"vscode-languageclient": "7.0.0",
|
"vscode-languageclient": "7.0.0",
|
||||||
"vscode-languageserver": "7.0.0",
|
"vscode-languageserver": "7.0.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user