16
16
import sys
17
17
import token
18
18
import tokenize
19
- from typing import IO , Callable , FrozenSet , Iterable , List , Tuple
20
-
21
- PATHS_TO_IGNORE : Tuple [str , ...] = ("asv_bench/env" ,)
19
+ from typing import IO , Callable , FrozenSet , Iterable , List , Set , Tuple
22
20
23
21
24
22
def _get_literal_string_prefix_len (token_string : str ) -> int :
@@ -114,6 +112,58 @@ def bare_pytest_raises(file_obj: IO[str]) -> Iterable[Tuple[int, str]]:
114
112
)
115
113
116
114
115
+ PRIVATE_FUNCTIONS_ALLOWED = {"sys._getframe" } # no known alternative
116
+
117
+
118
+ def private_function_across_module (file_obj : IO [str ]) -> Iterable [Tuple [int , str ]]:
119
+ """
120
+ Checking that a private function is not used across modules.
121
+ Parameters
122
+ ----------
123
+ file_obj : IO
124
+ File-like object containing the Python code to validate.
125
+ Yields
126
+ ------
127
+ line_number : int
128
+ Line number of the private function that is used across modules.
129
+ msg : str
130
+ Explenation of the error.
131
+ """
132
+ contents = file_obj .read ()
133
+ tree = ast .parse (contents )
134
+
135
+ imported_modules : Set [str ] = set ()
136
+
137
+ for node in ast .walk (tree ):
138
+ if isinstance (node , (ast .Import , ast .ImportFrom )):
139
+ for module in node .names :
140
+ module_fqdn = module .name if module .asname is None else module .asname
141
+ imported_modules .add (module_fqdn )
142
+
143
+ if not isinstance (node , ast .Call ):
144
+ continue
145
+
146
+ try :
147
+ module_name = node .func .value .id
148
+ function_name = node .func .attr
149
+ except AttributeError :
150
+ continue
151
+
152
+ # Exception section #
153
+
154
+ # (Debatable) Class case
155
+ if module_name [0 ].isupper ():
156
+ continue
157
+ # (Debatable) Dunder methods case
158
+ elif function_name .startswith ("__" ) and function_name .endswith ("__" ):
159
+ continue
160
+ elif module_name + "." + function_name in PRIVATE_FUNCTIONS_ALLOWED :
161
+ continue
162
+
163
+ if module_name in imported_modules and function_name .startswith ("_" ):
164
+ yield (node .lineno , f"Private function '{ module_name } .{ function_name } '" )
165
+
166
+
117
167
def strings_to_concatenate (file_obj : IO [str ]) -> Iterable [Tuple [int , str ]]:
118
168
"""
119
169
This test case is necessary after 'Black' (https://github.com/psf/black),
@@ -293,6 +343,7 @@ def main(
293
343
source_path : str ,
294
344
output_format : str ,
295
345
file_extensions_to_check : str ,
346
+ excluded_file_paths : str ,
296
347
) -> bool :
297
348
"""
298
349
Main entry point of the script.
@@ -305,6 +356,10 @@ def main(
305
356
Source path representing path to a file/directory.
306
357
output_format : str
307
358
Output format of the error message.
359
+ file_extensions_to_check : str
360
+ Coma seperated values of what file extensions to check.
361
+ excluded_file_paths : str
362
+ Coma seperated values of what file paths to exclude during the check.
308
363
309
364
Returns
310
365
-------
@@ -325,6 +380,7 @@ def main(
325
380
FILE_EXTENSIONS_TO_CHECK : FrozenSet [str ] = frozenset (
326
381
file_extensions_to_check .split ("," )
327
382
)
383
+ PATHS_TO_IGNORE = frozenset (excluded_file_paths .split ("," ))
328
384
329
385
if os .path .isfile (source_path ):
330
386
file_path = source_path
@@ -362,6 +418,7 @@ def main(
362
418
if __name__ == "__main__" :
363
419
available_validation_types : List [str ] = [
364
420
"bare_pytest_raises" ,
421
+ "private_function_across_module" ,
365
422
"strings_to_concatenate" ,
366
423
"strings_with_wrong_placed_whitespace" ,
367
424
]
@@ -389,6 +446,11 @@ def main(
389
446
default = "py,pyx,pxd,pxi" ,
390
447
help = "Coma seperated file extensions to check." ,
391
448
)
449
+ parser .add_argument (
450
+ "--excluded-file-paths" ,
451
+ default = "asv_bench/env" ,
452
+ help = "Comma separated file extensions to check." ,
453
+ )
392
454
393
455
args = parser .parse_args ()
394
456
@@ -398,5 +460,6 @@ def main(
398
460
source_path = args .path ,
399
461
output_format = args .format ,
400
462
file_extensions_to_check = args .included_file_extensions ,
463
+ excluded_file_paths = args .excluded_file_paths ,
401
464
)
402
465
)
0 commit comments