diff --git a/client/package.json b/client/package.json index 2073d29be..d0d67c80f 100644 --- a/client/package.json +++ b/client/package.json @@ -104,6 +104,17 @@ "default": false, "description": "Use library implementations to extract type information when type stub is not present.", "scope": "resource" + }, + "pyright.typeCheckingMode": { + "type": "string", + "default": "basic", + "enum": [ + "off", + "basic", + "strict" + ], + "description": "Defines the default rule set for type checking.", + "scope": "resource" } } }, diff --git a/client/schemas/pyrightconfig.schema.json b/client/schemas/pyrightconfig.schema.json index 302e6743a..d258b651d 100644 --- a/client/schemas/pyrightconfig.schema.json +++ b/client/schemas/pyrightconfig.schema.json @@ -64,6 +64,17 @@ "pattern": "^(.*)$" } }, + "typeCheckingMode": { + "$id": "#/properties/typeCheckingMode", + "type": "string", + "enum": [ + "off", + "basic", + "strict" + ], + "title": "Specifies the default rule set to use for type checking", + "default": "basic" + }, "typeshedPath": { "$id": "#/properties/typeshedPath", "type": "string", diff --git a/docs/configuration.md b/docs/configuration.md index 2f0dbefc6..80f454671 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -30,6 +30,8 @@ Relative paths specified within the config file are relative to the config file **executionEnvironments** [array of objects, optional]: Specifies a list of execution environments (see below). Execution environments are searched from start to finish by comparing the path of a source file with the root path specified in the execution environment. +**typeCheckingMode** ["off", "basic", "strict"]: Specifies the default rule set to use. Some rules can be overridden using additional configuration flags documented below. THe default value for this setting is "basic". If set to "off", all type-checking rules are disabled, but Python syntax and semantic errors are still reported. + ## Type Check Diagnostics Settings The following settings control pyright’s diagnostic output (warnings or errors). Unless otherwise specified, each diagnostic setting can specify a boolean value (`false` indicating that no error is generated and `true` indicating that an error is generated). Alternatively, a string value of `"none"`, `"warning"`, or `"error"` can be used to specify the diagnostic level. diff --git a/docs/settings.md b/docs/settings.md index ecb96a6b2..079790a24 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -6,6 +6,8 @@ The Pyright VS Code extension honors the following settings. **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. **python.analysis.typeshedPaths** [array of paths]: Paths to look for typeshed modules. Pyright currently honors only the first path in the array. diff --git a/server/src/analyzer/analyzerFileInfo.ts b/server/src/analyzer/analyzerFileInfo.ts index da75a553c..76bc883cb 100644 --- a/server/src/analyzer/analyzerFileInfo.ts +++ b/server/src/analyzer/analyzerFileInfo.ts @@ -8,7 +8,7 @@ * by the binder and checker. */ -import { DiagnosticSettings, ExecutionEnvironment } from '../common/configOptions'; +import { DiagnosticRuleSet, ExecutionEnvironment } from '../common/configOptions'; import { TextRangeDiagnosticSink } from '../common/diagnosticSink'; import { TextRange } from '../common/textRange'; import { TextRangeCollection } from '../common/textRangeCollection'; @@ -31,7 +31,7 @@ export interface AnalyzerFileInfo { collectionsModulePath?: string; diagnosticSink: TextRangeDiagnosticSink; executionEnvironment: ExecutionEnvironment; - diagnosticSettings: DiagnosticSettings; + diagnosticRuleSet: DiagnosticRuleSet; fileContents: string; lines: TextRangeCollection; filePath: string; diff --git a/server/src/analyzer/binder.ts b/server/src/analyzer/binder.ts index b9cdb6707..0cd3dd913 100644 --- a/server/src/analyzer/binder.ts +++ b/server/src/analyzer/binder.ts @@ -257,7 +257,7 @@ export class Binder extends ParseTreeWalker { if (importResult) { if (!importResult.isImportFound) { this._addDiagnostic( - this._fileInfo.diagnosticSettings.reportMissingImports, + this._fileInfo.diagnosticRuleSet.reportMissingImports, DiagnosticRule.reportMissingImports, `Import "${importResult.importName}" could not be resolved`, node @@ -265,7 +265,7 @@ export class Binder extends ParseTreeWalker { } else if (importResult.importType === ImportType.ThirdParty) { if (!importResult.isStubFile) { const diagnostic = this._addDiagnostic( - this._fileInfo.diagnosticSettings.reportMissingTypeStubs, + this._fileInfo.diagnosticRuleSet.reportMissingTypeStubs, DiagnosticRule.reportMissingTypeStubs, `Stub file not found for "${importResult.importName}"`, node @@ -991,7 +991,7 @@ export class Binder extends ParseTreeWalker { if (error.errorType === StringTokenUtils.UnescapeErrorType.InvalidEscapeSequence) { this._addDiagnostic( - this._fileInfo.diagnosticSettings.reportInvalidStringEscapeSequence, + this._fileInfo.diagnosticRuleSet.reportInvalidStringEscapeSequence, DiagnosticRule.reportInvalidStringEscapeSequence, 'Unsupported escape sequence in string literal', textRange @@ -2070,7 +2070,7 @@ export class Binder extends ParseTreeWalker { let symbol = memberAccessInfo.classScope.lookUpSymbol(name.value); if (!symbol) { symbol = memberAccessInfo.classScope.addSymbol(name.value, SymbolFlags.InitiallyUnbound); - const honorPrivateNaming = this._fileInfo.diagnosticSettings.reportPrivateUsage !== 'none'; + const honorPrivateNaming = this._fileInfo.diagnosticRuleSet.reportPrivateUsage !== 'none'; if (isPrivateOrProtectedName(name.value) && honorPrivateNaming) { symbol.setIsPrivateMember(); } @@ -2166,7 +2166,7 @@ export class Binder extends ParseTreeWalker { let symbol = memberAccessInfo.classScope.lookUpSymbol(name.value); if (!symbol) { symbol = memberAccessInfo.classScope.addSymbol(name.value, SymbolFlags.InitiallyUnbound); - const honorPrivateNaming = this._fileInfo.diagnosticSettings.reportPrivateUsage !== 'none'; + const honorPrivateNaming = this._fileInfo.diagnosticRuleSet.reportPrivateUsage !== 'none'; if (isPrivateOrProtectedName(name.value) && honorPrivateNaming) { symbol.setIsPrivateMember(); } diff --git a/server/src/analyzer/checker.ts b/server/src/analyzer/checker.ts index 360c9af4b..06398eae6 100644 --- a/server/src/analyzer/checker.ts +++ b/server/src/analyzer/checker.ts @@ -166,7 +166,7 @@ export class Checker extends ParseTreeWalker { const paramType = functionTypeResult.functionType.details.parameters[index].type; if (paramType.category === TypeCategory.Unknown) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownParameterType, + this._fileInfo.diagnosticRuleSet.reportUnknownParameterType, DiagnosticRule.reportUnknownParameterType, `Type of parameter "${param.name.value}" is unknown`, param.name @@ -175,7 +175,7 @@ export class Checker extends ParseTreeWalker { const diagAddendum = new DiagnosticAddendum(); diagAddendum.addMessage(`Parameter type is "${this._evaluator.printType(paramType)}"`); this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownParameterType, + this._fileInfo.diagnosticRuleSet.reportUnknownParameterType, DiagnosticRule.reportUnknownParameterType, `Type of parameter "${param.name.value}" is partially unknown` + diagAddendum.getString(), param.name @@ -235,14 +235,14 @@ export class Checker extends ParseTreeWalker { if (paramType) { if (paramType.category === TypeCategory.Unknown) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownLambdaType, + this._fileInfo.diagnosticRuleSet.reportUnknownLambdaType, DiagnosticRule.reportUnknownLambdaType, `Type of "${param.name.value}" is unknown`, param.name ); } else if (containsUnknown(paramType)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownLambdaType, + this._fileInfo.diagnosticRuleSet.reportUnknownLambdaType, DiagnosticRule.reportUnknownLambdaType, `Type of "${param.name.value}", ` + `"${this._evaluator.printType(paramType)}", is partially unknown`, @@ -257,14 +257,14 @@ export class Checker extends ParseTreeWalker { if (returnType) { if (returnType.category === TypeCategory.Unknown) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownLambdaType, + this._fileInfo.diagnosticRuleSet.reportUnknownLambdaType, DiagnosticRule.reportUnknownLambdaType, `Return type of lambda is unknown`, node.expression ); } else if (containsUnknown(returnType)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownLambdaType, + this._fileInfo.diagnosticRuleSet.reportUnknownLambdaType, DiagnosticRule.reportUnknownLambdaType, `Return type of lambda, "${this._evaluator.printType(returnType)}", is partially unknown`, node.expression @@ -284,7 +284,7 @@ export class Checker extends ParseTreeWalker { if (ParseTreeUtils.isWithinDefaultParamInitializer(node) && !this._fileInfo.isStubFile) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportCallInDefaultInitializer, + this._fileInfo.diagnosticRuleSet.reportCallInDefaultInitializer, DiagnosticRule.reportCallInDefaultInitializer, `Function calls within default value initializer are not permitted`, node @@ -341,7 +341,7 @@ export class Checker extends ParseTreeWalker { if (declaredReturnType) { if (isNoReturnType(declaredReturnType)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Function with declared return type "NoReturn" cannot include a return statement`, node @@ -354,7 +354,7 @@ export class Checker extends ParseTreeWalker { const specializedDeclaredType = specializeType(declaredReturnType, undefined); if (!this._evaluator.canAssignType(specializedDeclaredType, returnType, diagAddendum)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expression of type "${this._evaluator.printType(returnType)}" cannot be assigned ` + `to return type "${this._evaluator.printType(specializedDeclaredType)}"` + @@ -367,14 +367,14 @@ export class Checker extends ParseTreeWalker { if (returnType.category === TypeCategory.Unknown) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownVariableType, + this._fileInfo.diagnosticRuleSet.reportUnknownVariableType, DiagnosticRule.reportUnknownVariableType, `Return type is unknown`, node.returnExpression! ); } else if (containsUnknown(returnType)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownVariableType, + this._fileInfo.diagnosticRuleSet.reportUnknownVariableType, DiagnosticRule.reportUnknownVariableType, `Return type, "${this._evaluator.printType(returnType)}", is partially unknown`, node.returnExpression! @@ -447,7 +447,7 @@ export class Checker extends ParseTreeWalker { if (diagAddendum.getMessageCount() > 0) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expected exception class or object` + diagAddendum.getString(), node.typeExpression @@ -517,7 +517,7 @@ export class Checker extends ParseTreeWalker { if (type.classType.typeArguments.length > 0) { this._evaluator.addDiagnosticForTextRange( this._fileInfo, - this._fileInfo.diagnosticSettings.reportAssertAlwaysTrue, + this._fileInfo.diagnosticRuleSet.reportAssertAlwaysTrue, DiagnosticRule.reportAssertAlwaysTrue, `Assert expression always evaluates to true`, node.testExpression @@ -591,7 +591,7 @@ export class Checker extends ParseTreeWalker { if (node.strings.length > 1) { this._evaluator.addDiagnosticForTextRange( this._fileInfo, - this._fileInfo.diagnosticSettings.reportImplicitStringConcatenation, + this._fileInfo.diagnosticRuleSet.reportImplicitStringConcatenation, DiagnosticRule.reportImplicitStringConcatenation, `Implicit string concatenation not allowed`, node @@ -834,7 +834,7 @@ export class Checker extends ParseTreeWalker { for (const otherDecl of otherDecls) { if (otherDecl.type === DeclarationType.Class) { const diag = this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Class declaration "${name}" is obscured by a ${primaryDeclType}declaration of the same name`, otherDecl.node.name @@ -842,7 +842,7 @@ export class Checker extends ParseTreeWalker { addPrimaryDeclInfo(diag); } else if (otherDecl.type === DeclarationType.Function) { const diag = this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Function declaration "${name}" is obscured by a ${primaryDeclType}declaration of the same name`, otherDecl.node.name @@ -851,7 +851,7 @@ export class Checker extends ParseTreeWalker { } else if (otherDecl.type === DeclarationType.Parameter) { if (otherDecl.node.name) { const diag = this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Parameter "${name}" is obscured by a ${primaryDeclType}declaration of the same name`, otherDecl.node.name @@ -876,7 +876,7 @@ export class Checker extends ParseTreeWalker { if (!duplicateIsOk) { const diag = this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Declared type for "${name}" is obscured by an incompatible ${primaryDeclType}declaration`, otherDecl.node @@ -887,7 +887,7 @@ export class Checker extends ParseTreeWalker { } else if (primaryType && !isProperty(primaryType)) { if (primaryDecl.type === DeclarationType.Function || primaryDecl.type === DeclarationType.Class) { const diag = this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Declared ${primaryDeclType}already exists for "${name}"`, otherDecl.node @@ -929,7 +929,7 @@ export class Checker extends ParseTreeWalker { switch (decl.type) { case DeclarationType.Alias: - diagnosticLevel = this._fileInfo.diagnosticSettings.reportUnusedImport; + diagnosticLevel = this._fileInfo.diagnosticRuleSet.reportUnusedImport; rule = DiagnosticRule.reportUnusedImport; if (decl.node.nodeType === ParseNodeType.ImportAs) { if (decl.node.alias) { @@ -948,7 +948,7 @@ export class Checker extends ParseTreeWalker { this._evaluator.addDiagnosticForTextRange( this._fileInfo, - this._fileInfo.diagnosticSettings.reportUnusedImport, + this._fileInfo.diagnosticRuleSet.reportUnusedImport, DiagnosticRule.reportUnusedImport, `Import "${multipartName}" is not accessed`, textRange @@ -980,7 +980,7 @@ export class Checker extends ParseTreeWalker { if (!isPrivate) { return; } - diagnosticLevel = this._fileInfo.diagnosticSettings.reportUnusedVariable; + diagnosticLevel = this._fileInfo.diagnosticRuleSet.reportUnusedVariable; if (decl.node.nodeType === ParseNodeType.Name) { nameNode = decl.node; rule = DiagnosticRule.reportUnusedVariable; @@ -992,7 +992,7 @@ export class Checker extends ParseTreeWalker { if (!isPrivate) { return; } - diagnosticLevel = this._fileInfo.diagnosticSettings.reportUnusedClass; + diagnosticLevel = this._fileInfo.diagnosticRuleSet.reportUnusedClass; nameNode = decl.node.name; rule = DiagnosticRule.reportUnusedClass; message = `Class "${nameNode.value}" is not accessed`; @@ -1002,7 +1002,7 @@ export class Checker extends ParseTreeWalker { if (!isPrivate) { return; } - diagnosticLevel = this._fileInfo.diagnosticSettings.reportUnusedFunction; + diagnosticLevel = this._fileInfo.diagnosticRuleSet.reportUnusedFunction; nameNode = decl.node.name; rule = DiagnosticRule.reportUnusedFunction; message = `Function "${nameNode.value}" is not accessed`; @@ -1169,7 +1169,7 @@ export class Checker extends ParseTreeWalker { const callType = isInstanceCheck ? 'instance' : 'subclass'; if (filteredType.category === TypeCategory.Never) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnnecessaryIsInstance, + this._fileInfo.diagnosticRuleSet.reportUnnecessaryIsInstance, DiagnosticRule.reportUnnecessaryIsInstance, `Unnecessary ${callName} call; "${this._evaluator.printType(arg0Type)}" ` + `is never ${callType} of "${this._evaluator.printType(getTestType())}"`, @@ -1177,7 +1177,7 @@ export class Checker extends ParseTreeWalker { ); } else if (isTypeSame(filteredType, arg0Type)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnnecessaryIsInstance, + this._fileInfo.diagnosticRuleSet.reportUnnecessaryIsInstance, DiagnosticRule.reportUnnecessaryIsInstance, `Unnecessary ${callName} call; "${this._evaluator.printType(arg0Type)}" ` + `is always ${callType} of "${this._evaluator.printType(getTestType())}"`, @@ -1208,7 +1208,7 @@ export class Checker extends ParseTreeWalker { } private _conditionallyReportPrivateUsage(node: NameNode) { - if (this._fileInfo.diagnosticSettings.reportPrivateUsage === 'none') { + if (this._fileInfo.diagnosticRuleSet.reportPrivateUsage === 'none') { return; } @@ -1293,7 +1293,7 @@ export class Checker extends ParseTreeWalker { if (classOrModuleNode && !ParseTreeUtils.isNodeContainedWithin(node, classOrModuleNode)) { if (isProtectedAccess) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportPrivateUsage, + this._fileInfo.diagnosticRuleSet.reportPrivateUsage, DiagnosticRule.reportPrivateUsage, `"${nameValue}" is protected and used outside of a derived class`, node @@ -1302,7 +1302,7 @@ export class Checker extends ParseTreeWalker { const scopeName = classOrModuleNode.nodeType === ParseNodeType.Class ? 'class' : 'module'; this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportPrivateUsage, + this._fileInfo.diagnosticRuleSet.reportPrivateUsage, DiagnosticRule.reportPrivateUsage, `"${nameValue}" is private and used outside of the ${scopeName} in which it is declared`, node @@ -1355,14 +1355,14 @@ export class Checker extends ParseTreeWalker { if (declaredReturnType) { if (declaredReturnType.category === TypeCategory.Unknown) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownVariableType, + this._fileInfo.diagnosticRuleSet.reportUnknownVariableType, DiagnosticRule.reportUnknownVariableType, `Declared return type is unknown`, node.returnTypeAnnotation ); } else if (containsUnknown(declaredReturnType)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownVariableType, + this._fileInfo.diagnosticRuleSet.reportUnknownVariableType, DiagnosticRule.reportUnknownVariableType, `Declared return type, "${this._evaluator.printType( declaredReturnType @@ -1387,7 +1387,7 @@ export class Checker extends ParseTreeWalker { // the return type matches. if (!ParseTreeUtils.isSuiteEmpty(node.suite)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Function with declared type of "NoReturn" cannot return "None"`, node.returnTypeAnnotation @@ -1405,7 +1405,7 @@ export class Checker extends ParseTreeWalker { // the return type matches. if (!ParseTreeUtils.isSuiteEmpty(node.suite)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Function with declared type of "${this._evaluator.printType( declaredReturnType @@ -1420,14 +1420,14 @@ export class Checker extends ParseTreeWalker { const inferredReturnType = this._evaluator.getFunctionInferredReturnType(functionType); if (inferredReturnType.category === TypeCategory.Unknown) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownParameterType, + this._fileInfo.diagnosticRuleSet.reportUnknownParameterType, DiagnosticRule.reportUnknownParameterType, `Inferred return type is unknown`, node.name ); } else if (containsUnknown(inferredReturnType)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportUnknownParameterType, + this._fileInfo.diagnosticRuleSet.reportUnknownParameterType, DiagnosticRule.reportUnknownParameterType, `Return type "${this._evaluator.printType(inferredReturnType)}" is partially unknown`, node.name @@ -1486,7 +1486,7 @@ export class Checker extends ParseTreeWalker { const decl = getLastTypedDeclaredForSymbol(symbol); if (decl && decl.type === DeclarationType.Function) { const diag = this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportIncompatibleMethodOverride, + this._fileInfo.diagnosticRuleSet.reportIncompatibleMethodOverride, DiagnosticRule.reportIncompatibleMethodOverride, `Method "${name}" overrides class "${baseClassAndSymbol.classType.details.name}" ` + `in an incompatible manner` + @@ -1535,7 +1535,7 @@ export class Checker extends ParseTreeWalker { (node.parameters[0].name.value !== 'cls' && node.parameters[0].name.value !== 'mcs') ) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportSelfClsParameterName, + this._fileInfo.diagnosticRuleSet.reportSelfClsParameterName, DiagnosticRule.reportSelfClsParameterName, `The __new__ override should take a "cls" parameter`, node.parameters.length > 0 ? node.parameters[0] : node.name @@ -1545,7 +1545,7 @@ export class Checker extends ParseTreeWalker { // __init_subclass__ overrides should have a "cls" parameter. if (node.parameters.length === 0 || !node.parameters[0].name || node.parameters[0].name.value !== 'cls') { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportSelfClsParameterName, + this._fileInfo.diagnosticRuleSet.reportSelfClsParameterName, DiagnosticRule.reportSelfClsParameterName, `The __init_subclass__ override should take a "cls" parameter`, node.parameters.length > 0 ? node.parameters[0] : node.name @@ -1557,7 +1557,7 @@ export class Checker extends ParseTreeWalker { const paramName = node.parameters[0].name.value; if (paramName === 'self' || paramName === 'cls') { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportSelfClsParameterName, + this._fileInfo.diagnosticRuleSet.reportSelfClsParameterName, DiagnosticRule.reportSelfClsParameterName, `Static methods should not take a "self" or "cls" parameter`, node.parameters[0].name @@ -1575,7 +1575,7 @@ export class Checker extends ParseTreeWalker { if (paramName !== 'cls') { if (!this._fileInfo.isStubFile || (!paramName.startsWith('_') && paramName !== 'metacls')) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportSelfClsParameterName, + this._fileInfo.diagnosticRuleSet.reportSelfClsParameterName, DiagnosticRule.reportSelfClsParameterName, `Class methods should take a "cls" parameter`, node.parameters.length > 0 ? node.parameters[0] : node.name @@ -1619,7 +1619,7 @@ export class Checker extends ParseTreeWalker { if (!isLegalMetaclassName) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportSelfClsParameterName, + this._fileInfo.diagnosticRuleSet.reportSelfClsParameterName, DiagnosticRule.reportSelfClsParameterName, `Instance methods should take a "self" parameter`, node.parameters.length > 0 ? node.parameters[0] : node.name @@ -1647,7 +1647,7 @@ export class Checker extends ParseTreeWalker { if (declaredYieldType) { if (isNoReturnType(declaredYieldType)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Function with declared return type "NoReturn" cannot include a yield statement`, node @@ -1656,7 +1656,7 @@ export class Checker extends ParseTreeWalker { const diagAddendum = new DiagnosticAddendum(); if (!this._evaluator.canAssignType(declaredYieldType, adjustedYieldType, diagAddendum)) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportGeneralTypeIssues, + this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expression of type "${this._evaluator.printType(adjustedYieldType)}" cannot be assigned ` + `to yield type "${this._evaluator.printType(declaredYieldType)}"` + @@ -1684,7 +1684,7 @@ export class Checker extends ParseTreeWalker { const prevImport = symbolMap.get(importFromAs.name.value); if (prevImport) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportDuplicateImport, + this._fileInfo.diagnosticRuleSet.reportDuplicateImport, DiagnosticRule.reportDuplicateImport, `"${importFromAs.name.value}" is imported more than once`, importFromAs.name @@ -1700,7 +1700,7 @@ export class Checker extends ParseTreeWalker { const prevImport = importModuleMap.get(importStatement.moduleName); if (prevImport) { this._evaluator.addDiagnostic( - this._fileInfo.diagnosticSettings.reportDuplicateImport, + this._fileInfo.diagnosticRuleSet.reportDuplicateImport, DiagnosticRule.reportDuplicateImport, `"${importStatement.moduleName}" is imported more than once`, importStatement.subnode diff --git a/server/src/analyzer/commentUtils.ts b/server/src/analyzer/commentUtils.ts index adf475e0f..c19b20123 100644 --- a/server/src/analyzer/commentUtils.ts +++ b/server/src/analyzer/commentUtils.ts @@ -9,25 +9,25 @@ */ import { - cloneDiagnosticSettings, + cloneDiagnosticRuleSet, DiagnosticLevel, - DiagnosticSettings, - getBooleanDiagnosticSettings, - getDiagLevelSettings, - getStrictDiagnosticSettings, + DiagnosticRuleSet, + getBooleanDiagnosticRules, + getDiagLevelDiagnosticRules, + getStrictDiagnosticRuleSet, } from '../common/configOptions'; import { TextRangeCollection } from '../common/textRangeCollection'; import { Token } from '../parser/tokenizerTypes'; export function getFileLevelDirectives( tokens: TextRangeCollection, - defaultSettings: DiagnosticSettings, + defaultRuleSet: DiagnosticRuleSet, useStrict: boolean -): DiagnosticSettings { - let settings = cloneDiagnosticSettings(defaultSettings); +): DiagnosticRuleSet { + let ruleSet = cloneDiagnosticRuleSet(defaultRuleSet); if (useStrict) { - _applyStrictSettings(settings); + _applyStrictRules(ruleSet); } for (let i = 0; i < tokens.count; i++) { @@ -36,37 +36,37 @@ export function getFileLevelDirectives( for (const comment of token.comments) { const value = comment.value.trim(); - settings = _parsePyrightComment(value, settings); + ruleSet = _parsePyrightComment(value, ruleSet); } } } - return settings; + return ruleSet; } -function _applyStrictSettings(settings: DiagnosticSettings) { - const strictSettings = getStrictDiagnosticSettings(); - const boolSettingNames = getBooleanDiagnosticSettings(); - const diagSettingNames = getDiagLevelSettings(); +function _applyStrictRules(ruleSet: DiagnosticRuleSet) { + const strictRuleSet = getStrictDiagnosticRuleSet(); + const boolRuleNames = getBooleanDiagnosticRules(); + const diagRuleNames = getDiagLevelDiagnosticRules(); - // Enable the strict settings as appropriate. - for (const setting of boolSettingNames) { - if ((strictSettings as any)[setting]) { - (settings as any)[setting] = true; + // Enable the strict rules as appropriate. + for (const ruleName of boolRuleNames) { + if ((strictRuleSet as any)[ruleName]) { + (ruleSet as any)[ruleName] = true; } } - for (const setting of diagSettingNames) { - const strictValue: DiagnosticLevel = (strictSettings as any)[setting]; - const prevValue: DiagnosticLevel = (settings as any)[setting]; + for (const ruleName of diagRuleNames) { + const strictValue: DiagnosticLevel = (strictRuleSet as any)[ruleName]; + const prevValue: DiagnosticLevel = (ruleSet as any)[ruleName]; if (strictValue === 'error' || (strictValue === 'warning' && prevValue !== 'error')) { - (settings as any)[setting] = strictValue; + (ruleSet as any)[ruleName] = strictValue; } } } -function _parsePyrightComment(commentValue: string, settings: DiagnosticSettings) { +function _parsePyrightComment(commentValue: string, ruleSet: DiagnosticRuleSet) { // Is this a pyright or mspython-specific comment? const validPrefixes = ['pyright:', 'mspython:']; const prefix = validPrefixes.find((p) => commentValue.startsWith(p)); @@ -75,42 +75,42 @@ function _parsePyrightComment(commentValue: string, settings: DiagnosticSettings const operandList = operands.split(',').map((s) => s.trim()); // If it contains a "strict" operand, replace the existing - // diagnostic settings with their strict counterparts. + // diagnostic rules with their strict counterparts. if (operandList.some((s) => s === 'strict')) { - _applyStrictSettings(settings); + _applyStrictRules(ruleSet); } for (const operand of operandList) { - settings = _parsePyrightOperand(operand, settings); + ruleSet = _parsePyrightOperand(operand, ruleSet); } } - return settings; + return ruleSet; } -function _parsePyrightOperand(operand: string, settings: DiagnosticSettings) { +function _parsePyrightOperand(operand: string, ruleSet: DiagnosticRuleSet) { const operandSplit = operand.split('=').map((s) => s.trim()); if (operandSplit.length !== 2) { - return settings; + return ruleSet; } - const settingName = operandSplit[0]; - const boolSettings = getBooleanDiagnosticSettings(); - const diagLevelSettings = getDiagLevelSettings(); + const ruleName = operandSplit[0]; + const boolRules = getBooleanDiagnosticRules(); + const diagLevelRules = getDiagLevelDiagnosticRules(); - if (diagLevelSettings.find((s) => s === settingName)) { + if (diagLevelRules.find((r) => r === ruleName)) { const diagLevelValue = _parseDiagLevel(operandSplit[1]); if (diagLevelValue !== undefined) { - (settings as any)[settingName] = diagLevelValue; + (ruleSet as any)[ruleName] = diagLevelValue; } - } else if (boolSettings.find((s) => s === settingName)) { + } else if (boolRules.find((r) => r === ruleName)) { const boolValue = _parseBoolSetting(operandSplit[1]); if (boolValue !== undefined) { - (settings as any)[settingName] = boolValue; + (ruleSet as any)[ruleName] = boolValue; } } - return settings; + return ruleSet; } function _parseDiagLevel(value: string): DiagnosticLevel | undefined { diff --git a/server/src/analyzer/program.ts b/server/src/analyzer/program.ts index b585d5fa5..609a960d1 100644 --- a/server/src/analyzer/program.ts +++ b/server/src/analyzer/program.ts @@ -569,7 +569,7 @@ export class Program { // Don't bother checking third-party imports or typeshed files unless they're open. if ( fileToCheck.isThirdPartyImport || - (fileToCheck.isTypeshedFile && this._configOptions.diagnosticSettings.reportTypeshedErrors === 'none') + (fileToCheck.isTypeshedFile && this._configOptions.diagnosticRuleSet.reportTypeshedErrors === 'none') ) { if (!fileToCheck.isOpenByClient) { return false; @@ -592,7 +592,7 @@ export class Program { fileToCheck.sourceFile.check(this._evaluator); // Detect import cycles that involve the file. - if (this._configOptions.diagnosticSettings.reportImportCycles !== 'none') { + if (this._configOptions.diagnosticRuleSet.reportImportCycles !== 'none') { // Don't detect import cycles when doing type stub generation. Some // third-party modules are pretty convoluted. if (!this._allowedThirdPartyImports) { @@ -1215,7 +1215,7 @@ export class Program { } }); } else if (options.verboseOutput) { - if (!sourceFileInfo.isTypeshedFile || options.diagnosticSettings.reportTypeshedErrors !== 'none') { + if (!sourceFileInfo.isTypeshedFile || options.diagnosticRuleSet.reportTypeshedErrors !== 'none') { this._console.log( `Could not import '${importResult.importName}' ` + `in file '${sourceFileInfo.sourceFile.getFilePath()}'` diff --git a/server/src/analyzer/service.ts b/server/src/analyzer/service.ts index 4ad594ef8..52f0660f1 100644 --- a/server/src/analyzer/service.ts +++ b/server/src/analyzer/service.ts @@ -86,6 +86,7 @@ export class AnalyzerService { private _onCompletionCallback: AnalysisCompleteCallback | undefined; private _watchForSourceChanges = false; private _watchForLibraryChanges = false; + private _typeCheckingMode: string | undefined; private _verboseOutput = false; private _maxAnalysisTime?: MaxAnalysisTime; private _analyzeTimer: any; @@ -144,6 +145,7 @@ export class AnalyzerService { setOptions(commandLineOptions: CommandLineOptions): void { this._watchForSourceChanges = !!commandLineOptions.watchForSourceChanges; this._watchForLibraryChanges = !!commandLineOptions.watchForLibraryChanges; + this._typeCheckingMode = commandLineOptions.typeCheckingMode; this._verboseOutput = !!commandLineOptions.verboseOutput; this._configOptions = this._getConfigOptions(commandLineOptions); this._program.setConfigOptions(this._configOptions); @@ -336,7 +338,7 @@ export class AnalyzerService { } } - const configOptions = new ConfigOptions(projectRoot); + const configOptions = new ConfigOptions(projectRoot, this._typeCheckingMode); const defaultExcludes = ['**/node_modules', '**/__pycache__', '.git']; if (commandLineOptions.fileSpecs.length > 0) { @@ -364,7 +366,7 @@ export class AnalyzerService { this._console.log(`Loading configuration file at ${configFilePath}`); const configJsonObj = this._parseConfigFile(configFilePath); if (configJsonObj) { - configOptions.initializeFromJson(configJsonObj, this._console); + configOptions.initializeFromJson(configJsonObj, this._typeCheckingMode, this._console); const configFileDir = getDirectoryPath(configFilePath); @@ -1000,7 +1002,7 @@ export class AnalyzerService { this._console.log(`Reloading configuration file at ${this._configFilePath}`); const configJsonObj = this._parseConfigFile(this._configFilePath); if (configJsonObj) { - this._configOptions.initializeFromJson(configJsonObj, this._console); + this._configOptions.initializeFromJson(configJsonObj, this._typeCheckingMode, this._console); } this._applyConfigOptions(); diff --git a/server/src/analyzer/sourceFile.ts b/server/src/analyzer/sourceFile.ts index 5200b4485..2ab945f6b 100644 --- a/server/src/analyzer/sourceFile.ts +++ b/server/src/analyzer/sourceFile.ts @@ -16,7 +16,7 @@ import { } from 'vscode-languageserver'; import { OperationCanceledException } from '../common/cancellationUtils'; -import { ConfigOptions, ExecutionEnvironment, getDefaultDiagnosticSettings } from '../common/configOptions'; +import { ConfigOptions, ExecutionEnvironment, getDefaultDiagnosticRuleSet } from '../common/configOptions'; import { ConsoleInterface, StandardConsole } from '../common/console'; import { assert } from '../common/debug'; import { Diagnostic, DiagnosticCategory } from '../common/diagnostic'; @@ -124,7 +124,7 @@ export class SourceFile { private _checkerDiagnostics: Diagnostic[] = []; // Settings that control which diagnostics should be output. - private _diagnosticSettings = getDefaultDiagnosticSettings(); + private _diagnosticRuleSet = getDefaultDiagnosticRuleSet(); // Circular dependencies that have been reported in this file. private _circularDependencies: CircularDependency[] = []; @@ -210,7 +210,7 @@ export class SourceFile { diagList = diagList.concat(this._parseDiagnostics, this._bindDiagnostics, this._checkerDiagnostics); // Filter the diagnostics based on "type: ignore" lines. - if (options.diagnosticSettings.enableTypeIgnoreComments) { + if (options.diagnosticRuleSet.enableTypeIgnoreComments) { const typeIgnoreLines = this._parseResults ? this._parseResults.tokenizerOutput.typeIgnoreLines : {}; if (Object.keys(typeIgnoreLines).length > 0) { diagList = diagList.filter((d) => { @@ -225,9 +225,9 @@ export class SourceFile { } } - if (options.diagnosticSettings.reportImportCycles !== 'none' && this._circularDependencies.length > 0) { + if (options.diagnosticRuleSet.reportImportCycles !== 'none' && this._circularDependencies.length > 0) { const category = - options.diagnosticSettings.reportImportCycles === 'warning' + options.diagnosticRuleSet.reportImportCycles === 'warning' ? DiagnosticCategory.Warning : DiagnosticCategory.Error; @@ -257,9 +257,9 @@ export class SourceFile { } if (this._isTypeshedStubFile) { - if (options.diagnosticSettings.reportTypeshedErrors === 'none') { + if (options.diagnosticRuleSet.reportTypeshedErrors === 'none') { includeWarningsAndErrors = false; - } else if (options.diagnosticSettings.reportTypeshedErrors === 'warning') { + } else if (options.diagnosticRuleSet.reportTypeshedErrors === 'warning') { // Convert all the errors to warnings. diagList = diagList.map((diag) => { if (diag.category === DiagnosticCategory.Error) { @@ -277,7 +277,7 @@ export class SourceFile { // If there is a "type: ignore" comment at the top of the file, clear // the diagnostic list. - if (options.diagnosticSettings.enableTypeIgnoreComments) { + if (options.diagnosticRuleSet.enableTypeIgnoreComments) { if (this._parseResults && this._parseResults.tokenizerOutput.typeIgnoreAll) { diagList = []; } @@ -506,9 +506,9 @@ export class SourceFile { const useStrict = configOptions.strict.find((strictFileSpec) => strictFileSpec.regExp.test(this._filePath)) !== undefined; - this._diagnosticSettings = CommentUtils.getFileLevelDirectives( + this._diagnosticRuleSet = CommentUtils.getFileLevelDirectives( this._parseResults.tokenizerOutput.tokens, - configOptions.diagnosticSettings, + configOptions.diagnosticRuleSet, useStrict ); } catch (e) { @@ -879,7 +879,7 @@ export class SourceFile { collectionsModulePath: this._collectionsModulePath, diagnosticSink: analysisDiagnostics, executionEnvironment: configOptions.findExecEnvironment(this._filePath), - diagnosticSettings: this._diagnosticSettings, + diagnosticRuleSet: this._diagnosticRuleSet, fileContents, lines: this._parseResults!.tokenizerOutput.lines, filePath: this._filePath, diff --git a/server/src/analyzer/typeEvaluator.ts b/server/src/analyzer/typeEvaluator.ts index 68b0509ce..21c29e6cf 100644 --- a/server/src/analyzer/typeEvaluator.ts +++ b/server/src/analyzer/typeEvaluator.ts @@ -711,7 +711,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!typeResult) { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expected type but received a string literal`, node @@ -1387,7 +1387,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (errorNode) { const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${printType(subtype)}" is not awaitable`, errorNode @@ -1417,7 +1417,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (type.category === TypeCategory.Union && type.subtypes.some((t) => isNoneOrNever(t))) { if (errorNode) { addDiagnostic( - getFileInfo(errorNode).diagnosticSettings.reportOptionalIterable, + getFileInfo(errorNode).diagnosticRuleSet.reportOptionalIterable, DiagnosticRule.reportOptionalIterable, `Object of type "None" cannot be used as iterable value`, errorNode @@ -1876,7 +1876,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!canAssignType(declaredType, type, diagAddendum)) { addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expression of type "${printType(type)}" cannot be assigned to declared type "${printType( declaredType @@ -1902,7 +1902,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if ( !isConstant && - (!isPrivate || getFileInfo(nameNode).diagnosticSettings.reportPrivateUsage === 'none') + (!isPrivate || getFileInfo(nameNode).diagnosticRuleSet.reportPrivateUsage === 'none') ) { destType = stripLiteralValue(destType); } @@ -1917,7 +1917,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { // isn't the first assignment, generate an error. if (nameNode !== declarations[0].node) { addDiagnostic( - fileInfo.diagnosticSettings.reportConstantRedefinition, + fileInfo.diagnosticRuleSet.reportConstantRedefinition, DiagnosticRule.reportConstantRedefinition, `"${nameValue}" is constant and cannot be redefined`, nameNode @@ -2022,7 +2022,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { ) { if (typedDecls[0].isConstant) { addDiagnostic( - fileInfo.diagnosticSettings.reportConstantRedefinition, + fileInfo.diagnosticRuleSet.reportConstantRedefinition, DiagnosticRule.reportConstantRedefinition, `"${node.memberName.value}" is constant and cannot be redefined`, node.memberName @@ -2086,7 +2086,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { // There was no declared type, so we need to infer the type. if (srcExprNode) { reportPossibleUnknownAssignment( - fileInfo.diagnosticSettings.reportUnknownMemberType, + fileInfo.diagnosticRuleSet.reportUnknownMemberType, DiagnosticRule.reportUnknownMemberType, node.memberName, srcType, @@ -2137,7 +2137,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { } else { const fileInfo = getFileInfo(target); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Tuple size mismatch: expected at least ${target.expressions.length} entries but got ${entryCount}`, target @@ -2152,7 +2152,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { } else { const fileInfo = getFileInfo(target); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Tuple size mismatch: expected ${target.expressions.length} but got ${entryCount}`, target @@ -2215,7 +2215,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { } reportPossibleUnknownAssignment( - getFileInfo(target).diagnosticSettings.reportUnknownVariableType, + getFileInfo(target).diagnosticRuleSet.reportUnknownVariableType, DiagnosticRule.reportUnknownVariableType, target, type, @@ -2305,7 +2305,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { default: { const fileInfo = getFileInfo(target); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expression cannot be assignment target`, target @@ -2361,7 +2361,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { default: { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expression cannot be deleted`, node @@ -2543,14 +2543,14 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (isUnbound(type)) { addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${name}" is unbound`, node ); } else if (isPossiblyUnbound(type)) { addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${name}" is possibly unbound`, node @@ -2562,7 +2562,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { // Handle the special case of "reveal_type". if (name !== 'reveal_type') { addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${name}" is not defined`, node @@ -2675,7 +2675,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { } else { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${memberName}" is not a known member of module`, node.memberName @@ -2689,7 +2689,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { type = doForSubtypes(baseType, (subtype) => { if (isNoneOrNever(subtype)) { addDiagnostic( - getFileInfo(node).diagnosticSettings.reportOptionalMemberAccess, + getFileInfo(node).diagnosticRuleSet.reportOptionalMemberAccess, DiagnosticRule.reportOptionalMemberAccess, `"${memberName}" is not a known member of "None"`, node.memberName @@ -2738,7 +2738,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Cannot ${operationName} member "${memberName}" for type "${printType(baseType)}"` + diag.getString(), node.memberName @@ -3177,7 +3177,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (isNoneOrNever(subtype)) { addDiagnostic( - getFileInfo(node).diagnosticSettings.reportOptionalSubscript, + getFileInfo(node).diagnosticRuleSet.reportOptionalSubscript, DiagnosticRule.reportOptionalSubscript, `Object of type "None" cannot be subscripted`, node.baseExpression @@ -3189,7 +3189,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!isUnbound(subtype)) { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Object of type "${printType(subtype)}" cannot be subscripted`, node.baseExpression @@ -3245,7 +3245,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { } else if (usage.method === 'del' && entry.isRequired) { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${entryName}" is a required key and cannot be deleted`, node @@ -3269,7 +3269,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Could not ${operationName} item in TypedDict` + diag.getString(), node @@ -3301,7 +3301,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!itemMethodType) { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Object of type "${printType(baseType)}" does not define "${magicMethodName}"`, node.baseExpression @@ -3575,7 +3575,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (reportError) { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Second argument to "super" call must be object or class that derives from "${printType( targetClassType @@ -3667,7 +3667,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { ) { const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${className}" cannot be instantiated directly`, errorNode @@ -3704,7 +3704,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Cannot instantiate abstract class "${callType.details.name}"` + diagAddendum.getString(), errorNode @@ -3724,7 +3724,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { // as a function rather than a class, so we need to check for it here. if (callType.details.builtInName === 'namedtuple') { addDiagnostic( - getFileInfo(errorNode).diagnosticSettings.reportUntypedNamedTuple, + getFileInfo(errorNode).diagnosticRuleSet.reportUntypedNamedTuple, DiagnosticRule.reportUntypedNamedTuple, `"namedtuple" provides no types for tuple entries; use "NamedTuple" instead`, errorNode @@ -3775,7 +3775,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { ) { if (isTypeSame(castToType, castFromType.classType)) { addDiagnostic( - getFileInfo(errorNode).diagnosticSettings.reportUnnecessaryCast, + getFileInfo(errorNode).diagnosticRuleSet.reportUnnecessaryCast, DiagnosticRule.reportUnnecessaryCast, `Unnecessary "cast" call; type is already ${printType(castFromType)}`, errorNode @@ -3803,7 +3803,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { diagAddendum.addMessage(`Argument types: (${argTypes.join(', ')})`); const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `No overloads for "${exprString}" match parameters` + diagAddendum.getString(), errorNode @@ -3858,7 +3858,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { callType.subtypes.forEach((typeEntry) => { if (isNoneOrNever(typeEntry)) { addDiagnostic( - getFileInfo(errorNode).diagnosticSettings.reportOptionalCall, + getFileInfo(errorNode).diagnosticRuleSet.reportOptionalCall, DiagnosticRule.reportOptionalCall, `Object of type "None" cannot be called`, errorNode @@ -3898,7 +3898,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!type) { const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${ParseTreeUtils.printExpression(errorNode)}" has type "${printType(callType)}" and is not callable`, errorNode @@ -4050,7 +4050,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!validatedTypes && argList.length > 0) { const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expected no arguments to "${type.details.name}" constructor`, errorNode @@ -4164,7 +4164,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { diagAddendum.addMessage(`Argument types: (${argTypes.join(', ')})`); const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `No overloads for "${exprString}" match parameters` + diagAddendum.getString(), errorNode @@ -4179,7 +4179,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { } else { const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${callType.details.name}" cannot be instantiated`, errorNode @@ -4218,7 +4218,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { for (const type of callType.subtypes) { if (isNoneOrNever(type)) { addDiagnostic( - getFileInfo(errorNode).diagnosticSettings.reportOptionalCall, + getFileInfo(errorNode).diagnosticRuleSet.reportOptionalCall, DiagnosticRule.reportOptionalCall, `Object of type "None" cannot be called`, errorNode @@ -4348,7 +4348,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (argIndex < positionalOnlyIndex && argList[argIndex].name) { const fileInfo = getFileInfo(argList[argIndex].name!); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expected positional argument`, argList[argIndex].name! @@ -4360,7 +4360,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { const adjustedCount = positionalParamCount; const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Expected ${adjustedCount} positional ${adjustedCount === 1 ? 'argument' : 'arguments'}`, argList[argIndex].valueExpression || errorNode @@ -4632,7 +4632,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { const optionalParamName = argParam.paramName ? `"${argParam.paramName}" ` : ''; const fileInfo = getFileInfo(argParam.errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Argument of type "${printType( argType @@ -4646,7 +4646,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { const fileInfo = getFileInfo(argParam.errorNode); if (simplifiedType.category === TypeCategory.Unknown) { addDiagnostic( - fileInfo.diagnosticSettings.reportUnknownArgumentType, + fileInfo.diagnosticRuleSet.reportUnknownArgumentType, DiagnosticRule.reportUnknownArgumentType, `Type of argument is unknown`, argParam.errorNode @@ -4663,7 +4663,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { const diagAddendum = new DiagnosticAddendum(); diagAddendum.addMessage(`Argument type is "${printType(simplifiedType)}"`); addDiagnostic( - fileInfo.diagnosticSettings.reportUnknownArgumentType, + fileInfo.diagnosticRuleSet.reportUnknownArgumentType, DiagnosticRule.reportUnknownArgumentType, `Type of argument is partially unknown` + diagAddendum.getString(), argParam.errorNode @@ -5329,7 +5329,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (node.operator !== OperatorType.Not) { if (isOptionalType(exprType)) { addDiagnostic( - getFileInfo(node).diagnosticSettings.reportOptionalOperand, + getFileInfo(node).diagnosticRuleSet.reportOptionalOperand, DiagnosticRule.reportOptionalOperand, `Operator "${ParseTreeUtils.printOperator(node.operator)}" not supported for "None" type`, node.expression @@ -5355,7 +5355,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!type) { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Operator "${ParseTreeUtils.printOperator(node.operator)}" not supported for type "${printType( exprType @@ -5400,7 +5400,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { // None is a valid operand for these operators. if (node.operator !== OperatorType.Equals && node.operator !== OperatorType.NotEquals) { addDiagnostic( - getFileInfo(node).diagnosticSettings.reportOptionalOperand, + getFileInfo(node).diagnosticRuleSet.reportOptionalOperand, DiagnosticRule.reportOptionalOperand, `Operator "${ParseTreeUtils.printOperator(node.operator)}" not supported for "None" type`, node.leftExpression @@ -5591,7 +5591,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!type || type.category === TypeCategory.Never) { const fileInfo = getFileInfo(errorNode); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Operator "${ParseTreeUtils.printOperator(operator)}" not supported for types "${printType( leftType @@ -5877,7 +5877,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { // are the same type, we'll assume that all values in this dictionary should // be the same. if (valueTypes.length > 0) { - if (getFileInfo(node).diagnosticSettings.strictDictionaryInference) { + if (getFileInfo(node).diagnosticRuleSet.strictDictionaryInference) { valueType = combineTypes(valueTypes); } else { valueType = areTypesSame(valueTypes) ? valueTypes[0] : UnknownType.create(); @@ -5946,7 +5946,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (entryTypes.length > 0) { // If there was an expected type or we're using strict list inference, // combine the types into a union. - if (expectedType || getFileInfo(node).diagnosticSettings.strictListInference) { + if (expectedType || getFileInfo(node).diagnosticRuleSet.strictListInference) { inferredEntryType = combineTypes(entryTypes); } else { // Is the list homogeneous? If so, use stricter rules. Otherwise relax the rules. @@ -6160,7 +6160,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!canAssignType(optionalIntObject, exprType, diag)) { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Index for slice operation must be an int value or "None"` + diag.getString(), indexExpr @@ -6778,7 +6778,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { argType = transformTypeObjectToClass(argType); if (argType.category !== TypeCategory.Class) { addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Argument to class must be a base class`, arg @@ -6832,7 +6832,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { argType.subtypes.some((t) => t.category === TypeCategory.Unknown)) ) { addDiagnostic( - fileInfo.diagnosticSettings.reportUntypedBaseClass, + fileInfo.diagnosticRuleSet.reportUntypedBaseClass, DiagnosticRule.reportUntypedBaseClass, `Base class type is unknown, obscuring type of derived class`, arg @@ -6980,7 +6980,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { // Report this error only on the first unknown type. if (!foundUnknown) { addDiagnostic( - fileInfo.diagnosticSettings.reportUntypedClassDecorator, + fileInfo.diagnosticRuleSet.reportUntypedClassDecorator, DiagnosticRule.reportUntypedClassDecorator, `Untyped class decorator obscures type of class; ignoring decorator`, node.decorators[i].leftExpression @@ -7168,7 +7168,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (param.defaultValue.constType === KeywordType.None) { isNoneWithoutOptional = true; - if (!fileInfo.diagnosticSettings.strictParameterNoneValue) { + if (!fileInfo.diagnosticRuleSet.strictParameterNoneValue) { annotatedType = combineTypes([annotatedType, NoneType.create()]); } } @@ -7194,7 +7194,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!canAssignType(concreteAnnotatedType, defaultValueType, diagAddendum)) { const diag = addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Value of type "${printType( defaultValueType @@ -7276,7 +7276,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { // Report this error only on the first unknown type. if (!foundUnknown) { addDiagnostic( - fileInfo.diagnosticSettings.reportUntypedFunctionDecorator, + fileInfo.diagnosticRuleSet.reportUntypedFunctionDecorator, DiagnosticRule.reportUntypedFunctionDecorator, `Untyped function decorator obscures type of function; ignoring decorator`, node.decorators[i].leftExpression @@ -7933,7 +7933,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (isOptionalType(exprType)) { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportOptionalContextManager, + fileInfo.diagnosticRuleSet.reportOptionalContextManager, DiagnosticRule.reportOptionalContextManager, `Object of type "None" cannot be used with "with"`, node.expression @@ -7997,7 +7997,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { const fileInfo = getFileInfo(node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Object of type "${printType( subtype @@ -8083,7 +8083,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { !importLookupInfo.symbolTable.get('__getattr__') ) { addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `"${node.name.value}" is unknown import symbol`, node.name @@ -9428,7 +9428,7 @@ export function createTypeEvaluator(importLookup: ImportLookup): TypeEvaluator { if (!canAssignToTypeVar(typeParameters[index], typeArgType, diag)) { const fileInfo = getFileInfo(typeArgs![index].node); addDiagnostic( - fileInfo.diagnosticSettings.reportGeneralTypeIssues, + fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues, `Type "${printType(typeArgType)}" cannot be assigned to TypeVar "${ typeParameters[index].name diff --git a/server/src/common/commandLineOptions.ts b/server/src/common/commandLineOptions.ts index 6451a51dc..34cc41040 100644 --- a/server/src/common/commandLineOptions.ts +++ b/server/src/common/commandLineOptions.ts @@ -61,6 +61,10 @@ export class CommandLineOptions { // execution environments. autoSearchPaths?: boolean; + // Default type-checking rule set. Should be one of 'off', + // 'basic', or 'strict'. + typeCheckingMode?: string; + // Indicates that the settings came from VS Code rather than // from the command-line. Useful for providing clearer error // messages. diff --git a/server/src/common/configOptions.ts b/server/src/common/configOptions.ts index 731d474d2..82cd968e9 100644 --- a/server/src/common/configOptions.ts +++ b/server/src/common/configOptions.ts @@ -43,7 +43,7 @@ export class ExecutionEnvironment { export type DiagnosticLevel = 'none' | 'warning' | 'error'; -export interface DiagnosticSettings { +export interface DiagnosticRuleSet { // Use strict inference rules for list expressions? strictListInference: boolean; @@ -167,12 +167,12 @@ export interface DiagnosticSettings { reportImplicitStringConcatenation: DiagnosticLevel; } -export function cloneDiagnosticSettings(diagSettings: DiagnosticSettings): DiagnosticSettings { +export function cloneDiagnosticRuleSet(diagSettings: DiagnosticRuleSet): DiagnosticRuleSet { // Create a shallow copy of the existing object. return Object.assign({}, diagSettings); } -export function getBooleanDiagnosticSettings() { +export function getBooleanDiagnosticRules() { return [ DiagnosticRule.strictListInference, DiagnosticRule.strictDictionaryInference, @@ -185,7 +185,7 @@ export function getBooleanDiagnosticSettings() { ]; } -export function getDiagLevelSettings() { +export function getDiagLevelDiagnosticRules() { return [ DiagnosticRule.reportGeneralTypeIssues, DiagnosticRule.reportTypeshedErrors, @@ -225,8 +225,8 @@ export function getDiagLevelSettings() { ]; } -export function getStrictDiagnosticSettings(): DiagnosticSettings { - const diagSettings: DiagnosticSettings = { +export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { + const diagSettings: DiagnosticRuleSet = { strictListInference: true, strictDictionaryInference: true, strictParameterNoneValue: true, @@ -271,8 +271,54 @@ export function getStrictDiagnosticSettings(): DiagnosticSettings { return diagSettings; } -export function getDefaultDiagnosticSettings(): DiagnosticSettings { - const diagSettings: DiagnosticSettings = { +export function getNoTypeCheckingDiagnosticRuleSet(): DiagnosticRuleSet { + const diagSettings: DiagnosticRuleSet = { + strictListInference: false, + strictDictionaryInference: false, + strictParameterNoneValue: false, + enableTypeIgnoreComments: true, + reportGeneralTypeIssues: 'none', + reportTypeshedErrors: 'none', + reportMissingImports: 'none', + reportMissingTypeStubs: 'none', + reportImportCycles: 'none', + reportUnusedImport: 'none', + reportUnusedClass: 'none', + reportUnusedFunction: 'none', + reportUnusedVariable: 'none', + reportDuplicateImport: 'none', + reportOptionalSubscript: 'none', + reportOptionalMemberAccess: 'none', + reportOptionalCall: 'none', + reportOptionalIterable: 'none', + reportOptionalContextManager: 'none', + reportOptionalOperand: 'none', + reportUntypedFunctionDecorator: 'none', + reportUntypedClassDecorator: 'none', + reportUntypedBaseClass: 'none', + reportUntypedNamedTuple: 'none', + reportPrivateUsage: 'none', + reportConstantRedefinition: 'none', + reportIncompatibleMethodOverride: 'none', + reportInvalidStringEscapeSequence: 'none', + reportUnknownParameterType: 'none', + reportUnknownArgumentType: 'none', + reportUnknownLambdaType: 'none', + reportUnknownVariableType: 'none', + reportUnknownMemberType: 'none', + reportCallInDefaultInitializer: 'none', + reportUnnecessaryIsInstance: 'none', + reportUnnecessaryCast: 'none', + reportAssertAlwaysTrue: 'none', + reportSelfClsParameterName: 'none', + reportImplicitStringConcatenation: 'none', + }; + + return diagSettings; +} + +export function getDefaultDiagnosticRuleSet(): DiagnosticRuleSet { + const diagSettings: DiagnosticRuleSet = { strictListInference: false, strictDictionaryInference: false, strictParameterNoneValue: false, @@ -320,9 +366,9 @@ export function getDefaultDiagnosticSettings(): DiagnosticSettings { // Internal configuration options. These are derived from a combination // of the command line and from a JSON-based config file. export class ConfigOptions { - constructor(projectRoot: string) { + constructor(projectRoot: string, typeCheckingMode?: string) { this.projectRoot = projectRoot; - this.diagnosticSettings = getDefaultDiagnosticSettings(); + this.diagnosticRuleSet = ConfigOptions.getDiagnosticRuleSet(typeCheckingMode); } // Absolute directory of project. All relative paths in the config @@ -374,9 +420,9 @@ export class ConfigOptions { useLibraryCodeForTypes: boolean; //--------------------------------------------------------------- - // Diagnostics Settings + // Diagnostics Rule Set - diagnosticSettings: DiagnosticSettings; + diagnosticRuleSet: DiagnosticRuleSet; //--------------------------------------------------------------- // Parsing and Import Resolution Settings @@ -406,6 +452,18 @@ export class ConfigOptions { // Run additional analysis as part of test cases? internalTestMode?: boolean; + static getDiagnosticRuleSet(typeCheckingMode?: string): DiagnosticRuleSet { + if (typeCheckingMode === 'strict') { + return getStrictDiagnosticRuleSet(); + } + + if (typeCheckingMode === 'off') { + return getNoTypeCheckingDiagnosticRuleSet(); + } + + return getDefaultDiagnosticRuleSet(); + } + // Finds the best execution environment for a given file path. The // specified file path should be absolute. // If no matching execution environment can be found, a default @@ -440,7 +498,7 @@ export class ConfigOptions { } // Initialize the structure from a JSON object. - initializeFromJson(configObj: any, console: ConsoleInterface) { + initializeFromJson(configObj: any, typeCheckingMode: string | undefined, console: ConsoleInterface) { // Read the "include" entry. this.include = []; if (configObj.include !== undefined) { @@ -517,9 +575,23 @@ export class ConfigOptions { } } - const defaultSettings = getDefaultDiagnosticSettings(); + // If there is a "typeCheckingMode", it can override the provided setting. + let configTypeCheckingMode: string | undefined; + if (configObj.typeCheckingMode !== undefined) { + if ( + configObj.typeCheckingMode === 'off' || + configObj.typeCheckingMode === 'basic' || + configObj.typeCheckingMode === 'strict' + ) { + configTypeCheckingMode = configObj.typeCheckingMode; + } else { + console.log(`Config "typeCheckingMode" entry must contain "off", "basic", or "strict".`); + } + } - this.diagnosticSettings = { + const defaultSettings = ConfigOptions.getDiagnosticRuleSet(configTypeCheckingMode || typeCheckingMode); + + this.diagnosticRuleSet = { // Use strict inference rules for list expressions? strictListInference: this._convertBoolean( configObj.strictListInference, diff --git a/server/src/languageServerBase.ts b/server/src/languageServerBase.ts index 6b9ae211a..3ca63b77e 100644 --- a/server/src/languageServerBase.ts +++ b/server/src/languageServerBase.ts @@ -67,6 +67,7 @@ export interface ServerSettings { pythonPath?: string; typeshedPath?: string; openFilesOnly?: boolean; + typeCheckingMode?: string; useLibraryCodeForTypes?: boolean; disableLanguageServices?: boolean; autoSearchPaths?: boolean; diff --git a/server/src/languageService/analyzerServiceExecutor.ts b/server/src/languageService/analyzerServiceExecutor.ts index e6707dc66..24ade9eaf 100644 --- a/server/src/languageService/analyzerServiceExecutor.ts +++ b/server/src/languageService/analyzerServiceExecutor.ts @@ -40,6 +40,7 @@ function _getCommandLineOptions( commandLineOptions.checkOnlyOpenFiles = serverSettings.openFilesOnly; commandLineOptions.useLibraryCodeForTypes = serverSettings.useLibraryCodeForTypes; commandLineOptions.watchForLibraryChanges = serverSettings.watchForLibraryChanges; + commandLineOptions.typeCheckingMode = serverSettings.typeCheckingMode; // Disable watching of source files in the VS Code extension if we're // analyzing only open files. The file system watcher code has caused diff --git a/server/src/server.ts b/server/src/server.ts index a41bb32b3..0f9992e5f 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -46,10 +46,12 @@ class PyrightServer extends LanguageServerBase { serverSettings.openFilesOnly = !!pyrightSection.openFilesOnly; serverSettings.useLibraryCodeForTypes = !!pyrightSection.useLibraryCodeForTypes; serverSettings.disableLanguageServices = !!pyrightSection.disableLanguageServices; + serverSettings.typeCheckingMode = pyrightSection.typeCheckingMode; } else { serverSettings.openFilesOnly = true; serverSettings.useLibraryCodeForTypes = false; serverSettings.disableLanguageServices = false; + serverSettings.typeCheckingMode = undefined; } } catch (error) { this.console.log(`Error reading settings: ${error}`); diff --git a/server/src/tests/checker.test.ts b/server/src/tests/checker.test.ts index 2335e76c4..74f635b0d 100644 --- a/server/src/tests/checker.test.ts +++ b/server/src/tests/checker.test.ts @@ -496,22 +496,22 @@ test('Optional1', () => { validateResults(analysisResults, 0); // Turn on warnings. - configOptions.diagnosticSettings.reportOptionalSubscript = 'warning'; - configOptions.diagnosticSettings.reportOptionalMemberAccess = 'warning'; - configOptions.diagnosticSettings.reportOptionalCall = 'warning'; - configOptions.diagnosticSettings.reportOptionalIterable = 'warning'; - configOptions.diagnosticSettings.reportOptionalContextManager = 'warning'; - configOptions.diagnosticSettings.reportOptionalOperand = 'warning'; + configOptions.diagnosticRuleSet.reportOptionalSubscript = 'warning'; + configOptions.diagnosticRuleSet.reportOptionalMemberAccess = 'warning'; + configOptions.diagnosticRuleSet.reportOptionalCall = 'warning'; + configOptions.diagnosticRuleSet.reportOptionalIterable = 'warning'; + configOptions.diagnosticRuleSet.reportOptionalContextManager = 'warning'; + configOptions.diagnosticRuleSet.reportOptionalOperand = 'warning'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['optional1.py'], configOptions); validateResults(analysisResults, 0, 7); // Turn on errors. - configOptions.diagnosticSettings.reportOptionalSubscript = 'error'; - configOptions.diagnosticSettings.reportOptionalMemberAccess = 'error'; - configOptions.diagnosticSettings.reportOptionalCall = 'error'; - configOptions.diagnosticSettings.reportOptionalIterable = 'error'; - configOptions.diagnosticSettings.reportOptionalContextManager = 'error'; - configOptions.diagnosticSettings.reportOptionalOperand = 'error'; + configOptions.diagnosticRuleSet.reportOptionalSubscript = 'error'; + configOptions.diagnosticRuleSet.reportOptionalMemberAccess = 'error'; + configOptions.diagnosticRuleSet.reportOptionalCall = 'error'; + configOptions.diagnosticRuleSet.reportOptionalIterable = 'error'; + configOptions.diagnosticRuleSet.reportOptionalContextManager = 'error'; + configOptions.diagnosticRuleSet.reportOptionalOperand = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['optional1.py'], configOptions); validateResults(analysisResults, 7); }); @@ -524,7 +524,7 @@ test('Private1', () => { validateResults(analysisResults, 0); // Turn on errors. - configOptions.diagnosticSettings.reportPrivateUsage = 'error'; + configOptions.diagnosticRuleSet.reportPrivateUsage = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['private1.py'], configOptions); validateResults(analysisResults, 4); }); @@ -537,7 +537,7 @@ test('Constant1', () => { validateResults(analysisResults, 0); // Turn on errors. - configOptions.diagnosticSettings.reportConstantRedefinition = 'error'; + configOptions.diagnosticRuleSet.reportConstantRedefinition = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['constant1.py'], configOptions); validateResults(analysisResults, 5); }); @@ -766,7 +766,7 @@ test('Classes2', () => { validateResults(analysisResults, 0); // Turn on errors. - configOptions.diagnosticSettings.reportIncompatibleMethodOverride = 'error'; + configOptions.diagnosticRuleSet.reportIncompatibleMethodOverride = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['classes2.py'], configOptions); validateResults(analysisResults, 2); }); @@ -851,7 +851,7 @@ test('DefaultInitializer1', () => { validateResults(analysisResults, 0); // Turn on errors. - configOptions.diagnosticSettings.reportCallInDefaultInitializer = 'error'; + configOptions.diagnosticRuleSet.reportCallInDefaultInitializer = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions); validateResults(analysisResults, 2); }); @@ -881,7 +881,7 @@ test('UnnecessaryIsInstance1', () => { validateResults(analysisResults, 1); // Turn on errors. - configOptions.diagnosticSettings.reportUnnecessaryIsInstance = 'error'; + configOptions.diagnosticRuleSet.reportUnnecessaryIsInstance = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsInstance1.py'], configOptions); validateResults(analysisResults, 4); }); @@ -893,7 +893,7 @@ test('UnnecessaryIsSubclass1', () => { validateResults(analysisResults, 0); // Turn on errors. - configOptions.diagnosticSettings.reportUnnecessaryIsInstance = 'error'; + configOptions.diagnosticRuleSet.reportUnnecessaryIsInstance = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsSubclass1.py'], configOptions); validateResults(analysisResults, 3); }); @@ -911,7 +911,7 @@ test('UnnecessaryCast', () => { validateResults(analysisResults, 0); // Turn on errors. - configOptions.diagnosticSettings.reportUnnecessaryCast = 'error'; + configOptions.diagnosticRuleSet.reportUnnecessaryCast = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryCast1.py'], configOptions); validateResults(analysisResults, 1); }); @@ -924,12 +924,12 @@ test('AssertAlwaysTrue', () => { validateResults(analysisResults, 0, 1); // Enable it as an error. - configOptions.diagnosticSettings.reportAssertAlwaysTrue = 'error'; + configOptions.diagnosticRuleSet.reportAssertAlwaysTrue = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['assert1.py'], configOptions); validateResults(analysisResults, 1, 0); // Turn off the diagnostic. - configOptions.diagnosticSettings.reportAssertAlwaysTrue = 'none'; + configOptions.diagnosticRuleSet.reportAssertAlwaysTrue = 'none'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['assert1.py'], configOptions); validateResults(analysisResults, 0, 0); }); @@ -1169,7 +1169,7 @@ test('TypeIgnore1', () => { validateResults(analysisResults, 0); // Disable type ignore - configOptions.diagnosticSettings.enableTypeIgnoreComments = false; + configOptions.diagnosticRuleSet.enableTypeIgnoreComments = false; analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore1.py'], configOptions); validateResults(analysisResults, 2); }); @@ -1181,7 +1181,7 @@ test('TypeIgnore2', () => { validateResults(analysisResults, 0); // Disable type ignore - configOptions.diagnosticSettings.enableTypeIgnoreComments = false; + configOptions.diagnosticRuleSet.enableTypeIgnoreComments = false; analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeIgnore2.py'], configOptions); validateResults(analysisResults, 3); }); @@ -1289,7 +1289,7 @@ test('DuplicateImports1', () => { validateResults(analysisResults, 0); // Turn on errors. - configOptions.diagnosticSettings.reportDuplicateImport = 'error'; + configOptions.diagnosticRuleSet.reportDuplicateImport = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['duplicateImports1.py'], configOptions); validateResults(analysisResults, 2); }); @@ -1320,11 +1320,11 @@ test('ParamName1', () => { let analysisResults = TestUtils.typeAnalyzeSampleFiles(['paramNames1.py'], configOptions); validateResults(analysisResults, 0, 4); - configOptions.diagnosticSettings.reportSelfClsParameterName = 'none'; + configOptions.diagnosticRuleSet.reportSelfClsParameterName = 'none'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['paramNames1.py'], configOptions); validateResults(analysisResults, 0, 0); - configOptions.diagnosticSettings.reportSelfClsParameterName = 'error'; + configOptions.diagnosticRuleSet.reportSelfClsParameterName = 'error'; analysisResults = TestUtils.typeAnalyzeSampleFiles(['paramNames1.py'], configOptions); validateResults(analysisResults, 4, 0); }); diff --git a/server/src/tests/config.test.ts b/server/src/tests/config.test.ts index 2f4676448..7ca086ea8 100644 --- a/server/src/tests/config.test.ts +++ b/server/src/tests/config.test.ts @@ -179,7 +179,7 @@ test('PythonPlatform', () => { "extraPaths" : [] }]}`); - configOptions.initializeFromJson(json, nullConsole); + configOptions.initializeFromJson(json, undefined, nullConsole); const env = configOptions.executionEnvironments[0]; assert.equal(env.pythonPlatform, 'platform'); diff --git a/server/src/tests/harness/fourslash/testState.ts b/server/src/tests/harness/fourslash/testState.ts index 3d0cf626c..72b377935 100644 --- a/server/src/tests/harness/fourslash/testState.ts +++ b/server/src/tests/harness/fourslash/testState.ts @@ -107,7 +107,7 @@ export class TestState { throw new Error(`Failed to parse test ${file.fileName}: ${e.message}`); } - configOptions.initializeFromJson(configJson, nullConsole); + configOptions.initializeFromJson(configJson, 'basic', nullConsole); this._applyTestConfigOptions(configOptions); } else { files[file.fileName] = new vfs.File(file.content, { meta: file.fileOptions, encoding: 'utf8' }); diff --git a/server/src/tests/testState.test.ts b/server/src/tests/testState.test.ts index bdb52ceed..fa6902c6d 100644 --- a/server/src/tests/testState.test.ts +++ b/server/src/tests/testState.test.ts @@ -118,7 +118,7 @@ test('Configuration', () => { assert.equal(state.fs.cwd(), normalizeSlashes('/')); assert(state.fs.existsSync(normalizeSlashes(combinePaths(factory.srcFolder, 'file1.py')))); - assert.equal(state.configOptions.diagnosticSettings.reportMissingImports, 'error'); + assert.equal(state.configOptions.diagnosticRuleSet.reportMissingImports, 'error'); assert.equal(state.configOptions.typingsPath, normalizeSlashes('/src/typestubs')); }); diff --git a/server/src/tests/testUtils.ts b/server/src/tests/testUtils.ts index 4498852fa..c62917307 100644 --- a/server/src/tests/testUtils.ts +++ b/server/src/tests/testUtils.ts @@ -15,7 +15,7 @@ import { Binder } from '../analyzer/binder'; import { ImportResolver } from '../analyzer/importResolver'; import { Program } from '../analyzer/program'; import { TestWalker } from '../analyzer/testWalker'; -import { cloneDiagnosticSettings, ConfigOptions, ExecutionEnvironment } from '../common/configOptions'; +import { cloneDiagnosticRuleSet, ConfigOptions, ExecutionEnvironment } from '../common/configOptions'; import { fail } from '../common/debug'; import { Diagnostic, DiagnosticCategory } from '../common/diagnostic'; import { DiagnosticSink, TextRangeDiagnosticSink } from '../common/diagnosticSink'; @@ -96,7 +96,7 @@ export function buildAnalyzerFileInfo( builtinsScope: undefined, diagnosticSink: analysisDiagnostics, executionEnvironment: configOptions.findExecEnvironment(filePath), - diagnosticSettings: cloneDiagnosticSettings(configOptions.diagnosticSettings), + diagnosticRuleSet: cloneDiagnosticRuleSet(configOptions.diagnosticRuleSet), fileContents, lines: parseResults.tokenizerOutput.lines, filePath,