Skip to content

Commit

Permalink
docs refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
DetachHead committed Sep 29, 2024
1 parent 04163ee commit 903a2d7
Show file tree
Hide file tree
Showing 52 changed files with 1,330 additions and 727 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ thanks for taking the time to raise an issue. please consider the following:
- note that the default rules are different in pyright (most checks are disabled by default). make sure to check the configuration options on the sidebar if your issue is with a specific diagnostic rule
- if it does also occur in pyright, has it been raised upstream at https://github.com/microsoft/pyright? this isn't required, but it's useful to know if the problem has already been discussed. also if you're lucky and your issue doesn't get rejected, chances are it will be fixed faster.
- if you're reporting an issue installing basedpyright, please mention what operating system and python version you're using
- if you're interested in fixing the issue yourself, we've tried our best to make the process to get set up for local development as easy as possible: https://docs.basedpyright.com/#/build-debug?id=building-basedpyright
- if you're interested in fixing the issue yourself, we've tried our best to make the process to get set up for local development as easy as possible: https://docs.basedpyright.com/development/build-debug/#building-basedpyright
-->
9 changes: 7 additions & 2 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-python@v5
id: install_python
with:
python-version: 3.12
- name: Setup Pages
uses: actions/configure-pages@v4
- run: cp README.md docs/README.md
- name: build docs
run: ./pw pdm run mkdocs build --strict
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'docs'
path: 'site'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
12 changes: 4 additions & 8 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@ docstubs
*.json
**/.github/ISSUE_TEMPLATE/**

# ignore most markdown files to avoid unnecessary upstream conflicts, but whitelist files
# that either don't exist upstream or that we've completely rewritten
*.md
!/README.md
!/packages/browser-pyright/**/*.md
!/docs/installation.md
!/docs/upstream.md
!/docs/localization.md
# ignore markdown files that we haven't really touched to avoid unnecessary upstream conflicts
docs/configuration/*.md
docs/development/internals.md
docs/getting_started/**/*.md

**/.venv/**
.pyprojectx
496 changes: 5 additions & 491 deletions README.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion docs/_navbar.md

This file was deleted.

46 changes: 0 additions & 46 deletions docs/_sidebar.md

This file was deleted.

24 changes: 24 additions & 0 deletions docs/benefits-over-pyright/baseline.md
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).
11 changes: 11 additions & 0 deletions docs/benefits-over-pyright/better-defaults.md
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 docs/benefits-over-pyright/errors-on-invalid-configuration.md
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.
18 changes: 18 additions & 0 deletions docs/benefits-over-pyright/fixes-for-rules.md
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.
37 changes: 37 additions & 0 deletions docs/benefits-over-pyright/improved-ci-integration.md
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
```
5 changes: 5 additions & 0 deletions docs/benefits-over-pyright/improved-translations.md
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.
11 changes: 11 additions & 0 deletions docs/benefits-over-pyright/inline-typed-dict.md
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`.
157 changes: 157 additions & 0 deletions docs/benefits-over-pyright/new-diagnostic-rules.md
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.
Loading

0 comments on commit 903a2d7

Please sign in to comment.