-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
04163ee
commit 903a2d7
Showing
52 changed files
with
1,330 additions
and
727 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# baseline (beta) | ||
have you ever wanted to adopt a new tool or enable new checks in an existing project, only to be immediately bombarded with thousands of errors you'd have to fix? baseline solves this problem by allowing you to only report errors on new or modified code. it works by generating a baseline file keeping track of the existing errors in your project so that only errors in newly written or modified code get reported. | ||
|
||
to enable baseline, run `basedpyright --writebaseline` in your terminal or run the _"basedpyright: Write new errors to baseline"_ task in vscode. this will generate a `./basedpyright/baseline.json` for your project. you should commit this file so others working on your project can benefit from it too. | ||
|
||
this file gets automatically updated as errors are removed over time in both the CLI and the language server. if you ever need to baseline new errors or an error that resurfaced because you've modified the same line of code it was on, just run that command again. | ||
|
||
## how does it work? | ||
|
||
each baselined error is stored and matched by the following details: | ||
|
||
- the path of the file it's in (relative to the project root) | ||
- its diagnostic rule name (eg. `reportGeneralTypeIssues`) | ||
- the position of the error in the file (column only, which prevents errors from resurfacing when you add or remove lines in a file) | ||
|
||
no baseline matching strategy is perfect, so this is subject to change. baseline is in beta so if you have any feedback please [raise an issue](https://github.com/DetachHead/basedpyright/issues/new/choose). | ||
|
||
## how is this different to `# pyright: ignore` comments? | ||
|
||
ignore comments are typically used to suppress a false positive or workaround some limitation in the type checker. baselining is a way to suppress many valid instances of an error across your whole project, to avoid the burden of having to update thousands of lines of old code just to adopt stricter checks on your new code. | ||
|
||
## credit | ||
|
||
this is heavily inspired by [basedmypy](https://kotlinisland.github.io/basedmypy/baseline). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# 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 | ||
|
||
## `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. | ||
|
||
## `pythonPlatform` | ||
|
||
used to assume that the operating system pyright is being run on is the only operating system your code will run on, which is rarely the case. in basedpyright, `pythonPlatform` defaults to `All`, which assumes your code can run on any operating system. |
12 changes: 12 additions & 0 deletions
12
docs/benefits-over-pyright/errors-on-invalid-configuration.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# errors on invalid configuration | ||
|
||
in pyright, if you have any invalid configuration, it may or may not print a warning to the console, then it will continue type-checking and the exit code will be 0 as long as there were no type errors: | ||
|
||
```toml | ||
[tool.pyright] | ||
mode = "strict" # wrong! the setting you're looking for is called `typeCheckingMode` | ||
``` | ||
|
||
in this example, it's very easy for errors to go undetected because you thought you were on strict mode, but in reality pyright just ignored the setting and silently continued type-checking on "basic" mode. | ||
|
||
to solve this problem, basedpyright will exit with code 3 on any invalid config when using the CLI, and show an error notification when using the language server. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# fixes for the `reportRedeclaration` and `reportDuplicateImport` rules | ||
|
||
pyright does not report redeclarations if the redeclaration has the same type: | ||
|
||
```py | ||
foo: int = 1 | ||
foo: int = 2 # no error | ||
``` | ||
|
||
nor does it care if you have a duplicated import in multiple different `import` statements, or in aliases: | ||
|
||
```py | ||
from foo import bar | ||
from bar import bar # no error | ||
from baz import foo as baz, bar as baz # no error | ||
``` | ||
|
||
basedpyright solves both of these problems by always reporting an error on a redeclaration or an import with the same name as an existing import. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# improved integration with CI platforms | ||
|
||
regular pyright has third party integrations for github actions and gitlab, but they are difficult to install/set up. these integrations are built into basedpyright, which makes them much easier to use. | ||
|
||
## github actions | ||
|
||
basedpyright automatically detects when it's running in a github action, and modifies its output to use [github workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions). this means errors will be displayed on the affected lines of code in your pull requests automatically: | ||
|
||
![image](https://github.com/DetachHead/basedpyright/assets/57028336/cc820085-73c2-41f8-ab0b-0333b97e2fea) | ||
|
||
this is an improvement to regular pyright, which requires you to use a [third party action](https://github.com/jakebailey/pyright-action) that [requires boilerplate to get working](https://github.com/jakebailey/pyright-action?tab=readme-ov-file#use-with-a-virtualenv). basedpyright just does it automatically without you having to do anything special: | ||
|
||
```yaml | ||
# .github/workflows/your_workflow.yaml | ||
|
||
jobs: | ||
check: | ||
steps: | ||
- run: ... # checkout repo, install dependencies, etc | ||
- run: basedpyright # no additional arguments required. it automatically detects if it's running in a github action | ||
``` | ||
## gitlab code quality reports | ||
the `--gitlabcodequality` argument will output a [gitlab code quality report](https://docs.gitlab.com/ee/ci/testing/code_quality.html) which shows up on merge requests: | ||
|
||
![image](https://github.com/DetachHead/basedpyright/assets/57028336/407f0e61-15f2-4d04-b235-1946d49fd180) | ||
|
||
to enable this in your gitlab CI, just specify a file path to output the report to, and in the `artifacts.reports.codequality` section of your `.gitlab-ci.yml` file: | ||
|
||
```yaml | ||
basedpyright: | ||
script: basedpyright --gitlabcodequality report.json | ||
artifacts: | ||
reports: | ||
codequality: report.json | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# improved translations | ||
|
||
the translations in pyright come from microsoft's localization team, who are not programmers. not only does this result in poor quality translations, but microsoft also doesn't accept contributions to fix them ([more info here](https://github.com/microsoft/pyright/issues/7441#issuecomment-1987027067)). | ||
|
||
we accept translation fixes in basedpyright. [see the localization guidelines](../development/localization.md) for information on how to contribute. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# inline `TypedDict` support | ||
|
||
pyright used to support defining `TypedDict`s inline, like so: | ||
|
||
```py | ||
foo: dict[{"foo": int, "bar": str}] = {"foo": "a", "bar": 1} | ||
``` | ||
|
||
this was an experimental feature and was removed because it never made it into a PEP. but this functionality is very convenient and we see no reason not to continue supporting it, so we added it back in basedpyright. | ||
|
||
this can be disabled by setting `enableExperimentalFeatures` to `false`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# new diagnostic rules | ||
|
||
## `reportUnreachable` | ||
|
||
pyright often incorrectly marks code as unreachable. in most cases, unreachable code is a mistake and therefore should be an error, but pyright does not have an option to report unreachable code. in fact, unreachable code is not even type-checked at all: | ||
|
||
```py | ||
if sys.platform == "win32": | ||
1 + "" # no error | ||
``` | ||
|
||
by default, pyright will treat the body in the code above as unreachable if pyright itself was run on an operating system other than windows. this is bad of course, because chances are if you write such a check, you intend for your code to be executed on multiple platforms. | ||
|
||
to make things worse, unreachable code is not even type-checked, so the obviously invalid `1 + ""` above will go completely unnoticed by the type checker. | ||
|
||
basedpyright solves this issue with a `reportUnreachable` option, which will report an error on such unchecked code. in this example, you can [update your pyright config to specify more platforms using the `pythonPlatform` option](https://github.com/detachhead/basedpyright/blob/main/docs/configuration.md#main-configuration-options) if you intend for the code to be reachable. | ||
|
||
## `reportAny` | ||
|
||
pyright has a few options to ban "Unknown" types such as `reportUnknownVariableType`, `reportUnknownParameterType`, etc. but "Unknown" is not a real type, rather a distinction pyright uses used to represent `Any`s that come from untyped code or unfollowed imports. if you want to ban all kinds of `Any`, pyright has no way to do that: | ||
|
||
```py | ||
def foo(bar, baz: Any) -> Any: | ||
print(bar) # error: unknown type | ||
print(baz) # no error | ||
``` | ||
|
||
basedpyright introduces the `reportAny` option, which will report an error on usages of anything typed as `Any`. | ||
|
||
## `reportIgnoreCommentWithoutRule` | ||
|
||
it's good practice to specify an error code in your `pyright: ignore` comments: | ||
|
||
```py | ||
# pyright: ignore[reportUnreachable] | ||
``` | ||
|
||
this way, if the error changes or a new error appears on the same line in the future, you'll get a new error because the comment doesn't account for the other error. | ||
|
||
note that `type: ignore` comments (`enableTypeIgnoreComments`) are unsafe and are disabled by default (see [#330](https://github.com/DetachHead/basedpyright/issues/330) and [#55](https://github.com/DetachHead/basedpyright/issues/55)). we recommend using `pyright: ignore` comments instead. | ||
|
||
## `reportPrivateLocalImportUsage` | ||
|
||
pyright's `reportPrivateImportUsage` rule only checks for private imports of third party modules inside `py.typed` packages. but there's no reason your own code shouldn't be subject to the same restrictions. to explicitly re-export something, give it a redundant alias [as described in the "Stub Files" section of PEP484](https://peps.python.org/pep-0484/#stub-files) (although it only mentions stub files, other type checkers like mypy have also extended this behavior to source files as well): | ||
|
||
```py | ||
# foo.py | ||
|
||
from .some_module import a # private import | ||
from .some_module import b as b # explicit re-export | ||
``` | ||
|
||
```py | ||
# bar.py | ||
|
||
# reportPrivateLocalImportUsage error, because `a` is not explicitly re-exported by the `foo` module: | ||
from foo import a | ||
|
||
# no error, because `b` is explicitly re-exported: | ||
from foo import b | ||
``` | ||
|
||
## `reportImplicitRelativeImport` | ||
|
||
pyright allows invalid imports such as this: | ||
|
||
```py | ||
# ./module_name/foo.py: | ||
``` | ||
|
||
```py | ||
# ./module_name/bar.py: | ||
import foo # wrong! should be `import module_name.foo` or `from module_name import foo` | ||
``` | ||
|
||
this may look correct at first glance, and will work when running `bar.py` directly as a script, but when it's imported as a module, it will crash: | ||
|
||
```py | ||
# ./main.py: | ||
import module_name.bar # ModuleNotFoundError: No module named 'foo' | ||
``` | ||
|
||
the new `reportImplicitRelativeImport` rule bans imports like this. if you want to do a relative import, the correct way to do it is by importing it from `.` (the current package): | ||
|
||
```py | ||
# ./module_name/bar.py: | ||
from . import foo | ||
``` | ||
|
||
## `reportInvalidCast` | ||
|
||
most of the time when casting, you want to either cast to a narrower or wider type: | ||
|
||
```py | ||
foo: int | None | ||
cast(int, foo) # narrower type | ||
cast(object, foo) # wider type | ||
``` | ||
|
||
but pyright doesn't prevent casts to a type that doesn't overlap with the original: | ||
|
||
```py | ||
foo: int | ||
cast(str, foo) | ||
``` | ||
|
||
in this example, it's impossible to be `foo` to be a `str` if it's also an `int`, because the `int` and `str` types do not overlap. the `reportInvalidCast` rule will report invalid casts like these. | ||
|
||
### note about casting with `TypedDict`s | ||
|
||
a common use case of `cast` is to convert a regular `dict` into a `TypedDict`: | ||
|
||
```py | ||
foo: dict[str, int | str] | ||
bar = cast(dict[{"foo": int, "bar": str}], foo) | ||
``` | ||
|
||
unfortunately, this will cause a `reportInvalidCast` error when this rule is enabled, because although at runtime `TypedDict` is a `dict`, type checkers treat it as an unrelated subtype of `Mapping` that doesn't have a `clear` method, which would break its type-safety if it were to be called on a `TypedDict`. | ||
|
||
this means that although casting between them is a common use case, `TypedDict`s and `dict`s technically do not overlap. | ||
|
||
## `reportUnsafeMultipleInheritance` | ||
|
||
multiple inheritance in python is awful: | ||
|
||
```py | ||
class Foo: | ||
def __init__(self): | ||
super().__init__() | ||
class Bar: | ||
def __init__(self): | ||
... | ||
|
||
class Baz(Foo, Bar): | ||
... | ||
|
||
Baz() | ||
``` | ||
|
||
in this example, `Baz()` calls `Foo.__init__`, and the `super().__init__()` in `Foo` now calls to `Bar.__init__` even though `Foo` does not extend `Bar`. | ||
|
||
this is complete nonsense and very unsafe, because there's no way to statically know what the super class will be. | ||
|
||
pyright has the `reportMissingSuperCall` rule which, for this reason, complains even when your class doesn't have a base class. but that sucks because there's no way to know what arguments the unknown `__init__` takes, which means even if you do add a call to `super().__init__()` you have no clue what arguments it may take. so this rule is super annoying when it's enabled, and has very little benefit because it barely makes a difference in terms of safety. | ||
|
||
`reportUnsafeMultipleInheritance` bans multiple inheritance when there are multiple base classes with an `__init__` or `__new__` method, as there's no way to guarantee that all of them will get called with the correct arguments (or at all). this allows `reportMissingSuperCall` to be more lenient. ie. when `reportUnsafeMultipleInheritance` is enabled, missing `super()` calls will only be reported on classes that actually have a base class. | ||
|
||
## `reportUnusedParameter` | ||
|
||
pyright will report an unused diagnostic on unused function parameters: | ||
|
||
```py | ||
def print_value(value: str): # "value" is not accessed | ||
print("something else") | ||
``` | ||
|
||
but like with [unreachable code](#reportunreachable), this just greys out code instead of actually reporting it as an error. basedpyright introduces a new `reportUnusedParameter` diagnostic rule which supports all the severity options (`"error"`, `"warning"` and `"none"`) as well as `"unused"`, which is the default behavior in pyright. |
Oops, something went wrong.