Last active
March 19, 2022 10:22
-
-
Save emilk/f79d43a578554a43b13b to your computer and use it in GitHub Desktop.
C++ linter in Python using libclang
This file contains hidden or 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
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" C++ linter using libclang. Call with [filenames] """ | |
from __future__ import print_function | |
from clang.cindex import Config, TypeKind, CursorKind, Index | |
from pprint import pprint | |
import platform | |
import sys | |
if platform.system() == "Linux": | |
Config.set_library_file("/usr/lib/llvm-3.4/lib/libclang.so") | |
else: | |
Config.set_library_file("/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib") | |
def error(out_errors, node, msg): | |
error_msg = "{}:{}: {}".format(node.location.file, node.location.line, msg) | |
out_errors.append(error_msg) | |
print(error_msg, file=sys.stderr) | |
def is_mut_ref(arg_type): | |
if arg_type.kind in [TypeKind.POINTER, TypeKind.LVALUEREFERENCE, | |
TypeKind.INCOMPLETEARRAY, TypeKind.CONSTANTARRAY]: | |
if arg_type.kind in [TypeKind.POINTER, TypeKind.LVALUEREFERENCE]: | |
pointee_type = arg_type.get_pointee() | |
else: | |
pointee_type = arg_type.get_array_element_type() | |
# print("pointee_type.kind: {}".format(pointee_type.kind)) | |
# print("pointee_type.is_const_qualified(): {}".format(pointee_type.is_const_qualified())) | |
if not pointee_type.is_const_qualified(): | |
return True | |
return False | |
def check_argument(out_errors, function, node, function_parse_progress): | |
assert node.kind == CursorKind.PARM_DECL | |
if function.kind == CursorKind.FUNCTION_DECL and function.spelling == "main": | |
# Ignore main function | |
return | |
# print("") | |
# print("node.spelling: {}".format(node.spelling)) | |
# print("node.type.kind: {}".format(node.type.kind)) | |
# print("node.type.get_ref_qualifier(): {}".format(node.type.get_ref_qualifier())) | |
# print("node.type.is_const_qualified(): {}".format(node.type.is_const_qualified())) | |
# pprint(dir(node)) | |
name = node.spelling | |
if not name: | |
# Ignore nameless arguments (e.g. Foo(Foo&)) | |
return | |
if is_mut_ref(node.type): | |
if name.startswith("o_"): | |
function_parse_progress["state"] = "out" | |
elif name.startswith("io_"): | |
if function_parse_progress["state"] != "io": | |
error(out_errors, node, "io_ arguments should be first") | |
function_parse_progress["state"] = "io" | |
else: | |
error(out_errors, node, | |
"Non-const reference/pointer/array argument should be prefixed with " \ | |
"either o_ (for out) or io_ (for in-out), e.g. 'o_{}'".format(name)) | |
else: | |
if function_parse_progress["state"] == "out": | |
error(out_errors, node, "input arguments should come before output arguments") | |
function_parse_progress["state"] = "in" | |
def do_lint(out_errors, node, root_file): | |
if node.location.file and node.location.file.name != root_file.name: | |
# This is ugly, but works. | |
return | |
# print("{}:{} node.kind: {}".format(node.location.file, node.location.line, node.kind)) | |
# print("node.translation_unit: {}".format(node.translation_unit)) | |
# # pprint(dir(node.translation_unit)) | |
# print("node.location.file: {}".format(node.location.file)) | |
# pprint(dir(node.location)) | |
# exit() | |
# CursorKind.CONSTRUCTOR excluded: references there are often stored, so not o_ or io_ | |
if node.kind in [CursorKind.FUNCTION_DECL, CursorKind.CXX_METHOD]: | |
# print("Found a function!") | |
# print("node.spelling: {}".format(node.spelling)) | |
# print("node.displayname: {}".format(node.displayname)) | |
function_parse_progress = { | |
"state": "io", # "io", "in" or "out" | |
} | |
for arg in node.get_arguments(): | |
check_argument(out_errors, node, arg, function_parse_progress) | |
# Recurse for children of this node | |
for c in node.get_children(): | |
do_lint(out_errors, c, root_file) | |
def lint_file(filepath): | |
index = Index.create() | |
tu = index.parse(filepath) | |
root_file = tu.get_file(tu.spelling) | |
errors = [] | |
do_lint(errors, tu.cursor, root_file) | |
return errors | |
def main(): | |
if len(sys.argv) == 1: | |
print("Usage: {} [filenames]".format(sys.argv[0])) | |
return | |
for filepath in sys.argv[1:]: | |
lint_file(filepath) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment