mirror of
https://github.com/microsoft/pyright.git
synced 2024-08-16 19:30:26 +03:00
Added new rule "reportCallInDefaultInitializer" that reports usage of function calls within default value initialization expressions.
This commit is contained in:
parent
7146c5ced6
commit
85a3eaf63c
@ -234,6 +234,12 @@
|
||||
"title": "Controls reporting class and instance variables whose types are unknown",
|
||||
"default": "none"
|
||||
},
|
||||
"reportCallInDefaultInitializer": {
|
||||
"$id": "#/properties/reportCallInDefaultInitializer",
|
||||
"$ref": "#/definitions/diagnostic",
|
||||
"title": "Controls reporting usage of function calls within a default value initializer expression",
|
||||
"default": "none"
|
||||
},
|
||||
"pythonVersion": {
|
||||
"$id": "#/properties/pythonVersion",
|
||||
"type": "string",
|
||||
|
@ -84,6 +84,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'.
|
||||
|
||||
**reportCallInDefaultInitializer** [boolean or string, optional]: Generate or suppress diagnostics for function calls within a default value initialization expression. Such calls can mask expensive operations that are performed at module initialization time. The default value for this setting is 'none'.
|
||||
|
||||
|
||||
## Execution Environment Options
|
||||
Pyright allows multiple “execution environments” to be defined for different portions of your source tree. For example, a subtree may be designed to run with different import search paths or a different version of the python interpreter than the rest of the source base.
|
||||
|
@ -17,9 +17,11 @@ export class CommentUtils {
|
||||
static getFileLevelDirectives(tokens: TextRangeCollection<Token>,
|
||||
defaultSettings: DiagnosticSettings, useStrict: boolean): DiagnosticSettings {
|
||||
|
||||
let settings = useStrict ?
|
||||
getStrictDiagnosticSettings() :
|
||||
cloneDiagnosticSettings(defaultSettings);
|
||||
let settings = cloneDiagnosticSettings(defaultSettings);
|
||||
|
||||
if (useStrict) {
|
||||
this._applyStrictSettings(settings);
|
||||
}
|
||||
|
||||
for (let i = 0; i < tokens.count; i++) {
|
||||
const token = tokens.getItemAt(i);
|
||||
@ -35,6 +37,28 @@ export class CommentUtils {
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static _applyStrictSettings(settings: DiagnosticSettings) {
|
||||
const strictSettings = getStrictDiagnosticSettings();
|
||||
const boolSettingNames = getBooleanDiagnosticSettings();
|
||||
const diagSettingNames = getDiagLevelSettings();
|
||||
|
||||
// Enable the strict settings as appropriate.
|
||||
for (const setting of boolSettingNames) {
|
||||
if ((strictSettings as any)[setting]) {
|
||||
(settings as any)[setting] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const setting of diagSettingNames) {
|
||||
const strictValue: DiagnosticLevel = (strictSettings as any)[setting];
|
||||
const prevValue: DiagnosticLevel = (settings as any)[setting];
|
||||
|
||||
if (strictValue === 'error' || (strictValue === 'warning' && prevValue !== 'error')) {
|
||||
(settings as any)[setting] = strictValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static _parsePyrightComment(commentValue: string, settings: DiagnosticSettings) {
|
||||
// Is this a pyright-specific comment?
|
||||
const pyrightPrefix = 'pyright:';
|
||||
@ -45,7 +69,7 @@ export class CommentUtils {
|
||||
// If it contains a "strict" operand, replace the existing
|
||||
// diagnostic settings with their strict counterparts.
|
||||
if (operandList.some(s => s === 'strict')) {
|
||||
settings = getStrictDiagnosticSettings();
|
||||
this._applyStrictSettings(settings);
|
||||
}
|
||||
|
||||
for (let operand of operandList) {
|
||||
|
@ -26,7 +26,7 @@ import { AssertNode, AssignmentNode, AugmentedAssignmentExpressionNode,
|
||||
SuiteNode, TernaryExpressionNode, TryNode, TupleExpressionNode,
|
||||
TypeAnnotationExpressionNode, UnaryExpressionNode, UnpackExpressionNode, WhileNode,
|
||||
WithNode, YieldExpressionNode, YieldFromExpressionNode } from '../parser/parseNodes';
|
||||
import { KeywordType, OperatorType } from '../parser/tokenizerTypes';
|
||||
import { KeywordType } from '../parser/tokenizerTypes';
|
||||
import { ScopeUtils } from '../scopeUtils';
|
||||
import { AnalyzerFileInfo } from './analyzerFileInfo';
|
||||
import { AnalyzerNodeInfo } from './analyzerNodeInfo';
|
||||
@ -36,16 +36,15 @@ import { ImportResult, ImportType } from './importResult';
|
||||
import { DefaultTypeSourceId, TypeSourceId } from './inferredType';
|
||||
import { ParseTreeUtils } from './parseTreeUtils';
|
||||
import { ParseTreeWalker } from './parseTreeWalker';
|
||||
import { Scope, ScopeType, SymbolWithScope } from './scope';
|
||||
import { Scope, ScopeType } from './scope';
|
||||
import { Declaration, Symbol, SymbolCategory, SymbolTable } from './symbol';
|
||||
import { SymbolUtils } from './symbolUtils';
|
||||
import { TypeConstraintBuilder } from './typeConstraint';
|
||||
import { TypeConstraintUtils } from './typeConstraintUtils';
|
||||
import { AnyType, ClassType, ClassTypeFlags, FunctionParameter, FunctionType,
|
||||
FunctionTypeFlags, ModuleType, NeverType, NoneType, ObjectType,
|
||||
OverloadedFunctionType, PropertyType, Type, TypeCategory, TypeVarType, UnboundType,
|
||||
UnionType,
|
||||
UnknownType } from './types';
|
||||
FunctionTypeFlags, ModuleType, NoneType, ObjectType,
|
||||
OverloadedFunctionType, PropertyType, Type, TypeCategory, TypeVarType,
|
||||
UnboundType, UnionType, UnknownType } from './types';
|
||||
import { ClassMemberLookupFlags, TypeUtils } from './typeUtils';
|
||||
|
||||
interface AliasMapEntry {
|
||||
@ -73,6 +72,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
private readonly _moduleNode: ModuleNode;
|
||||
private readonly _fileInfo: AnalyzerFileInfo;
|
||||
private _currentScope: Scope;
|
||||
private _defaultValueInitializerExpression = false;
|
||||
|
||||
// Indicates where there was a change in the type analysis
|
||||
// the last time analyze() was called. Callers should repeatedly
|
||||
@ -319,7 +319,10 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
if (param.defaultValue) {
|
||||
defaultValueType = this._getTypeOfExpression(param.defaultValue,
|
||||
EvaluatorFlags.ConvertEllipsisToAny);
|
||||
|
||||
this._defaultValueInitializerExpression = true;
|
||||
this.walk(param.defaultValue);
|
||||
this._defaultValueInitializerExpression = false;
|
||||
}
|
||||
|
||||
if (param.typeAnnotation) {
|
||||
@ -589,6 +592,13 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
this._currentScope.setAlwaysRaises();
|
||||
}
|
||||
|
||||
if (this._defaultValueInitializerExpression && !this._fileInfo.isStubFile) {
|
||||
this._addDiagnostic(
|
||||
this._fileInfo.diagnosticSettings.reportCallInDefaultInitializer,
|
||||
`Function calls within default value initializer are not permitted`,
|
||||
node);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -123,6 +123,10 @@ export interface DiagnosticSettings {
|
||||
|
||||
// Report usage of unknown input or return parameters?
|
||||
reportUnknownMemberType: DiagnosticLevel;
|
||||
|
||||
// Report usage of function call within default value
|
||||
// initialization expression?
|
||||
reportCallInDefaultInitializer: DiagnosticLevel;
|
||||
}
|
||||
|
||||
export function cloneDiagnosticSettings(
|
||||
@ -165,7 +169,8 @@ export function getDiagLevelSettings() {
|
||||
'reportInvalidStringEscapeSequence',
|
||||
'reportUnknownParameterType',
|
||||
'reportUnknownVariableType',
|
||||
'reportUnknownMemberType'
|
||||
'reportUnknownMemberType',
|
||||
'reportCallInDefaultInitializer'
|
||||
];
|
||||
}
|
||||
|
||||
@ -197,7 +202,8 @@ export function getStrictDiagnosticSettings(): DiagnosticSettings {
|
||||
reportInvalidStringEscapeSequence: 'error',
|
||||
reportUnknownParameterType: 'error',
|
||||
reportUnknownVariableType: 'error',
|
||||
reportUnknownMemberType: 'error'
|
||||
reportUnknownMemberType: 'error',
|
||||
reportCallInDefaultInitializer: 'none'
|
||||
};
|
||||
|
||||
return diagSettings;
|
||||
@ -231,7 +237,8 @@ export function getDefaultDiagnosticSettings(): DiagnosticSettings {
|
||||
reportInvalidStringEscapeSequence: 'warning',
|
||||
reportUnknownParameterType: 'none',
|
||||
reportUnknownVariableType: 'none',
|
||||
reportUnknownMemberType: 'none'
|
||||
reportUnknownMemberType: 'none',
|
||||
reportCallInDefaultInitializer: 'none'
|
||||
};
|
||||
|
||||
return diagSettings;
|
||||
@ -544,7 +551,12 @@ export class ConfigOptions {
|
||||
// Read the "reportUnknownMemberType" entry.
|
||||
reportUnknownMemberType: this._convertDiagnosticLevel(
|
||||
configObj.reportUnknownMemberType, 'reportUnknownMemberType',
|
||||
defaultSettings.reportUnknownMemberType)
|
||||
defaultSettings.reportUnknownMemberType),
|
||||
|
||||
// Read the "reportCallInDefaultInitializer" entry.
|
||||
reportCallInDefaultInitializer: this._convertDiagnosticLevel(
|
||||
configObj.reportCallInDefaultInitializer, 'reportCallInDefaultInitializer',
|
||||
defaultSettings.reportCallInDefaultInitializer)
|
||||
};
|
||||
|
||||
// Read the "venvPath".
|
||||
|
10
server/src/tests/samples/defaultInitializer1.py
Normal file
10
server/src/tests/samples/defaultInitializer1.py
Normal file
@ -0,0 +1,10 @@
|
||||
# This sample tests the type analyzer's reporting of issues
|
||||
# with parameter default initializer expressions.
|
||||
|
||||
def foo(
|
||||
a = None,
|
||||
# This should generate an error
|
||||
b = dict(),
|
||||
# This should generate an error
|
||||
c = max(3, 4)):
|
||||
return 3
|
@ -462,3 +462,16 @@ test('AugmentedAssignment1', () => {
|
||||
|
||||
validateResults(analysisResults, 3);
|
||||
});
|
||||
|
||||
test('DefaultInitializer1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
|
||||
// By default, optional diagnostics are ignored.
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions);
|
||||
validateResults(analysisResults, 0);
|
||||
|
||||
// Turn on errors.
|
||||
configOptions.diagnosticSettings.reportCallInDefaultInitializer = 'error';
|
||||
analysisResults = TestUtils.typeAnalyzeSampleFiles(['defaultInitializer1.py'], configOptions);
|
||||
validateResults(analysisResults, 2);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user