Added new diagnostic check "reportMissingParameterType" that checks for function and method input parameters that are missing a type annotation.

This commit is contained in:
Eric Traut 2021-10-06 21:55:12 -07:00
parent ca2c251fff
commit ff55b895f9
12 changed files with 87 additions and 4 deletions

View File

@ -126,6 +126,8 @@ The following settings control pyrights 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" |

View File

@ -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
);
}
}
}

View File

@ -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,

View File

@ -54,6 +54,7 @@ export enum DiagnosticRule {
reportUnknownLambdaType = 'reportUnknownLambdaType',
reportUnknownVariableType = 'reportUnknownVariableType',
reportUnknownMemberType = 'reportUnknownMemberType',
reportMissingParameterType = 'reportMissingParameterType',
reportMissingTypeArgument = 'reportMissingTypeArgument',
reportInvalidTypeVarUse = 'reportInvalidTypeVarUse',
reportCallInDefaultInitializer = 'reportCallInDefaultInitializer',

View File

@ -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');

View File

@ -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",

View File

@ -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())

View File

@ -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

View File

@ -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
...

View File

@ -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);
});

View File

@ -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.",

View File

@ -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",