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

51 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 

7from contextlib import contextmanager 

8from dataclasses import dataclass, field 

9from typing import Callable, Iterator, List, Optional 

10 

11from libcst import CSTNode, Module 

12from libcst._nodes.internal import CodegenState 

13from libcst.metadata.base_provider import BaseMetadataProvider 

14 

15 

16@dataclass(frozen=True) 

17class CodeSpan: 

18 """ 

19 Represents the position of a piece of code by its starting position and length. 

20 

21 Note: This class does not specify the unit of distance - it can be bytes, 

22 Unicode characters, or something else entirely. 

23 """ 

24 

25 #: Offset of the code from the beginning of the file. Can be 0. 

26 start: int 

27 #: Length of the span 

28 length: int 

29 

30 

31@dataclass(frozen=False) 

32class SpanProvidingCodegenState(CodegenState): 

33 provider: BaseMetadataProvider[CodeSpan] 

34 get_length: Optional[Callable[[str], int]] = None 

35 position: int = 0 

36 _stack: List[int] = field(default_factory=list) 

37 

38 def add_indent_tokens(self) -> None: 

39 super().add_indent_tokens() 

40 for token in self.indent_tokens: 

41 self._update_position(token) 

42 

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

44 super().add_token(value) 

45 self._update_position(value) 

46 

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

48 get_length = self.get_length or len 

49 self.position += get_length(value) 

50 

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

52 self._stack.append(self.position) 

53 

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

55 start = self._stack.pop() 

56 

57 if node not in self.provider._computed: 

58 end = self.position 

59 self.provider._computed[node] = CodeSpan(start, length=end - start) 

60 

61 @contextmanager 

62 def record_syntactic_position( 

63 self, 

64 node: CSTNode, 

65 *, 

66 start_node: Optional[CSTNode] = None, 

67 end_node: Optional[CSTNode] = None, 

68 ) -> Iterator[None]: 

69 start = self.position 

70 try: 

71 yield 

72 finally: 

73 end = self.position 

74 start = ( 

75 self.provider._computed[start_node].start 

76 if start_node is not None 

77 else start 

78 ) 

79 if end_node is not None: 

80 end_span = self.provider._computed[end_node] 

81 length = (end_span.start + end_span.length) - start 

82 else: 

83 length = end - start 

84 self.provider._computed[node] = CodeSpan(start, length=length) 

85 

86 

87def byte_length_in_utf8(value: str) -> int: 

88 return len(value.encode("utf8")) 

89 

90 

91class ByteSpanPositionProvider(BaseMetadataProvider[CodeSpan]): 

92 """ 

93 Generates offset and length metadata for nodes' positions. 

94 

95 For each :class:`CSTNode` this provider generates a :class:`CodeSpan` that 

96 contains the byte-offset of the node from the start of the file, and its 

97 length (also in bytes). The whitespace owned by the node is not included in 

98 this length. 

99 

100 Note: offset and length measure bytes, not characters (which is significant for 

101 example in the case of Unicode characters encoded in more than one byte) 

102 """ 

103 

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

105 state = SpanProvidingCodegenState( 

106 default_indent=module.default_indent, 

107 default_newline=module.default_newline, 

108 provider=self, 

109 get_length=byte_length_in_utf8, 

110 ) 

111 module._codegen(state)