Skip to content

Commit 99ea555

Browse files
committed
Added Pyre and typeshed d/l
1 parent 82b30a2 commit 99ea555

3 files changed

Lines changed: 81 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# CHANGELOG
22

3+
## 1.0.2 - 2023-10-16
4+
5+
* Added Pyre
6+
* Typecheckers now use a current clone of `typeshed` vs their shipped version
7+
8+
## 1.0.1 - 2023-10-10
9+
10+
* Improved error handling
11+
* Pinning for linter versions
12+
* Using PyPi location of `flake8-sarif-formatter`
13+
* Quieter log output
14+
315
## 1.0.0 - 2023-10-06
416

517
* Initial open source release

action.yml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ inputs:
88
description: 'The linter to use'
99
required: true
1010
default: 'flake8'
11-
choices: ['ruff', 'flake8', 'pylint', 'mypy', 'pyright', 'pytype', 'fixit']
11+
choices: ['ruff', 'flake8', 'pylint', 'mypy', 'pyright', 'pytype', 'fixit', 'pyre']
1212
target:
1313
description: 'The target to lint'
1414
required: true
@@ -50,6 +50,14 @@ inputs:
5050
description: 'The version of fixit to use'
5151
required: false
5252
default: 'latest'
53+
pyre-version:
54+
description: 'The version of pyre to use'
55+
required: false
56+
default: 'latest'
57+
typeshed-version:
58+
description: 'The version of typeshed to use'
59+
required: false
60+
default: 'main'
5361
runs:
5462
using: 'composite'
5563
steps:
@@ -73,8 +81,9 @@ runs:
7381
"${PYTHON_CMD}" -mpip install --upgrade pip
7482
7583
# set up linter variables
76-
linters=('ruff' 'flake8' 'pylint' 'mypy' 'pyright' 'pytype' 'fixit')
84+
linters=('ruff' 'flake8' 'pylint' 'mypy' 'pyright' 'pytype' 'fixit', 'pyre')
7785
install_flake8_formatter_linters=('ruff', 'flake8')
86+
install_typeshed_linters=('pyre', 'pytype', 'mypy', 'pyright')
7887
EXTRA_PIP_FLAGS=''
7988
LINTER_VERSION_CONSTRAINT=''
8089
EXTRA_LINTER_SCRIPT_FLAGS=''
@@ -115,6 +124,10 @@ runs:
115124
if [[ "${INPUTS_PYTYPE_VERSION}" != "latest" ]]; then
116125
LINTER_VERSION_CONSTRAINT="==${INPUTS_PYTYPE_VERSION}"
117126
fi
127+
elif [[ "${INPUTS_LINTER}" == "pyre" ]]; then
128+
if [[ "${INPUTS_PYRE_VERSION}" != "latest" ]]; then
129+
LINTER_VERSION_CONSTRAINT="==${INPUTS_PYRE_VERSION}"
130+
fi
118131
fi
119132
fi
120133
@@ -141,6 +154,14 @@ runs:
141154
EXTRA_LINTER_SCRIPT_FLAGS=" --debug"
142155
fi
143156
157+
# install typeshed if needed (for typecheckers)
158+
if [[ "${install_typeshed_linters[*]}" =~ (^|[^[:alpha:]])${INPUTS_LINTER}([^[:alpha:]]|$) ]]; then
159+
echo "::debug::Installing typeshed for ${INPUTS_LINTER}"
160+
# clone from GitHub
161+
gh repo clone python/typeshed -- --depth 1 --branch "${INPUTS_TYPESHED_VERSION}" "${GITHUB_WORKSPACE}/typeshed" || ( echo "::error::typeshed failed to install for Python ${INPUTS_PYTHON_VERSION}" && exit 1 )
162+
EXTRA_LINTER_SCRIPT_FLAGS+=" --typeshed-path=${GITHUB_WORKSPACE}/typeshed"
163+
fi
164+
144165
# run linter
145166
if ! "${PYTHON_CMD}" "${GITHUB_ACTION_PATH}"/python_lint.py "${INPUTS_LINTER}" --target="${INPUTS_TARGET}" --output="${GITHUB_WORKSPACE}/${INPUTS_OUTPUT}" ${EXTRA_LINTER_SCRIPT_FLAGS}; then
146167
# don't fail "hard" if it's known failures that we cannot account for (yet)
@@ -168,6 +189,8 @@ runs:
168189
INPUTS_PYRIGHT_VERSION: ${{ inputs.pyright-version }}
169190
INPUTS_PYTYPE_VERSION: ${{ inputs.pytype-version }}
170191
INPUTS_FIXIT_VERSION: ${{ inputs.fixit-version }}
192+
INPUTS_PYRE_VERSION: ${{ inputs.pyre-version }}
193+
INPUTS_TYPESHED_VERSION: ${{ inputs.typeshed-version }}
171194
shell: bash
172195
- name: Upload SARIF
173196
if: ${{ hashFiles(inputs.output) != '' }}

python_lint.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"""
1212

1313
import sys
14+
import os
1415
import logging
1516
from argparse import ArgumentParser
1617
from pathlib import Path
@@ -54,7 +55,7 @@ def make_sarif_run(tool_name: str) -> dict:
5455
return sarif_run
5556

5657

57-
def flake8_linter(target: Path) -> None:
58+
def flake8_linter(target: Path, *args) -> None:
5859
"""Run the flake8 linter.
5960
6061
In contrast to the other linters, flake8 has plugin architecture.
@@ -154,7 +155,7 @@ def ruff_format_sarif(results: List[Dict[str, Any]], target: Path) -> dict:
154155
return sarif_run
155156

156157

157-
def ruff_linter(target: Path) -> Optional[dict]:
158+
def ruff_linter(target: Path, *args) -> Optional[dict]:
158159
"""Run the ruff linter."""
159160
try:
160161
# pylint: disable=import-outside-toplevel
@@ -256,7 +257,7 @@ def pylint_format_sarif(results: List[Dict[str, Any]], target: Path) -> dict:
256257
return sarif_run
257258

258259

259-
def pylint_linter(target: Path) -> Optional[dict]:
260+
def pylint_linter(target: Path, *args) -> Optional[dict]:
260261
"""Run the pylint linter."""
261262
process = run(
262263
["pylint", "--output-format=json", "--recursive=y", target.absolute().as_posix()],
@@ -371,7 +372,7 @@ def mypy_format_sarif(mypy_results: str, target: Path) -> dict:
371372
return sarif_run
372373

373374

374-
def mypy_linter(target: Path) -> Optional[dict]:
375+
def mypy_linter(target: Path, typeshed_path: Path) -> Optional[dict]:
375376
"""Run the mypy linter."""
376377
mypy_args = [
377378
"--install-types",
@@ -383,6 +384,8 @@ def mypy_linter(target: Path) -> Optional[dict]:
383384
"--show-column-numbers",
384385
"--show-error-end",
385386
"--show-absolute-path",
387+
"--custom-typeshed-dir",
388+
typeshed_path.as_posix(),
386389
]
387390

388391
process_lint = run(["mypy", *mypy_args, target.absolute().as_posix()], capture_output=True, check=False)
@@ -462,9 +465,9 @@ def pyright_format_sarif(results: dict, target: Path) -> dict:
462465
return sarif_run
463466

464467

465-
def pyright_linter(target: Path) -> Optional[dict]:
468+
def pyright_linter(target: Path, typeshed_path: Path) -> Optional[dict]:
466469
"""Run the pyright linter."""
467-
process = run(["pyright", "--outputjson", target.absolute().as_posix()], capture_output=True, check=False)
470+
process = run(["pyright", "--outputjson", "--typeshedpath", typeshed_path, target.absolute().as_posix()], capture_output=True, check=False)
468471

469472
if process.stderr:
470473
LOG.error("STDERR: %s", process.stderr.decode("utf-8"))
@@ -545,10 +548,12 @@ def pytype_format_sarif(results: str, target: Path) -> dict:
545548
return sarif_run
546549

547550

548-
def pytype_linter(target: Path) -> Optional[dict]:
551+
def pytype_linter(target: Path, typeshed_path: Path) -> Optional[dict]:
549552
"""Run the pytype linter."""
553+
os.environ["TYPESHED_HOME"] = typeshed_path.as_posix()
554+
550555
process = run(
551-
["pytype", "--exclude", ".pytype/", "--", target.absolute().as_posix()], capture_output=True, check=False
556+
["pytype", "--exclude", ".pytype/", "--", target.as_posix()], capture_output=True, check=False,
552557
)
553558

554559
if process.stderr:
@@ -567,6 +572,33 @@ def pytype_linter(target: Path) -> Optional[dict]:
567572
return sarif_run
568573

569574

575+
def pyre_linter(target: Path, typeshed_path: Path) -> Optional[dict]:
576+
"""Run the pytype linter."""
577+
process = run(
578+
["pyre", "--source-directory", target.as_posix(), "--output", "sarif", "--typeshed", typeshed_path.as_posix(), "check"], capture_output=True, check=False
579+
)
580+
581+
if process.stderr:
582+
LOG.debug("STDERR: %s", process.stderr.decode("utf-8"))
583+
584+
if not process.stdout:
585+
LOG.error("No output from pytype")
586+
return None
587+
588+
try:
589+
sarif = json.loads(process.stdout.decode("utf-8"))
590+
except json.JSONDecodeError as err:
591+
LOG.error("Unable to parse pyre output: %s", err)
592+
LOG.debug("Output: %s", process.stdout.decode("utf-8"))
593+
return None
594+
595+
if "runs" in sarif and len(sarif["runs"]) > 0:
596+
return sarif["runs"][0]
597+
598+
LOG.error("SARIF not correctly formed, or no runs to output")
599+
return None
600+
601+
570602
def make_fixit_description(rule: str) -> str:
571603
"""Format 'SomeRuleDescription' into 'Some rule description'."""
572604
rule = FIND_CAMEL_CASE.sub(lambda x: x.group(0).lower() + " ", rule)
@@ -670,6 +702,7 @@ def make_paths_relative_to_target(runs: List[dict], target: Path) -> None:
670702
"mypy": mypy_linter,
671703
"pyright": pyright_linter,
672704
"fixit": fixit_linter,
705+
"pyre": pyre_linter,
673706
}
674707

675708
# pytype is only supported on Python 3.10 and below, at the time of writing
@@ -682,6 +715,7 @@ def add_args(parser: ArgumentParser) -> None:
682715
parser.add_argument("linter", choices=LINTERS.keys(), nargs="+", help="The linter(s) to use")
683716
parser.add_argument("--target", "-t", default=".", required=False, help="Target path for the linter")
684717
parser.add_argument("--output", "-o", default="python_linter.sarif", required=False, help="Output filename")
718+
parser.add_argument("--typeshed-path",required=False, help="Path to typeshed")
685719
parser.add_argument("--debug", "-d", action="store_true", required=False, help="Enable debug logging")
686720

687721

@@ -700,11 +734,12 @@ def main() -> None:
700734
sarif_runs: List[dict] = []
701735

702736
target = Path(args.target).resolve().absolute()
737+
typeshed_path = Path(args.typeshed_path).resolve().absolute()
703738

704739
for linter in args.linter:
705740
LOG.debug("Running %s", linter)
706741

707-
sarif_run = LINTERS[linter](target)
742+
sarif_run = LINTERS[linter](target, typeshed_path)
708743

709744
if sarif_run is not None:
710745
sarif_runs.append(sarif_run)

0 commit comments

Comments
 (0)