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