Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/metadata/position_provider.py: 57%

63 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:43 +0000

1# Copyright (c) Meta Platforms, Inc. and affiliates. 

2# 

3# This source code is licensed under the MIT license found in the 

4# LICENSE file in the root directory of this source tree. 

5 

6 

7import re 

8from contextlib import contextmanager 

9from dataclasses import dataclass, field 

10from typing import Iterator, List, Optional, Pattern 

11 

12from libcst._add_slots import add_slots 

13from libcst._nodes.base import CSTNode 

14from libcst._nodes.internal import CodegenState 

15from libcst._nodes.module import Module 

16from libcst._position import CodePosition, CodeRange 

17from libcst.metadata.base_provider import BaseMetadataProvider 

18 

19NEWLINE_RE: Pattern[str] = re.compile(r"\r\n?|\n") 

20 

21 

22@add_slots 

23@dataclass(frozen=False) 

24class WhitespaceInclusivePositionProvidingCodegenState(CodegenState): 

25 # These are derived from a Module 

26 default_indent: str 

27 default_newline: str 

28 provider: BaseMetadataProvider[CodeRange] 

29 

30 indent_tokens: List[str] = field(default_factory=list) 

31 tokens: List[str] = field(default_factory=list) 

32 

33 line: int = 1 # one-indexed 

34 column: int = 0 # zero-indexed 

35 _stack: List[CodePosition] = field(init=False, default_factory=list) 

36 

37 def add_indent_tokens(self) -> None: 

38 self.tokens.extend(self.indent_tokens) 

39 for token in self.indent_tokens: 

40 self._update_position(token) 

41 

42 def add_token(self, value: str) -> None: 

43 self.tokens.append(value) 

44 self._update_position(value) 

45 

46 def _update_position(self, value: str) -> None: 

47 """ 

48 Computes new line and column numbers from adding the token [value]. 

49 """ 

50 segments = NEWLINE_RE.split(value) 

51 if len(segments) == 1: # contains no newlines 

52 # no change to self.lines 

53 self.column += len(value) 

54 else: 

55 self.line += len(segments) - 1 

56 # newline resets column back to 0, but a trailing token may shift column 

57 self.column = len(segments[-1]) 

58 

59 def before_codegen(self, node: "CSTNode") -> None: 

60 self._stack.append(CodePosition(self.line, self.column)) 

61 

62 def after_codegen(self, node: "CSTNode") -> None: 

63 # we must unconditionally pop the stack, else we could end up in a broken state 

64 start_pos = self._stack.pop() 

65 

66 # Don't overwrite existing position information 

67 # (i.e. semantic position has already been recorded) 

68 if node not in self.provider._computed: 

69 end_pos = CodePosition(self.line, self.column) 

70 node_range = CodeRange(start_pos, end_pos) 

71 self.provider._computed[node] = node_range 

72 

73 

74class WhitespaceInclusivePositionProvider(BaseMetadataProvider[CodeRange]): 

75 """ 

76 Generates line and column metadata. 

77 

78 The start and ending bounds of the positions produced by this provider include all 

79 whitespace owned by the node. 

80 """ 

81 

82 def _gen_impl(self, module: Module) -> None: 

83 state = WhitespaceInclusivePositionProvidingCodegenState( 

84 default_indent=module.default_indent, 

85 default_newline=module.default_newline, 

86 provider=self, 

87 ) 

88 module._codegen(state) 

89 

90 

91@add_slots 

92@dataclass(frozen=False) 

93class PositionProvidingCodegenState(WhitespaceInclusivePositionProvidingCodegenState): 

94 @contextmanager 

95 def record_syntactic_position( 

96 self, 

97 node: CSTNode, 

98 *, 

99 start_node: Optional[CSTNode] = None, 

100 end_node: Optional[CSTNode] = None, 

101 ) -> Iterator[None]: 

102 start = CodePosition(self.line, self.column) 

103 try: 

104 yield 

105 finally: 

106 end = CodePosition(self.line, self.column) 

107 

108 # Override with positions hoisted from child nodes if provided 

109 start = ( 

110 self.provider._computed[start_node].start 

111 if start_node is not None 

112 else start 

113 ) 

114 end = self.provider._computed[end_node].end if end_node is not None else end 

115 

116 self.provider._computed[node] = CodeRange(start, end) 

117 

118 

119class PositionProvider(BaseMetadataProvider[CodeRange]): 

120 """ 

121 Generates line and column metadata. 

122 

123 These positions are defined by the start and ending bounds of a node ignoring most 

124 instances of leading and trailing whitespace when it is not syntactically 

125 significant. 

126 

127 The positions provided by this provider should eventually match the positions used 

128 by `Pyre <https://github.com/facebook/pyre-check>`__ for equivalent nodes. 

129 """ 

130 

131 def _gen_impl(self, module: Module) -> None: 

132 state = PositionProvidingCodegenState( 

133 default_indent=module.default_indent, 

134 default_newline=module.default_newline, 

135 provider=self, 

136 ) 

137 module._codegen(state)