Unverified Commit 11512a65 authored by Evan W. Patton's avatar Evan W. Patton Committed by GitHub

Implement checkstyle rules for App Inventor (#2149)

* Implement checkstyle rules for App Inventor

This commit implements checkstyle for App Inventor's Java code. The
App Inventor style rules are based on the Google style rules, with an
exception added for methods in the components/runtime package that are
annotated with `@Simple*` or `@Design*` annotations. The App Inventor
specific rules are placed at the end of the appinventor_checks.xml
file.

In addition, there is a script called checkstyle_git.py that is
written in Python 3 and will check the git diff against the rules by
running checkstyle on the changed files and crossreferencing the line
numbers with the checkstyle output. This can be used as a git
pre-commit hook to check that changes conform to the style guide. The
script returns -1 on error, which will abort the commit until the
changes confirm. This behavior can be bypassed by exporting the
BYPASS_CHECKSTYLE environment variable.

These two changes are made available as ant targets:

- `ant checkstyle`: Run the checkstyle_git.py script to check the
  git diff for problems.
- `ant checkstyle-all`: Run checkstyle on the whole repo (slow).

Change-Id: I26c6049bd49d1ce3f9b8dbd48f8f115a89fb5b52

* Revamp checkstyle-git script logic

Change-Id: Ie417057b2d009ba70279e70df102851b2f4552cd

* Add custom checks for EventDispatcher in @SimpleEvent

Change-Id: I72a704e9f8d489aa08cefd50ca180b1130b3e5fa
parent e1122b90
...@@ -129,6 +129,35 @@ ...@@ -129,6 +129,35 @@
</javadoc> </javadoc>
</target> </target>
<target name="checkstyle-all">
<java classname="com.puppycrawl.tools.checkstyle.Main" fork="true">
<classpath>
<pathelement path="lib/checkstyle/checkstyle.jar"/>
<pathelement path="lib/checkstyle/appinventor-checks.jar"/>
</classpath>
<arg value="-c"/>
<arg value="lib/checkstyle/appinventor-checks.xml"/>
<arg value="appengine/src"/>
<arg value="appengine/tests"/>
<arg value="blocklyeditor/src"/>
<arg value="blocklyeditor/tests"/>
<arg value="buildserver/src"/>
<arg value="buildserver/tests"/>
<arg value="common/src"/>
<arg value="common/tests"/>
<arg value="components/src"/>
<arg value="components/tests"/>
</java>
</target>
<target name="checkstyle">
<property name="commit" value="HEAD"/>
<exec executable="misc/checkstyle/checkstyle-git.py"
failonerror="true">
<arg value="${commit}"/>
</exec>
</target>
<target name="clean"> <target name="clean">
<ant inheritAll="false" useNativeBasedir="true" dir="appengine" target="clean"/> <ant inheritAll="false" useNativeBasedir="true" dir="appengine" target="clean"/>
<ant inheritAll="false" useNativeBasedir="true" dir="blocklyeditor" target="clean"/> <ant inheritAll="false" useNativeBasedir="true" dir="blocklyeditor" target="clean"/>
......
This diff is collapsed.
#!/usr/bin/env python3
import subprocess
from io import StringIO
from re import compile
import os
import sys
parent_commit = 'HEAD' if len(sys.argv) < 2 else sys.argv[1]
checkstyle_pattern = compile(r'^\[(?P<severity>[^\]]*)\] (?P<filename>[^:]*):(?P<line>[0-9]*):(?P<message>.*)'
r' \[(?P<rule>[^\]]*)\]$')
compile(r'^\[(?P<severity>[^\]]*)\]')
basedir = os.getcwd() if os.path.exists('build.xml') else os.path.join(os.getcwd(), 'appinventor')
def checkstyle(current_file):
return StringIO(subprocess.check_output(['java', '-cp',
'lib/checkstyle/checkstyle.jar:lib/checkstyle/appinventor-checks.jar',
'com.puppycrawl.tools.checkstyle.Main', '-c',
'lib/checkstyle/appinventor-checks.xml', current_file],
encoding='utf-8', cwd=basedir))
def check_chunks(checkstyle_output, chunks):
"""
:param checkstyle_output:
:param chunks:
:type chunks: list[(int, int)]
:return:
"""
success = True
i = 0
for line in checkstyle_output:
match = checkstyle_pattern.match(line.strip())
if match:
linenum = int(match.group('line'))
while i < len(chunks):
if chunks[i][0] <= linenum < chunks[i][1]:
print(line.strip())
success = False
break
elif chunks[i][0] <= linenum:
i += 1
if i >= len(chunks):
return success
else:
break
return success
def process_chunk_info(line):
"""
Processes a unified diff chunk header and returns a tuple indication the start and length of the deletion and
addition hunks.
:param line: Unified diff chunk marker, beginning with '@@'
:type line: str
:return: a 4-tuple of deletion start, deletion length, addition start, addition length
:rtype: tuple[int]
"""
parts = line.split(' ')
del_info = parts[1][1:]
add_info = parts[2][1:]
del_start, del_length = map(int, del_info.split(',')) if ',' in del_info else (int(del_info), 1)
add_start, add_length = map(int, add_info.split(',')) if ',' in add_info else (int(add_info), 1)
return del_start, del_length, add_start, add_length
def main(parent):
current_file, chunk_start, chunk_length = '', 0, 0
checkstyle_output = None
chunks = []
passed = True
for line in StringIO(subprocess.check_output(['git', 'diff', '-U0', parent], encoding='utf-8')):
if line.startswith('diff --git'):
pass
elif line.startswith('index '):
pass
elif line.startswith('---'):
if '/dev/null' in line:
new_file = True
else:
new_file = False
pass
elif line.startswith('+++'):
if line.startswith('+++ /dev/null'):
continue # Deleted file
if len(chunks) > 0 and current_file != '':
passed = check_chunks(checkstyle_output, chunks) and passed
# Handle new file
current_file = line[6:].strip()
current_file = os.path.join('..', current_file)
checkstyle_output = checkstyle(current_file)
chunks = []
elif line.startswith('@@'):
# Handle chunk
del_start, del_length, add_start, add_length = process_chunk_info(line)
if add_length > 0:
# Addition or replacement
chunks.append((add_start, add_start + add_length))
else:
# Code removal. Check next line to ensure it didn't introduce an error
chunks.append((del_start, del_start + 1))
if len(chunks) > 0:
passed = check_chunks(checkstyle_output, chunks) and passed
return passed
if __name__ == '__main__':
if 'BYPASS_CHECKSTYLE' in os.environ:
sys.exit(0)
if main(parent_commit):
sys.exit(0)
else:
sys.exit(1)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment