mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Fix windows bug, add PEP 604 printing, fix unused variable action, add stubPath and diagnosticMode (#673)
This commit is contained in:
parent
37c3abf734
commit
1d56d6765f
@ -81,6 +81,25 @@
|
||||
"description": "Automatically add common search paths like 'src'?",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.analysis.stubPath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Path to directory containing custom type stub files.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.analysis.diagnosticMode": {
|
||||
"type": "string",
|
||||
"default": "openFilesOnly",
|
||||
"enum": [
|
||||
"openFilesOnly",
|
||||
"workspace"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Analyzes and reports errors on only open files.",
|
||||
"Analyzes and reports errors on all files in the workspace."
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"python.pythonPath": {
|
||||
"type": "string",
|
||||
"default": "python",
|
||||
@ -105,12 +124,6 @@
|
||||
"description": "Disables the “Organize Imports” command.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"pyright.openFilesOnly": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Report errors only for currently-open files.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"pyright.useLibraryCodeForTypes": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
@ -82,8 +82,8 @@
|
||||
"default": "",
|
||||
"pattern": "^(.*)$"
|
||||
},
|
||||
"typingsPath": {
|
||||
"$id": "#/properties/typingsPath",
|
||||
"stubPath": {
|
||||
"$id": "#/properties/stubPath",
|
||||
"type": "string",
|
||||
"title": "Path to directory containing custom type stub files",
|
||||
"default": "",
|
||||
|
@ -16,7 +16,7 @@ Relative paths specified within the config file are relative to the config file
|
||||
|
||||
**typeshedPath** [path, optional]: Path to a directory that contains typeshed type stub files. Pyright ships with an internal copy of some typeshed type stubs (those that cover the Python stdlib packages). If you want to use a full copy of the typeshed type stubs (including those for third-party packages), you can clone the [typeshed github repo](https://github.com/python/typeshed) to a local directory and reference the location with this path.
|
||||
|
||||
**typingsPath** [path, optional]: Path to a directory that contains custom type stubs. Each package's type stub file(s) are expected to be in its own subdirectory. The default value of this setting is "./typings".
|
||||
**stubPath** [path, optional]: Path to a directory that contains custom type stubs. Each package's type stub file(s) are expected to be in its own subdirectory. The default value of this setting is "./typings". (typingsPath is now deprecated)
|
||||
|
||||
**venvPath** [path, optional]: Path to a directory containing one or more subdirectories, each of which contains a virtual environment. Each execution environment (see below for details) can refer to a different virtual environment. When used in conjunction with a **venv** setting (see below), pyright will search for imports in the virtual environment’s site-packages directory rather than the paths specified in PYTHONPATH.
|
||||
|
||||
@ -165,7 +165,7 @@ The following is an example of a pyright config file:
|
||||
"src/oldstuff"
|
||||
],
|
||||
|
||||
"typingsPath": "src/typestubs",
|
||||
"stubPath": "src/typestubs",
|
||||
"venvPath": "/home/foo/.venvs",
|
||||
|
||||
"reportTypeshedErrors": false,
|
||||
|
@ -6,8 +6,6 @@ The Pyright VS Code extension honors the following settings.
|
||||
|
||||
**pyright.disableOrganizeImports** [boolean]: Disables the “Organize Imports” command. This is useful if you are using another extension that provides similar functionality and you don’t want the two extensions to fight each other.
|
||||
|
||||
**pyright.openFilesOnly** [boolean]: Determines whether pyright analyzes (and reports errors for) all files in the workspace, as indicated by the config file. If this option is set to true, pyright analyzes only open files.
|
||||
|
||||
**pyright.typeCheckingMode** ["off", "basic", "strict"]: Determines the default type-checking level used by pyright. This can be overridden in the configuration file.
|
||||
|
||||
**pyright.useLibraryCodeForTypes** [boolean]: Determines whether pyright reads, parses and analyzes library code to extract type information in the absence of type stub files. This can add significant overhead and may result in poor-quality type information. The default value for this option is false.
|
||||
@ -22,4 +20,7 @@ The Pyright VS Code extension honors the following settings.
|
||||
|
||||
**python.venvPath** [path]: Path to folder with subdirectories that contain virtual environments.
|
||||
|
||||
**python.analysis.stubPath** [path]: Path to directory containing custom type stub files.
|
||||
|
||||
**python.analysis.diagnosticMode** ["openFilesOnly", "workspace"]: Determines whether pyright analyzes (and reports errors for) all files in the workspace, as indicated by the config file. If this option is set to "openFilesOnly", pyright analyzes only open files.
|
||||
|
||||
|
@ -19,7 +19,7 @@ If you’re serious about static type checking for your Python source base, it
|
||||
## Generating Type Stubs
|
||||
If you use only a few classes, methods or functions within a library, writing a type stub file by hand is feasible. For large libraries, this can become tedious and error-prone. Pyright can generate “draft” versions of type stub files for you.
|
||||
|
||||
To generate a type stub file from within VS Code, enable the reportMissingTypeStubs” setting in your pyrightconfig.json file or by adding a comment `# pyright: reportMissingTypeStubs=true` to individual source files. Make sure you have the target library installed in the python environment that pyright is configured to use for import resolution. Optionally specify a “typingsPath” in your pyrightconfig.json file. This is where pyright will generate your type stub files. By default, the typingsPath is set to "./typings".
|
||||
To generate a type stub file from within VS Code, enable the reportMissingTypeStubs” setting in your pyrightconfig.json file or by adding a comment `# pyright: reportMissingTypeStubs=true` to individual source files. Make sure you have the target library installed in the python environment that pyright is configured to use for import resolution. Optionally specify a “stubPath” in your pyrightconfig.json file. This is where pyright will generate your type stub files. By default, the stubPath is set to "./typings".
|
||||
|
||||
### Generating Type Stubs in VS Code
|
||||
If “reportMissingTypeStubs” is enabled, pyright will highlight any imports that have no type stub. Hover over the error message, and you will see a “Quick Fix” link. Clicking on this link will reveal a popup menu item titled “Create Type Stub For XXX”. The example below shows a missing typestub for the `django` library.
|
||||
|
@ -140,15 +140,15 @@ export class BackgroundAnalysisProgram {
|
||||
async writeTypeStub(
|
||||
targetImportPath: string,
|
||||
targetIsSingleFile: boolean,
|
||||
typingsPath: string,
|
||||
stubPath: string,
|
||||
token: CancellationToken
|
||||
): Promise<any> {
|
||||
if (this._backgroundAnalysis) {
|
||||
return this._backgroundAnalysis.writeTypeStub(targetImportPath, targetIsSingleFile, typingsPath, token);
|
||||
return this._backgroundAnalysis.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
|
||||
}
|
||||
|
||||
analyzeProgram(this._program, undefined, this._configOptions, this._onAnalysisCompletion, this._console, token);
|
||||
return this._program.writeTypeStub(targetImportPath, targetIsSingleFile, typingsPath, token);
|
||||
return this._program.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
|
||||
}
|
||||
|
||||
invalidateAndForceReanalysis() {
|
||||
|
@ -12,6 +12,7 @@
|
||||
* cannot (or should not be) performed lazily.
|
||||
*/
|
||||
|
||||
import { Commands } from '../commands/commands';
|
||||
import { DiagnosticLevel } from '../common/configOptions';
|
||||
import { assert } from '../common/debug';
|
||||
import { Diagnostic, DiagnosticAddendum } from '../common/diagnostic';
|
||||
@ -963,7 +964,8 @@ export class Checker extends ParseTreeWalker {
|
||||
TextRange.extend(textRange, nameParts[nameParts.length - 1]);
|
||||
this._fileInfo.diagnosticSink.addUnusedCodeWithTextRange(
|
||||
`"${multipartName}" is not accessed`,
|
||||
textRange
|
||||
textRange,
|
||||
{ action: Commands.unusedImport }
|
||||
);
|
||||
|
||||
this._evaluator.addDiagnosticForTextRange(
|
||||
@ -1033,7 +1035,12 @@ export class Checker extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
if (nameNode && rule !== undefined && message) {
|
||||
this._fileInfo.diagnosticSink.addUnusedCodeWithTextRange(`"${nameNode.value}" is not accessed`, nameNode);
|
||||
const action = rule === DiagnosticRule.reportUnusedImport ? { action: Commands.unusedImport } : undefined;
|
||||
this._fileInfo.diagnosticSink.addUnusedCodeWithTextRange(
|
||||
`"${nameNode.value}" is not accessed`,
|
||||
nameNode,
|
||||
action
|
||||
);
|
||||
this._evaluator.addDiagnostic(diagnosticLevel, rule, message, nameNode);
|
||||
}
|
||||
}
|
||||
|
@ -193,11 +193,11 @@ export class ImportResolver {
|
||||
}
|
||||
|
||||
if (allowPyi) {
|
||||
// Check for a typings file.
|
||||
if (this._configOptions.typingsPath) {
|
||||
importFailureInfo.push(`Looking in typingsPath '${this._configOptions.typingsPath}'`);
|
||||
// Check for a stub file.
|
||||
if (this._configOptions.stubPath) {
|
||||
importFailureInfo.push(`Looking in stubPath '${this._configOptions.stubPath}'`);
|
||||
const typingsImport = this.resolveAbsoluteImport(
|
||||
this._configOptions.typingsPath,
|
||||
this._configOptions.stubPath,
|
||||
moduleDescriptor,
|
||||
importName,
|
||||
importFailureInfo
|
||||
@ -327,9 +327,9 @@ export class ImportResolver {
|
||||
}
|
||||
|
||||
// Check for a typings file.
|
||||
if (this._configOptions.typingsPath) {
|
||||
if (this._configOptions.stubPath) {
|
||||
this._getCompletionSuggestionsAbsolute(
|
||||
this._configOptions.typingsPath,
|
||||
this._configOptions.stubPath,
|
||||
moduleDescriptor,
|
||||
suggestions,
|
||||
similarityLimit
|
||||
@ -383,8 +383,8 @@ export class ImportResolver {
|
||||
}
|
||||
|
||||
// Check for a typings file.
|
||||
if (this._configOptions.typingsPath) {
|
||||
const candidateModuleName = this._getModuleNameFromPath(this._configOptions.typingsPath, filePath);
|
||||
if (this._configOptions.stubPath) {
|
||||
const candidateModuleName = this._getModuleNameFromPath(this._configOptions.stubPath, filePath);
|
||||
|
||||
// Does this candidate look better than the previous best module name?
|
||||
// We'll always try to use the shortest version.
|
||||
|
@ -8,6 +8,9 @@
|
||||
* import statements in a python source file.
|
||||
*/
|
||||
|
||||
import { CancellationToken } from 'vscode-languageserver';
|
||||
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { TextEditAction } from '../common/editAction';
|
||||
import { convertOffsetToPosition } from '../common/positionUtils';
|
||||
import { Position } from '../common/textRange';
|
||||
@ -378,8 +381,10 @@ function _formatModuleName(node: ModuleNameNode): string {
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
export function getContainingImportStatement(node: ParseNode | undefined) {
|
||||
export function getContainingImportStatement(node: ParseNode | undefined, token: CancellationToken) {
|
||||
while (node) {
|
||||
throwIfCancellationRequested(token);
|
||||
|
||||
if (node.nodeType === ParseNodeType.Import || node.nodeType === ParseNodeType.ImportFrom) {
|
||||
break;
|
||||
}
|
||||
|
@ -109,16 +109,16 @@ export class Program {
|
||||
private _extension?: LanguageServiceExtension
|
||||
) {
|
||||
this._console = console || new StandardConsole();
|
||||
this._evaluator = createTypeEvaluator(this._lookUpImport, Program._getPrintTypeFlags(initialConfigOptions));
|
||||
this._importResolver = initialImportResolver;
|
||||
this._configOptions = initialConfigOptions;
|
||||
this._createNewEvaluator();
|
||||
}
|
||||
|
||||
setConfigOptions(configOptions: ConfigOptions) {
|
||||
this._configOptions = configOptions;
|
||||
|
||||
// Create a new evaluator with the updated config options.
|
||||
this._evaluator = createTypeEvaluator(this._lookUpImport, Program._getPrintTypeFlags(this._configOptions));
|
||||
this._createNewEvaluator();
|
||||
}
|
||||
|
||||
setImportResolver(importResolver: ImportResolver) {
|
||||
@ -432,12 +432,7 @@ export class Program {
|
||||
}
|
||||
}
|
||||
|
||||
writeTypeStub(
|
||||
targetImportPath: string,
|
||||
targetIsSingleFile: boolean,
|
||||
typingsPath: string,
|
||||
token: CancellationToken
|
||||
) {
|
||||
writeTypeStub(targetImportPath: string, targetIsSingleFile: boolean, stubPath: string, token: CancellationToken) {
|
||||
for (const sourceFileInfo of this._sourceFileList) {
|
||||
throwIfCancellationRequested(token);
|
||||
|
||||
@ -447,7 +442,7 @@ export class Program {
|
||||
// not any files that the target module happened to import.
|
||||
const relativePath = getRelativePath(filePath, targetImportPath);
|
||||
if (relativePath !== undefined) {
|
||||
let typeStubPath = normalizePath(combinePaths(typingsPath, relativePath));
|
||||
let typeStubPath = normalizePath(combinePaths(stubPath, relativePath));
|
||||
|
||||
// If the target is a single file implementation, as opposed to
|
||||
// a package in a directory, transform the name of the type stub
|
||||
@ -461,7 +456,7 @@ export class Program {
|
||||
const typeStubDir = getDirectoryPath(typeStubPath);
|
||||
|
||||
try {
|
||||
makeDirectories(this._fs, typeStubDir, typingsPath);
|
||||
makeDirectories(this._fs, typeStubDir, stubPath);
|
||||
} catch (e) {
|
||||
const errMsg = `Could not create directory for '${typeStubDir}'`;
|
||||
throw new Error(errMsg);
|
||||
@ -488,6 +483,10 @@ export class Program {
|
||||
flags |= PrintTypeFlags.OmitTypeArgumentsIfAny;
|
||||
}
|
||||
|
||||
if (configOptions.diagnosticRuleSet.pep604Printing) {
|
||||
flags |= PrintTypeFlags.PEP604;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
@ -458,9 +458,17 @@ export class AnalyzerService {
|
||||
configOptions.checkOnlyOpenFiles = !!commandLineOptions.checkOnlyOpenFiles;
|
||||
configOptions.useLibraryCodeForTypes = !!commandLineOptions.useLibraryCodeForTypes;
|
||||
|
||||
// If there was no typings path specified, use a default path.
|
||||
if (configOptions.typingsPath === undefined) {
|
||||
configOptions.typingsPath = normalizePath(combinePaths(configOptions.projectRoot, 'typings'));
|
||||
// If there was no stub path specified, use a default path.
|
||||
if (commandLineOptions.stubPath) {
|
||||
if (!configOptions.stubPath) {
|
||||
configOptions.stubPath = commandLineOptions.stubPath;
|
||||
} else {
|
||||
reportDuplicateSetting('stubPath');
|
||||
}
|
||||
} else {
|
||||
if (!configOptions.stubPath) {
|
||||
configOptions.stubPath = normalizePath(combinePaths(configOptions.projectRoot, 'typings'));
|
||||
}
|
||||
}
|
||||
|
||||
// Do some sanity checks on the specified settings and report missing
|
||||
@ -544,9 +552,9 @@ export class AnalyzerService {
|
||||
}
|
||||
}
|
||||
|
||||
if (configOptions.typingsPath) {
|
||||
if (!this._fs.existsSync(configOptions.typingsPath) || !isDirectory(this._fs, configOptions.typingsPath)) {
|
||||
this._console.log(`typingsPath ${configOptions.typingsPath} is not a valid directory.`);
|
||||
if (configOptions.stubPath) {
|
||||
if (!this._fs.existsSync(configOptions.stubPath) || !isDirectory(this._fs, configOptions.stubPath)) {
|
||||
this._console.log(`stubPath ${configOptions.stubPath} is not a valid directory.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -604,13 +612,13 @@ export class AnalyzerService {
|
||||
}
|
||||
|
||||
private _getTypeStubFolder() {
|
||||
const typingsPath = this._configOptions.typingsPath;
|
||||
const stubPath = this._configOptions.stubPath;
|
||||
if (!this._typeStubTargetPath || !this._typeStubTargetImportName) {
|
||||
const errMsg = `Import '${this._typeStubTargetImportName}'` + ` could not be resolved`;
|
||||
this._console.error(errMsg);
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
if (!typingsPath) {
|
||||
if (!stubPath) {
|
||||
// We should never get here because we always generate a
|
||||
// default typings path if none was specified.
|
||||
const errMsg = 'No typings path was specified';
|
||||
@ -627,16 +635,16 @@ export class AnalyzerService {
|
||||
}
|
||||
try {
|
||||
// Generate a new typings directory if necessary.
|
||||
if (!this._fs.existsSync(typingsPath)) {
|
||||
this._fs.mkdirSync(typingsPath);
|
||||
if (!this._fs.existsSync(stubPath)) {
|
||||
this._fs.mkdirSync(stubPath);
|
||||
}
|
||||
} catch (e) {
|
||||
const errMsg = `Could not create typings directory '${typingsPath}'`;
|
||||
const errMsg = `Could not create typings directory '${stubPath}'`;
|
||||
this._console.error(errMsg);
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
// Generate a typings subdirectory.
|
||||
const typingsSubdirPath = combinePaths(typingsPath, typeStubInputTargetParts[0]);
|
||||
const typingsSubdirPath = combinePaths(stubPath, typeStubInputTargetParts[0]);
|
||||
try {
|
||||
// Generate a new typings subdirectory if necessary.
|
||||
if (!this._fs.existsSync(typingsSubdirPath)) {
|
||||
|
@ -299,6 +299,9 @@ export const enum PrintTypeFlags {
|
||||
|
||||
// Omit type arguments for generic classes if they are "Any".
|
||||
OmitTypeArgumentsIfAny = 1 << 1,
|
||||
|
||||
// Print Union and Optional in PEP 604 format.
|
||||
PEP604 = 1 << 2,
|
||||
}
|
||||
|
||||
interface ParamAssignmentInfo {
|
||||
@ -12229,8 +12232,16 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
});
|
||||
|
||||
const returnType = getFunctionEffectiveReturnType(type);
|
||||
const returnTypeString =
|
||||
recursionCount < maxTypeRecursionCount ? printType(returnType, recursionCount + 1) : '';
|
||||
let returnTypeString = recursionCount < maxTypeRecursionCount ? printType(returnType, recursionCount + 1) : '';
|
||||
|
||||
if (
|
||||
printTypeFlags & PrintTypeFlags.PEP604 &&
|
||||
returnType.category === TypeCategory.Union &&
|
||||
recursionCount > 0
|
||||
) {
|
||||
returnTypeString = `(${returnTypeString})`;
|
||||
}
|
||||
|
||||
return [paramTypeStrings, returnTypeString];
|
||||
}
|
||||
|
||||
@ -12293,6 +12304,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
|
||||
if (subtypes.find((t) => t.category === TypeCategory.None) !== undefined) {
|
||||
const optionalType = printType(removeNoneFromUnion(unionType), recursionCount + 1);
|
||||
|
||||
if (printTypeFlags & PrintTypeFlags.PEP604) {
|
||||
return optionalType + ' | None';
|
||||
}
|
||||
|
||||
return 'Optional[' + optionalType + ']';
|
||||
}
|
||||
|
||||
@ -12327,6 +12343,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
return subtypeStrings[0];
|
||||
}
|
||||
|
||||
if (printTypeFlags & PrintTypeFlags.PEP604) {
|
||||
return subtypeStrings.join(' | ');
|
||||
}
|
||||
|
||||
return `Union[${subtypeStrings.join(', ')}]`;
|
||||
}
|
||||
|
||||
|
@ -127,13 +127,13 @@ export class TypeStubWriter extends ParseTreeWalker {
|
||||
private _trackedImportFrom = new Map<string, TrackedImportFrom>();
|
||||
private _accessedImportedSymbols = new Map<string, boolean>();
|
||||
|
||||
constructor(private _typingsPath: string, private _sourceFile: SourceFile, private _evaluator: TypeEvaluator) {
|
||||
constructor(private _stubPath: string, private _sourceFile: SourceFile, private _evaluator: TypeEvaluator) {
|
||||
super();
|
||||
|
||||
// As a heuristic, we'll include all of the import statements
|
||||
// in "__init__.pyi" files even if they're not locally referenced
|
||||
// because these are often used as ways to re-export symbols.
|
||||
if (this._typingsPath.endsWith('__init__.pyi')) {
|
||||
if (this._stubPath.endsWith('__init__.pyi')) {
|
||||
this._includeAllImports = true;
|
||||
}
|
||||
}
|
||||
@ -662,6 +662,6 @@ export class TypeStubWriter extends ParseTreeWalker {
|
||||
finalText += this._printTrackedImports();
|
||||
finalText += this._typeStubText;
|
||||
|
||||
this._sourceFile.fileSystem.writeFileSync(this._typingsPath, finalText, 'utf8');
|
||||
this._sourceFile.fileSystem.writeFileSync(this._stubPath, finalText, 'utf8');
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ export class BackgroundAnalysisBase {
|
||||
async writeTypeStub(
|
||||
targetImportPath: string,
|
||||
targetIsSingleFile: boolean,
|
||||
typingsPath: string,
|
||||
stubPath: string,
|
||||
token: CancellationToken
|
||||
): Promise<any> {
|
||||
throwIfCancellationRequested(token);
|
||||
@ -163,7 +163,7 @@ export class BackgroundAnalysisBase {
|
||||
const cancellationId = getCancellationTokenId(token);
|
||||
this._enqueueRequest({
|
||||
requestType: 'writeTypeStub',
|
||||
data: { targetImportPath, targetIsSingleFile, typingsPath, cancellationId },
|
||||
data: { targetImportPath, targetIsSingleFile, stubPath, cancellationId },
|
||||
port: port2,
|
||||
});
|
||||
|
||||
@ -253,7 +253,7 @@ export class BackgroundAnalysisRunnerBase {
|
||||
|
||||
case 'writeTypeStub': {
|
||||
run(() => {
|
||||
const { targetImportPath, targetIsSingleFile, typingsPath, cancellationId } = msg.data;
|
||||
const { targetImportPath, targetIsSingleFile, stubPath, cancellationId } = msg.data;
|
||||
const token = getCancellationTokenFromId(cancellationId);
|
||||
|
||||
analyzeProgram(
|
||||
@ -264,7 +264,7 @@ export class BackgroundAnalysisRunnerBase {
|
||||
this._getConsole(),
|
||||
token
|
||||
);
|
||||
this._program.writeTypeStub(targetImportPath, targetIsSingleFile, typingsPath, token);
|
||||
this._program.writeTypeStub(targetImportPath, targetIsSingleFile, stubPath, token);
|
||||
}, msg.port!);
|
||||
break;
|
||||
}
|
||||
@ -392,7 +392,7 @@ function createConfigOptionsFrom(jsonObject: any): ConfigOptions {
|
||||
|
||||
configOptions.pythonPath = jsonObject.pythonPath;
|
||||
configOptions.typeshedPath = jsonObject.typeshedPath;
|
||||
configOptions.typingsPath = jsonObject.typingsPath;
|
||||
configOptions.stubPath = jsonObject.stubPath;
|
||||
configOptions.autoExcludeVenv = jsonObject.autoExcludeVenv;
|
||||
configOptions.verboseOutput = jsonObject.verboseOutput;
|
||||
configOptions.checkOnlyOpenFiles = jsonObject.checkOnlyOpenFiles;
|
||||
|
@ -12,4 +12,5 @@ export const enum Commands {
|
||||
restartServer = 'pyright.restartserver',
|
||||
orderImports = 'pyright.organizeimports',
|
||||
addMissingOptionalToParam = 'pyright.addoptionalforparam',
|
||||
unusedImport = 'pyright.unusedImport',
|
||||
}
|
||||
|
@ -68,12 +68,6 @@ export class CreateTypeStubCommand implements ServerCommand {
|
||||
// Creates a service instance that's used for creating type
|
||||
// stubs for a specified target library.
|
||||
private async _createTypeStubService(callingFile?: string): Promise<AnalyzerService> {
|
||||
return this._createAnalyzerService(callingFile);
|
||||
}
|
||||
|
||||
private async _createAnalyzerService(callingFile: string | undefined) {
|
||||
this._ls.console.log('Starting type stub service instance');
|
||||
|
||||
if (callingFile) {
|
||||
// this should let us to inherit all execution env of the calling file
|
||||
// if it is invoked from IDE through code action
|
||||
|
@ -40,6 +40,9 @@ export class CommandLineOptions {
|
||||
// Path of typeshed stubs.
|
||||
typeshedPath?: string;
|
||||
|
||||
// Path of typing folder
|
||||
stubPath?: string;
|
||||
|
||||
// Absolute execution root (current working directory).
|
||||
executionRoot: string;
|
||||
|
||||
|
@ -51,6 +51,9 @@ export interface DiagnosticRuleSet {
|
||||
// when printed if all arguments are Unknown or Any?
|
||||
omitTypeArgsIfAny: boolean;
|
||||
|
||||
// Should Union and Optional types be printed in PEP 604 format?
|
||||
pep604Printing: boolean;
|
||||
|
||||
// Use strict inference rules for list expressions?
|
||||
strictListInference: boolean;
|
||||
|
||||
@ -254,6 +257,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
|
||||
const diagSettings: DiagnosticRuleSet = {
|
||||
printUnknownAsAny: false,
|
||||
omitTypeArgsIfAny: false,
|
||||
pep604Printing: true,
|
||||
strictListInference: true,
|
||||
strictDictionaryInference: true,
|
||||
strictParameterNoneValue: true,
|
||||
@ -305,6 +309,7 @@ export function getNoTypeCheckingDiagnosticRuleSet(): DiagnosticRuleSet {
|
||||
const diagSettings: DiagnosticRuleSet = {
|
||||
printUnknownAsAny: true,
|
||||
omitTypeArgsIfAny: true,
|
||||
pep604Printing: true,
|
||||
strictListInference: false,
|
||||
strictDictionaryInference: false,
|
||||
strictParameterNoneValue: false,
|
||||
@ -356,6 +361,7 @@ export function getDefaultDiagnosticRuleSet(): DiagnosticRuleSet {
|
||||
const diagSettings: DiagnosticRuleSet = {
|
||||
printUnknownAsAny: false,
|
||||
omitTypeArgsIfAny: false,
|
||||
pep604Printing: true,
|
||||
strictListInference: false,
|
||||
strictDictionaryInference: false,
|
||||
strictParameterNoneValue: false,
|
||||
@ -422,7 +428,7 @@ export class ConfigOptions {
|
||||
typeshedPath?: string;
|
||||
|
||||
// Path to custom typings (stub) modules.
|
||||
typingsPath?: string;
|
||||
stubPath?: string;
|
||||
|
||||
// A list of file specs to include in the analysis. Can contain
|
||||
// directories, in which case all "*.py" files within those directories
|
||||
@ -648,6 +654,7 @@ export class ConfigOptions {
|
||||
this.diagnosticRuleSet = {
|
||||
printUnknownAsAny: defaultSettings.printUnknownAsAny,
|
||||
omitTypeArgsIfAny: defaultSettings.omitTypeArgsIfAny,
|
||||
pep604Printing: defaultSettings.pep604Printing,
|
||||
|
||||
// Use strict inference rules for list expressions?
|
||||
strictListInference: this._convertBoolean(
|
||||
@ -1002,13 +1009,24 @@ export class ConfigOptions {
|
||||
}
|
||||
}
|
||||
|
||||
// Read the "typingsPath" setting.
|
||||
this.typingsPath = undefined;
|
||||
// Read the "stubPath" setting.
|
||||
this.stubPath = undefined;
|
||||
|
||||
// Keep this for backward compatibility
|
||||
if (configObj.typingsPath !== undefined) {
|
||||
if (typeof configObj.typingsPath !== 'string') {
|
||||
console.log(`Config "typingsPath" field must contain a string.`);
|
||||
} else {
|
||||
this.typingsPath = normalizePath(combinePaths(this.projectRoot, configObj.typingsPath));
|
||||
console.log(`Config "typingsPath" is now deprecated. Please, use stubPath instead.`);
|
||||
this.stubPath = normalizePath(combinePaths(this.projectRoot, configObj.typingsPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (configObj.stubPath !== undefined) {
|
||||
if (typeof configObj.stubPath !== 'string') {
|
||||
console.log(`Config "stubPath" field must contain a string.`);
|
||||
} else {
|
||||
this.stubPath = normalizePath(combinePaths(this.projectRoot, configObj.stubPath));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Class that represents errors and warnings.
|
||||
*/
|
||||
|
||||
import { Diagnostic, DiagnosticCategory } from './diagnostic';
|
||||
import { Diagnostic, DiagnosticAction, DiagnosticCategory } from './diagnostic';
|
||||
import { convertOffsetsToRange } from './positionUtils';
|
||||
import { Range, TextRange } from './textRange';
|
||||
import { TextRangeCollection } from './textRangeCollection';
|
||||
@ -43,8 +43,12 @@ export class DiagnosticSink {
|
||||
return this.addDiagnostic(new Diagnostic(DiagnosticCategory.Warning, message, range));
|
||||
}
|
||||
|
||||
addUnusedCode(message: string, range: Range) {
|
||||
return this.addDiagnostic(new Diagnostic(DiagnosticCategory.UnusedCode, message, range));
|
||||
addUnusedCode(message: string, range: Range, action?: DiagnosticAction) {
|
||||
const diag = new Diagnostic(DiagnosticCategory.UnusedCode, message, range);
|
||||
if (action) {
|
||||
diag.addAction(action);
|
||||
}
|
||||
return this.addDiagnostic(diag);
|
||||
}
|
||||
|
||||
addDiagnostic(diag: Diagnostic) {
|
||||
@ -91,7 +95,11 @@ export class TextRangeDiagnosticSink extends DiagnosticSink {
|
||||
return this.addWarning(message, convertOffsetsToRange(range.start, range.start + range.length, this._lines));
|
||||
}
|
||||
|
||||
addUnusedCodeWithTextRange(message: string, range: TextRange) {
|
||||
return this.addUnusedCode(message, convertOffsetsToRange(range.start, range.start + range.length, this._lines));
|
||||
addUnusedCodeWithTextRange(message: string, range: TextRange, action?: DiagnosticAction) {
|
||||
return this.addUnusedCode(
|
||||
message,
|
||||
convertOffsetsToRange(range.start, range.start + range.length, this._lines),
|
||||
action
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -511,8 +511,8 @@ export function getFileExtension(fileName: string, multiDotExtension = false) {
|
||||
return path.extname(fileName);
|
||||
}
|
||||
|
||||
const lastDividerIndex = fileName.lastIndexOf(path.sep);
|
||||
const firstDotIndex = fileName.indexOf('.', lastDividerIndex + 1);
|
||||
fileName = getFileName(fileName);
|
||||
const firstDotIndex = fileName.indexOf('.');
|
||||
return fileName.substr(firstDotIndex);
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,7 @@ export interface ServerSettings {
|
||||
venvPath?: string;
|
||||
pythonPath?: string;
|
||||
typeshedPath?: string;
|
||||
stubPath?: string;
|
||||
openFilesOnly?: boolean;
|
||||
typeCheckingMode?: string;
|
||||
useLibraryCodeForTypes?: boolean;
|
||||
@ -216,6 +217,10 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected isOpenFilesOnly(diagnosticMode: string): boolean {
|
||||
return diagnosticMode !== 'workspace';
|
||||
}
|
||||
|
||||
protected createImportResolver(fs: FileSystem, options: ConfigOptions): ImportResolver {
|
||||
return new ImportResolver(fs, options);
|
||||
}
|
||||
|
@ -73,7 +73,15 @@ function _getCommandLineOptions(
|
||||
// Pyright supports only one typeshed path currently, whereas the
|
||||
// official VS Code Python extension supports multiple typeshed paths.
|
||||
// We'll use the first one specified and ignore the rest.
|
||||
commandLineOptions.typeshedPath = _expandPathVariables(languageServiceRootPath, serverSettings.typeshedPath);
|
||||
commandLineOptions.typeshedPath = normalizePath(
|
||||
_expandPathVariables(languageServiceRootPath, serverSettings.typeshedPath)
|
||||
);
|
||||
}
|
||||
|
||||
if (serverSettings.stubPath) {
|
||||
commandLineOptions.stubPath = normalizePath(
|
||||
_expandPathVariables(languageServiceRootPath, serverSettings.stubPath)
|
||||
);
|
||||
}
|
||||
|
||||
if (typeStubTargetImportName) {
|
||||
|
@ -12,7 +12,7 @@ import { BackgroundAnalysis } from './backgroundAnalysis';
|
||||
import { BackgroundAnalysisBase } from './backgroundAnalysisBase';
|
||||
import { CommandController } from './commands/commandController';
|
||||
import { getCancellationFolderName } from './common/cancellationUtils';
|
||||
import { isDebugMode } from './common/core';
|
||||
import { isDebugMode, isString } from './common/core';
|
||||
import { convertUriToPath, getDirectoryPath, normalizeSlashes } from './common/pathUtils';
|
||||
import { LanguageServerBase, ServerSettings, WorkspaceServiceInstance } from './languageServerBase';
|
||||
import { CodeActionProvider } from './languageService/codeActionProvider';
|
||||
@ -45,6 +45,16 @@ class PyrightServer extends LanguageServerBase {
|
||||
if (typeshedPaths && isArray(typeshedPaths) && typeshedPaths.length > 0) {
|
||||
serverSettings.typeshedPath = normalizeSlashes(typeshedPaths[0]);
|
||||
}
|
||||
|
||||
const stubPath = pythonAnalysisSection.stubPath;
|
||||
if (stubPath && isString(stubPath)) {
|
||||
serverSettings.stubPath = normalizeSlashes(stubPath);
|
||||
}
|
||||
|
||||
if (pythonAnalysisSection.diagnosticMode !== undefined) {
|
||||
serverSettings.openFilesOnly = this.isOpenFilesOnly(pythonAnalysisSection.diagnosticMode);
|
||||
}
|
||||
|
||||
serverSettings.autoSearchPaths = !!pythonAnalysisSection.autoSearchPaths;
|
||||
|
||||
const extraPaths = pythonAnalysisSection.extraPaths;
|
||||
@ -57,13 +67,17 @@ class PyrightServer extends LanguageServerBase {
|
||||
|
||||
const pyrightSection = await this.getConfiguration(workspace, 'pyright');
|
||||
if (pyrightSection) {
|
||||
if (pyrightSection.openFilesOnly !== undefined) {
|
||||
serverSettings.openFilesOnly = !!pyrightSection.openFilesOnly;
|
||||
}
|
||||
|
||||
serverSettings.useLibraryCodeForTypes = !!pyrightSection.useLibraryCodeForTypes;
|
||||
serverSettings.disableLanguageServices = !!pyrightSection.disableLanguageServices;
|
||||
serverSettings.disableOrganizeImports = !!pyrightSection.disableOrganizeImports;
|
||||
serverSettings.typeCheckingMode = pyrightSection.typeCheckingMode;
|
||||
} else {
|
||||
serverSettings.openFilesOnly = true;
|
||||
// Unless openFilesOnly is set explicitly, set it to true by default.
|
||||
serverSettings.openFilesOnly = serverSettings.openFilesOnly ?? true;
|
||||
serverSettings.useLibraryCodeForTypes = false;
|
||||
serverSettings.disableLanguageServices = false;
|
||||
serverSettings.disableOrganizeImports = false;
|
||||
|
@ -117,9 +117,12 @@ declare namespace _ {
|
||||
moveCaretRight(count: number): void;
|
||||
|
||||
verifyDiagnostics(map?: { [marker: string]: { category: string; message: string } }): void;
|
||||
verifyCodeActions(map: {
|
||||
verifyCodeActions(
|
||||
map: {
|
||||
[marker: string]: { codeActions: { title: string; kind: string; command: Command }[] };
|
||||
}): void;
|
||||
},
|
||||
verifyCodeActionCount?: boolean
|
||||
): void;
|
||||
verifyCommand(command: Command, files: { [filePath: string]: string }): void;
|
||||
verifyInvokeCodeAction(
|
||||
map: {
|
||||
|
@ -31,8 +31,8 @@ const xInitSignatures = [
|
||||
|
||||
const xComplicatedSignatures = [
|
||||
{
|
||||
label: '(a: int, b: int, c: int = 1234, d: Optional[str] = None, **kwargs) -> Union[int, str]',
|
||||
parameters: ['a: int', 'b: int', 'c: int = 1234', 'd: Optional[str] = None', '**kwargs'],
|
||||
label: '(a: int, b: int, c: int = 1234, d: str | None = None, **kwargs) -> int | str',
|
||||
parameters: ['a: int', 'b: int', 'c: int = 1234', 'd: str | None = None', '**kwargs'],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -575,23 +575,37 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
async verifyCodeActions(map: {
|
||||
async verifyCodeActions(
|
||||
map: {
|
||||
[marker: string]: { codeActions: { title: string; kind: string; command: Command }[] };
|
||||
}): Promise<any> {
|
||||
},
|
||||
verifyCodeActionCount?: boolean
|
||||
): Promise<any> {
|
||||
this._analyze();
|
||||
|
||||
for (const range of this.getRanges()) {
|
||||
const name = this.getMarkerName(range.marker!);
|
||||
for (const expected of map[name].codeActions) {
|
||||
const actual = await this._getCodeActions(range);
|
||||
if (!map[name]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const codeActions = await this._getCodeActions(range);
|
||||
if (verifyCodeActionCount) {
|
||||
if (codeActions.length !== map[name].codeActions.length) {
|
||||
this._raiseError(
|
||||
`doesn't contain expected result: ${stringify(map[name])}, actual: ${stringify(codeActions)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const expected of map[name].codeActions) {
|
||||
const expectedCommand = {
|
||||
title: expected.command.title,
|
||||
command: expected.command.command,
|
||||
arguments: convertToString(expected.command.arguments),
|
||||
};
|
||||
|
||||
const matches = actual.filter((a) => {
|
||||
const matches = codeActions.filter((a) => {
|
||||
const actualCommand = a.command
|
||||
? {
|
||||
title: a.command.title,
|
||||
@ -609,7 +623,7 @@ export class TestState {
|
||||
|
||||
if (matches.length !== 1) {
|
||||
this._raiseError(
|
||||
`doesn't contain expected result: ${stringify(expected)}, actual: ${stringify(actual)}`
|
||||
`doesn't contain expected result: ${stringify(expected)}, actual: ${stringify(codeActions)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1035,8 +1049,8 @@ export class TestState {
|
||||
configOptions.defaultVenv = vfs.MODULE_PATH;
|
||||
|
||||
// make sure we set typing path
|
||||
if (configOptions.typingsPath === undefined) {
|
||||
configOptions.typingsPath = normalizePath(combinePaths(vfs.MODULE_PATH, 'typings'));
|
||||
if (configOptions.stubPath === undefined) {
|
||||
configOptions.stubPath = normalizePath(combinePaths(vfs.MODULE_PATH, 'typings'));
|
||||
}
|
||||
|
||||
return configOptions;
|
||||
|
@ -120,7 +120,32 @@ test('Configuration', () => {
|
||||
|
||||
assert.equal(state.configOptions.diagnosticRuleSet.reportMissingImports, 'error');
|
||||
assert.equal(state.configOptions.diagnosticRuleSet.reportMissingModuleSource, 'warning');
|
||||
assert.equal(state.configOptions.typingsPath, normalizeSlashes('/src/typestubs'));
|
||||
assert.equal(state.configOptions.stubPath, normalizeSlashes('/src/typestubs'));
|
||||
});
|
||||
|
||||
test('stubPath configuration', () => {
|
||||
const code = `
|
||||
// @filename: mspythonconfig.json
|
||||
//// {
|
||||
//// "stubPath": "src/typestubs"
|
||||
//// }
|
||||
`;
|
||||
|
||||
const state = parseAndGetTestState(code).state;
|
||||
assert.equal(state.configOptions.stubPath, normalizeSlashes('/src/typestubs'));
|
||||
});
|
||||
|
||||
test('Duplicated stubPath configuration', () => {
|
||||
const code = `
|
||||
// @filename: mspythonconfig.json
|
||||
//// {
|
||||
//// "typingsPath": "src/typestubs1",
|
||||
//// "stubPath": "src/typestubs2"
|
||||
//// }
|
||||
`;
|
||||
|
||||
const state = parseAndGetTestState(code).state;
|
||||
assert.equal(state.configOptions.stubPath, normalizeSlashes('/src/typestubs2'));
|
||||
});
|
||||
|
||||
test('ProjectRoot', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user