This patch adds a script based on pylint for static code analysis during build.

https://fedorahosted.org/freeipa/ticket/867

--
Jan Cholasta
>From 12e7ee4cd3b479c677fdbac10e99c223f1378da5 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 7 Apr 2011 16:49:05 +0200
Subject: [PATCH] Add lint script for static code analysis.

ticket 867
---
 Makefile  |    3 +
 make-lint |  183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 186 insertions(+), 0 deletions(-)
 create mode 100755 make-lint

diff --git a/Makefile b/Makefile
index 4cc9dea..f8f5987 100644
--- a/Makefile
+++ b/Makefile
@@ -72,6 +72,9 @@ client-install: client
 		python setup-client.py install --root $(DESTDIR); \
 	fi
 
+lint:
+	./make-lint
+
 test:
 	$(MAKE) -C install/po test_lang
 	./make-test
diff --git a/make-lint b/make-lint
new file mode 100755
index 0000000..ee7577b
--- /dev/null
+++ b/make-lint
@@ -0,0 +1,183 @@
+#!/usr/bin/python
+#
+# Authors:
+#   Jakub Hrozek <jhro...@redhat.com>
+#   Jan Cholasta <jchol...@redhat.com>
+#
+# Copyright (C) 2011  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+from optparse import OptionParser
+from fnmatch import fnmatch, fnmatchcase
+
+from pylint import checkers
+from pylint.lint import PyLinter
+from pylint.reporters.text import ParseableTextReporter
+from pylint.checkers.typecheck import TypeChecker
+from logilab.astng import Class, Instance, InferenceError
+
+# File names to ignore when searching for python source files
+IGNORE_FILES = ('.*', '*~', '*.in', '*.pyc', '*.pyo')
+IGNORE_PATHS = ('build', 'tests')
+
+class IPATypeChecker(TypeChecker):
+    # 'class': ('generated', 'properties',)
+    ignore = {
+        'ipalib.base.NameSpace': ('find',),
+        'ipalib.cli.Collector': ('__options',),
+        'ipalib.config.Env': ('*'),
+        'ipalib.plugable.API': ('Command', 'Object', 'Method', 'Property',
+            'Backend', 'log', 'plugins'),
+        'ipalib.plugable.Plugin': ('Command', 'Object', 'Method', 'Property',
+            'Backend', 'env', 'debug', 'info', 'warning', 'error', 'critical',
+            'exception', 'context', 'log'),
+        'ipalib.plugins.baseldap.CallbackInterface': ('pre_callback',
+            'post_callback', 'exc_callback'),
+        'ipalib.plugins.misc.env': ('env',),
+        'ipalib.parameters.Param': ('cli_name', 'cli_short_name', 'label',
+            'doc', 'required', 'multivalue', 'primary_key', 'normalizer',
+            'default', 'default_from', 'create_default', 'autofill', 'query',
+            'attribute', 'include', 'exclude', 'flags', 'hint', 'alwaysask'),
+        'ipalib.parameters.Bool': ('truths', 'falsehoods'),
+        'ipalib.parameters.Int': ('minvalue', 'maxvalue'),
+        'ipalib.parameters.Float': ('minvalue', 'maxvalue'),
+        'ipalib.parameters.Data': ('minlength', 'maxlength', 'length',
+            'pattern', 'pattern_errmsg'),
+        'ipalib.parameters.Enum': ('values',),
+        'ipalib.parameters.List': ('separator', 'skipspace'),
+        'ipalib.parameters.File': ('stdin_if_missing'),
+        'urlparse.SplitResult': ('netloc',),
+    }
+
+    def _related_classes(self, klass):
+        yield klass
+        for base in klass.ancestors():
+            yield base
+
+    def _class_full_name(self, klass):
+        return klass.root().name + '.' + klass.name
+
+    def _find_ignored_attrs(self, owner):
+        attrs = []
+        for klass in self._related_classes(owner):
+            name = self._class_full_name(klass)
+            if name in self.ignore:
+                attrs += self.ignore[name]
+        return attrs
+
+    def visit_getattr(self, node):
+        try:
+            infered = list(node.expr.infer())
+        except InferenceError:
+            return
+
+        for owner in infered:
+            if not isinstance(owner, Class) and not isinstance(owner, Instance):
+                continue
+
+            ignored = self._find_ignored_attrs(owner)
+            for pattern in ignored:
+                if fnmatchcase(node.attrname, pattern):
+                    return
+
+        super(IPATypeChecker, self).visit_getattr(node)
+
+class IPALinter(PyLinter):
+    ignore = (TypeChecker,)
+
+    def register_checker(self, checker):
+        if type(checker) in self.ignore:
+            return
+        super(IPALinter, self).register_checker(checker)
+
+def find_files(path, basepath):
+    for pattern in IGNORE_PATHS:
+        if path == os.path.join(basepath, pattern):
+            return []
+
+    entries = os.listdir(path)
+
+    # If this directory is a python package, look no further
+    if '__init__.py' in entries:
+        return [path]
+
+    result = []
+    for filename in entries:
+        for pattern in IGNORE_FILES:
+            if fnmatch(filename, pattern):
+                filename = None
+                break
+        if not filename:
+            continue
+
+        filepath = os.path.join(path, filename)
+
+        if os.path.islink(filepath):
+            continue
+
+        # Recurse into subdirectories
+        if os.path.isdir(filepath):
+            result += find_files(filepath, basepath)
+            continue
+
+        # Add all *.py files
+        if filename.endswith('.py'):
+            result.append(filepath)
+            continue
+
+        # Add any other files beginning with a shebang and having
+        # the word "python" on the first line
+        file = open(filepath, 'r')
+        line = file.readline(128)
+        file.close()
+
+        if line[:2] == '#!' and line.find('python') >= 0:
+            result.append(filepath)
+
+    return result
+
+def main():
+    optparser = OptionParser()
+
+    options, args = optparser.parse_args()
+    cwd = os.getcwd()
+
+    if len(args) == 0:
+        files = find_files(cwd, cwd)
+    else:
+        files = args
+
+    for filename in files:
+        dirname = os.path.dirname(filename)
+        if dirname not in sys.path:
+            sys.path.insert(0, dirname)
+
+    linter = IPALinter()
+    checkers.initialize(linter)
+    linter.register_checker(IPATypeChecker(linter))
+
+    linter.set_reporter(ParseableTextReporter())
+
+    linter.disable_noerror_messages()
+    linter.set_option('include-ids', True)
+    linter.set_option('reports', False)
+
+    linter.check(files)
+
+if __name__ == "__main__":
+    main()
-- 
1.7.4.2

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to