# -*- encoding: utf-8 -*- # Software Management Providers # # Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Authors: Michal Minar """ Pylint checker for CIM provider modules and classes. """ import re from logilab.astng import node_classes # pylint: disable=W0403 from logilab.astng import scoped_nodes # pylint: disable=W0403 from pylint.interfaces import IASTNGChecker from pylint.checkers import BaseChecker _RE_PROVIDER_CLASS_NAME = re.compile( r'^(?P[A-Z][a-zA-Z0-9]*)_(?P[A-Z][a-zA-Z]*)$') _RE_PROVIDER_MODULE_NAME = re.compile( r'^((?P.*)\.)?(?P(?P[A-Z][a-zA-Z0-9]*)' r'_(?P[A-Z][a-zA-Z]*))$') _RE_COMMON_MODULE_NAME = re.compile( r'^([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)$') def recursively_clean_cls_values(linter, node): """ Values class under provider defines property values, under nested classes that are popular target of pylint messages. Since these are generated and known to any developper, we don't want to be bothered by these warnings. Supress them for any nested class recursively. """ # class has too few public methods linter.disable('R0903', scope='module', line=node.fromlineno) # class has not docstring linter.disable('C0111', scope='module', line=node.fromlineno) for child in node.get_children(): if isinstance(child, scoped_nodes.Class): recursively_clean_cls_values(linter, child) def supress_cim_provider_messages(linter, node): """ Supress some warnings for CIMProvider2 subclass. @param node is a subclass of CIMProvider2 """ assert isinstance(node, scoped_nodes.Class) # class has too many public methods linter.disable('R0904', scope='module', line=node.fromlineno) if '__init__' in node: # __init__ not called for base class linter.disable('W0231', scope='module', line=node['__init__'].fromlineno) if ( 'Values' in node and isinstance(node['Values'], scoped_nodes.Class)): recursively_clean_cls_values(linter, node['Values']) generated_methods = ( 'get_instance', 'enum_instances', 'set_instance', 'delete_instance') # implementation of generated methods may not use all arguments for child in node.get_children(): if ( not isinstance(child, scoped_nodes.Function) or ( child.name not in generated_methods and not child.name.startswith('cim_method_'))): continue # provider method could be a function linter.disable('R0201', scope='module', line=child.fromlineno) for arg in child.args.get_children(): linter.disable('W0613', scope='module', line=arg.fromlineno) class CIMProviderChecker(BaseChecker): """ Checks for compliance to naming conventions for python cim providers. """ __implements__ = IASTNGChecker name = 'cim_provider' msgs = { 'C9905': ('Invalid provider module name %s', "Module containing cim provider(s) should be named as " "_. Where both and are " "CamelCased strings."), 'C9906': ('Prefixes of module and class does not match: %s != %s', "Module prefix has to match provider class prefix."), 'W9908': ("get_providers in module %s is not a function", "get_providers should be a callable function."), 'W9909': ("Missing provider name \"%s\" in providers dictionary.", "Function get_providers returns providers association" " dictionary, that must include all module's provider" " classes."), 'C9910': ("Prefix different from LMI: %s", "All OpenLMI CIM classes have LMI_ ORGID prefix."), 'C9911': ("Invalid module name: %s", "All modules, that does not declare cim provider class" " should be named according to this regexp:" " '^(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$'."), } # this is important so that your checker is executed before others priority = -2 def visit_class(self, node): """ Check every class, which inherits from pywbem.cim_provider2.CIMProvider2. """ if "CIMProvider2" in [a.name for a in node.ancestors()]: supress_cim_provider_messages(self.linter, node) parent = node.parent while not isinstance(parent, scoped_nodes.Module): parent = parent.parent clsm = _RE_PROVIDER_CLASS_NAME.match(node.name) modm = _RE_PROVIDER_MODULE_NAME.match(parent.name) if clsm and not modm: self.add_message('C9905', node=node, args=parent.name) if clsm and modm and clsm.group('prefix') != modm.group('prefix'): self.add_message('C9906', node=node, args=(modm.group('prefix'), clsm.group('prefix'))) if clsm and clsm.group('prefix') != 'LMI': self.add_message('C9910', node=node, args=clsm.group('prefix')) if 'get_providers' in parent.keys(): getprovs = parent['get_providers'] if not isinstance(getprovs, scoped_nodes.Function): self.add_message('W9908', node=getprovs, args=parent.name) ret = getprovs.last_child() if not isinstance(ret, node_classes.Return): return dictionary = ret.get_children().next() if not isinstance(dictionary, node_classes.Dict): return if not node.name in [i[0].value for i in dictionary.items]: self.add_message('W9909', node=getprovs, args=node.name) elif node.name == "Values" and len([a for a in node.ancestors()]) == 1: recursively_clean_cls_values(self.linter, node) def visit_module(self, node): """ Check for invalid module name. """ modm = _RE_PROVIDER_MODULE_NAME.match(node.name) if modm: if not _RE_COMMON_MODULE_NAME.match(node.name): for child in node.get_children(): if ( isinstance(child, scoped_nodes.Class) and 'CIMProvider2' in [ a.name for a in child.ancestors()]): break else: self.add_message('C9911', node=node, args=node.name) def register(linter): """required method to auto register our checker""" linter.register_checker(CIMProviderChecker(linter))