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
« 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.
7from contextlib import contextmanager
8from dataclasses import dataclass, field
9from typing import Callable, Iterator, List, Optional
11from libcst import CSTNode, Module
12from libcst._nodes.internal import CodegenState
13from libcst.metadata.base_provider import BaseMetadataProvider
16@dataclass(frozen=True)
17class CodeSpan:
18 """
19 Represents the position of a piece of code by its starting position and length.
21 Note: This class does not specify the unit of distance - it can be bytes,
22 Unicode characters, or something else entirely.
23 """
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
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)
38 def add_indent_tokens(self) -> None:
39 super().add_indent_tokens()
40 for token in self.indent_tokens:
41 self._update_position(token)
43 def add_token(self, value: str) -> None:
44 super().add_token(value)
45 self._update_position(value)
47 def _update_position(self, value: str) -> None:
48 get_length = self.get_length or len
49 self.position += get_length(value)
51 def before_codegen(self, node: CSTNode) -> None:
52 self._stack.append(self.position)
54 def after_codegen(self, node: CSTNode) -> None:
55 start = self._stack.pop()
57 if node not in self.provider._computed:
58 end = self.position
59 self.provider._computed[node] = CodeSpan(start, length=end - start)
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)
87def byte_length_in_utf8(value: str) -> int:
88 return len(value.encode("utf8"))
91class ByteSpanPositionProvider(BaseMetadataProvider[CodeSpan]):
92 """
93 Generates offset and length metadata for nodes' positions.
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.
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 """
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)