diff --git a/docs/configuration.md b/docs/configuration.md index 58deddef1..c91263833 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -126,6 +126,8 @@ The following settings control pyright’s diagnostic output (warnings or errors **reportUnknownMemberType** [boolean or string, optional]: Generate or suppress diagnostics for class or instance variables that have an unknown type. The default value for this setting is 'none'. +**reportMissingParameterType** [boolean or string, optional]: Generate or suppress diagnostics for input parameters for functions or methods that are missing a type annotation. The 'self' and 'cls' parameters used within methods are exempt from this check. The default value for this setting is 'none'. + **reportMissingTypeArgument** [boolean or string, optional]: Generate or suppress diagnostics when a generic class is used without providing explicit or implicit type arguments. The default value for this setting is 'none'. **reportInvalidTypeVarUse** [boolean or string, optional]: Generate or suppress diagnostics when a TypeVar is used inappropriately (e.g. if a TypeVar appears only once) within a generic function signature. The default value for this setting is 'none'. @@ -308,6 +310,7 @@ The following table lists the default severity levels for each diagnostic rule w | reportUnknownLambdaType | "none" | "none" | "error" | | reportUnknownVariableType | "none" | "none" | "error" | | reportUnknownMemberType | "none" | "none" | "error" | +| reportMissingParameterType | "none" | "none" | "error" | | reportMissingTypeArgument | "none" | "none" | "error" | | reportInvalidTypeVarUse | "none" | "warning" | "error" | | reportCallInDefaultInitializer | "none" | "none" | "none" | diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index a3e486b3d..3191e03e4 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -340,7 +340,7 @@ export class Checker extends ParseTreeWalker { // parameters after this need to be flagged as an error. let sawParamSpecArgs = false; - // Report any unknown parameter types. + // Report any unknown or missing parameter types. node.parameters.forEach((param, index) => { if (param.name) { // Determine whether this is a P.args parameter. @@ -370,7 +370,7 @@ export class Checker extends ParseTreeWalker { ); } - // Allow unknown param types if the param is named '_'. + // Allow unknown and missing param types if the param is named '_'. if (param.name && param.name.value !== '_') { if (index < functionTypeResult.functionType.details.parameters.length) { const paramType = functionTypeResult.functionType.details.parameters[index].type; @@ -403,6 +403,26 @@ export class Checker extends ParseTreeWalker { param.name ); } + + let hasAnnotation = false; + + if (functionTypeResult.functionType.details.parameters[index].typeAnnotation) { + hasAnnotation = true; + } else { + // See if this is a "self" and "cls" parameter. They are exempt from this rule. + if (isTypeVar(paramType) && paramType.details.isSynthesizedSelfCls) { + hasAnnotation = true; + } + } + + if (!hasAnnotation) { + this._evaluator.addDiagnostic( + this._fileInfo.diagnosticRuleSet.reportMissingParameterType, + DiagnosticRule.reportMissingParameterType, + Localizer.Diagnostic.paramAnnotationMissing().format({ name: param.name.value }), + param.name + ); + } } } diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index f2628108b..e68908d6f 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -214,6 +214,9 @@ export interface DiagnosticRuleSet { // Report usage of unknown input or return parameters? reportUnknownMemberType: DiagnosticLevel; + // Report input parameters that are missing type annotations? + reportMissingParameterType: DiagnosticLevel; + // Report usage of generic class without explicit type arguments? reportMissingTypeArgument: DiagnosticLevel; @@ -328,6 +331,7 @@ export function getDiagLevelDiagnosticRules() { DiagnosticRule.reportUnknownLambdaType, DiagnosticRule.reportUnknownVariableType, DiagnosticRule.reportUnknownMemberType, + DiagnosticRule.reportMissingParameterType, DiagnosticRule.reportMissingTypeArgument, DiagnosticRule.reportInvalidTypeVarUse, DiagnosticRule.reportCallInDefaultInitializer, @@ -402,6 +406,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet { reportUnknownLambdaType: 'none', reportUnknownVariableType: 'none', reportUnknownMemberType: 'none', + reportMissingParameterType: 'none', reportMissingTypeArgument: 'none', reportInvalidTypeVarUse: 'none', reportCallInDefaultInitializer: 'none', @@ -472,6 +477,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet { reportUnknownLambdaType: 'none', reportUnknownVariableType: 'none', reportUnknownMemberType: 'none', + reportMissingParameterType: 'none', reportMissingTypeArgument: 'none', reportInvalidTypeVarUse: 'warning', reportCallInDefaultInitializer: 'none', @@ -542,6 +548,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { reportUnknownLambdaType: 'error', reportUnknownVariableType: 'error', reportUnknownMemberType: 'error', + reportMissingParameterType: 'error', reportMissingTypeArgument: 'error', reportInvalidTypeVarUse: 'error', reportCallInDefaultInitializer: 'none', @@ -1155,6 +1162,13 @@ export class ConfigOptions { defaultSettings.reportUnknownMemberType ), + // Read the "reportMissingParameterType" entry. + reportMissingParameterType: this._convertDiagnosticLevel( + configObj.reportMissingParameterType, + DiagnosticRule.reportMissingParameterType, + defaultSettings.reportMissingParameterType + ), + // Read the "reportMissingTypeArgument" entry. reportMissingTypeArgument: this._convertDiagnosticLevel( configObj.reportMissingTypeArgument, diff --git a/packages/pyright-internal/src/common/diagnosticRules.ts b/packages/pyright-internal/src/common/diagnosticRules.ts index 98a56da85..5bea80c81 100644 --- a/packages/pyright-internal/src/common/diagnosticRules.ts +++ b/packages/pyright-internal/src/common/diagnosticRules.ts @@ -54,6 +54,7 @@ export enum DiagnosticRule { reportUnknownLambdaType = 'reportUnknownLambdaType', reportUnknownVariableType = 'reportUnknownVariableType', reportUnknownMemberType = 'reportUnknownMemberType', + reportMissingParameterType = 'reportMissingParameterType', reportMissingTypeArgument = 'reportMissingTypeArgument', reportInvalidTypeVarUse = 'reportInvalidTypeVarUse', reportCallInDefaultInitializer = 'reportCallInDefaultInitializer', diff --git a/packages/pyright-internal/src/localization/localize.ts b/packages/pyright-internal/src/localization/localize.ts index 252c6f326..53a88e85b 100644 --- a/packages/pyright-internal/src/localization/localize.ts +++ b/packages/pyright-internal/src/localization/localize.ts @@ -550,6 +550,8 @@ export namespace Localizer { export const paramAfterKwargsParam = () => getRawString('Diagnostic.paramAfterKwargsParam'); export const paramAlreadyAssigned = () => new ParameterizedString<{ name: string }>(getRawString('Diagnostic.paramAlreadyAssigned')); + export const paramAnnotationMissing = () => + new ParameterizedString<{ name: string }>(getRawString('Diagnostic.paramAnnotationMissing')); export const paramNameMissing = () => new ParameterizedString<{ name: string }>(getRawString('Diagnostic.paramNameMissing')); export const paramSpecArgsKwargsUsage = () => getRawString('Diagnostic.paramSpecArgsKwargsUsage'); diff --git a/packages/pyright-internal/src/localization/package.nls.en-us.json b/packages/pyright-internal/src/localization/package.nls.en-us.json index dd24769e3..2015f73d7 100644 --- a/packages/pyright-internal/src/localization/package.nls.en-us.json +++ b/packages/pyright-internal/src/localization/package.nls.en-us.json @@ -255,6 +255,7 @@ "overloadWithoutImplementation": "\"{name}\" is marked as overload, but no implementation is provided", "paramAfterKwargsParam": "Parameter cannot follow \"**\" parameter", "paramAlreadyAssigned": "Parameter \"{name}\" is already assigned", + "paramAnnotationMissing": "Type annotation is missing for parameter \"{name}\"", "paramNameMissing": "No parameter named \"{name}\"", "paramSpecArgsKwargsUsage": "\"args\" and \"kwargs\" members of ParamSpec must both appear within a function signature", "paramSpecArgsUsage": "\"args\" member of ParamSpec is valid only when used with *args parameter", diff --git a/packages/pyright-internal/src/tests/samples/functionAnnotation1.py b/packages/pyright-internal/src/tests/samples/functionAnnotation1.py index 5f49d9d0c..8d4829dcf 100644 --- a/packages/pyright-internal/src/tests/samples/functionAnnotation1.py +++ b/packages/pyright-internal/src/tests/samples/functionAnnotation1.py @@ -1,6 +1,6 @@ # This sample tests support for comment-style function annotations. -# pyright: strict +# pyright: strict, reportMissingParameterType=false from typing import Optional @@ -47,6 +47,7 @@ def func1f(a): class Foo: pass + def func1g(*args, **kwargs): # type: (*int, **float) -> int return sum(args) + sum(round(kwarg) for kwarg in kwargs.values()) diff --git a/packages/pyright-internal/src/tests/samples/genericTypes20.py b/packages/pyright-internal/src/tests/samples/genericTypes20.py index a8f599f64..b4acc93a7 100644 --- a/packages/pyright-internal/src/tests/samples/genericTypes20.py +++ b/packages/pyright-internal/src/tests/samples/genericTypes20.py @@ -4,7 +4,7 @@ # We use "strict" here because we want to ensure that there are # no "unknown" types remaining in this file. -# pyright: strict, reportUnknownParameterType=false +# pyright: strict, reportUnknownParameterType=false, reportMissingParameterType=false from logging import Handler, NOTSET diff --git a/packages/pyright-internal/src/tests/samples/parameters1.py b/packages/pyright-internal/src/tests/samples/parameters1.py new file mode 100644 index 000000000..fde1c8d27 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/parameters1.py @@ -0,0 +1,12 @@ +# This sample tests the reportMissingParameterType check. + + +class A: + # This should generate an error if reportMissingParameterType is enabled + # because 'y' is missing a type annotation. + def method1(self, x: int, _, y) -> int: + ... + + def method2(self, x, y): + # type: (int, int) -> int + ... diff --git a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts index e9f4caf58..44d168513 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts @@ -1006,3 +1006,15 @@ test('Slots2', () => { TestUtils.validateResults(analysisResults, 3); }); + +test('Parameters1', () => { + const configOptions = new ConfigOptions('.'); + + configOptions.diagnosticRuleSet.reportMissingParameterType = 'none'; + const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['parameters1.py'], configOptions); + TestUtils.validateResults(analysisResults1, 0); + + configOptions.diagnosticRuleSet.reportMissingParameterType = 'error'; + const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['parameters1.py'], configOptions); + TestUtils.validateResults(analysisResults2, 1); +}); diff --git a/packages/vscode-pyright/package.json b/packages/vscode-pyright/package.json index 234d241f8..09241ddaa 100644 --- a/packages/vscode-pyright/package.json +++ b/packages/vscode-pyright/package.json @@ -520,6 +520,17 @@ "error" ] }, + "reportMissingParameterType": { + "type": "string", + "description": "Diagnostics for parameters that are missing a type annotation.", + "default": "none", + "enum": [ + "none", + "information", + "warning", + "error" + ] + }, "reportMissingTypeArgument": { "type": "string", "description": "Diagnostics for generic class reference with missing type arguments.", diff --git a/packages/vscode-pyright/schemas/pyrightconfig.schema.json b/packages/vscode-pyright/schemas/pyrightconfig.schema.json index 26224b9d1..01b164544 100644 --- a/packages/vscode-pyright/schemas/pyrightconfig.schema.json +++ b/packages/vscode-pyright/schemas/pyrightconfig.schema.json @@ -353,6 +353,12 @@ "title": "Controls reporting class and instance variables whose types are unknown", "default": "none" }, + "reportMissingParameterType": { + "$id": "#/properties/reportMissingParameterType", + "$ref": "#/definitions/diagnostic", + "title": "Controls reporting input parameters that are missing a type annotation", + "default": "none" + }, "reportMissingTypeArgument": { "$id": "#/properties/reportMissingTypeArgument", "$ref": "#/definitions/diagnostic",