xref: /aosp_15_r20/external/yapf/yapf/yapflib/object_state.py (revision 7249d1a64f4850ccf838e62a46276f891f72998e)
1# Copyright 2017 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Represents the state of Python objects being formatted.
15
16Objects (e.g., list comprehensions, dictionaries, etc.) have specific
17requirements on how they're formatted. These state objects keep track of these
18requirements.
19"""
20
21from __future__ import absolute_import
22from __future__ import division
23from __future__ import print_function
24
25from yapf.yapflib import format_token
26from yapf.yapflib import py3compat
27from yapf.yapflib import style
28from yapf.yapflib import subtypes
29
30
31class ComprehensionState(object):
32  """Maintains the state of list comprehension formatting decisions.
33
34  A stack of ComprehensionState objects are kept to ensure that list
35  comprehensions are wrapped with well-defined rules.
36
37  Attributes:
38    expr_token: The first token in the comprehension.
39    for_token: The first 'for' token of the comprehension.
40    opening_bracket: The opening bracket of the list comprehension.
41    closing_bracket: The closing bracket of the list comprehension.
42    has_split_at_for: Whether there is a newline immediately before the
43      for_token.
44    has_interior_split: Whether there is a newline within the comprehension.
45      That is, a split somewhere after expr_token or before closing_bracket.
46  """
47
48  def __init__(self, expr_token):
49    self.expr_token = expr_token
50    self.for_token = None
51    self.has_split_at_for = False
52    self.has_interior_split = False
53
54  def HasTrivialExpr(self):
55    """Returns whether the comp_expr is "trivial" i.e. is a single token."""
56    return self.expr_token.next_token.value == 'for'
57
58  @property
59  def opening_bracket(self):
60    return self.expr_token.previous_token
61
62  @property
63  def closing_bracket(self):
64    return self.opening_bracket.matching_bracket
65
66  def Clone(self):
67    clone = ComprehensionState(self.expr_token)
68    clone.for_token = self.for_token
69    clone.has_split_at_for = self.has_split_at_for
70    clone.has_interior_split = self.has_interior_split
71    return clone
72
73  def __repr__(self):
74    return ('[opening_bracket::%s, for_token::%s, has_split_at_for::%s,'
75            ' has_interior_split::%s, has_trivial_expr::%s]' %
76            (self.opening_bracket, self.for_token, self.has_split_at_for,
77             self.has_interior_split, self.HasTrivialExpr()))
78
79  def __eq__(self, other):
80    return hash(self) == hash(other)
81
82  def __ne__(self, other):
83    return not self == other
84
85  def __hash__(self, *args, **kwargs):
86    return hash((self.expr_token, self.for_token, self.has_split_at_for,
87                 self.has_interior_split))
88
89
90class ParameterListState(object):
91  """Maintains the state of function parameter list formatting decisions.
92
93  Attributes:
94    opening_bracket: The opening bracket of the parameter list.
95    closing_bracket: The closing bracket of the parameter list.
96    has_typed_return: True if the function definition has a typed return.
97    ends_in_comma: True if the parameter list ends in a comma.
98    last_token: Returns the last token of the function declaration.
99    has_default_values: True if the parameters have default values.
100    has_split_before_first_param: Whether there is a newline before the first
101      parameter.
102    opening_column: The position of the opening parameter before a newline.
103    parameters: A list of parameter objects (Parameter).
104    split_before_closing_bracket: Split before the closing bracket. Sometimes
105      needed if the indentation would collide.
106  """
107
108  def __init__(self, opening_bracket, newline, opening_column):
109    self.opening_bracket = opening_bracket
110    self.has_split_before_first_param = newline
111    self.opening_column = opening_column
112    self.parameters = opening_bracket.parameters
113    self.split_before_closing_bracket = False
114
115  @property
116  def closing_bracket(self):
117    return self.opening_bracket.matching_bracket
118
119  @property
120  def has_typed_return(self):
121    return self.closing_bracket.next_token.value == '->'
122
123  @property
124  @py3compat.lru_cache()
125  def has_default_values(self):
126    return any(param.has_default_value for param in self.parameters)
127
128  @property
129  @py3compat.lru_cache()
130  def ends_in_comma(self):
131    if not self.parameters:
132      return False
133    return self.parameters[-1].last_token.next_token.value == ','
134
135  @property
136  @py3compat.lru_cache()
137  def last_token(self):
138    token = self.opening_bracket.matching_bracket
139    while not token.is_comment and token.next_token:
140      token = token.next_token
141    return token
142
143  @py3compat.lru_cache()
144  def LastParamFitsOnLine(self, indent):
145    """Return true if the last parameter fits on a single line."""
146    if not self.has_typed_return:
147      return False
148    if not self.parameters:
149      return True
150    total_length = self.last_token.total_length
151    last_param = self.parameters[-1].first_token
152    total_length -= last_param.total_length - len(last_param.value)
153    return total_length + indent <= style.Get('COLUMN_LIMIT')
154
155  @py3compat.lru_cache()
156  def SplitBeforeClosingBracket(self, indent):
157    """Return true if there's a split before the closing bracket."""
158    if style.Get('DEDENT_CLOSING_BRACKETS'):
159      return True
160    if self.ends_in_comma:
161      return True
162    if not self.parameters:
163      return False
164    total_length = self.last_token.total_length
165    last_param = self.parameters[-1].first_token
166    total_length -= last_param.total_length - len(last_param.value)
167    return total_length + indent > style.Get('COLUMN_LIMIT')
168
169  def Clone(self):
170    clone = ParameterListState(self.opening_bracket,
171                               self.has_split_before_first_param,
172                               self.opening_column)
173    clone.split_before_closing_bracket = self.split_before_closing_bracket
174    clone.parameters = [param.Clone() for param in self.parameters]
175    return clone
176
177  def __repr__(self):
178    return ('[opening_bracket::%s, has_split_before_first_param::%s, '
179            'opening_column::%d]' %
180            (self.opening_bracket, self.has_split_before_first_param,
181             self.opening_column))
182
183  def __eq__(self, other):
184    return hash(self) == hash(other)
185
186  def __ne__(self, other):
187    return not self == other
188
189  def __hash__(self, *args, **kwargs):
190    return hash(
191        (self.opening_bracket, self.has_split_before_first_param,
192         self.opening_column, (hash(param) for param in self.parameters)))
193
194
195class Parameter(object):
196  """A parameter in a parameter list.
197
198  Attributes:
199    first_token: (format_token.FormatToken) First token of parameter.
200    last_token: (format_token.FormatToken) Last token of parameter.
201    has_default_value: (boolean) True if the parameter has a default value
202  """
203
204  def __init__(self, first_token, last_token):
205    self.first_token = first_token
206    self.last_token = last_token
207
208  @property
209  @py3compat.lru_cache()
210  def has_default_value(self):
211    """Returns true if the parameter has a default value."""
212    tok = self.first_token
213    while tok != self.last_token:
214      if subtypes.DEFAULT_OR_NAMED_ASSIGN in tok.subtypes:
215        return True
216      tok = tok.matching_bracket if tok.OpensScope() else tok.next_token
217    return False
218
219  def Clone(self):
220    return Parameter(self.first_token, self.last_token)
221
222  def __repr__(self):
223    return '[first_token::%s, last_token:%s]' % (self.first_token,
224                                                 self.last_token)
225
226  def __eq__(self, other):
227    return hash(self) == hash(other)
228
229  def __ne__(self, other):
230    return not self == other
231
232  def __hash__(self, *args, **kwargs):
233    return hash((self.first_token, self.last_token))
234