Coverage for src / renaissance / syntax_tree / ast_refactor_actions.py: 91%

47 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-09-09 14:04 +0000

1from collections.abc import Sequence 

2from functools import cache 

3from typing import TYPE_CHECKING 

4 

5from ..integrations.types import BogusType, Type 

6from .ast_finder import ASTFinder, matches_kind 

7from .ast_node import ASTNode 

8from .ast_processor import ASTProcessor 

9from .match_finder import MatchFinder, PatternMatch 

10 

11if TYPE_CHECKING: 

12 from renaissance.integrations.clang.c_pattern_factory import CPPPatternFactory 

13 

14 

15class ASTRefactorActions: 

16 def __init__(self, processor: ASTProcessor, pattern_factory: CPPPatternFactory) -> None: 

17 self.processor = processor 

18 self.pattern_factory = pattern_factory 

19 self.replaced: set[int] = set() 

20 

21 def replace_expr(self, name: str, replacement: str, kind: type[Type]): 

22 def test(n: ASTNode): 

23 if (kind and matches_kind(n, kind)) and n.name == name: 

24 yield n 

25 

26 [self.processor.replace(found.text.replace(found.name, replacement, 1), found) for found in self.processor.find_all(test)] 

27 

28 def replace_name( 

29 self, 

30 name: str, 

31 replacement: str, 

32 kind: type[Type] = None, 

33 skip_kind: type[Type] = BogusType, 

34 ): 

35 def matches_name(n1: ASTNode) -> bool: 

36 return ( 

37 (not kind or ASTFinder.matches_kind(n1, kind)) 

38 and (not skip_kind or not ASTFinder.matches_kind(n1, skip_kind)) 

39 and n1 

40 and n1.name == name 

41 ) 

42 

43 found_nodes = self.processor.find_all(matches_name) 

44 [self.replaced.add(found.offset) for found in found_nodes if found.offset not in self.replaced] 

45 for n in found_nodes: 

46 self.processor.replace(n.text.replace(n.name, replacement, 1), n) 

47 

48 def replace_text( 

49 self, 

50 text: str, 

51 replacement: str, 

52 kind: type[Type] = None, 

53 skip_kind: type[Type] = BogusType, 

54 ): 

55 def matches_text(n: ASTNode) -> bool: 

56 return ( 

57 (not kind or ASTFinder.matches_kind(n, kind)) 

58 and (not skip_kind or not ASTFinder.matches_kind(n, skip_kind)) 

59 and n is not None 

60 and n.text == text 

61 ) 

62 

63 found_nodes = self.processor.find_all(matches_text) 

64 [self.replaced.add(found.offset) for found in found_nodes if found.offset not in self.replaced] 

65 

66 [self.processor.replace(n.text.replace(n.name, replacement, 1), n) for n in found_nodes] 

67 

68 def replace_declaration(self, declaration: str, replacement: str): 

69 for match in self.find_declaration(declaration): 

70 self.processor.replace(replacement, match) 

71 

72 def _replace_patterns( 

73 self, 

74 node: ASTNode, 

75 replacement: str, 

76 patterns: Sequence[Sequence[ASTNode]], 

77 matches: Sequence[PatternMatch], 

78 ): 

79 if not patterns: 

80 self.processor.replace(replacement, matches) 

81 return 

82 [ 

83 self._replace_patterns(m.nodes[0], replacement, patterns[1:], list(matches) + [m]) 

84 for m in MatchFinder.find_all([node], patterns[0]) 

85 ] 

86 

87 @cache 

88 def find_declaration(self, decl_pattern: str): 

89 pattern = self.pattern_factory.create_declaration(decl_pattern) 

90 return self.processor.find_match(pattern) 

91 

92 @cache 

93 def collect(self, pattern: str, pattern_kind: str): 

94 root = self.pattern_factory.create(pattern, pattern_kind) 

95 

96 return self.processor.find_match(root)