mirror of
https://github.com/microsoft/pyright.git
synced 2024-10-26 19:01:08 +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",
|
"title": "Controls reporting class and instance variables whose types are unknown",
|
||||||
"default": "none"
|
"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": {
|
"pythonVersion": {
|
||||||
"$id": "#/properties/pythonVersion",
|
"$id": "#/properties/pythonVersion",
|
||||||
"type": "string",
|
"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'.
|
**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
|
## 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.
|
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>,
|
static getFileLevelDirectives(tokens: TextRangeCollection<Token>,
|
||||||
defaultSettings: DiagnosticSettings, useStrict: boolean): DiagnosticSettings {
|
defaultSettings: DiagnosticSettings, useStrict: boolean): DiagnosticSettings {
|
||||||
|
|
||||||
let settings = useStrict ?
|
let settings = cloneDiagnosticSettings(defaultSettings);
|
||||||
getStrictDiagnosticSettings() :
|
|
||||||
cloneDiagnosticSettings(defaultSettings);
|
if (useStrict) {
|
||||||
|
this._applyStrictSettings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < tokens.count; i++) {
|
for (let i = 0; i < tokens.count; i++) {
|
||||||
const token = tokens.getItemAt(i);
|
const token = tokens.getItemAt(i);
|
||||||
@ -35,6 +37,28 @@ export class CommentUtils {
|
|||||||
return settings;
|
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) {
|
private static _parsePyrightComment(commentValue: string, settings: DiagnosticSettings) {
|
||||||
// Is this a pyright-specific comment?
|
// Is this a pyright-specific comment?
|
||||||
const pyrightPrefix = 'pyright:';
|
const pyrightPrefix = 'pyright:';
|
||||||
@ -45,7 +69,7 @@ export class CommentUtils {
|
|||||||
// If it contains a "strict" operand, replace the existing
|
// If it contains a "strict" operand, replace the existing
|
||||||
// diagnostic settings with their strict counterparts.
|
// diagnostic settings with their strict counterparts.
|
||||||
if (operandList.some(s => s === 'strict')) {
|
if (operandList.some(s => s === 'strict')) {
|
||||||
settings = getStrictDiagnosticSettings();
|
this._applyStrictSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let operand of operandList) {
|
for (let operand of operandList) {
|
||||||
|
@ -26,7 +26,7 @@ import { AssertNode, AssignmentNode, AugmentedAssignmentExpressionNode,
|
|||||||
SuiteNode, TernaryExpressionNode, TryNode, TupleExpressionNode,
|
SuiteNode, TernaryExpressionNode, TryNode, TupleExpressionNode,
|
||||||
TypeAnnotationExpressionNode, UnaryExpressionNode, UnpackExpressionNode, WhileNode,
|
TypeAnnotationExpressionNode, UnaryExpressionNode, UnpackExpressionNode, WhileNode,
|
||||||
WithNode, YieldExpressionNode, YieldFromExpressionNode } from '../parser/parseNodes';
|
WithNode, YieldExpressionNode, YieldFromExpressionNode } from '../parser/parseNodes';
|
||||||
import { KeywordType, OperatorType } from '../parser/tokenizerTypes';
|
import { KeywordType } from '../parser/tokenizerTypes';
|
||||||
import { ScopeUtils } from '../scopeUtils';
|
import { ScopeUtils } from '../scopeUtils';
|
||||||
import { AnalyzerFileInfo } from './analyzerFileInfo';
|
import { AnalyzerFileInfo } from './analyzerFileInfo';
|
||||||
import { AnalyzerNodeInfo } from './analyzerNodeInfo';
|
import { AnalyzerNodeInfo } from './analyzerNodeInfo';
|
||||||
@ -36,16 +36,15 @@ import { ImportResult, ImportType } from './importResult';
|
|||||||
import { DefaultTypeSourceId, TypeSourceId } from './inferredType';
|
import { DefaultTypeSourceId, TypeSourceId } from './inferredType';
|
||||||
import { ParseTreeUtils } from './parseTreeUtils';
|
import { ParseTreeUtils } from './parseTreeUtils';
|
||||||
import { ParseTreeWalker } from './parseTreeWalker';
|
import { ParseTreeWalker } from './parseTreeWalker';
|
||||||
import { Scope, ScopeType, SymbolWithScope } from './scope';
|
import { Scope, ScopeType } from './scope';
|
||||||
import { Declaration, Symbol, SymbolCategory, SymbolTable } from './symbol';
|
import { Declaration, Symbol, SymbolCategory, SymbolTable } from './symbol';
|
||||||
import { SymbolUtils } from './symbolUtils';
|
import { SymbolUtils } from './symbolUtils';
|
||||||
import { TypeConstraintBuilder } from './typeConstraint';
|
import { TypeConstraintBuilder } from './typeConstraint';
|
||||||
import { TypeConstraintUtils } from './typeConstraintUtils';
|
import { TypeConstraintUtils } from './typeConstraintUtils';
|
||||||
import { AnyType, ClassType, ClassTypeFlags, FunctionParameter, FunctionType,
|
import { AnyType, ClassType, ClassTypeFlags, FunctionParameter, FunctionType,
|
||||||
FunctionTypeFlags, ModuleType, NeverType, NoneType, ObjectType,
|
FunctionTypeFlags, ModuleType, NoneType, ObjectType,
|
||||||
OverloadedFunctionType, PropertyType, Type, TypeCategory, TypeVarType, UnboundType,
|
OverloadedFunctionType, PropertyType, Type, TypeCategory, TypeVarType,
|
||||||
UnionType,
|
UnboundType, UnionType, UnknownType } from './types';
|
||||||
UnknownType } from './types';
|
|
||||||
import { ClassMemberLookupFlags, TypeUtils } from './typeUtils';
|
import { ClassMemberLookupFlags, TypeUtils } from './typeUtils';
|
||||||
|
|
||||||
interface AliasMapEntry {
|
interface AliasMapEntry {
|
||||||
@ -73,6 +72,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
|||||||
private readonly _moduleNode: ModuleNode;
|
private readonly _moduleNode: ModuleNode;
|
||||||
private readonly _fileInfo: AnalyzerFileInfo;
|
private readonly _fileInfo: AnalyzerFileInfo;
|
||||||
private _currentScope: Scope;
|
private _currentScope: Scope;
|
||||||
|
private _defaultValueInitializerExpression = false;
|
||||||
|
|
||||||
// Indicates where there was a change in the type analysis
|
// Indicates where there was a change in the type analysis
|
||||||
// the last time analyze() was called. Callers should repeatedly
|
// the last time analyze() was called. Callers should repeatedly
|
||||||
@ -319,7 +319,10 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
|||||||
if (param.defaultValue) {
|
if (param.defaultValue) {
|
||||||
defaultValueType = this._getTypeOfExpression(param.defaultValue,
|
defaultValueType = this._getTypeOfExpression(param.defaultValue,
|
||||||
EvaluatorFlags.ConvertEllipsisToAny);
|
EvaluatorFlags.ConvertEllipsisToAny);
|
||||||
|
|
||||||
|
this._defaultValueInitializerExpression = true;
|
||||||
this.walk(param.defaultValue);
|
this.walk(param.defaultValue);
|
||||||
|
this._defaultValueInitializerExpression = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.typeAnnotation) {
|
if (param.typeAnnotation) {
|
||||||
@ -589,6 +592,13 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
|||||||
this._currentScope.setAlwaysRaises();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +123,10 @@ export interface DiagnosticSettings {
|
|||||||
|
|
||||||
// Report usage of unknown input or return parameters?
|
// Report usage of unknown input or return parameters?
|
||||||
reportUnknownMemberType: DiagnosticLevel;
|
reportUnknownMemberType: DiagnosticLevel;
|
||||||
|
|
||||||
|
// Report usage of function call within default value
|
||||||
|
// initialization expression?
|
||||||
|
reportCallInDefaultInitializer: DiagnosticLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cloneDiagnosticSettings(
|
export function cloneDiagnosticSettings(
|
||||||
@ -165,7 +169,8 @@ export function getDiagLevelSettings() {
|
|||||||
'reportInvalidStringEscapeSequence',
|
'reportInvalidStringEscapeSequence',
|
||||||
'reportUnknownParameterType',
|
'reportUnknownParameterType',
|
||||||
'reportUnknownVariableType',
|
'reportUnknownVariableType',
|
||||||
'reportUnknownMemberType'
|
'reportUnknownMemberType',
|
||||||
|
'reportCallInDefaultInitializer'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +202,8 @@ export function getStrictDiagnosticSettings(): DiagnosticSettings {
|
|||||||
reportInvalidStringEscapeSequence: 'error',
|
reportInvalidStringEscapeSequence: 'error',
|
||||||
reportUnknownParameterType: 'error',
|
reportUnknownParameterType: 'error',
|
||||||
reportUnknownVariableType: 'error',
|
reportUnknownVariableType: 'error',
|
||||||
reportUnknownMemberType: 'error'
|
reportUnknownMemberType: 'error',
|
||||||
|
reportCallInDefaultInitializer: 'none'
|
||||||
};
|
};
|
||||||
|
|
||||||
return diagSettings;
|
return diagSettings;
|
||||||
@ -231,7 +237,8 @@ export function getDefaultDiagnosticSettings(): DiagnosticSettings {
|
|||||||
reportInvalidStringEscapeSequence: 'warning',
|
reportInvalidStringEscapeSequence: 'warning',
|
||||||
reportUnknownParameterType: 'none',
|
reportUnknownParameterType: 'none',
|
||||||
reportUnknownVariableType: 'none',
|
reportUnknownVariableType: 'none',
|
||||||
reportUnknownMemberType: 'none'
|
reportUnknownMemberType: 'none',
|
||||||
|
reportCallInDefaultInitializer: 'none'
|
||||||
};
|
};
|
||||||
|
|
||||||
return diagSettings;
|
return diagSettings;
|
||||||
@ -544,7 +551,12 @@ export class ConfigOptions {
|
|||||||
// Read the "reportUnknownMemberType" entry.
|
// Read the "reportUnknownMemberType" entry.
|
||||||
reportUnknownMemberType: this._convertDiagnosticLevel(
|
reportUnknownMemberType: this._convertDiagnosticLevel(
|
||||||
configObj.reportUnknownMemberType, 'reportUnknownMemberType',
|
configObj.reportUnknownMemberType, 'reportUnknownMemberType',
|
||||||
defaultSettings.reportUnknownMemberType)
|
defaultSettings.reportUnknownMemberType),
|
||||||
|
|
||||||
|
// Read the "reportCallInDefaultInitializer" entry.
|
||||||
|
reportCallInDefaultInitializer: this._convertDiagnosticLevel(
|
||||||
|
configObj.reportCallInDefaultInitializer, 'reportCallInDefaultInitializer',
|
||||||
|
defaultSettings.reportCallInDefaultInitializer)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read the "venvPath".
|
// 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);
|
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