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
« 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.
7import re
8from contextlib import contextmanager
9from dataclasses import dataclass, field
10from typing import Iterator, List, Optional, Pattern
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
19NEWLINE_RE: Pattern[str] = re.compile(r"\r\n?|\n")
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]
30 indent_tokens: List[str] = field(default_factory=list)
31 tokens: List[str] = field(default_factory=list)
33 line: int = 1 # one-indexed
34 column: int = 0 # zero-indexed
35 _stack: List[CodePosition] = field(init=False, default_factory=list)
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)
42 def add_token(self, value: str) -> None:
43 self.tokens.append(value)
44 self._update_position(value)
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])
59 def before_codegen(self, node: "CSTNode") -> None:
60 self._stack.append(CodePosition(self.line, self.column))
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()
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
74class WhitespaceInclusivePositionProvider(BaseMetadataProvider[CodeRange]):
75 """
76 Generates line and column metadata.
78 The start and ending bounds of the positions produced by this provider include all
79 whitespace owned by the node.
80 """
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)
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)
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
116 self.provider._computed[node] = CodeRange(start, end)
119class PositionProvider(BaseMetadataProvider[CodeRange]):
120 """
121 Generates line and column metadata.
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.
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 """
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)