Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/inference/analysis.py: 19%

123 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

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