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

134 statements  

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

1from contextlib import contextmanager 

2from typing import Dict, List 

3 

4 

5class _NormalizerMeta(type): 

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

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

8 new_cls.rule_value_classes = {} 

9 new_cls.rule_type_classes = {} 

10 return new_cls 

11 

12 

13class Normalizer(metaclass=_NormalizerMeta): 

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

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

16 

17 def __init__(self, grammar, config): 

18 self.grammar = grammar 

19 self._config = config 

20 self.issues = [] 

21 

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

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

24 

25 def _instantiate_rules(self, attr): 

26 dct = {} 

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

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

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

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

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

32 return dct 

33 

34 def walk(self, node): 

35 self.initialize(node) 

36 value = self.visit(node) 

37 self.finalize() 

38 return value 

39 

40 def visit(self, node): 

41 try: 

42 children = node.children 

43 except AttributeError: 

44 return self.visit_leaf(node) 

45 else: 

46 with self.visit_node(node): 

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

48 

49 @contextmanager 

50 def visit_node(self, node): 

51 self._check_type_rules(node) 

52 yield 

53 

54 def _check_type_rules(self, node): 

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

56 rule.feed_node(node) 

57 

58 def visit_leaf(self, leaf): 

59 self._check_type_rules(leaf) 

60 

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

62 rule.feed_node(leaf) 

63 

64 return leaf.prefix + leaf.value 

65 

66 def initialize(self, node): 

67 pass 

68 

69 def finalize(self): 

70 pass 

71 

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

73 issue = Issue(node, code, message) 

74 if issue not in self.issues: 

75 self.issues.append(issue) 

76 return True 

77 

78 @classmethod 

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

80 """ 

81 Use it as a class decorator:: 

82 

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

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

85 class MyRule(Rule): 

86 error_code = 42 

87 """ 

88 values = list(values) 

89 types = list(types) 

90 if value is not None: 

91 values.append(value) 

92 if type is not None: 

93 types.append(type) 

94 

95 if not values and not types: 

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

97 

98 def decorator(rule_cls): 

99 for v in values: 

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

101 for t in types: 

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

103 return rule_cls 

104 

105 return decorator 

106 

107 

108class NormalizerConfig: 

109 normalizer_class = Normalizer 

110 

111 def create_normalizer(self, grammar): 

112 if self.normalizer_class is None: 

113 return None 

114 

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)