ballpython 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ballpython/__init__.py +9 -0
- ballpython/__main__.py +8 -0
- ballpython/cli.py +7 -0
- ballpython-2.0.0.dist-info/METADATA +322 -0
- ballpython-2.0.0.dist-info/RECORD +24 -0
- ballpython-2.0.0.dist-info/WHEEL +5 -0
- ballpython-2.0.0.dist-info/entry_points.txt +3 -0
- ballpython-2.0.0.dist-info/top_level.txt +2 -0
- pycleaner/__init__.py +53 -0
- pycleaner/__main__.py +8 -0
- pycleaner/cli.py +1548 -0
- pycleaner/complexity_analyzer.py +473 -0
- pycleaner/config.py +254 -0
- pycleaner/dead_code_detector.py +515 -0
- pycleaner/dependency_auditor.py +331 -0
- pycleaner/import_resolver.py +832 -0
- pycleaner/linter_formatter.py +590 -0
- pycleaner/pipeline.py +349 -0
- pycleaner/security_scanner.py +563 -0
- pycleaner/syntax_healer.py +577 -0
- pycleaner/taint_engine.py +720 -0
- pycleaner/test_generator.py +444 -0
- pycleaner/type_checker.py +989 -0
- pycleaner/typeshed_resolver.py +395 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Typeshed stub parser and symbol signature resolver.
|
|
3
|
+
|
|
4
|
+
Parses .pyi stub files, extracts typed function and method signatures,
|
|
5
|
+
and provides return types, argument types, and class attributes for
|
|
6
|
+
built-in and standard library symbols.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
import os
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import ClassVar
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class FunctionSignature:
|
|
20
|
+
"""Type signature for a function or method."""
|
|
21
|
+
|
|
22
|
+
name: str
|
|
23
|
+
param_types: dict[str, str] = field(default_factory=dict)
|
|
24
|
+
return_type: str = "Any"
|
|
25
|
+
is_method: bool = False
|
|
26
|
+
is_static: bool = False
|
|
27
|
+
is_class_method: bool = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True)
|
|
31
|
+
class ClassSignature:
|
|
32
|
+
"""Type signature for a class definition."""
|
|
33
|
+
|
|
34
|
+
name: str
|
|
35
|
+
methods: dict[str, FunctionSignature] = field(default_factory=dict)
|
|
36
|
+
attributes: dict[str, str] = field(default_factory=dict)
|
|
37
|
+
base_classes: list[str] = field(default_factory=list)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TypeshedResolver:
|
|
41
|
+
"""Resolves type annotations and signatures from typeshed stubs and built-in catalogs."""
|
|
42
|
+
|
|
43
|
+
DEFAULT_TYPESHED_CANDIDATES: ClassVar[list[str]] = [
|
|
44
|
+
r"C:\Temp\pyrefly_bundled_typeshed_df16aab5f050",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Core built-in function return types (guaranteed baseline without file I/O)
|
|
48
|
+
BUILTIN_FUNCTIONS: ClassVar[dict[str, str]] = {
|
|
49
|
+
"len": "int",
|
|
50
|
+
"str": "str",
|
|
51
|
+
"int": "int",
|
|
52
|
+
"float": "float",
|
|
53
|
+
"bool": "bool",
|
|
54
|
+
"list": "list",
|
|
55
|
+
"dict": "dict",
|
|
56
|
+
"set": "set",
|
|
57
|
+
"tuple": "tuple",
|
|
58
|
+
"bytes": "bytes",
|
|
59
|
+
"abs": "int | float",
|
|
60
|
+
"all": "bool",
|
|
61
|
+
"any": "bool",
|
|
62
|
+
"bin": "str",
|
|
63
|
+
"hex": "str",
|
|
64
|
+
"oct": "str",
|
|
65
|
+
"chr": "str",
|
|
66
|
+
"ord": "int",
|
|
67
|
+
"hash": "int",
|
|
68
|
+
"id": "int",
|
|
69
|
+
"repr": "str",
|
|
70
|
+
"round": "int | float",
|
|
71
|
+
"sum": "int | float",
|
|
72
|
+
"min": "Any",
|
|
73
|
+
"max": "Any",
|
|
74
|
+
"open": "typing.IO[Any]",
|
|
75
|
+
"isinstance": "bool",
|
|
76
|
+
"issubclass": "bool",
|
|
77
|
+
"callable": "bool",
|
|
78
|
+
"hasattr": "bool",
|
|
79
|
+
"getattr": "Any",
|
|
80
|
+
"pow": "int | float",
|
|
81
|
+
"divmod": "tuple[int, int]",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Core built-in method return types
|
|
85
|
+
BUILTIN_METHODS: ClassVar[dict[str, dict[str, str]]] = {
|
|
86
|
+
"str": {
|
|
87
|
+
"upper": "str",
|
|
88
|
+
"lower": "str",
|
|
89
|
+
"strip": "str",
|
|
90
|
+
"lstrip": "str",
|
|
91
|
+
"rstrip": "str",
|
|
92
|
+
"split": "list[str]",
|
|
93
|
+
"rsplit": "list[str]",
|
|
94
|
+
"splitlines": "list[str]",
|
|
95
|
+
"join": "str",
|
|
96
|
+
"replace": "str",
|
|
97
|
+
"startswith": "bool",
|
|
98
|
+
"endswith": "bool",
|
|
99
|
+
"find": "int",
|
|
100
|
+
"rfind": "int",
|
|
101
|
+
"index": "int",
|
|
102
|
+
"rindex": "int",
|
|
103
|
+
"count": "int",
|
|
104
|
+
"encode": "bytes",
|
|
105
|
+
"format": "str",
|
|
106
|
+
"isdigit": "bool",
|
|
107
|
+
"isalpha": "bool",
|
|
108
|
+
"isalnum": "bool",
|
|
109
|
+
"isspace": "bool",
|
|
110
|
+
"title": "str",
|
|
111
|
+
"capitalize": "str",
|
|
112
|
+
},
|
|
113
|
+
"list": {
|
|
114
|
+
"append": "None",
|
|
115
|
+
"extend": "None",
|
|
116
|
+
"insert": "None",
|
|
117
|
+
"remove": "None",
|
|
118
|
+
"pop": "Any",
|
|
119
|
+
"clear": "None",
|
|
120
|
+
"index": "int",
|
|
121
|
+
"count": "int",
|
|
122
|
+
"sort": "None",
|
|
123
|
+
"reverse": "None",
|
|
124
|
+
"copy": "list[Any]",
|
|
125
|
+
},
|
|
126
|
+
"dict": {
|
|
127
|
+
"get": "Any",
|
|
128
|
+
"keys": "typing.KeysView[Any]",
|
|
129
|
+
"values": "typing.ValuesView[Any]",
|
|
130
|
+
"items": "typing.ItemsView[Any, Any]",
|
|
131
|
+
"pop": "Any",
|
|
132
|
+
"popitem": "tuple[Any, Any]",
|
|
133
|
+
"clear": "None",
|
|
134
|
+
"update": "None",
|
|
135
|
+
"setdefault": "Any",
|
|
136
|
+
"copy": "dict[Any, Any]",
|
|
137
|
+
},
|
|
138
|
+
"set": {
|
|
139
|
+
"add": "None",
|
|
140
|
+
"remove": "None",
|
|
141
|
+
"discard": "None",
|
|
142
|
+
"pop": "Any",
|
|
143
|
+
"clear": "None",
|
|
144
|
+
"union": "set[Any]",
|
|
145
|
+
"intersection": "set[Any]",
|
|
146
|
+
"difference": "set[Any]",
|
|
147
|
+
"symmetric_difference": "set[Any]",
|
|
148
|
+
"issubset": "bool",
|
|
149
|
+
"issuperset": "bool",
|
|
150
|
+
"isdisjoint": "bool",
|
|
151
|
+
"copy": "set[Any]",
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Common standard library function signatures
|
|
156
|
+
STDLIB_FUNCTIONS: ClassVar[dict[str, dict[str, str]]] = {
|
|
157
|
+
"math": {
|
|
158
|
+
"sqrt": "float",
|
|
159
|
+
"ceil": "int",
|
|
160
|
+
"floor": "int",
|
|
161
|
+
"sin": "float",
|
|
162
|
+
"cos": "float",
|
|
163
|
+
"tan": "float",
|
|
164
|
+
"log": "float",
|
|
165
|
+
"exp": "float",
|
|
166
|
+
"radians": "float",
|
|
167
|
+
"degrees": "float",
|
|
168
|
+
},
|
|
169
|
+
"os": {
|
|
170
|
+
"getcwd": "str",
|
|
171
|
+
"listdir": "list[str]",
|
|
172
|
+
"system": "int",
|
|
173
|
+
},
|
|
174
|
+
"os.path": {
|
|
175
|
+
"join": "str",
|
|
176
|
+
"abspath": "str",
|
|
177
|
+
"basename": "str",
|
|
178
|
+
"dirname": "str",
|
|
179
|
+
"exists": "bool",
|
|
180
|
+
"isfile": "bool",
|
|
181
|
+
"isdir": "bool",
|
|
182
|
+
},
|
|
183
|
+
"json": {
|
|
184
|
+
"dumps": "str",
|
|
185
|
+
"loads": "Any",
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
def __init__(self, typeshed_path: str | Path | None = None) -> None:
|
|
190
|
+
self.typeshed_root: Path | None = self._locate_typeshed(typeshed_path)
|
|
191
|
+
self._parsed_stubs: dict[str, ast.Module] = {}
|
|
192
|
+
self._class_cache: dict[str, ClassSignature] = {}
|
|
193
|
+
self._func_cache: dict[str, FunctionSignature] = {}
|
|
194
|
+
|
|
195
|
+
def _locate_typeshed(self, custom_path: str | Path | None) -> Path | None:
|
|
196
|
+
if custom_path:
|
|
197
|
+
p = Path(custom_path).resolve()
|
|
198
|
+
if p.is_dir():
|
|
199
|
+
return p
|
|
200
|
+
|
|
201
|
+
for candidate in self.DEFAULT_TYPESHED_CANDIDATES:
|
|
202
|
+
cand_path = Path(candidate)
|
|
203
|
+
if cand_path.is_dir():
|
|
204
|
+
return cand_path
|
|
205
|
+
|
|
206
|
+
# Check environment variable
|
|
207
|
+
env_path = os.environ.get("TYPESHED_PATH")
|
|
208
|
+
if env_path:
|
|
209
|
+
p = Path(env_path).resolve()
|
|
210
|
+
if p.is_dir():
|
|
211
|
+
return p
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def get_builtin_return_type(self, func_name: str) -> str | None:
|
|
216
|
+
"""Get return type for a built-in function."""
|
|
217
|
+
return self.BUILTIN_FUNCTIONS.get(func_name)
|
|
218
|
+
|
|
219
|
+
def get_builtin_method_return_type(
|
|
220
|
+
self, type_name: str, method_name: str
|
|
221
|
+
) -> str | None:
|
|
222
|
+
"""Get return type for a method on a built-in type."""
|
|
223
|
+
methods = self.BUILTIN_METHODS.get(type_name)
|
|
224
|
+
if methods:
|
|
225
|
+
return methods.get(method_name)
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
def load_stub_for_module(self, module_name: str) -> ast.Module | None:
|
|
229
|
+
"""Load and parse the .pyi stub for a module."""
|
|
230
|
+
if not self.typeshed_root:
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
if module_name in self._parsed_stubs:
|
|
234
|
+
return self._parsed_stubs[module_name]
|
|
235
|
+
|
|
236
|
+
# Look for module.pyi or module/__init__.pyi
|
|
237
|
+
rel_parts = module_name.split(".")
|
|
238
|
+
candidate_file = self.typeshed_root.joinpath(*rel_parts).with_suffix(".pyi")
|
|
239
|
+
candidate_pkg = self.typeshed_root.joinpath(*rel_parts, "__init__.pyi")
|
|
240
|
+
|
|
241
|
+
target_file: Path | None = None
|
|
242
|
+
if candidate_file.is_file():
|
|
243
|
+
target_file = candidate_file
|
|
244
|
+
elif candidate_pkg.is_file():
|
|
245
|
+
target_file = candidate_pkg
|
|
246
|
+
elif len(rel_parts) == 1 and rel_parts[0] == "builtins":
|
|
247
|
+
builtins_stub = self.typeshed_root / "builtins.pyi"
|
|
248
|
+
if builtins_stub.is_file():
|
|
249
|
+
target_file = builtins_stub
|
|
250
|
+
|
|
251
|
+
if not target_file:
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
content = target_file.read_text(encoding="utf-8", errors="replace")
|
|
256
|
+
tree = ast.parse(content, filename=str(target_file))
|
|
257
|
+
self._parsed_stubs[module_name] = tree
|
|
258
|
+
return tree
|
|
259
|
+
except SyntaxError:
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
@staticmethod
|
|
263
|
+
def _normalize_func_target(
|
|
264
|
+
module_name: str, func_name: str | None
|
|
265
|
+
) -> tuple[str, str]:
|
|
266
|
+
if func_name is not None:
|
|
267
|
+
return module_name, func_name
|
|
268
|
+
if "." in module_name:
|
|
269
|
+
mod, fn = module_name.rsplit(".", 1)
|
|
270
|
+
return mod, fn
|
|
271
|
+
return "builtins", module_name
|
|
272
|
+
|
|
273
|
+
def _resolve_func_from_catalog(
|
|
274
|
+
self, module_name: str, func_name: str
|
|
275
|
+
) -> FunctionSignature | None:
|
|
276
|
+
if module_name in ("builtins", "") and func_name in self.BUILTIN_FUNCTIONS:
|
|
277
|
+
return FunctionSignature(
|
|
278
|
+
name=func_name, return_type=self.BUILTIN_FUNCTIONS[func_name]
|
|
279
|
+
)
|
|
280
|
+
if (
|
|
281
|
+
module_name in self.STDLIB_FUNCTIONS
|
|
282
|
+
and func_name in self.STDLIB_FUNCTIONS[module_name]
|
|
283
|
+
):
|
|
284
|
+
return FunctionSignature(
|
|
285
|
+
name=func_name,
|
|
286
|
+
return_type=self.STDLIB_FUNCTIONS[module_name][func_name],
|
|
287
|
+
)
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
def _resolve_func_from_stub(
|
|
291
|
+
self, module_name: str, func_name: str
|
|
292
|
+
) -> FunctionSignature | None:
|
|
293
|
+
tree = self.load_stub_for_module(module_name)
|
|
294
|
+
if not tree:
|
|
295
|
+
return None
|
|
296
|
+
for node in tree.body:
|
|
297
|
+
if (
|
|
298
|
+
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
299
|
+
and node.name == func_name
|
|
300
|
+
):
|
|
301
|
+
return self._extract_function_signature(node)
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
def resolve_function(
|
|
305
|
+
self,
|
|
306
|
+
module_name: str,
|
|
307
|
+
func_name: str | None = None,
|
|
308
|
+
) -> FunctionSignature | None:
|
|
309
|
+
"""Resolve a function's typed signature from stubs or catalog tables."""
|
|
310
|
+
mod, fn = self._normalize_func_target(module_name, func_name)
|
|
311
|
+
cache_key = f"{mod}.{fn}"
|
|
312
|
+
if cache_key in self._func_cache:
|
|
313
|
+
return self._func_cache[cache_key]
|
|
314
|
+
|
|
315
|
+
sig = self._resolve_func_from_catalog(mod, fn) or self._resolve_func_from_stub(
|
|
316
|
+
mod, fn
|
|
317
|
+
)
|
|
318
|
+
if sig is not None:
|
|
319
|
+
self._func_cache[cache_key] = sig
|
|
320
|
+
return sig
|
|
321
|
+
|
|
322
|
+
def resolve_class(self, module_name: str, class_name: str) -> ClassSignature | None:
|
|
323
|
+
"""Resolve a class's typed signature and member signatures from stubs."""
|
|
324
|
+
cache_key = f"{module_name}.{class_name}"
|
|
325
|
+
if cache_key in self._class_cache:
|
|
326
|
+
return self._class_cache[cache_key]
|
|
327
|
+
|
|
328
|
+
tree = self.load_stub_for_module(module_name)
|
|
329
|
+
if not tree:
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
for node in tree.body:
|
|
333
|
+
if isinstance(node, ast.ClassDef) and node.name == class_name:
|
|
334
|
+
cls_sig = self._extract_class_signature(node)
|
|
335
|
+
self._class_cache[cache_key] = cls_sig
|
|
336
|
+
return cls_sig
|
|
337
|
+
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
def _extract_function_signature(
|
|
341
|
+
self,
|
|
342
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
343
|
+
is_method: bool = False,
|
|
344
|
+
) -> FunctionSignature:
|
|
345
|
+
"""Extract a FunctionSignature from an AST node."""
|
|
346
|
+
param_types: dict[str, str] = {}
|
|
347
|
+
all_args = node.args.posonlyargs + node.args.args + node.args.kwonlyargs
|
|
348
|
+
for arg in all_args:
|
|
349
|
+
if arg.annotation:
|
|
350
|
+
param_types[arg.arg] = ast.unparse(arg.annotation)
|
|
351
|
+
else:
|
|
352
|
+
param_types[arg.arg] = "Any"
|
|
353
|
+
|
|
354
|
+
return_type = "Any"
|
|
355
|
+
if node.returns:
|
|
356
|
+
return_type = ast.unparse(node.returns)
|
|
357
|
+
|
|
358
|
+
is_static = any(
|
|
359
|
+
isinstance(d, ast.Name) and d.id == "staticmethod"
|
|
360
|
+
for d in node.decorator_list
|
|
361
|
+
)
|
|
362
|
+
is_class = any(
|
|
363
|
+
isinstance(d, ast.Name) and d.id == "classmethod"
|
|
364
|
+
for d in node.decorator_list
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return FunctionSignature(
|
|
368
|
+
name=node.name,
|
|
369
|
+
param_types=param_types,
|
|
370
|
+
return_type=return_type,
|
|
371
|
+
is_method=is_method,
|
|
372
|
+
is_static=is_static,
|
|
373
|
+
is_class_method=is_class,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
def _extract_class_signature(self, node: ast.ClassDef) -> ClassSignature:
|
|
377
|
+
"""Extract a ClassSignature from an AST ClassDef node."""
|
|
378
|
+
methods: dict[str, FunctionSignature] = {}
|
|
379
|
+
attributes: dict[str, str] = {}
|
|
380
|
+
base_classes: list[str] = [ast.unparse(b) for b in node.bases]
|
|
381
|
+
|
|
382
|
+
for stmt in node.body:
|
|
383
|
+
if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
384
|
+
methods[stmt.name] = self._extract_function_signature(
|
|
385
|
+
stmt, is_method=True
|
|
386
|
+
)
|
|
387
|
+
elif isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
|
|
388
|
+
attributes[stmt.target.id] = ast.unparse(stmt.annotation)
|
|
389
|
+
|
|
390
|
+
return ClassSignature(
|
|
391
|
+
name=node.name,
|
|
392
|
+
methods=methods,
|
|
393
|
+
attributes=attributes,
|
|
394
|
+
base_classes=base_classes,
|
|
395
|
+
)
|