From 214f735e3441372e058defd81803e3570c99e92b Mon Sep 17 00:00:00 2001 From: Alexandre Deckner Date: Sat, 31 Oct 2009 22:20:25 +0000 Subject: [PATCH] * Style checker for the Haiku C++ coding guidelines * Added an html report output with highlights and tooltips * Still a few false positives and rules missing. Some regex could be reworked * TODO: ignore some rules when in comments, multifile support git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@33856 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/tools/checkstyle/checkstyle.py | 55 ++++++++++ src/tools/checkstyle/utils.py | 165 +++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 src/tools/checkstyle/checkstyle.py create mode 100644 src/tools/checkstyle/utils.py diff --git a/src/tools/checkstyle/checkstyle.py b/src/tools/checkstyle/checkstyle.py new file mode 100644 index 0000000000..291d60368d --- /dev/null +++ b/src/tools/checkstyle/checkstyle.py @@ -0,0 +1,55 @@ +# +# Copyright 2009, Alexandre Deckner, alex@zappotek.com +# Distributed under the terms of the MIT License. +# +import re, sys +from utils import * + + +def processMatches(matches, name, text, highlights): + for match in matches: + printMatch(name, match, text) + highlights.append((match.start(), match.end(), name)) + + +def run(sourceFile, rules): + file = open(sourceFile, 'r') + text = file.read() + + highlights = [] + + for name, regexp in rules.items(): + processMatches(regexp.finditer(text), name, text, highlights) + + highlights.sort() + highlights = checkHighlights(highlights) + + file.close() + renderHtml(text, highlights, sourceFile, "styleviolations.html") + + +cppRules = {} +cppRules["Line over 80 char"] = re.compile('[^\n]{81,}') +cppRules["Spaces instead of tabs"] = re.compile(' ') +cppRules["Missing space after for/if/select/while"] = re.compile('(for|if|select|while)\(') +cppRules["Missing space at comment start"] = re.compile('//[a-zA-Z0-9]') +cppRules["Missing space after operator"] \ + = re.compile('[a-zA-Z0-9](==|[,=>/+\-*;\|])[a-zA-Z0-9]') +cppRules["Operator at line end"] = re.compile('([*=/+\-\|\&\?]|\&&|\|\|)\n') +cppRules["Missing space"] = re.compile('\){') +cppRules["Mixed tabs/spaces"] = re.compile('( \t]|\t )+') +cppRules["Malformed else"] = re.compile('}[ \t]*\n[ \t]*else') +cppRules["Lines between functions > 2"] = re.compile('\n}([ \t]*\n){4,}') +cppRules["Lines between functions < 2"] = re.compile('\n}([ \t]*\n){0,2}.') + +# TODO: ignore some rules in comments +#cppRules["-Comment 1"] = re.compile('[^/]/\*(.|[\r\n])*?\*/') +#cppRules["-Comment 2"] = re.compile('(//)[^\n]*') + + +if len(sys.argv) == 2 and sys.argv[1] != "--help": + run(sys.argv[1], cppRules) +else: + print "Usage: python checkstyle.py file.cpp\n" + print "Checks a c++ source file against the Haiku Coding Guidelines." + print "Outputs an highlighted html report in the styleviolations.html file.\n" diff --git a/src/tools/checkstyle/utils.py b/src/tools/checkstyle/utils.py new file mode 100644 index 0000000000..2f57457233 --- /dev/null +++ b/src/tools/checkstyle/utils.py @@ -0,0 +1,165 @@ +# +# Copyright 2009, Alexandre Deckner, alex@zappotek.com +# Distributed under the terms of the MIT License. +# +from cgi import escape + + +# prints match to stdout +def printMatch(name, match, source): + start = match.start() + end = match.end() + startLine = source.count('\n', 0, start) + startColumn = start - source.rfind('\n', 0, start) + print name + " (line " + str(startLine + 1) + ", " + str(startColumn) \ + + "): '" + match.group().replace('\n','\\n') + "'" + + +# render in html, the file style.css is embedded +def renderHtml(text, highlights, sourceFileName, outputFileName): + splittedText = highlightSplit(text, highlights) + + #styleFile = open("style.css") + #style = styleFile.read() + #styleFile.close() + + file = open(outputFileName, 'w') + file.write(""" + + + + Style violations in """ + sourceFileName.split('/')[-1] \ + + """ + + + + +

""" + sourceFileName + """

+
""")
+
+    temp = ""
+    count = 0
+    for slice in splittedText:
+        if count % 2 == 0:
+            temp += escape(slice) + ''
+        else:
+            temp += escape(slice) + "" + highlights[(count - 1) / 2][2] \
+                + ""
+        count += 1
+
+    temp += "" # close the superfluous last highlight
+
+    count = 1
+    for line in temp.split('\n'):
+        file.write(str(count).rjust(4) + ' |' + line + '
') + count += 1 + + file.write(""" +
+ + """) + + file.close() + + +# highlight overlap check +def highlightOverlaps(highlight1, highlight2): + #print "hl1", highlight1, "hl2", highlight2 + return not(highlight2[0] > highlight1[1] or highlight1[0] > highlight2[1]) + + +# splits the string in three parts before, between and after the highlight +def splitByHighlight(string, highlight): + return (string[:highlight[0]], string[highlight[0]:highlight[1]], \ + string[highlight[1]:]) + + +# splits the source text on highlights boundaries so that we can escape to html +# without the need to recalculate the highlights positions +def highlightSplit(string, highlights): + splittedString = [] + text = string + offset = 0 + lastEnd = 0 + for (start, end, name) in highlights: + if start >= lastEnd: + (before, between, after) = splitByHighlight( \ + text, (start - offset, end - offset)) + splittedString.append(before) + splittedString.append(between) + text = after + lastEnd = end + offset += len(before + between) + else: + print "overlap ", (start, end, name) + splittedString.append(text) + return splittedString + + +# checkHighlights() checks for highlights overlaps +def checkHighlights(highlights): + highlights.sort() + + index = 0 + lastHighlight = (-2, -1, '') + + # merge overlapping highlights + for highlight in highlights: + if highlightOverlaps(highlight, lastHighlight): + + newStart = min(lastHighlight[0], highlight[0]) + newEnd = max(lastHighlight[1], highlight[1]) + newComment = lastHighlight[2] + + if (newComment.find(highlight[2]) == -1): + newComment += " + " + highlight[2] + + highlight = (newStart, newEnd, newComment) + highlights[index] = highlight + + # mark highlight to be deleted + highlights[index - 1] = (0, 0, "") + + lastHighlight = highlight + index += 1 + + # remove "to be deleted" highlights + return [ (start, end, comment) for (start, end, comment) in highlights \ + if (start, end, comment) != (0, 0, "") ] + + +def cssStyle(): + return """ + highlight { + background: #ffff00; + color: #000000; + } + + div.code pre { + font-family: monospace; + } + + highlight.tooltip em { + display:none; + } + + highlight.tooltip:hover { + border: 0; + position: relative; + z-index: 500; + text-decoration:none; + } + + highlight.tooltip:hover em { + font-style: normal; + display: block; + position: absolute; + top: 20px; + left: -10px; + padding: 5px; + color: #000; + border: 1px solid #bbb; + background: #ffc; + width: auto; + }"""