Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/parso/normalizer.py: 45%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

137 statements  

1from contextlib import contextmanager 

2from typing import Dict, List, Any 

3 

4 

5class _NormalizerMeta(type): 

6 rule_value_classes: Any 

7 rule_type_classes: Any 

8 

9 def __new__(cls, name, bases, dct): 

10 new_cls = type.__new__(cls, name, bases, dct) 

11 new_cls.rule_value_classes = {} 

12 new_cls.rule_type_classes = {} 

13 return new_cls 

14 

15 

16class Normalizer(metaclass=_NormalizerMeta): 

17 _rule_type_instances: Dict[str, List[type]] = {} 

18 _rule_value_instances: Dict[str, List[type]] = {} 

19 

20 def __init__(self, grammar, config): 

21 self.grammar = grammar 

22 self._config = config 

23 self.issues = [] 

24 

25 self._rule_type_instances = self._instantiate_rules('rule_type_classes') 

26 self._rule_value_instances = self._instantiate_rules('rule_value_classes') 

27 

28 def _instantiate_rules(self, attr): 

29 dct = {} 

30 for base in type(self).mro(): 

31 rules_map = getattr(base, attr, {}) 

32 for type_, rule_classes in rules_map.items(): 

33 new = [rule_cls(self) for rule_cls in rule_classes] 

34 dct.setdefault(type_, []).extend(new) 

35 return dct 

36 

37 def walk(self, node): 

38 self.initialize(node) 

39 value = self.visit(node) 

40 self.finalize() 

41 return value 

42 

43 def visit(self, node): 

44 try: 

45 children = node.children 

46 except AttributeError: 

47 return self.visit_leaf(node) 

48 else: 

49 with self.visit_node(node): 

50 return ''.join(self.visit(child) for child in children) 

51 

52 @contextmanager 

53 def visit_node(self, node): 

54 self._check_type_rules(node) 

55 yield 

56 

57 def _check_type_rules(self, node): 

58 for rule in self._rule_type_instances.get(node.type, []): 

59 rule.feed_node(node) 

60 

61 def visit_leaf(self, leaf): 

62 self._check_type_rules(leaf) 

63 

64 for rule in self._rule_value_instances.get(leaf.value, []): 

65 rule.feed_node(leaf) 

66 

67 return leaf.prefix + leaf.value 

68 

69 def initialize(self, node): 

70 pass 

71 

72 def finalize(self): 

73 pass 

74 

75 def add_issue(self, node, code, message): 

76 issue = Issue(node, code, message) 

77 if issue not in self.issues: 

78 self.issues.append(issue) 

79 return True 

80 

81 @classmethod 

82 def register_rule(cls, *, value=None, values=(), type=None, types=()): 

83 """ 

84 Use it as a class decorator:: 

85 

86 normalizer = Normalizer('grammar', 'config') 

87 @normalizer.register_rule(value='foo') 

88 class MyRule(Rule): 

89 error_code = 42 

90 """ 

91 values = list(values) 

92 types = list(types) 

93 if value is not None: 

94 values.append(value) 

95 if type is not None: 

96 types.append(type) 

97 

98 if not values and not types: 

99 raise ValueError("You must register at least something.") 

100 

101 def decorator(rule_cls): 

102 for v in values: 

103 cls.rule_value_classes.setdefault(v, []).append(rule_cls) 

104 for t in types: 

105 cls.rule_type_classes.setdefault(t, []).append(rule_cls) 

106 return rule_cls 

107 

108 return decorator 

109 

110 

111class NormalizerConfig: 

112 normalizer_class = Normalizer 

113 

114 def create_normalizer(self, grammar): 

115 return self.normalizer_class(grammar, self) 

116 

117 

118class Issue: 

119 def __init__(self, node, code, message): 

120 self.code = code 

121 """ 

122 An integer code that stands for the type of error. 

123 """ 

124 self.message = message 

125 """ 

126 A message (string) for the issue. 

127 """ 

128 self.start_pos = node.start_pos 

129 """ 

130 The start position position of the error as a tuple (line, column). As 

131 always in |parso| the first line is 1 and the first column 0. 

132 """ 

133 self.end_pos = node.end_pos 

134 

135 def __eq__(self, other): 

136 return self.start_pos == other.start_pos and self.code == other.code 

137 

138 def __ne__(self, other): 

139 return not self.__eq__(other) 

140 

141 def __hash__(self): 

142 return hash((self.code, self.start_pos)) 

143 

144 def __repr__(self): 

145 return '<%s: %s>' % (self.__class__.__name__, self.code) 

146 

147 

148class Rule: 

149 code: int 

150 message: str 

151 

152 def __init__(self, normalizer): 

153 self._normalizer = normalizer 

154 

155 def is_issue(self, node): 

156 raise NotImplementedError() 

157 

158 def get_node(self, node): 

159 return node 

160 

161 def _get_message(self, message, node): 

162 if message is None: 

163 message = self.message 

164 if message is None: 

165 raise ValueError("The message on the class is not set.") 

166 return message 

167 

168 def add_issue(self, node, code=None, message=None): 

169 if code is None: 

170 code = self.code 

171 if code is None: 

172 raise ValueError("The error code on the class is not set.") 

173 

174 message = self._get_message(message, node) 

175 

176 self._normalizer.add_issue(node, code, message) 

177 

178 def feed_node(self, node): 

179 if self.is_issue(node): 

180 issue_node = self.get_node(node) 

181 self.add_issue(issue_node) 

182 

183 

184class RefactoringNormalizer(Normalizer): 

185 def __init__(self, node_to_str_map): 

186 self._node_to_str_map = node_to_str_map 

187 

188 def visit(self, node): 

189 try: 

190 return self._node_to_str_map[node] 

191 except KeyError: 

192 return super().visit(node) 

193 

194 def visit_leaf(self, leaf): 

195 try: 

196 return self._node_to_str_map[leaf] 

197 except KeyError: 

198 return super().visit_leaf(leaf)