Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/libcst/_nodes/module.py: 64%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

58 statements  

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 

6from dataclasses import dataclass 

7from typing import cast, Optional, Sequence, TYPE_CHECKING, TypeVar, Union 

8 

9from libcst._add_slots import add_slots 

10from libcst._nodes.base import CSTNode 

11from libcst._nodes.internal import CodegenState, visit_body_sequence, visit_sequence 

12from libcst._nodes.statement import ( 

13 BaseCompoundStatement, 

14 get_docstring_impl, 

15 SimpleStatementLine, 

16) 

17from libcst._nodes.whitespace import EmptyLine 

18from libcst._removal_sentinel import RemovalSentinel 

19from libcst._visitors import CSTVisitorT 

20 

21if TYPE_CHECKING: 

22 # This is circular, so import the type only in type checking 

23 from libcst._parser.types.config import PartialParserConfig 

24 

25 

26_ModuleSelfT = TypeVar("_ModuleSelfT", bound="Module") 

27 

28# type alias needed for scope overlap in type definition 

29builtin_bytes = bytes 

30 

31 

32@add_slots 

33@dataclass(frozen=True) 

34class Module(CSTNode): 

35 """ 

36 Contains some top-level information inferred from the file letting us set correct 

37 defaults when printing the tree about global formatting rules. All code parsed 

38 with :func:`parse_module` will be encapsulated in a module. 

39 """ 

40 

41 #: A list of zero or more statements that make up this module. 

42 body: Sequence[Union[SimpleStatementLine, BaseCompoundStatement]] 

43 

44 #: Normally any whitespace/comments are assigned to the next node visited, but 

45 #: :class:`Module` is a special case, and comments at the top of the file tend 

46 #: to refer to the module itself, so we assign them to the :class:`Module` 

47 #: instead of the first statement in the body. 

48 header: Sequence[EmptyLine] = () 

49 

50 #: Any trailing whitespace/comments found after the last statement. 

51 footer: Sequence[EmptyLine] = () 

52 

53 #: The file's encoding format. When parsing a ``bytes`` object, this value may be 

54 #: inferred from the contents of the parsed source code. When parsing a ``str``, 

55 #: this value defaults to ``"utf-8"``. 

56 #: 

57 #: This value affects how :attr:`bytes` encodes the source code. 

58 encoding: str = "utf-8" 

59 

60 #: The indentation of the file, expressed as a series of tabs and/or spaces. This 

61 #: value is inferred from the contents of the parsed source code by default. 

62 default_indent: str = " " * 4 

63 

64 #: The newline of the file, expressed as ``\n``, ``\r\n``, or ``\r``. This value is 

65 #: inferred from the contents of the parsed source code by default. 

66 default_newline: str = "\n" 

67 

68 #: Whether the module has a trailing newline or not. 

69 has_trailing_newline: bool = True 

70 

71 def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "Module": 

72 return Module( 

73 header=visit_sequence(self, "header", self.header, visitor), 

74 body=visit_body_sequence(self, "body", self.body, visitor), 

75 footer=visit_sequence(self, "footer", self.footer, visitor), 

76 encoding=self.encoding, 

77 default_indent=self.default_indent, 

78 default_newline=self.default_newline, 

79 has_trailing_newline=self.has_trailing_newline, 

80 ) 

81 

82 def visit(self: _ModuleSelfT, visitor: CSTVisitorT) -> _ModuleSelfT: 

83 """ 

84 Returns the result of running a visitor over this module. 

85 

86 :class:`Module` overrides the default visitor entry point to resolve metadata 

87 dependencies declared by 'visitor'. 

88 """ 

89 result = super(Module, self).visit(visitor) 

90 if isinstance(result, RemovalSentinel): 

91 return self.with_changes(body=(), header=(), footer=()) 

92 else: # is a Module 

93 return cast(_ModuleSelfT, result) 

94 

95 def _codegen_impl(self, state: CodegenState) -> None: 

96 for h in self.header: 

97 h._codegen(state) 

98 for stmt in self.body: 

99 stmt._codegen(state) 

100 for f in self.footer: 

101 f._codegen(state) 

102 if self.has_trailing_newline: 

103 if len(state.tokens) == 0: 

104 # There was nothing in the header, footer, or body. Just add a newline 

105 # to preserve the trailing newline. 

106 state.add_token(state.default_newline) 

107 else: # has_trailing_newline is false 

108 state.pop_trailing_newline() 

109 

110 @property 

111 def code(self) -> str: 

112 """ 

113 The string representation of this module, respecting the inferred indentation 

114 and newline type. 

115 """ 

116 return self.code_for_node(self) 

117 

118 @property 

119 def bytes(self) -> builtin_bytes: 

120 """ 

121 The bytes representation of this module, respecting the inferred indentation 

122 and newline type, using the current encoding. 

123 """ 

124 return self.code.encode(self.encoding) 

125 

126 def code_for_node(self, node: CSTNode) -> str: 

127 """ 

128 Generates the code for the given node in the context of this module. This is a 

129 method of Module, not CSTNode, because we need to know the module's default 

130 indentation and newline formats. 

131 """ 

132 

133 state = CodegenState( 

134 default_indent=self.default_indent, default_newline=self.default_newline 

135 ) 

136 node._codegen(state) 

137 return "".join(state.tokens) 

138 

139 @property 

140 def config_for_parsing(self) -> "PartialParserConfig": 

141 """ 

142 Generates a parser config appropriate for passing to a :func:`parse_expression` 

143 or :func:`parse_statement` call. This is useful when using either parser 

144 function to generate code from a string template. By using a generated parser 

145 config instead of the default, you can guarantee that trees generated from 

146 both statement and expression strings have the same inferred defaults for things 

147 like newlines, indents and similar:: 

148 

149 module = cst.parse_module("pass\\n") 

150 expression = cst.parse_expression("1 + 2", config=module.config_for_parsing) 

151 """ 

152 

153 from libcst._parser.types.config import PartialParserConfig 

154 

155 return PartialParserConfig( 

156 encoding=self.encoding, 

157 default_indent=self.default_indent, 

158 default_newline=self.default_newline, 

159 ) 

160 

161 def get_docstring(self, clean: bool = True) -> Optional[str]: 

162 """ 

163 Returns a :func:`inspect.cleandoc` cleaned docstring if the docstring is available, ``None`` otherwise. 

164 """ 

165 return get_docstring_impl(self.body, clean)