summaryrefslogtreecommitdiffstats
path: root/tools/pylint/plugins/cim_provider_checker.py
blob: b1b1ae9cb874e79d74e06c06b00c9e14a1680305 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# -*- encoding: utf-8 -*-
# Software Management Providers
#
# Copyright (C) 2012 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 <miminar@redhat.com>
"""
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<prefix>[A-Z][a-zA-Z0-9]*)_(?P<name>[A-Z][a-zA-Z]*)$')
_RE_PROVIDER_MODULE_NAME = re.compile(
    r'^((?P<parent>.*)\.)?(?P<basename>(?P<prefix>[A-Z][a-zA-Z0-9]*)'
    r'_(?P<name>[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 "
                  "<prefix>_<name>. Where both <prefix> and <name> 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))