1""" 
    2Module for statical analysis. 
    3""" 
    4from parso.python import tree 
    5 
    6from jedi import debug 
    7from jedi.inference.helpers import is_string 
    8 
    9 
    10CODES = { 
    11    'attribute-error': (1, AttributeError, 'Potential AttributeError.'), 
    12    'name-error': (2, NameError, 'Potential NameError.'), 
    13    'import-error': (3, ImportError, 'Potential ImportError.'), 
    14    'type-error-too-many-arguments': (4, TypeError, None), 
    15    'type-error-too-few-arguments': (5, TypeError, None), 
    16    'type-error-keyword-argument': (6, TypeError, None), 
    17    'type-error-multiple-values': (7, TypeError, None), 
    18    'type-error-star-star': (8, TypeError, None), 
    19    'type-error-star': (9, TypeError, None), 
    20    'type-error-operation': (10, TypeError, None), 
    21    'type-error-not-iterable': (11, TypeError, None), 
    22    'type-error-isinstance': (12, TypeError, None), 
    23    'type-error-not-subscriptable': (13, TypeError, None), 
    24    'value-error-too-many-values': (14, ValueError, None), 
    25    'value-error-too-few-values': (15, ValueError, None), 
    26} 
    27 
    28 
    29class Error: 
    30    def __init__(self, name, module_path, start_pos, message=None): 
    31        self.path = module_path 
    32        self._start_pos = start_pos 
    33        self.name = name 
    34        if message is None: 
    35            message = CODES[self.name][2] 
    36        self.message = message 
    37 
    38    @property 
    39    def line(self): 
    40        return self._start_pos[0] 
    41 
    42    @property 
    43    def column(self): 
    44        return self._start_pos[1] 
    45 
    46    @property 
    47    def code(self): 
    48        # The class name start 
    49        first = self.__class__.__name__[0] 
    50        return first + str(CODES[self.name][0]) 
    51 
    52    def __str__(self): 
    53        return '%s:%s:%s: %s %s' % (self.path, self.line, self.column, 
    54                                    self.code, self.message) 
    55 
    56    def __eq__(self, other): 
    57        return (self.path == other.path and self.name == other.name 
    58                and self._start_pos == other._start_pos) 
    59 
    60    def __ne__(self, other): 
    61        return not self.__eq__(other) 
    62 
    63    def __hash__(self): 
    64        return hash((self.path, self._start_pos, self.name)) 
    65 
    66    def __repr__(self): 
    67        return '<%s %s: %s@%s,%s>' % (self.__class__.__name__, 
    68                                      self.name, self.path, 
    69                                      self._start_pos[0], self._start_pos[1]) 
    70 
    71 
    72class Warning(Error): 
    73    pass 
    74 
    75 
    76def add(node_context, error_name, node, message=None, typ=Error, payload=None): 
    77    exception = CODES[error_name][1] 
    78    if _check_for_exception_catch(node_context, node, exception, payload): 
    79        return 
    80 
    81    # TODO this path is probably not right 
    82    module_context = node_context.get_root_context() 
    83    module_path = module_context.py__file__() 
    84    issue_instance = typ(error_name, module_path, node.start_pos, message) 
    85    debug.warning(str(issue_instance), format=False) 
    86    node_context.inference_state.analysis.append(issue_instance) 
    87    return issue_instance 
    88 
    89 
    90def _check_for_setattr(instance): 
    91    """ 
    92    Check if there's any setattr method inside an instance. If so, return True. 
    93    """ 
    94    module = instance.get_root_context() 
    95    node = module.tree_node 
    96    if node is None: 
    97        # If it's a compiled module or doesn't have a tree_node 
    98        return False 
    99 
    100    try: 
    101        stmt_names = node.get_used_names()['setattr'] 
    102    except KeyError: 
    103        return False 
    104 
    105    return any(node.start_pos < n.start_pos < node.end_pos 
    106               # Check if it's a function called setattr. 
    107               and not (n.parent.type == 'funcdef' and n.parent.name == n) 
    108               for n in stmt_names) 
    109 
    110 
    111def add_attribute_error(name_context, lookup_value, name): 
    112    message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name)) 
    113    # Check for __getattr__/__getattribute__ existance and issue a warning 
    114    # instead of an error, if that happens. 
    115    typ = Error 
    116    if lookup_value.is_instance() and not lookup_value.is_compiled(): 
    117        # TODO maybe make a warning for __getattr__/__getattribute__ 
    118 
    119        if _check_for_setattr(lookup_value): 
    120            typ = Warning 
    121 
    122    payload = lookup_value, name 
    123    add(name_context, 'attribute-error', name, message, typ, payload) 
    124 
    125 
    126def _check_for_exception_catch(node_context, jedi_name, exception, payload=None): 
    127    """ 
    128    Checks if a jedi object (e.g. `Statement`) sits inside a try/catch and 
    129    doesn't count as an error (if equal to `exception`). 
    130    Also checks `hasattr` for AttributeErrors and uses the `payload` to compare 
    131    it. 
    132    Returns True if the exception was catched. 
    133    """ 
    134    def check_match(cls, exception): 
    135        if not cls.is_class(): 
    136            return False 
    137 
    138        for python_cls in exception.mro(): 
    139            if cls.py__name__() == python_cls.__name__ \ 
    140                    and cls.parent_context.is_builtins_module(): 
    141                return True 
    142        return False 
    143 
    144    def check_try_for_except(obj, exception): 
    145        # Only nodes in try 
    146        iterator = iter(obj.children) 
    147        for branch_type in iterator: 
    148            next(iterator)  # The colon 
    149            suite = next(iterator) 
    150            if branch_type == 'try' \ 
    151                    and not (branch_type.start_pos < jedi_name.start_pos <= suite.end_pos): 
    152                return False 
    153 
    154        for node in obj.get_except_clause_tests(): 
    155            if node is None: 
    156                return True  # An exception block that catches everything. 
    157            else: 
    158                except_classes = node_context.infer_node(node) 
    159                for cls in except_classes: 
    160                    from jedi.inference.value import iterable 
    161                    if isinstance(cls, iterable.Sequence) and \ 
    162                            cls.array_type == 'tuple': 
    163                        # multiple exceptions 
    164                        for lazy_value in cls.py__iter__(): 
    165                            for typ in lazy_value.infer(): 
    166                                if check_match(typ, exception): 
    167                                    return True 
    168                    else: 
    169                        if check_match(cls, exception): 
    170                            return True 
    171 
    172    def check_hasattr(node, suite): 
    173        try: 
    174            assert suite.start_pos <= jedi_name.start_pos < suite.end_pos 
    175            assert node.type in ('power', 'atom_expr') 
    176            base = node.children[0] 
    177            assert base.type == 'name' and base.value == 'hasattr' 
    178            trailer = node.children[1] 
    179            assert trailer.type == 'trailer' 
    180            arglist = trailer.children[1] 
    181            assert arglist.type == 'arglist' 
    182            from jedi.inference.arguments import TreeArguments 
    183            args = TreeArguments(node_context.inference_state, node_context, arglist) 
    184            unpacked_args = list(args.unpack()) 
    185            # Arguments should be very simple 
    186            assert len(unpacked_args) == 2 
    187 
    188            # Check name 
    189            key, lazy_value = unpacked_args[1] 
    190            names = list(lazy_value.infer()) 
    191            assert len(names) == 1 and is_string(names[0]) 
    192            assert names[0].get_safe_value() == payload[1].value 
    193 
    194            # Check objects 
    195            key, lazy_value = unpacked_args[0] 
    196            objects = lazy_value.infer() 
    197            return payload[0] in objects 
    198        except AssertionError: 
    199            return False 
    200 
    201    obj = jedi_name 
    202    while obj is not None and not isinstance(obj, (tree.Function, tree.Class)): 
    203        if isinstance(obj, tree.Flow): 
    204            # try/except catch check 
    205            if obj.type == 'try_stmt' and check_try_for_except(obj, exception): 
    206                return True 
    207            # hasattr check 
    208            if exception == AttributeError and obj.type in ('if_stmt', 'while_stmt'): 
    209                if check_hasattr(obj.children[1], obj.children[3]): 
    210                    return True 
    211        obj = obj.parent 
    212 
    213    return False