Skip to content

Commit

Permalink
Merge pull request #612 from lukaspiatkowski/pyright
Browse files Browse the repository at this point in the history
Add pyright as an optional tool
  • Loading branch information
carlio authored May 7, 2023
2 parents ebe72af + 1c06e47 commit c798436
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 7 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Contributors
* Joe Wallis (`@Peilonrayz <https://github.com/Peilonrayz>`_)
* Jon Parise (`@jparise <https://github.com/jparise>`_)
* Kristian Glass (`@doismellburning <https://github.com/doismellburning>`_)
* Lukasz Piatkowski (`@lukaspiatkowski <https://github.com/lukaspiatkowski>`_)
* Luke Hinds (`@lukehinds <https://github.com/lukehinds>`_)
* Matt Seymour (`@mattseymour <https://github.com/mattseymour>`_)
* Michael Tinsley (`@michaeltinsley <https://github.com/michaeltinsley>`_)
Expand Down
27 changes: 26 additions & 1 deletion docs/profiles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ Individual Configuration Options

Each tool can be individually configured with a section beginning with the tool name
(in lowercase). Valid values are ``bandit``, ``dodgy``, ``frosted``, ``mccabe``, ``mypy``, ``pydocstyle``, ``pycodestyle``,
``pyflakes``, ``pylint``, ``pyroma`` and ``vulture``.
``pyflakes``, ``pylint``, ``pyright``, ``pyroma`` and ``vulture``.

Enabling and Disabling Tools
............................
Expand Down Expand Up @@ -402,16 +402,35 @@ The available options are:
+----------------+------------------------+----------------------------------------------+
| bandit | confidence | report only issues of a given confidence |
+----------------+------------------------+----------------------------------------------+
| pyright | level | Minimum diagnostic level (error or warning) |
+----------------+------------------------+----------------------------------------------+
| pyright | project | Path to location of configuration file |
+----------------+------------------------+----------------------------------------------+
| pyright | pythonplatform | Analyze for a specific platform (Darwin, |
| | | Linux, Windows) |
+----------------+------------------------+----------------------------------------------+
| pyright | pythonversion | Analyze for a specific version |
+----------------+------------------------+----------------------------------------------+
| pyright | skipunannotated | Skip analysis of functions with no type |
| | | annotations |
+----------------+------------------------+----------------------------------------------+
| pyright | typeshed-path | Path to location of typeshed type stubs |
+----------------+------------------------+----------------------------------------------+
| pyright | venv-path | Directory that contains virtual environments |
+----------------+------------------------+----------------------------------------------+

See `mypy options`_ for more details

See `bandit options`_ for more details

See `pyright options`_ for more details



.. _pylint options: https://pylint.readthedocs.io/en/latest/user_guide/run.html
.. _bandit options: https://bandit.readthedocs.io/en/latest/config.html
.. _mypy options: https://mypy.readthedocs.io/en/stable/command_line.html
.. _pyright options: https://microsoft.github.io/pyright/#/command-line



Expand Down Expand Up @@ -493,6 +512,12 @@ Next is another example using most options::
- PYR15
- PYR18

pyright:
options:
level: warning
pythonversion: 3.7
skipunannotated: true

mypy:
run: true
options:
Expand Down
5 changes: 5 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ Tool classes
.. autoclass:: prospector.tools.pyroma.PyromaTool
:members:

:class:`PyrightTool`
-------------------
.. autoclass:: prospector.tools.pyright.PyrightTool
:members:

:class:`VultureTool`
--------------------
.. autoclass:: prospector.tools.vulture.VultureTool
Expand Down
12 changes: 11 additions & 1 deletion docs/supported_tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ To install and use::
prospector --with-tool mypy



`Bandit <https://github.com/PyCQA/bandit>`_
```````````````````````````````````````````
Bandit finds common security issues in Python code.
Expand All @@ -174,3 +173,14 @@ To install and use::

pip install prospector[with_bandit]
prospector --with-tool bandit


`Pyright <https://github.com/microsoft/pyright>`_
````````````````````````````````````````
Pyright is a full-featured, standards-based static type checker for Python. It
is designed for high performance and can be used with large Python source bases.

To install and use::

pip install prospector[with_pyright]
prospector --with-tool pyright
31 changes: 27 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions prospector/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def _profile_validator_tool(*args, **kwargs):
"profile-validator": _profile_validator_tool,
"vulture": _optional_tool("vulture"),
"pyroma": _optional_tool("pyroma"),
"pyright": _optional_tool("pyright"),
"mypy": _optional_tool("mypy"),
"bandit": _optional_tool("bandit"),
}
Expand Down
88 changes: 88 additions & 0 deletions prospector/tools/pyright/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import json
import subprocess

import pyright

from prospector.message import Location, Message
from prospector.tools import ToolBase

__all__ = ("PyrightTool",)

from prospector.tools.exceptions import BadToolConfig

VALID_OPTIONS = [
"level",
"project",
"pythonplatform",
"pythonversion",
"skipunannotated",
"typeshed-path",
"venv-path",
]


def format_messages(json_encoded):
json_decoded = json.loads(json_encoded)
diagnostics = json_decoded.get("generalDiagnostics", [])
messages = []
for diag in diagnostics:
start_range = diag.get("range", {}).get("start", {})
location = Location(
path=diag["file"],
module=None,
function=None,
line=start_range.get("line", -1),
character=start_range.get("character", -1),
)
messages.append(
Message(source="pyright", code=diag.get("rule", ""), location=location, message=diag.get("message", ""))
)

return messages


class PyrightTool(ToolBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.checker = pyright
self.options = ["--outputjson"]

def configure(self, prospector_config, _):
options = prospector_config.tool_options("pyright")

for option_key in options.keys():
if option_key not in VALID_OPTIONS:
url = "https://github.com/PyCQA/prospector/blob/master/prospector/tools/pyright/__init__.py"
raise BadToolConfig(
"pyright", f"Option {option_key} is not valid. " f"See the list of possible options: {url}"
)

level = options.get("level", None)
project = options.get("project", None)
pythonplatform = options.get("pythonplatform", None)
pythonversion = options.get("pythonversion", None)
skipunannotated = options.get("skipunannotated", False)
typeshed_path = options.get("typeshed-path", None)
venv_path = options.get("venv-path", None)

if level:
self.options.extend(["--level", level])
if project:
self.options.extend(["--project", project])
if pythonplatform:
self.options.extend(["--pythonplatform", pythonplatform])
if pythonversion:
self.options.extend(["--pythonversion", pythonversion])
if skipunannotated:
self.options.append("--skipunannotated")
if typeshed_path:
self.options.extend(["--typeshed-path", typeshed_path])
if venv_path:
self.options.extend(["--venv-path", venv_path])

def run(self, found_files):
paths = [str(path) for path in found_files.python_modules]
paths.extend(self.options)
result = self.checker.run(*paths, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

return format_messages(result.stdout)
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ toml = "^0.10.2"
bandit = {version = ">=1.5.1", optional = true}
vulture = {version = ">=1.5", optional = true}
mypy = {version = ">=0.600", optional = true}
pyright = {version = ">=1.1.3", optional = true}
pyroma = {version = ">=2.4", optional = true}
setoptconf-tmp = "^0.3.1"
GitPython = "^3.1.27"
Expand All @@ -65,9 +66,10 @@ packaging = "*"
[tool.poetry.extras]
with_bandit = ["bandit"]
with_mypy = ["mypy"]
with_pyright = ["pyright"]
with_pyroma = ["pyroma"]
with_vulture = ["vulture"]
with_everything = ["bandit", "mypy", "pyroma", "vulture"]
with_everything = ["bandit", "mypy", "pyright", "pyroma", "vulture"]

[tool.poetry.dev-dependencies]
coveralls = "^3.3.1"
Expand Down
Empty file added tests/tools/pyright/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions tests/tools/pyright/test_profiles/pyright_bad_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pyright:
run: yes
options:
warn-unreachable: true
4 changes: 4 additions & 0 deletions tests/tools/pyright/test_profiles/pyright_good_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pyright:
run: yes
options:
level: error
96 changes: 96 additions & 0 deletions tests/tools/pyright/test_pyright_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import json
from pathlib import Path
from unittest import SkipTest, TestCase
from unittest.mock import patch

from prospector.config import ProspectorConfig
from prospector.finder import FileFinder
from prospector.message import Location, Message
from prospector.tools.exceptions import BadToolConfig

try:
from prospector.tools.pyright import format_messages
except ImportError:
raise SkipTest


class TestPyrightTool(TestCase):
@staticmethod
def _get_config(profile_name: str) -> ProspectorConfig:
profile_path = Path(__file__).parent / f"test_profiles/{profile_name}.yaml"
with patch("sys.argv", ["prospector", "--profile", str(profile_path.absolute())]):
return ProspectorConfig()

def test_unrecognised_options(self):
finder = FileFinder(Path(__file__).parent)
self.assertRaises(BadToolConfig, self._get_config("pyright_bad_options").get_tools, finder)

def test_good_options(self):
finder = FileFinder(Path(__file__).parent)
self._get_config("pyright_good_options").get_tools(finder)


class TestPyrightMessageFormat(TestCase):
def _encode_messages(self, messages):
return json.dumps({"generalDiagnostics": messages})

def test_format_message_with_character(self):
location = Location(path="file.py", module=None, function=None, line=17, character=2)
expected = Message(source="pyright", code="error", location=location, message="Important error")
self.assertEqual(
format_messages(
self._encode_messages(
[
{
"file": "file.py",
"message": "Important error",
"rule": "error",
"range": {"start": {"line": 17, "character": 2}},
}
]
)
),
[expected],
)

def test_format_message_without_character(self):
location = Location(path="file.py", module=None, function=None, line=17, character=-1)
expected = Message(source="pyright", code="note", location=location, message="Important error")
self.assertEqual(
format_messages(
self._encode_messages(
[
{
"file": "file.py",
"message": "Important error",
"rule": "note",
"range": {"start": {"line": 17}},
}
]
)
),
[expected],
)

def test_format_message_without_line(self):
location = Location(path="file.py", module=None, function=None, line=-1, character=-1)
expected = Message(
source="pyright",
code="error",
location=location,
message="Important error",
)
self.assertEqual(
format_messages(
self._encode_messages(
[
{
"file": "file.py",
"message": "Important error",
"rule": "error",
}
]
)
),
[expected],
)

0 comments on commit c798436

Please sign in to comment.