From 6e2507bd3a7549947a1a07d3778c260d9b258f07 Mon Sep 17 00:00:00 2001 From: detachhead Date: Wed, 9 Oct 2024 20:49:56 +1000 Subject: [PATCH 1/7] add new `"recommended"` `typeCheckingMode` which is the new default that sets the category to warning for less severe diagnostics, and `failOnWarnings` config option which is enabled by default in this mode --- docs/benefits-over-pyright/better-defaults.md | 7 +- docs/configuration.md | 16 +- .../configuration/language-server-settings.md | 2 +- .../pyright-internal/src/analyzer/service.ts | 30 +-- .../src/common/commandLineOptions.ts | 3 +- .../src/common/configOptions.ts | 177 ++++++++++++++++-- .../src/common/diagnosticRules.ts | 1 + packages/pyright-internal/src/pyright.ts | 11 +- .../src/tests/baseline.test.ts | 4 +- .../pyright-internal/src/tests/config.test.ts | 8 +- .../src/tests/languageServer.test.ts | 2 +- .../src/tests/typeEvaluatorBased.test.ts | 12 +- packages/vscode-pyright/package.json | 3 +- .../schemas/pyrightconfig.schema.json | 3 +- pyproject.toml | 1 - 15 files changed, 228 insertions(+), 52 deletions(-) diff --git a/docs/benefits-over-pyright/better-defaults.md b/docs/benefits-over-pyright/better-defaults.md index cf955cbb2a..b561146aea 100644 --- a/docs/benefits-over-pyright/better-defaults.md +++ b/docs/benefits-over-pyright/better-defaults.md @@ -1,10 +1,13 @@ # better defaults -we believe that type checkers and linters should be as strict as possible by default, making the user aware of all the available rules so they can more easily make informed decisions about which rules they don't want enabled in their project. that's why the following defaults have been changed in basedpyright +we believe that type checkers and linters should be as strict as possible by default. this ensures that the user aware of all the available rules so they can more easily make informed decisions about which rules they don't want enabled in their project. that's why the following defaults have been changed in basedpyright. ## `typeCheckingMode` -used to be `basic`, but now defaults to `all`. while this may seem daunting at first, we support [baselining](./baseline.md) to allow for easy adoption of more strict rules in existing codebases. +used to be `"basic"`, but now defaults to `"recommended"`, which enables all diagnostic rules by default. this may seem daunting at first, however we have some solutions to address some concerns users may have with this mode: + +- less severe diagnostic rules are reported as warnings instead of errors. this reduces [alarm fatigue](https://en.wikipedia.org/wiki/Alarm_fatigue) while still ensuring that the user is made aware of all potentential issues that basedpyright can detect. `failOnWarnings` is also enabled by default in this mode, which causes the CLI to exit with a non-zero exit code if any warnings are detected. you disable this behavior by setting `failOnWarnings` to `false`. +- we support [baselining](./baseline.md) to allow for easy adoption of more strict rules in existing codebases. ## `pythonPlatform` diff --git a/docs/configuration.md b/docs/configuration.md index 4b750d0ed0..6422ef9504 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -73,7 +73,7 @@ The following settings determine how different types should be evaluated. ## Type Check Diagnostics Settings The following settings control pyright’s diagnostic output (warnings or errors). -- **typeCheckingMode** ["off", "basic", "standard", "strict", "all"]: Specifies the default rule set to use. Some rules can be overridden using additional configuration flags documented below. The default value for this setting is "all". If set to "off", all type-checking rules are disabled, but Python syntax and semantic errors are still reported. +- **typeCheckingMode** ["off", "basic", "standard", "strict", "recommended", "all"]: Specifies the default rule set to use. Some rules can be overridden using additional configuration flags documented below. The default value for this setting is "recommended". If set to "off", all type-checking rules are disabled, but Python syntax and semantic errors are still reported. - **ignore** [array of paths, optional]: Paths of directories or files whose diagnostic output (errors and warnings) should be suppressed even if they are an included file or within the transitive closure of an included file. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). This setting can be overridden using the [language server settings](./language-server-settings.md). @@ -247,6 +247,8 @@ The following settings allow more fine grained control over the **typeCheckingMo the following additional options are not available in regular pyright: +- **failOnWarnings** [boolean]: Whether to exit with a non-zero exit code in the CLI if any `"warning"` diagnostics are reported. Has no effect on the language server. This is equivalent to the `--warnings` CLI argument. + - **reportUnreachable** [boolean or string, optional]: Generate or suppress diagnostics for unreachable code. - **reportAny** [boolean or string, optional]: Ban all usages of the `Any` type. this accounts for all scenarios not covered by the `reportUnknown*` rules (since "Unknown" isn't a real type, but a distinction pyright makes to disallow the `Any` type only in certain circumstances). @@ -384,9 +386,17 @@ Each diagnostic setting has a default that is dictated by the specified type che Some rules have an additional severity level such as `"unused"`, `"deprecated"` or `"unreachable"`. These are only used by the language server so that your editor can grey out or add a strikethrough to the symbol, which you can disable by setting it to `"off"`. it does not effect the outcome when running basedpyright via the CLI, so in that context these severity levels essentially mean the same thing as `"off"`. -The following table lists the default severity levels for each diagnostic rule within each type checking mode (`"off"`, `"basic"`, `"standard"`, `"strict"` and `"all"`). +The following table lists the default severity levels for each diagnostic rule within each type checking mode (`"off"`, `"basic"`, `"standard"`, `"strict"`, `"recommended"` and `"all"`). + + +### `"recommended"` and `"all"` + +basedpyright introduces two new diagnostic rulesets in addition to the ones in pyright: `"recommended"` and `"all"`. `"recommended"` enables all diagnostic rules as either `"warning"` or `"error"`, but sets `failOnWarnings` to `true` so that all diagnostics will still cause a non-zero exit code when run in the CLI. this means `"recommended"` is essentially the same as `"all"`, but makes it easier to differentiate errors that are likely to cause a runtime crash like an undefined variable from less serious warnings such as a missing type annotation. + +!!! note + + some settings which are enabled by default in pyright are disabled by default in basedpyright (even when `typeCheckingMode` is `"all"`). this is because these rules are [discouraged](#discouraged-options), but in the interest of backwards compatibility with pyright, they remain available to any users who still want to use them. -note that some settings which are enabled by default in pyright are disabled by default in basedpyright (even though the default `typeCheckingMode` is `"all"`). this is because these rules are discouraged, but in the interest of backwards compatibility with pyright, they remain available to any users who still want to use them. | Diagnostic Rule | Off | Basic | Standard | Strict | All | |:-----------------------------------------|:--------------|:--------------|:--------------|:--------------|:--------| diff --git a/docs/configuration/language-server-settings.md b/docs/configuration/language-server-settings.md index 2c5014c85c..19101e2be3 100644 --- a/docs/configuration/language-server-settings.md +++ b/docs/configuration/language-server-settings.md @@ -69,7 +69,7 @@ however these settings are still suppored to maintain compatibility with pyright **basedpyright.analysis.stubPath** [path]: Path to directory containing custom type stub files. -**basedpyright.analysis.typeCheckingMode** ["off", "basic", "standard", "strict", "all"]: Determines the default type-checking level used by pyright. This can be overridden in the configuration file. (Note: This setting used to be called "pyright.typeCheckingMode". The old name is deprecated but is still currently honored.) +**basedpyright.analysis.typeCheckingMode** ["off", "basic", "standard", "strict", "recommended", "all"]: Determines the default type-checking level used by pyright. This can be overridden in the configuration file. (Note: This setting used to be called "basedpyright.typeCheckingMode". The old name is deprecated but is still currently honored.) **basedpyright.analysis.typeshedPaths** [array of paths]: Paths to look for typeshed modules. Pyright currently honors only the first path in the array. diff --git a/packages/pyright-internal/src/analyzer/service.ts b/packages/pyright-internal/src/analyzer/service.ts index ec748f3ca0..ce89348194 100644 --- a/packages/pyright-internal/src/analyzer/service.ts +++ b/packages/pyright-internal/src/analyzer/service.ts @@ -674,7 +674,7 @@ export class AnalyzerService { throw e; } } - configOptions.initializeTypeCheckingMode('all'); + configOptions.initializeTypeCheckingMode('recommended'); if (configs && configs.length > 0) { // Then we apply the config file settings. This can update the // the typeCheckingMode. @@ -694,15 +694,24 @@ export class AnalyzerService { // When not in language server mode, command line options override config file options. if (!commandLineOptions.fromLanguageServer) { - this._applyCommandLineOverrides(configOptions, commandLineOptions.configSettings, projectRoot, false); + errors.push( + ...this._applyCommandLineOverrides( + configOptions, + commandLineOptions.configSettings, + projectRoot, + false + ) + ); } } else { // If there are no config files, we can then directly apply the command line options. - this._applyCommandLineOverrides( - configOptions, - commandLineOptions.configSettings, - projectRoot, - commandLineOptions.fromLanguageServer + errors.push( + ...this._applyCommandLineOverrides( + configOptions, + commandLineOptions.configSettings, + projectRoot, + commandLineOptions.fromLanguageServer + ) ); } @@ -922,10 +931,8 @@ export class AnalyzerService { commandLineOptions: CommandLineConfigOptions, projectRoot: Uri, fromLanguageServer: boolean - ) { - if (commandLineOptions.typeCheckingMode) { - configOptions.initializeTypeCheckingMode(commandLineOptions.typeCheckingMode); - } + ): string[] { + const errors = configOptions.initializeTypeCheckingModeFromString(commandLineOptions.typeCheckingMode); if (commandLineOptions.extraPaths) { configOptions.ensureDefaultExtraPaths( @@ -1029,6 +1036,7 @@ export class AnalyzerService { reportDuplicateSetting('stubPath', configOptions.stubPath.toUserVisibleString()); } } + return errors; } // Loads the config JSON object from the specified config file along with any diff --git a/packages/pyright-internal/src/common/commandLineOptions.ts b/packages/pyright-internal/src/common/commandLineOptions.ts index 091270dcd8..d1486cdb98 100644 --- a/packages/pyright-internal/src/common/commandLineOptions.ts +++ b/packages/pyright-internal/src/common/commandLineOptions.ts @@ -94,8 +94,7 @@ export class CommandLineConfigOptions { // when user has not explicitly defined execution environments. extraPaths?: string[] | undefined; - // Default type-checking rule set. Should be one of 'off', - // 'basic', 'standard', 'strict' or 'all'. + // Default type-checking rule set. typeCheckingMode?: string | undefined; // Indicates diagnostic severity overrides diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index 529d84e814..50fcc7f34b 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -97,6 +97,8 @@ type DeprecatedDiagnosticLevel = DiagnosticLevel | 'deprecated'; export type LspDiagnosticLevel = DiagnosticLevel | 'unreachable' | 'unused' | 'deprecated'; +export type TypeCheckingMode = (typeof allTypeCheckingModes)[number]; + export enum SignatureDisplayType { compact = 'compact', formatted = 'formatted', @@ -410,6 +412,15 @@ export interface DiagnosticRuleSet { reportImplicitOverride: DiagnosticLevel; // basedpyright options: + + /** + * this probably doesn't really fit as a diagnostic rule config, but it's here because we want the default + * "fail on warnings" behavior to be different depending on the `typeCheckingMode`. this is kinda weird, + * but a compromize we're making to enforce the strictest checks by default without bombarding the user + * with too many errors. + * @see https://github.com/DetachHead/basedpyright/issues/603#issuecomment-2303297625 + */ + failOnWarnings: boolean; reportUnreachable: UnreachableDiagnosticLevel; reportAny: DiagnosticLevel; reportIgnoreCommentWithoutRule: DiagnosticLevel; @@ -446,6 +457,7 @@ export function getBooleanDiagnosticRules(includeNonOverridable = false) { // it within pyright comments. boolRules.push(DiagnosticRule.enableTypeIgnoreComments); boolRules.push(DiagnosticRule.enableReachabilityAnalysis); + boolRules.push(DiagnosticRule.failOnWarnings); } return boolRules; @@ -684,6 +696,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet { reportMatchNotExhaustive: 'none', reportShadowedImports: 'none', reportImplicitOverride: 'none', + failOnWarnings: false, reportUnreachable: 'unreachable', reportAny: 'none', reportIgnoreCommentWithoutRule: 'none', @@ -796,6 +809,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet { reportMatchNotExhaustive: 'none', reportShadowedImports: 'none', reportImplicitOverride: 'none', + failOnWarnings: false, reportUnreachable: 'unreachable', reportAny: 'none', reportIgnoreCommentWithoutRule: 'none', @@ -908,6 +922,7 @@ export function getStandardDiagnosticRuleSet(): DiagnosticRuleSet { reportMatchNotExhaustive: 'none', reportShadowedImports: 'none', reportImplicitOverride: 'none', + failOnWarnings: false, reportUnreachable: 'unreachable', reportAny: 'none', reportIgnoreCommentWithoutRule: 'none', @@ -922,6 +937,115 @@ export function getStandardDiagnosticRuleSet(): DiagnosticRuleSet { return diagSettings; } +export const getRecommendedDiagnosticRuleSet = (): DiagnosticRuleSet => ({ + printUnknownAsAny: false, + omitTypeArgsIfUnknown: false, + omitUnannotatedParamType: false, + omitConditionalConstraint: false, + pep604Printing: true, + strictListInference: true, + strictSetInference: true, + strictDictionaryInference: true, + analyzeUnannotatedFunctions: true, + strictParameterNoneValue: true, + enableExperimentalFeatures: true, + enableTypeIgnoreComments: false, + enableReachabilityAnalysis: true, + deprecateTypingAliases: true, + disableBytesTypePromotions: true, + reportGeneralTypeIssues: 'error', + reportPropertyTypeMismatch: 'warning', + reportFunctionMemberAccess: 'error', + reportMissingImports: 'error', + reportMissingModuleSource: 'error', + reportInvalidTypeForm: 'error', + reportMissingTypeStubs: 'error', + reportImportCycles: 'error', + reportUnusedImport: 'warning', + reportUnusedClass: 'warning', + reportUnusedFunction: 'warning', + reportUnusedVariable: 'warning', + reportDuplicateImport: 'warning', + reportWildcardImportFromLibrary: 'warning', + reportAbstractUsage: 'error', + reportArgumentType: 'error', + reportAssertTypeFailure: 'error', + reportAssignmentType: 'error', + reportAttributeAccessIssue: 'error', + reportCallIssue: 'error', + reportInconsistentOverload: 'error', + reportIndexIssue: 'error', + reportInvalidTypeArguments: 'error', + reportNoOverloadImplementation: 'error', + reportOperatorIssue: 'error', + reportOptionalSubscript: 'error', + reportOptionalMemberAccess: 'error', + reportOptionalCall: 'error', + reportOptionalIterable: 'error', + reportOptionalContextManager: 'error', + reportOptionalOperand: 'error', + reportRedeclaration: 'error', + reportReturnType: 'error', + reportTypedDictNotRequiredAccess: 'error', + reportUntypedFunctionDecorator: 'warning', + reportUntypedClassDecorator: 'warning', + reportUntypedBaseClass: 'warning', + reportUntypedNamedTuple: 'warning', + reportPrivateUsage: 'warning', + reportTypeCommentUsage: 'warning', + reportPrivateImportUsage: 'warning', + reportConstantRedefinition: 'error', + reportDeprecated: 'warning', + reportIncompatibleMethodOverride: 'error', + reportIncompatibleVariableOverride: 'error', + reportInconsistentConstructor: 'error', + reportOverlappingOverload: 'error', + reportPossiblyUnboundVariable: 'error', + reportMissingSuperCall: 'error', + reportUninitializedInstanceVariable: 'error', + reportInvalidStringEscapeSequence: 'error', + reportUnknownParameterType: 'warning', + reportUnknownArgumentType: 'warning', + reportUnknownLambdaType: 'warning', + reportUnknownVariableType: 'warning', + reportUnknownMemberType: 'warning', + reportMissingParameterType: 'warning', + reportMissingTypeArgument: 'error', + reportInvalidTypeVarUse: 'warning', + reportCallInDefaultInitializer: 'warning', + reportUnnecessaryIsInstance: 'warning', + reportUnnecessaryCast: 'warning', + reportUnnecessaryComparison: 'warning', + reportUnnecessaryContains: 'warning', + reportAssertAlwaysTrue: 'warning', + reportSelfClsParameterName: 'error', + reportImplicitStringConcatenation: 'warning', + reportUnboundVariable: 'error', + reportUnhashable: 'error', + reportUndefinedVariable: 'error', + reportInvalidStubStatement: 'warning', + reportIncompleteStub: 'warning', + reportUnsupportedDunderAll: 'warning', + reportUnusedCallResult: 'warning', + reportUnusedCoroutine: 'warning', + reportUnusedExcept: 'error', + reportUnusedExpression: 'warning', + reportUnnecessaryTypeIgnoreComment: 'warning', + reportMatchNotExhaustive: 'warning', + reportShadowedImports: 'warning', + reportImplicitOverride: 'warning', + failOnWarnings: true, + reportUnreachable: 'warning', + reportAny: 'warning', + reportIgnoreCommentWithoutRule: 'warning', + reportPrivateLocalImportUsage: 'warning', + reportImplicitRelativeImport: 'error', + reportInvalidCast: 'error', + reportUnsafeMultipleInheritance: 'error', + reportUnusedParameter: 'warning', + reportImplicitAbstractClass: 'warning', +}); + export const getAllDiagnosticRuleSet = (): DiagnosticRuleSet => ({ printUnknownAsAny: false, omitTypeArgsIfUnknown: false, @@ -1019,6 +1143,7 @@ export const getAllDiagnosticRuleSet = (): DiagnosticRuleSet => ({ reportMatchNotExhaustive: 'error', reportShadowedImports: 'error', reportImplicitOverride: 'error', + failOnWarnings: true, reportUnreachable: 'error', reportAny: 'error', reportIgnoreCommentWithoutRule: 'error', @@ -1128,6 +1253,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { reportMatchNotExhaustive: 'error', reportShadowedImports: 'none', reportImplicitOverride: 'none', + failOnWarnings: false, reportUnreachable: 'unreachable', reportAny: 'none', reportIgnoreCommentWithoutRule: 'none', @@ -1142,6 +1268,8 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet { return diagSettings; } +export const allTypeCheckingModes = ['off', 'basic', 'standard', 'strict', 'recommended', 'all'] as const; + export function matchFileSpecs(configOptions: ConfigOptions, uri: Uri, isFile = true) { for (const includeSpec of configOptions.include) { if (FileSpec.matchIncludeFileSpec(includeSpec.regExp, configOptions.exclude, uri, isFile)) { @@ -1315,7 +1443,7 @@ export class ConfigOptions { configFileSource?: Uri | undefined; // Determines the effective default type checking mode. - effectiveTypeCheckingMode: 'all' | 'strict' | 'basic' | 'off' | 'standard' = 'standard'; + effectiveTypeCheckingMode: 'all' | 'strict' | 'basic' | 'off' | 'standard' = 'standard'; // TODO: we default to "recommended", wheres this default being used? // https://github.com/microsoft/TypeScript/issues/3841 declare ['constructor']: typeof ConfigOptions; @@ -1326,7 +1454,11 @@ export class ConfigOptions { this.functionSignatureDisplay = SignatureDisplayType.formatted; } - static getDiagnosticRuleSet(typeCheckingMode?: string): DiagnosticRuleSet { + static getDiagnosticRuleSet(typeCheckingMode?: TypeCheckingMode): DiagnosticRuleSet { + if (typeCheckingMode === 'recommended') { + return getRecommendedDiagnosticRuleSet(); + } + if (typeCheckingMode === 'all') { return getAllDiagnosticRuleSet(); } @@ -1342,7 +1474,7 @@ export class ConfigOptions { if (typeCheckingMode === 'off') { return getOffDiagnosticRuleSet(); } - + typeCheckingMode satisfies 'standard' | undefined; return getStandardDiagnosticRuleSet(); } @@ -1380,17 +1512,37 @@ export class ConfigOptions { } initializeTypeCheckingMode( - typeCheckingMode: string | undefined, + typeCheckingMode: TypeCheckingMode | undefined, severityOverrides?: DiagnosticSeverityOverridesMap ) { this.diagnosticRuleSet = this.constructor.getDiagnosticRuleSet(typeCheckingMode); - this.effectiveTypeCheckingMode = typeCheckingMode as 'all' | 'strict' | 'basic' | 'off' | 'standard'; + this.effectiveTypeCheckingMode = typeCheckingMode as TypeCheckingMode; if (severityOverrides) { this.applyDiagnosticOverrides(severityOverrides); } } + /** + * initializes `typeCheckingMode` from an unknown, potentially invalid value + * + * @returns any errors that occurred + */ + initializeTypeCheckingModeFromString(typeCheckingMode: string | undefined): string[] { + if (typeCheckingMode !== undefined) { + if ((allTypeCheckingModes as readonly string[]).includes(typeCheckingMode)) { + this.initializeTypeCheckingMode(typeCheckingMode as TypeCheckingMode); + } else { + return [ + `invalid "typeCheckingMode" value: "${typeCheckingMode}". expected: ${userFacingOptionsList( + allTypeCheckingModes + )}`, + ]; + } + } + return []; + } + // Initialize the structure from a JSON object. initializeFromJson(configObj: any, configDirUri: Uri, serviceProvider: ServiceProvider, host: Host): string[] { this.initializedFromJson = true; @@ -1427,16 +1579,7 @@ export class ConfigOptions { } // If there is a "typeCheckingMode", it can override the provided setting. - if (configObj.typeCheckingMode !== undefined) { - const validTypeCheckingModes = ['off', 'basic', 'standard', 'strict', 'all']; - if (validTypeCheckingModes.includes(configObj.typeCheckingMode)) { - this.initializeTypeCheckingMode(configObj.typeCheckingMode); - } else { - errors.push( - `Config "typeCheckingMode" entry must contain ${userFacingOptionsList(validTypeCheckingModes)}.` - ); - } - } + errors.push(...this.initializeTypeCheckingModeFromString(configObj.typeCheckingMode)); if (configObj.useLibraryCodeForTypes !== undefined) { if (typeof configObj.useLibraryCodeForTypes === 'boolean') { @@ -1946,9 +2089,9 @@ export class ConfigOptions { * preserve the behavior of the original in tests and anything else that i'm too scared to touch */ export class BasedConfigOptions extends ConfigOptions { - static override getDiagnosticRuleSet(typeCheckingMode?: string): DiagnosticRuleSet { + static override getDiagnosticRuleSet(typeCheckingMode?: TypeCheckingMode): DiagnosticRuleSet { if (typeCheckingMode === undefined) { - return getAllDiagnosticRuleSet(); + return getRecommendedDiagnosticRuleSet(); } return super.getDiagnosticRuleSet(typeCheckingMode); } diff --git a/packages/pyright-internal/src/common/diagnosticRules.ts b/packages/pyright-internal/src/common/diagnosticRules.ts index 90cd5231c5..3b95407fa8 100644 --- a/packages/pyright-internal/src/common/diagnosticRules.ts +++ b/packages/pyright-internal/src/common/diagnosticRules.ts @@ -105,6 +105,7 @@ export enum DiagnosticRule { reportImplicitOverride = 'reportImplicitOverride', // basedpyright options: + failOnWarnings = 'failOnWarnings', reportUnreachable = 'reportUnreachable', reportAny = 'reportAny', reportIgnoreCommentWithoutRule = 'reportIgnoreCommentWithoutRule', diff --git a/packages/pyright-internal/src/pyright.ts b/packages/pyright-internal/src/pyright.ts index f1e6980ecc..859f0becc0 100644 --- a/packages/pyright-internal/src/pyright.ts +++ b/packages/pyright-internal/src/pyright.ts @@ -469,7 +469,16 @@ const outputResults = ( } const filteredDiagnostics = baselineFile.filterOutBaselinedDiagnostics(results.diagnostics); - const treatWarningsAsErrors = !!args.warnings; + const treatWarningsAsErrors = + !!args.warnings || + filteredDiagnostics.some( + (fileWithDiagnostics) => + fileWithDiagnostics.diagnostics.some( + (diagnostic) => diagnostic.category === DiagnosticCategory.Warning + ) && + service.backgroundAnalysisProgram.configOptions.findExecEnvironment(fileWithDiagnostics.fileUri) + .diagnosticRuleSet.failOnWarnings + ); let errorCount = 0; let report: DiagnosticResult; if (args.outputjson) { diff --git a/packages/pyright-internal/src/tests/baseline.test.ts b/packages/pyright-internal/src/tests/baseline.test.ts index 3348e1ced4..a5facaea16 100644 --- a/packages/pyright-internal/src/tests/baseline.test.ts +++ b/packages/pyright-internal/src/tests/baseline.test.ts @@ -19,8 +19,8 @@ test('baselined error not reported', () => { errors: [ { line: 0, code: DiagnosticRule.reportAssignmentType, baselineStatus: 'baselined' }, { line: 1, code: DiagnosticRule.reportUndefinedVariable }, - { line: 1, code: DiagnosticRule.reportUnusedExpression }, ], + warnings: [{ line: 1, code: DiagnosticRule.reportUnusedExpression }], }); }); @@ -29,6 +29,6 @@ test('baselined error that can be reported as a hint gets converted to a hint', validateResultsButBased(analysisResults, { unreachableCodes: [{ line: 1, code: DiagnosticRule.reportUnreachable, baselineStatus: 'baselined with hint' }], - errors: [{ line: 3, code: DiagnosticRule.reportUnreachable }], + warnings: [{ line: 3, code: DiagnosticRule.reportUnreachable }], }); }); diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index 5fbb1f0c53..c1642c2852 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -248,7 +248,7 @@ describe('invalid config', () => { const errors = configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost()); assert.deepStrictEqual(errors, [ - 'Config "typeCheckingMode" entry must contain "off", "basic", "standard", "strict", or "all".', + 'invalid "typeCheckingMode" value: "asdf". expected: "off", "basic", "standard", "strict", "recommended", or "all"', ]); }); test('unknown value in execution environments', () => { @@ -652,7 +652,7 @@ test('Language server specific settings are set whether or not there is a pyproj assert.equal(options.venvPath?.pathIncludes('test_venv_path'), false); }); -test('default typeCheckingMode should be "all"', () => { +test('default typeCheckingMode should be "recommended"', () => { const cwd = normalizePath(process.cwd()); const service = createAnalyzer(); const commandLineOptions = new CommandLineOptions(cwd, /* fromVsCodeExtension */ true); @@ -662,7 +662,7 @@ test('default typeCheckingMode should be "all"', () => { service.setOptions(commandLineOptions); // as far as i can tell the typeCheckingMode value isn't saved anywhere, so we instead just check some options - // that are typically disabled by default unless typeCheckingMode is set to `"all"` + // that are typically disabled by default unless typeCheckingMode is set to `"recommended"` assert(configOptions.diagnosticRuleSet.deprecateTypingAliases); - assert.equal(configOptions.diagnosticRuleSet.reportAny, 'error'); + assert.equal(configOptions.diagnosticRuleSet.reportAny, 'warning'); }); diff --git a/packages/pyright-internal/src/tests/languageServer.test.ts b/packages/pyright-internal/src/tests/languageServer.test.ts index 45b0b9ed69..b65b577209 100644 --- a/packages/pyright-internal/src/tests/languageServer.test.ts +++ b/packages/pyright-internal/src/tests/languageServer.test.ts @@ -925,7 +925,7 @@ describe(`Basic language server tests`, () => { assert(info.notifications.length === 1); assert( info.notifications[0].message === - 'Config "typeCheckingMode" entry must contain "off", "basic", "standard", "strict", or "all".' + 'invalid "typeCheckingMode" value: "asdf". expected: "off", "basic", "standard", "strict", "recommended", or "all"' ); }); diff --git a/packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts b/packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts index 6070fba6f3..613af3ebd9 100644 --- a/packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts @@ -39,7 +39,7 @@ test('unreachable assert_never', () => { }); }); -test('default typeCheckingMode=all', () => { +test('default typeCheckingMode=recommended', () => { // there's a better test for this in `config.test.ts` which tests it in a more complete way. // the logic for loading the config seems very convoluted and messy. the default typeCheckingMode // seems to be determined multiple times. and there was an upstream change that broke our defaulting @@ -49,22 +49,24 @@ test('default typeCheckingMode=all', () => { const configOptions = new BasedConfigOptions(Uri.empty()); const analysisResults = typeAnalyzeSampleFiles(['unreachable1.py'], configOptions); validateResultsButBased(analysisResults, { - errors: [ + warnings: [ ...[78, 89, 106, 110, 118, 126].map((line) => ({ code: DiagnosticRule.reportUnreachable, line })), - { line: 16, code: DiagnosticRule.reportUninitializedInstanceVariable }, { line: 19, code: DiagnosticRule.reportUnknownParameterType }, { line: 33, code: DiagnosticRule.reportUnknownParameterType }, { line: 94, code: DiagnosticRule.reportUnnecessaryComparison }, { line: 102, code: DiagnosticRule.reportUnusedVariable }, { line: 113, code: DiagnosticRule.reportUnknownParameterType }, - { line: 113, code: DiagnosticRule.reportMissingTypeArgument }, { line: 114, code: DiagnosticRule.reportUnnecessaryIsInstance }, { line: 115, code: DiagnosticRule.reportUnknownVariableType }, { line: 121, code: DiagnosticRule.reportUnknownParameterType }, - { line: 121, code: DiagnosticRule.reportMissingTypeArgument }, { line: 122, code: DiagnosticRule.reportUnnecessaryIsInstance }, { line: 123, code: DiagnosticRule.reportUnknownVariableType }, ], + errors: [ + { line: 16, code: DiagnosticRule.reportUninitializedInstanceVariable }, + { line: 113, code: DiagnosticRule.reportMissingTypeArgument }, + { line: 121, code: DiagnosticRule.reportMissingTypeArgument }, + ], infos: [{ line: 95 }, { line: 98 }], }); }); diff --git a/packages/vscode-pyright/package.json b/packages/vscode-pyright/package.json index e48e60cccb..d40769e392 100644 --- a/packages/vscode-pyright/package.json +++ b/packages/vscode-pyright/package.json @@ -1661,12 +1661,13 @@ }, "basedpyright.analysis.typeCheckingMode": { "type": "string", - "default": "all", + "default": "recommended", "enum": [ "off", "basic", "standard", "strict", + "recommended", "all" ], "description": "Defines the default rule set for type checking.", diff --git a/packages/vscode-pyright/schemas/pyrightconfig.schema.json b/packages/vscode-pyright/schemas/pyrightconfig.schema.json index 4dce5c200a..8c3f0c0b49 100644 --- a/packages/vscode-pyright/schemas/pyrightconfig.schema.json +++ b/packages/vscode-pyright/schemas/pyrightconfig.schema.json @@ -642,10 +642,11 @@ "basic", "standard", "strict", + "recommended", "all" ], "title": "Specifies the default rule set to use for type checking", - "default": "all" + "default": "recommended" }, "useLibraryCodeForTypes": { "type": "boolean", diff --git a/pyproject.toml b/pyproject.toml index 769ad91790..236ab45932 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -215,7 +215,6 @@ include = ["basedpyright", "based_build", "pdm_build.py", "tests"] # https://github.com/DetachHead/basedpyright/issues/31 ignore = ["pw", "basedpyright/dist", "packages", "build"] exclude = ["pw", "basedpyright/dist", "packages", "build"] -typeCheckingMode = "all" reportImplicitStringConcatenation = false # conflicts with ruff formatter # currently commented out due to https://github.com/DetachHead/basedpyright/issues/570 From 60a5354d724567ebf4ff857ca997b87093d75712 Mon Sep 17 00:00:00 2001 From: detachhead Date: Fri, 11 Oct 2024 01:04:25 +1000 Subject: [PATCH 2/7] generate the `typeCheckingMode` defaults table in the docs --- .github/workflows/docs.yaml | 2 +- based_build/docs_macros.py | 29 +++++ docs/configuration.md | 107 +----------------- docs/configuration/config-files.md | 8 +- docs/stylesheets/extra.css | 2 +- mkdocs.yml | 3 + .../src/common/configOptions.ts | 2 +- .../src/common/stringUtils.ts | 2 +- packages/pyright-internal/src/pyright.ts | 25 +++- pyproject.toml | 6 +- 10 files changed, 75 insertions(+), 111 deletions(-) create mode 100644 based_build/docs_macros.py diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 387abe5683..4fb6670ecd 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -43,7 +43,7 @@ jobs: git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" - name: install dependencies - run: ./pw pdm install --no-self + run: ./pw pdm install - name: validate docs run: ./pw pdm run mkdocs build --strict - name: deploy dev docs diff --git a/based_build/docs_macros.py b/based_build/docs_macros.py new file mode 100644 index 0000000000..ae50439bfa --- /dev/null +++ b/based_build/docs_macros.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import json +from functools import partial +from subprocess import run as stupid_run +from typing import TYPE_CHECKING, Iterable + +if TYPE_CHECKING: + from mkdocs_macros.plugin import MacrosPlugin + +run = partial(stupid_run, check=True, capture_output=True) + + +def define_env(env: MacrosPlugin): + env.macro(generate_diagnostic_rule_table) # pyright:ignore[reportUnknownMemberType] + + +def generate_diagnostic_rule_table() -> str: + """generates the table of `typeCheckingMode` defaults""" + _ = run(["npm", "run", "build:cli:dev"]) + diagnostic_rulesets: list[dict[str, bool | str]] = json.loads( + run(["node", "packages/pyright/index.js", "--printdiagnosticrulesets"]).stdout + ) + headers = diagnostic_rulesets[0].keys() + result: list[Iterable[str]] = [(f"**{header}**" for header in headers), (":-" for _ in headers)] + for row in diagnostic_rulesets: + diagnostic_rule, *values = row.values() + result.append([str(diagnostic_rule), *(json.dumps(value) for value in values)]) + return "\n".join("|".join(row) for row in result) diff --git a/docs/configuration.md b/docs/configuration.md index 6422ef9504..4cc386aedf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,3 +1,4 @@ + ## Pyright Configuration basedpyright offers flexible configuration options specified in a JSON-formatted text configuration. By default, the file is called “pyrightconfig.json” and is located within the root directory of your project. Multi-root workspaces (“Add Folder to Workspace…”) are supported, and each workspace root can have its own “pyrightconfig.json” file. For a sample pyrightconfig.json file, see [below](../configuration/config-files.md#sample-config-file). @@ -396,110 +397,11 @@ basedpyright introduces two new diagnostic rulesets in addition to the ones in p !!! note some settings which are enabled by default in pyright are disabled by default in basedpyright (even when `typeCheckingMode` is `"all"`). this is because these rules are [discouraged](#discouraged-options), but in the interest of backwards compatibility with pyright, they remain available to any users who still want to use them. + + -| Diagnostic Rule | Off | Basic | Standard | Strict | All | -|:-----------------------------------------|:--------------|:--------------|:--------------|:--------------|:--------| -| analyzeUnannotatedFunctions | true | true | true | true | true | -| disableBytesTypePromotions | true | true | true | true | true | -| strictParameterNoneValue | false | true | true | true | true | -| enableTypeIgnoreComments | true | true | true | true | false | -| enableReachabilityAnalysis | false | true | true | true | true | -| strictListInference | false | false | false | true | true | -| strictDictionaryInference | false | false | false | true | true | -| strictSetInference | false | false | false | true | true | -| deprecateTypingAliases | false | false | false | false | true | -| enableExperimentalFeatures | false | false | false | false | true | -| reportMissingModuleSource | "none" | "warning" | "warning" | "warning" | "error" | -| reportInvalidTypeForm | "none" | "error" | "error" | "error" | "error" | -| reportMissingImports | "none" | "error" | "error" | "error" | "error" | -| reportUndefinedVariable | "none" | "error" | "error" | "error" | "error" | -| reportAssertAlwaysTrue | "none" | "warning" | "warning" | "error" | "error" | -| reportInvalidStringEscapeSequence | "none" | "warning" | "warning" | "error" | "error" | -| reportInvalidTypeVarUse | "none" | "warning" | "warning" | "error" | "error" | -| reportMissingTypeStubs | "none" | "none" | "none" | "error" | "error" | -| reportSelfClsParameterName | "none" | "warning" | "warning" | "error" | "error" | -| reportUnsupportedDunderAll | "none" | "warning" | "warning" | "error" | "error" | -| reportUnusedExpression | "none" | "warning" | "warning" | "error" | "error" | -| reportWildcardImportFromLibrary | "none" | "warning" | "warning" | "error" | "error" | -| reportAbstractUsage | "none" | "error" | "error" | "error" | "error" | -| reportArgumentType | "none" | "error" | "error" | "error" | "error" | -| reportAssertTypeFailure | "none" | "error" | "error" | "error" | "error" | -| reportAssignmentType | "none" | "error" | "error" | "error" | "error" | -| reportAttributeAccessIssue | "none" | "error" | "error" | "error" | "error" | -| reportCallIssue | "none" | "error" | "error" | "error" | "error" | -| reportGeneralTypeIssues | "none" | "error" | "error" | "error" | "error" | -| reportInconsistentOverload | "none" | "error" | "error" | "error" | "error" | -| reportIndexIssue | "none" | "error" | "error" | "error" | "error" | -| reportInvalidTypeArguments | "none" | "error" | "error" | "error" | "error" | -| reportNoOverloadImplementation | "none" | "error" | "error" | "error" | "error" | -| reportOperatorIssue | "none" | "error" | "error" | "error" | "error" | -| reportOptionalSubscript | "none" | "error" | "error" | "error" | "error" | -| reportOptionalMemberAccess | "none" | "error" | "error" | "error" | "error" | -| reportOptionalCall | "none" | "error" | "error" | "error" | "error" | -| reportOptionalIterable | "none" | "error" | "error" | "error" | "error" | -| reportOptionalContextManager | "none" | "error" | "error" | "error" | "error" | -| reportOptionalOperand | "none" | "error" | "error" | "error" | "error" | -| reportRedeclaration | "none" | "error" | "error" | "error" | "error" | -| reportReturnType | "none" | "error" | "error" | "error" | "error" | -| reportTypedDictNotRequiredAccess | "none" | "error" | "error" | "error" | "error" | -| reportPrivateImportUsage | "none" | "error" | "error" | "error" | "error" | -| reportUnboundVariable | "none" | "error" | "error" | "error" | "error" | -| reportUnhashable | "none" | "error" | "error" | "error" | "error" | -| reportUnusedCoroutine | "none" | "error" | "error" | "error" | "error" | -| reportUnusedExcept | "unreachable" | "error" | "error" | "error" | "error" | -| reportFunctionMemberAccess | "none" | "none" | "error" | "error" | "error" | -| reportIncompatibleMethodOverride | "none" | "none" | "error" | "error" | "error" | -| reportIncompatibleVariableOverride | "none" | "none" | "error" | "error" | "error" | -| reportOverlappingOverload | "none" | "none" | "error" | "error" | "error" | -| reportPossiblyUnboundVariable | "none" | "none" | "error" | "error" | "error" | -| reportConstantRedefinition | "none" | "none" | "none" | "error" | "error" | -| reportDeprecated | "deprecated" | "deprecated" | "deprecated" | "error" | "error" | -| reportDuplicateImport | "none" | "none" | "none" | "error" | "error" | -| reportIncompleteStub | "none" | "none" | "none" | "error" | "error" | -| reportInconsistentConstructor | "none" | "none" | "none" | "error" | "error" | -| reportInvalidStubStatement | "none" | "none" | "none" | "error" | "error" | -| reportMatchNotExhaustive | "none" | "none" | "none" | "error" | "error" | -| reportMissingParameterType | "none" | "none" | "none" | "error" | "error" | -| reportMissingTypeArgument | "none" | "none" | "none" | "error" | "error" | -| reportPrivateUsage | "none" | "none" | "none" | "error" | "error" | -| reportTypeCommentUsage | "deprecated" | "deprecated" | "deprecated" | "error" | "error" | -| reportUnknownArgumentType | "none" | "none" | "none" | "error" | "error" | -| reportUnknownLambdaType | "none" | "none" | "none" | "error" | "error" | -| reportUnknownMemberType | "none" | "none" | "none" | "error" | "error" | -| reportUnknownParameterType | "none" | "none" | "none" | "error" | "error" | -| reportUnknownVariableType | "none" | "none" | "none" | "error" | "error" | -| reportUnnecessaryCast | "none" | "none" | "none" | "error" | "error" | -| reportUnnecessaryComparison | "none" | "none" | "none" | "error" | "error" | -| reportUnnecessaryContains | "none" | "none" | "none" | "error" | "error" | -| reportUnnecessaryIsInstance | "none" | "none" | "none" | "error" | "error" | -| reportUnusedClass | "unused" | "unused" | "unused" | "error" | "error" | -| reportUnusedImport | "unused" | "unused" | "unused" | "error" | "error" | -| reportUnusedFunction | "unused" | "unused" | "unused" | "error" | "error" | -| reportUnusedVariable | "unused" | "unused" | "unused" | "error" | "error" | -| reportUntypedBaseClass | "none" | "none" | "none" | "error" | "error" | -| reportUntypedClassDecorator | "none" | "none" | "none" | "error" | "error" | -| reportUntypedFunctionDecorator | "none" | "none" | "none" | "error" | "error" | -| reportUntypedNamedTuple | "none" | "none" | "none" | "error" | "error" | -| reportCallInDefaultInitializer | "none" | "none" | "none" | "none" | "error" | -| reportImplicitOverride | "none" | "none" | "none" | "none" | "error" | -| reportImplicitStringConcatenation | "none" | "none" | "none" | "none" | "error" | -| reportImportCycles | "none" | "none" | "none" | "none" | "error" | -| reportMissingSuperCall | "none" | "none" | "none" | "none" | "error" | -| reportPropertyTypeMismatch | "none" | "none" | "none" | "none" | "error" | -| reportShadowedImports | "none" | "none" | "none" | "none" | "error" | -| reportUninitializedInstanceVariable | "none" | "none" | "none" | "none" | "error" | -| reportUnnecessaryTypeIgnoreComment | "none" | "none" | "none" | "none" | "error" | -| reportUnusedCallResult | "none" | "none" | "none" | "none" | "error" | -| reportUnreachable | "unreachable" | "unreachable" | "unreachable" | "unreachable" | "error" | -| reportAny | "none" | "none" | "none" | "none" | "error" | -| reportIgnoreCommentWithoutRule | "none" | "none" | "none" | "none" | "error" | -| reportPrivateLocalImportUsage | "none" | "none" | "none" | "none" | "error" | -| reportImplicitRelativeImport | "none" | "none" | "none" | "none" | "error" | -| reportInvalidCast | "none" | "none" | "none" | "none" | "error" | -| reportUnsafeMultipleInheritance | "none" | "none" | "none" | "none" | "error" | -| reportUnusedParameter | "unused" | "unused" | "unused" | "unused" | "error" | - + ## Overriding language server settings If a `pyproject.toml` (with a `basedpyright` or `pyright` section) or a `pyrightconfig.json` exists, any [dicouraged language server settings](./language-server-settings.md#discouraged-settings) (eg. in a VS Code `settings.json`) will be ignored. `pyrightconfig.json` is prescribing the environment to be used for a particular project. Changing the environment configuration options per user is not supported. @@ -520,3 +422,4 @@ LANGUAGE="fr" ``` When running in VS Code, the editor's locale takes precedence. Setting these environment variables applies only when using pyright outside of VS Code. + diff --git a/docs/configuration/config-files.md b/docs/configuration/config-files.md index 2e943ffc88..95f75b918e 100644 --- a/docs/configuration/config-files.md +++ b/docs/configuration/config-files.md @@ -1,6 +1,10 @@ ---8<-- "docs/configuration.md" \ No newline at end of file +--8<-- "docs/configuration.md:before-table" + +{{ generate_diagnostic_rule_table() }} + +--8<-- "docs/configuration.md:after-table" \ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 2adba5a107..0eeaa2dd06 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,3 +1,3 @@ .md-grid { - max-width: 1500px; + max-width: 1600px; } diff --git a/mkdocs.yml b/mkdocs.yml index 0fa2c01a22..1fc4d2e5b8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,6 +50,9 @@ plugins: - awesome-pages # https://github.com/wilhelmer/mkdocs-unused-files/issues/12 # - unused_files + - macros: + module_name: based_build/docs_macros + on_undefined: strict nav: - index.md - ... | benefits-over-pyright/**/*.md diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index 50fcc7f34b..ba0b1f8a32 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -1443,7 +1443,7 @@ export class ConfigOptions { configFileSource?: Uri | undefined; // Determines the effective default type checking mode. - effectiveTypeCheckingMode: 'all' | 'strict' | 'basic' | 'off' | 'standard' = 'standard'; // TODO: we default to "recommended", wheres this default being used? + effectiveTypeCheckingMode: TypeCheckingMode = 'standard'; // TODO: we default to "recommended", wheres this default being used? // https://github.com/microsoft/TypeScript/issues/3841 declare ['constructor']: typeof ConfigOptions; diff --git a/packages/pyright-internal/src/common/stringUtils.ts b/packages/pyright-internal/src/common/stringUtils.ts index 20b90d935d..228801b537 100644 --- a/packages/pyright-internal/src/common/stringUtils.ts +++ b/packages/pyright-internal/src/common/stringUtils.ts @@ -190,7 +190,7 @@ export function escapeRegExp(text: string) { * @example * userFacingOptionsList(['a', 'b', 'c']) // `"a", "b" or "c"` */ -export const userFacingOptionsList = (values: string[]) => +export const userFacingOptionsList = (values: readonly string[]) => values.map((mode, index) => (index < values.length - 1 ? `"${mode}", ` : `or "${mode}"`)).join(''); export const pluralize = (n: number, singular: string, plural: string = `${singular}s`) => diff --git a/packages/pyright-internal/src/pyright.ts b/packages/pyright-internal/src/pyright.ts index 859f0becc0..9771b215ad 100644 --- a/packages/pyright-internal/src/pyright.ts +++ b/packages/pyright-internal/src/pyright.ts @@ -50,6 +50,12 @@ import { convertDiagnostics } from 'pyright-to-gitlab-ci/src/converter'; import path from 'path'; import { BaselineHandler } from './baseline'; import { pluralize } from './common/stringUtils'; +import { + allTypeCheckingModes, + ConfigOptions, + getBooleanDiagnosticRules, + getDiagLevelDiagnosticRules, +} from './common/configOptions'; type SeverityLevel = 'error' | 'warning' | 'information'; @@ -178,6 +184,8 @@ async function processArgs(): Promise { { name: 'version', type: Boolean }, { name: 'warnings', type: Boolean }, { name: 'watch', alias: 'w', type: Boolean }, + // undocumented option only used internally for generating docs. pretty cringe but it's the least messy way i could think of to do it + { name: 'printdiagnosticrulesets', type: Boolean }, ]; let args: CommandLineOptions; @@ -194,7 +202,22 @@ async function processArgs(): Promise { console.error(`Unexpected error\n${toolName} --help for usage`); return ExitStatus.ParameterError; } - + if (args.printdiagnosticrulesets) { + console.log( + JSON.stringify( + [...getBooleanDiagnosticRules(true), ...getDiagLevelDiagnosticRules()].map((rule) => ({ + 'Diagnostic Rule': rule, + ...Object.fromEntries( + allTypeCheckingModes.map((typeCheckingMode) => [ + typeCheckingMode, + ConfigOptions.getDiagnosticRuleSet(typeCheckingMode)[rule], + ]) + ), + })) + ) + ); + return ExitStatus.NoErrors; + } if (args.help !== undefined) { printUsage(); return ExitStatus.NoErrors; diff --git a/pyproject.toml b/pyproject.toml index 236ab45932..1bd71211b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dev = [ "mkdocs-material>=9.5.37", "mkdocs-awesome-pages-plugin>=2.9.2", "mike>=2.1.3", + "mkdocs-macros-plugin>=1.2.0", ] docstubs = [ # these deps are also needed in build-system.requires. see https://github.com/pdm-project/pdm/issues/2947 @@ -282,6 +283,7 @@ ignore = [ "CPY001", # missing-copyright-notice "C901", # max-complexity "DOC402", # docstring-missing-yields + "DOC201", # docstring-missing-returns ] [tool.ruff.lint.pycodestyle] @@ -289,8 +291,8 @@ ignore-overlong-task-comments = true [tool.ruff.lint.per-file-ignores] "*.pyi" = ["A001", "A002", "N"] # we don't control names in 3rd party modules -"tests/**/*.py" = [ - "S", # none of these security focused rules are relevant for the tests +"{tests,based_build}/**/*.py" = [ + "S", # none of these security focused rules are relevant for tests/build scripts ] [tool.ruff.lint.isort] combine-as-imports = true From 648097a6badc5f8f53e3ddf74b492c41acbffe61 Mon Sep 17 00:00:00 2001 From: detachhead Date: Fri, 11 Oct 2024 01:04:50 +1000 Subject: [PATCH 3/7] add "Python Current File (justMyCode=false)" vscode debug config --- .vscode/launch.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index b3c55f10dc..b76e1a125b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,14 @@ { "version": "0.2.0", "configurations": [ + { + "name":"Python Current File (justMyCode=false)", + "type":"debugpy", + "request":"launch", + "program":"${file}", + "console":"integratedTerminal", + "justMyCode": false + }, { "name": "Pyright current file", "type": "node", From 48fe9a076b521b70fde6e0a5f7412a9345be60f2 Mon Sep 17 00:00:00 2001 From: detachhead Date: Sun, 13 Oct 2024 11:50:29 +1000 Subject: [PATCH 4/7] fix pdm install scripts/tasks --- .vscode/tasks.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 863fb97546..d0d609e0b8 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -80,7 +80,7 @@ "label": "install dependencies", "type": "shell", "command": "./pw", - "args": ["pdm", "sync", "--clean", "-G:all"], + "args": ["pdm", "sync", "--clean", "-G", "dev", "-G", "docstubs", "-G", "lochelper"], "presentation": { "clear": true }, diff --git a/pyproject.toml b/pyproject.toml index 1bd71211b1..e3805f1918 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ source = "call" getter = "based_build.get_version:get_version" [tool.pdm.scripts] -update = "pdm update -G dev -G docstubs -d lochelper" +update = "pdm update -G dev -G docstubs -G lochelper" # we specify the python range here because our dev dependencies require at least python 3.9 but we still want to # support 3.8 in the published package. ideally we wouldn't because it's EOL but we want to support all versions # of python supported by pyright. https://github.com/DetachHead/basedpyright/issues/658 From c5f0b218659b6e3103dbd1a12d16b416367ebe7f Mon Sep 17 00:00:00 2001 From: detachhead Date: Sun, 13 Oct 2024 11:50:44 +1000 Subject: [PATCH 5/7] update pdm lockfiles --- pdm.docstubs_old.lock | 2 +- pdm.lock | 63 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/pdm.docstubs_old.lock b/pdm.docstubs_old.lock index 5864498093..baa25d8f75 100644 --- a/pdm.docstubs_old.lock +++ b/pdm.docstubs_old.lock @@ -5,7 +5,7 @@ groups = ["default", "docstubs_old"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:4d2181df5e4438caa1d57236b091659bf903295d1324b6d57abc99d7eaad00ba" +content_hash = "sha256:25e93fe07bd10293480fef497999ff1e7996b3fc50c58c505382a902909d6449" [[metadata.targets]] requires_python = ">=3.8" diff --git a/pdm.lock b/pdm.lock index ac7bb1cd18..53157ed412 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev", "docstubs", "lochelper"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:4d2181df5e4438caa1d57236b091659bf903295d1324b6d57abc99d7eaad00ba" +content_hash = "sha256:25e93fe07bd10293480fef497999ff1e7996b3fc50c58c505382a902909d6449" [[metadata.targets]] requires_python = ">=3.9" @@ -248,6 +248,17 @@ files = [ {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, ] +[[package]] +name = "hjson" +version = "3.1.0" +summary = "Hjson, a user interface for JSON." +groups = ["dev"] +marker = "python_version >= \"3.9\"" +files = [ + {file = "hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89"}, + {file = "hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75"}, +] + [[package]] name = "idna" version = "3.10" @@ -633,6 +644,29 @@ files = [ {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, ] +[[package]] +name = "mkdocs-macros-plugin" +version = "1.3.5" +requires_python = ">=3.8" +summary = "Unleash the power of MkDocs with macros and variables" +groups = ["dev"] +marker = "python_version >= \"3.9\"" +dependencies = [ + "hjson", + "jinja2", + "mkdocs>=0.17", + "packaging", + "pathspec", + "python-dateutil", + "pyyaml", + "super-collections", + "termcolor", +] +files = [ + {file = "mkdocs-macros-plugin-1.3.5.tar.gz", hash = "sha256:5fd6969e2c43e23031ffb719bebe7421163ea26f4dc360af2343144ca979b04b"}, + {file = "mkdocs_macros_plugin-1.3.5-py3-none-any.whl", hash = "sha256:58bd47ea7097d1a2824dc9d0d912c211823c5e6e6fe8a19a3ecf33346f7d6547"}, +] + [[package]] name = "mkdocs-material" version = "9.5.40" @@ -1160,6 +1194,33 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "super-collections" +version = "0.5.3" +requires_python = ">=3.8" +summary = "file: README.md" +groups = ["dev"] +marker = "python_version >= \"3.9\"" +dependencies = [ + "hjson", +] +files = [ + {file = "super_collections-0.5.3-py3-none-any.whl", hash = "sha256:907d35b25dc4070910e8254bf2f5c928348af1cf8a1f1e8259e06c666e902cff"}, + {file = "super_collections-0.5.3.tar.gz", hash = "sha256:94c1ec96c0a0d5e8e7d389ed8cde6882ac246940507c5e6b86e91945c2968d46"}, +] + +[[package]] +name = "termcolor" +version = "2.5.0" +requires_python = ">=3.9" +summary = "ANSI color formatting for output in terminal" +groups = ["dev"] +marker = "python_version >= \"3.9\"" +files = [ + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, +] + [[package]] name = "textual" version = "0.83.0" From 1d3aa1d1fa3f7987d90b2eece1aee1da01c187be Mon Sep 17 00:00:00 2001 From: detachhead Date: Sun, 13 Oct 2024 13:22:05 +1000 Subject: [PATCH 6/7] remove note in the docs about diagnostic severity overrides not being able to reduce severity level. as far as i can tell this isn't true even in upstream pyright, and i see no reason to impose such a restriction --- docs/configuration.md | 2 +- packages/pyright-internal/src/common/configOptions.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 4cc386aedf..896c33d413 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -383,7 +383,7 @@ executionEnvironments = [ ## Diagnostic Settings Defaults -Each diagnostic setting has a default that is dictated by the specified type checking mode. The default for each rule can be overridden in the configuration file or settings. In strict type checking mode, overrides may only increase the strictness (e.g. increase the severity level from `"warning"` to `"error"`). +Each diagnostic setting has a default that is dictated by the specified type checking mode. The default for each rule can be overridden in the configuration file or settings. Some rules have an additional severity level such as `"unused"`, `"deprecated"` or `"unreachable"`. These are only used by the language server so that your editor can grey out or add a strikethrough to the symbol, which you can disable by setting it to `"off"`. it does not effect the outcome when running basedpyright via the CLI, so in that context these severity levels essentially mean the same thing as `"off"`. diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index ba0b1f8a32..f34b9aa9d0 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -453,8 +453,7 @@ export function getBooleanDiagnosticRules(includeNonOverridable = false) { if (includeNonOverridable) { // Do not include these because we don't - // want to override it in strict mode or support - // it within pyright comments. + // want to support it within pyright comments. boolRules.push(DiagnosticRule.enableTypeIgnoreComments); boolRules.push(DiagnosticRule.enableReachabilityAnalysis); boolRules.push(DiagnosticRule.failOnWarnings); @@ -592,6 +591,9 @@ export const extraOptionDiagnosticRules = [ deprecatedDiagnosticRules, ]; +// TODO: should this be removed? there was documentation stating that when typeCheckingMode is "strict" +// diagnostics can't be overridden with a less strict level. as far as i can tell this isn't the case +// so i think this function is useless export function getStrictModeNotOverriddenRules() { // In strict mode, the value in the user config file should be honored and // not overwritten by the value from the strict rule set. From 9f0be60c9cb0f2f0993fe6348e68be3f15e882b9 Mon Sep 17 00:00:00 2001 From: detachhead Date: Mon, 14 Oct 2024 21:56:48 +1000 Subject: [PATCH 7/7] change some defaults for recommended ruleset --- packages/pyright-internal/src/common/configOptions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index f34b9aa9d0..1f243c2c6f 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -961,7 +961,7 @@ export const getRecommendedDiagnosticRuleSet = (): DiagnosticRuleSet => ({ reportMissingImports: 'error', reportMissingModuleSource: 'error', reportInvalidTypeForm: 'error', - reportMissingTypeStubs: 'error', + reportMissingTypeStubs: 'warning', reportImportCycles: 'error', reportUnusedImport: 'warning', reportUnusedClass: 'warning', @@ -986,7 +986,7 @@ export const getRecommendedDiagnosticRuleSet = (): DiagnosticRuleSet => ({ reportOptionalIterable: 'error', reportOptionalContextManager: 'error', reportOptionalOperand: 'error', - reportRedeclaration: 'error', + reportRedeclaration: 'warning', reportReturnType: 'error', reportTypedDictNotRequiredAccess: 'error', reportUntypedFunctionDecorator: 'warning', @@ -1019,7 +1019,7 @@ export const getRecommendedDiagnosticRuleSet = (): DiagnosticRuleSet => ({ reportUnnecessaryCast: 'warning', reportUnnecessaryComparison: 'warning', reportUnnecessaryContains: 'warning', - reportAssertAlwaysTrue: 'warning', + reportAssertAlwaysTrue: 'error', reportSelfClsParameterName: 'error', reportImplicitStringConcatenation: 'warning', reportUnboundVariable: 'error',