Last active
September 14, 2018 00:17
-
-
Save boxed/e60e3e19967385dc2c7f0de483723502 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
from parso import parse | |
from collections import defaultdict | |
def get_arguments(i): | |
# parso represents functions with one argument and multiple arguments very differently, need to handle this here | |
if i.type == 'arglist': | |
return i.children | |
else: | |
return [i] | |
def param_name(i): | |
if i.children[0].type == 'tfpdef': | |
return i.children[0].children[0].value | |
else: | |
return i.children[0].value | |
def handle_list(result, filename): | |
for i in result.children: | |
handle_node(i, filename=filename) | |
def handle_node(i, filename): | |
t = i.type | |
if t == 'atom_expr': | |
arguments = None | |
if len(i.children) == 2: | |
# normal function call | |
if i.children[0].type == 'name': | |
function_name = i.children[0].value | |
arguments = [node for node in get_arguments(i.children[1].children[1]) if node.type != 'operator'] # filter out , | |
else: | |
if (i.children[-2].children[0].type, i.children[-2].children[0].value) == ('operator', '.'): | |
# member function call | |
function_name = i.children[-2].children[1].value | |
arguments = [node for node in get_arguments(i.children[-1].children[1]) if node.type != 'operator'] # filter out , | |
else: | |
# list comprehensions and stuff | |
pass | |
if arguments and arguments[0].type != 'subscript' and len(i.children) > 2: | |
argument_values = [handle_argument(argument) for argument in arguments] | |
if all([x != None for x in argument_values]) and len(argument_values) > 3: # all positional | |
print(filename, i.start_pos[0]) | |
print(' ', i.get_code()) | |
if hasattr(i, 'children'): | |
handle_list(i, filename=filename) | |
def handle_argument(argument): | |
global args_kwargs, args, kwargs, kwargs_foo_equal_foo | |
if argument.type == 'name': | |
# positional argument | |
return argument.value | |
def analyse_directory(directory): | |
for root, dirs, files in os.walk(directory): | |
dirs[:] = [d for d in dirs if not d.startswith('.') and not d.startswith('env') and not d.startswith('venv') and not d.endswith('_env') and d != 'node_modules'] | |
for filename in files: | |
if filename.endswith('.py'): | |
with open(os.path.join(root, filename)) as file: | |
try: | |
contents = file.read() | |
except: | |
continue | |
handle_list(parse(contents, error_recovery=True), filename=filename) | |
import sys | |
if len(sys.argv) != 2: | |
print('This tool analyses your code base for cases where a short for for keyword arguments would be nice to have.') | |
print('Usage: supply one directory path to the code you wish to analyse.') | |
exit(1) | |
analyse_directory(sys.argv[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment