From e302c0658a90c09b28bbb5ec02f23546bdd7a363 Mon Sep 17 00:00:00 2001 From: Wizzerinus Date: Fri, 27 Dec 2024 10:25:54 +0300 Subject: [PATCH] feat: implemented allowed-untyped-libraries resolves #701 --- .../pyright-internal/src/analyzer/binder.ts | 8 +++++- .../src/analyzer/typeEvaluator.ts | 12 +++++++++ .../src/common/configOptions.ts | 26 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/pyright-internal/src/analyzer/binder.ts b/packages/pyright-internal/src/analyzer/binder.ts index 3f702cb2e1..32b1cf95bd 100644 --- a/packages/pyright-internal/src/analyzer/binder.ts +++ b/packages/pyright-internal/src/analyzer/binder.ts @@ -403,10 +403,12 @@ export class Binder extends ParseTreeWalker { } // A source file was found, but the type stub was missing. + // If the module is allowed as an untyped library, we don't need the stub if ( !importResult.isStubFile && importResult.importType === ImportType.ThirdParty && - !importResult.pyTypedInfo + !importResult.pyTypedInfo && + !this._ignoreUntypedModule(importResult.importName) ) { const diagnostic = this._addDiagnostic( DiagnosticRule.reportMissingTypeStubs, @@ -4331,6 +4333,10 @@ export class Binder extends ParseTreeWalker { private _addSyntaxError(message: string, textRange: TextRange) { return this._fileInfo.diagnosticSink.addDiagnosticWithTextRange('error', message, textRange); } + + private _ignoreUntypedModule(module: string) { + return this._fileInfo.diagnosticRuleSet.allowedUntypedLibraries.some(x => (module + ".").startsWith(x + ".")); + } } export class YieldFinder extends ParseTreeWalker { diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 9e0d3a3e6e..883fe994f0 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -15114,6 +15114,10 @@ export function createTypeEvaluator( return { type, isIncomplete, typeErrors }; } + function _ignoreUntypedModule(ruleset: DiagnosticRuleSet, module: string) { + return ruleset.allowedUntypedLibraries.some(x => (module + ".").startsWith(x + ".")); + } + function reportPossibleUnknownAssignment( ruleset: DiagnosticRuleSet, rule: DiagnosticRule, @@ -15127,6 +15131,14 @@ export function createTypeEvaluator( return; } + // Or if the object is in an untyped library that was explicitly mentioned. + if (type.shared && "moduleName" in type.shared) { + const moduleName = type.shared.moduleName; + if (_ignoreUntypedModule(ruleset, moduleName)) { + return; + } + } + const nameValue = target.d.value; // Sometimes variables contain an "unbound" type if they're diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index fb7795579a..1820d5ee79 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -423,6 +423,7 @@ export interface DiagnosticRuleSet { reportUnusedParameter: DiagnosticLevel; reportImplicitAbstractClass: DiagnosticLevel; reportUnannotatedClassAttribute: DiagnosticLevel; + allowedUntypedLibraries: string[]; } export function cloneDiagnosticRuleSet(diagSettings: DiagnosticRuleSet): DiagnosticRuleSet { @@ -687,6 +688,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet { reportUnusedParameter: 'hint', reportImplicitAbstractClass: 'none', reportUnannotatedClassAttribute: 'none', + allowedUntypedLibraries: [], }; return diagSettings; @@ -803,6 +805,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet { reportUnusedParameter: 'hint', reportImplicitAbstractClass: 'none', reportUnannotatedClassAttribute: 'none', + allowedUntypedLibraries: [], }; return diagSettings; @@ -919,6 +922,7 @@ export function getStandardDiagnosticRuleSet(): DiagnosticRuleSet { reportUnusedParameter: 'hint', reportImplicitAbstractClass: 'none', reportUnannotatedClassAttribute: 'none', + allowedUntypedLibraries: [], }; return diagSettings; @@ -1034,6 +1038,7 @@ export const getRecommendedDiagnosticRuleSet = (): DiagnosticRuleSet => ({ reportUnusedParameter: 'warning', reportImplicitAbstractClass: 'warning', reportUnannotatedClassAttribute: 'warning', + allowedUntypedLibraries: [], }); export const getAllDiagnosticRuleSet = (): DiagnosticRuleSet => ({ @@ -1146,6 +1151,7 @@ export const getAllDiagnosticRuleSet = (): DiagnosticRuleSet => ({ reportUnusedParameter: 'error', reportImplicitAbstractClass: 'error', reportUnannotatedClassAttribute: 'error', + allowedUntypedLibraries: [], }); export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { @@ -1259,6 +1265,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { reportUnusedParameter: 'hint', reportImplicitAbstractClass: 'none', reportUnannotatedClassAttribute: 'none', + allowedUntypedLibraries: [], }; return diagSettings; @@ -1601,6 +1608,25 @@ export class ConfigOptions { console ); }); + + // Read the config "allowedUntypedLibraries". + const allowedUntypedLibraries: string[] = []; + if (configObj.allowedUntypedLibraries !== undefined) { + if (!Array.isArray(configObj.allowedUntypedLibraries)) { + console.error(`Config "allowedUntypedLibraries" field must contain an array.`); + } else { + const pathList = configObj.allowedUntypedLibraries as string[]; + pathList.forEach((lib, libIndex) => { + if (typeof lib !== 'string') { + console.error(`Config "allowedUntypedLibraries" field ${libIndex} must be a string.`); + } else { + allowedUntypedLibraries.push(lib); + } + }); + configRuleSet.allowedUntypedLibraries = allowedUntypedLibraries; + } + } + this.diagnosticRuleSet = { ...configRuleSet }; // Read the "venvPath".