2
2
import io
3
3
import os
4
4
import re
5
+ import subprocess
6
+ import tempfile
5
7
import tokenize
6
8
from dataclasses import dataclass , field
7
9
from pathlib import Path
8
10
from typing import ClassVar , TypeAlias
9
11
10
- from mypy import api
11
-
12
12
ROOT_DIR = Path (__file__ ).parent .parent
13
- MYPY_CONFIG = ROOT_DIR / "pyproject.toml"
14
13
15
14
16
15
ChallengeName : TypeAlias = str
@@ -47,12 +46,6 @@ class TypeCheckResult:
47
46
48
47
49
48
class ChallengeManager :
50
- EXPECT_ERROR_COMMENT = "expect-type-error"
51
-
52
- # Each mypy error should look like: `<filename>:<line_number>: <error|note>: <message>`
53
- # Here we only capture the error messages and line numbers
54
- MYPY_MESSAGE_REGEX = r"^(?:.+?):(\d+):(\s*error:.+)$"
55
-
56
49
def __init__ (self ):
57
50
self .challenges = self ._load_challenges ()
58
51
self .challenge_names = [
@@ -69,7 +62,7 @@ def get_challenge(self, name: str) -> Challenge:
69
62
def run_challenge (self , name : str , user_code : str ) -> TypeCheckResult :
70
63
challenge = self .get_challenge (name )
71
64
code = f"{ user_code } \n { challenge .test_code } "
72
- return self ._type_check_with_mypy (code )
65
+ return self ._type_check_with_pyright (code )
73
66
74
67
@staticmethod
75
68
def _load_challenges () -> dict [ChallengeName , Challenge ]:
@@ -87,8 +80,15 @@ def _load_challenges() -> dict[ChallengeName, Challenge]:
87
80
88
81
return challenges
89
82
83
+ EXPECT_ERROR_COMMENT = "expect-type-error"
84
+
85
+ # Pyright error messages look like:
86
+ # `<filename>:<line_no>:<col_no> - <error|warning|information>: <message>`
87
+ # Here we only capture the error messages and line numbers
88
+ PYRIGHT_MESSAGE_REGEX = r"^(?:.+?):(\d+):[\s\-\d]+(error:.+)$"
89
+
90
90
@classmethod
91
- def _type_check_with_mypy (cls , code : str ) -> TypeCheckResult :
91
+ def _type_check_with_pyright (cls , code : str ) -> TypeCheckResult :
92
92
buffer = io .StringIO (code )
93
93
94
94
# This produces a stream of TokenInfos, example:
@@ -104,11 +104,19 @@ def _type_check_with_mypy(cls, code: str) -> TypeCheckResult:
104
104
if token .type == tokenize .COMMENT
105
105
and token .string [1 :].strip () == cls .EXPECT_ERROR_COMMENT
106
106
]
107
- raw_result = api .run (["--config-file" , str (MYPY_CONFIG ), "-c" , code ])
107
+
108
+ with tempfile .NamedTemporaryFile (suffix = ".py" ) as temp :
109
+ temp .write (code .encode ())
110
+ temp .flush ()
111
+ # TODO: switch to json output to simplify output parsing.
112
+ # https://microsoft.github.io/pyright/#/command-line?id=json-output
113
+ raw_result = subprocess .run (
114
+ ["pyright" , temp .name ], capture_output = True , text = True
115
+ ).stdout
108
116
error_lines : list [str ] = []
109
117
110
- for line in raw_result [ 0 ] .splitlines ():
111
- m = re .match (cls .MYPY_MESSAGE_REGEX , line )
118
+ for line in raw_result .splitlines ():
119
+ m = re .match (cls .PYRIGHT_MESSAGE_REGEX , line )
112
120
if m is None :
113
121
continue
114
122
line_number , message = int (m .group (1 )), m .group (2 )
@@ -119,7 +127,7 @@ def _type_check_with_mypy(cls, code: str) -> TypeCheckResult:
119
127
continue
120
128
error_lines .append (f"{ line_number } :{ message } " )
121
129
122
- # If there are any lines that are expected to fail but not reported by mypy ,
130
+ # If there are any lines that are expected to fail but not reported by pyright ,
123
131
# they should be considered as errors.
124
132
for line_number in expect_error_line_numbers :
125
133
error_lines .append (f"{ line_number } : error: Expected type error" )
0 commit comments