| Index: lint_shell_commands.py
|
| diff --git a/lint_shell_commands.py b/lint_shell_commands.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..87bc81b764541852dc4b7fb621e972cc090d8f58
|
| --- /dev/null
|
| +++ b/lint_shell_commands.py
|
| @@ -0,0 +1,74 @@
|
| +#!/usr/bin/env python
|
| +import argparse
|
| +import ast
|
| +import logging
|
| +import os
|
| +import sys
|
| +
|
| +logging.basicConfig(format='%(levelname)s:%(message)s', stream=sys.stdout)
|
| +
|
| +
|
| +def is_true(node):
|
| + return isinstance(node, ast.Name) and node.id == 'True'
|
| +
|
| +
|
| +def node_repr(node):
|
| + if isinstance(node, ast.Name):
|
| + return node.id
|
| + else:
|
| + raise NotImplementedError(ast.dump(node))
|
| +
|
| +
|
| +def lint_shell_command_calls(basename, filename):
|
| + with open(os.path.join(basename, filename)) as f:
|
| + try:
|
| + tree = ast.parse(f.read(), filename)
|
| + except Exception:
|
| + logging.warning('%s: Parse error, skipping.', filename)
|
| + return
|
| +
|
| + for node in ast.walk(tree):
|
| + if (isinstance(node, ast.Call) and
|
| + isinstance(node.func, ast.Attribute) and
|
| + node.func.attr == 'RunShellCommand'):
|
| +
|
| + if node.args and isinstance(node.args[0], ast.Str):
|
| + logging.warning(
|
| + '%s:%s: Avoid passing command as a string.', filename, node.lineno)
|
| +
|
| + kwargs = {kw.arg: kw.value for kw in node.keywords}
|
| + check_return = kwargs.get('check_return')
|
| + if check_return is None:
|
| + logging.warning(
|
| + '%s:%s: Missing check_return=True.', filename, node.lineno)
|
| + elif not is_true(check_return):
|
| + logging.warning(
|
| + '%s:%s: Consider switching check_return=%s to True.',
|
| + filename, node.lineno, node_repr(check_return))
|
| +
|
| +
|
| +def ignore_dir(dirname):
|
| + return dirname.startswith('.') or dirname == 'third_party'
|
| +
|
| +
|
| +def iter_python_files(root):
|
| + for abs_dirpath, dirnames, filenames in os.walk(root):
|
| + dirpath = os.path.relpath(abs_dirpath, root)
|
| + for filename in filenames:
|
| + if filename.endswith('.py'):
|
| + yield os.path.join(dirpath, filename)
|
| + dirnames[:] = [d for d in dirnames if not ignore_dir(d)]
|
| +
|
| +
|
| +def main():
|
| + parser = argparse.ArgumentParser()
|
| + parser.add_argument('root')
|
| + args = parser.parse_args()
|
| +
|
| + args.root = os.path.realpath(args.root)
|
| + for filename in iter_python_files(args.root):
|
| + lint_shell_command_calls(args.root, filename)
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|