1111"""
1212
1313import sys
14+ import os
1415import logging
1516from argparse import ArgumentParser
1617from 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+
570602def 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