1 # Copyright 2015 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import itertools 6 import keyword 7 import symbol 8 import token 9 10 from catapult_base.refactor.annotated_symbol import base_symbol 11 from catapult_base.refactor import snippet 12 13 14 __all__ = [ 15 'AsName', 16 'DottedName', 17 'Import', 18 'ImportFrom', 19 'ImportName', 20 ] 21 22 23 class DottedName(base_symbol.AnnotatedSymbol): 24 @classmethod 25 def Annotate(cls, symbol_type, children): 26 if symbol_type != symbol.dotted_name: 27 return None 28 return cls(symbol_type, children) 29 30 @property 31 def value(self): 32 return ''.join(token_snippet.value for token_snippet in self._children) 33 34 @value.setter 35 def value(self, value): 36 value_parts = value.split('.') 37 for value_part in value_parts: 38 if keyword.iskeyword(value_part): 39 raise ValueError('%s is a reserved keyword.' % value_part) 40 41 # If we have too many children, cut the list down to size. 42 self._children = self._children[:len(value_parts)*2-1] 43 44 # Update child nodes. 45 for child, value_part in itertools.izip_longest( 46 self._children[::2], value_parts): 47 if child: 48 # Modify existing children. This helps preserve comments and spaces. 49 child.value = value_part 50 else: 51 # Add children as needed. 52 self._children.append(snippet.TokenSnippet.Create(token.DOT, '.')) 53 self._children.append( 54 snippet.TokenSnippet.Create(token.NAME, value_part)) 55 56 57 class AsName(base_symbol.AnnotatedSymbol): 58 @classmethod 59 def Annotate(cls, symbol_type, children): 60 if (symbol_type != symbol.dotted_as_name and 61 symbol_type != symbol.import_as_name): 62 return None 63 return cls(symbol_type, children) 64 65 @property 66 def name(self): 67 return self.children[0].value 68 69 @name.setter 70 def name(self, value): 71 self.children[0].value = value 72 73 @property 74 def alias(self): 75 if len(self.children) < 3: 76 return None 77 return self.children[2].value 78 79 @alias.setter 80 def alias(self, value): 81 if keyword.iskeyword(value): 82 raise ValueError('%s is a reserved keyword.' % value) 83 84 if value: 85 if len(self.children) < 3: 86 # If we currently have no alias, add one. 87 self.children.append( 88 snippet.TokenSnippet.Create(token.NAME, 'as', (0, 1))) 89 self.children.append( 90 snippet.TokenSnippet.Create(token.NAME, value, (0, 1))) 91 else: 92 # We already have an alias. Just update the value. 93 self.children[2].value = value 94 else: 95 # Removing the alias. Strip the "as foo". 96 self.children = [self.children[0]] 97 98 99 class Import(base_symbol.AnnotatedSymbol): 100 """An import statement. 101 102 Example: 103 import a.b.c as d 104 from a.b import c as d 105 106 In these examples, 107 path == 'a.b.c' 108 alias == 'd' 109 root == 'a.b' (only for "from" imports) 110 module == 'c' (only for "from" imports) 111 name (read-only) == the name used by references to the module, which is the 112 alias if there is one, the full module path in "full" imports, and the 113 module name in "from" imports. 114 """ 115 @property 116 def has_from(self): 117 """Returns True iff the import statment is of the form "from x import y".""" 118 raise NotImplementedError() 119 120 @property 121 def values(self): 122 raise NotImplementedError() 123 124 @property 125 def paths(self): 126 raise NotImplementedError() 127 128 @property 129 def aliases(self): 130 raise NotImplementedError() 131 132 @property 133 def path(self): 134 """The full dotted path of the module.""" 135 raise NotImplementedError() 136 137 @path.setter 138 def path(self, value): 139 raise NotImplementedError() 140 141 @property 142 def alias(self): 143 """The alias, if the module is renamed with "as". None otherwise.""" 144 raise NotImplementedError() 145 146 @alias.setter 147 def alias(self, value): 148 raise NotImplementedError() 149 150 @property 151 def name(self): 152 """The name used to reference this import's module.""" 153 raise NotImplementedError() 154 155 156 class ImportName(Import): 157 @classmethod 158 def Annotate(cls, symbol_type, children): 159 if symbol_type != symbol.import_stmt: 160 return None 161 if children[0].type != symbol.import_name: 162 return None 163 assert len(children) == 1 164 return cls(symbol_type, children[0].children) 165 166 @property 167 def has_from(self): 168 return False 169 170 @property 171 def values(self): 172 dotted_as_names = self.children[1] 173 return tuple((dotted_as_name.name, dotted_as_name.alias) 174 for dotted_as_name in dotted_as_names.children[::2]) 175 176 @property 177 def paths(self): 178 return tuple(path for path, _ in self.values) 179 180 @property 181 def aliases(self): 182 return tuple(alias for _, alias in self.values) 183 184 @property 185 def _dotted_as_name(self): 186 dotted_as_names = self.children[1] 187 if len(dotted_as_names.children) != 1: 188 raise NotImplementedError( 189 'This method only works if the statement has one import.') 190 return dotted_as_names.children[0] 191 192 @property 193 def path(self): 194 return self._dotted_as_name.name 195 196 @path.setter 197 def path(self, value): # pylint: disable=arguments-differ 198 self._dotted_as_name.name = value 199 200 @property 201 def alias(self): 202 return self._dotted_as_name.alias 203 204 @alias.setter 205 def alias(self, value): # pylint: disable=arguments-differ 206 self._dotted_as_name.alias = value 207 208 @property 209 def name(self): 210 if self.alias: 211 return self.alias 212 else: 213 return self.path 214 215 216 class ImportFrom(Import): 217 @classmethod 218 def Annotate(cls, symbol_type, children): 219 if symbol_type != symbol.import_stmt: 220 return None 221 if children[0].type != symbol.import_from: 222 return None 223 assert len(children) == 1 224 return cls(symbol_type, children[0].children) 225 226 @property 227 def has_from(self): 228 return True 229 230 @property 231 def values(self): 232 try: 233 import_as_names = self.FindChild(symbol.import_as_names) 234 except ValueError: 235 return (('*', None),) 236 237 return tuple((import_as_name.name, import_as_name.alias) 238 for import_as_name in import_as_names.children[::2]) 239 240 @property 241 def paths(self): 242 module = self.module 243 return tuple('.'.join((module, name)) for name, _ in self.values) 244 245 @property 246 def aliases(self): 247 return tuple(alias for _, alias in self.values) 248 249 @property 250 def root(self): 251 return self.FindChild(symbol.dotted_name).value 252 253 @root.setter 254 def root(self, value): 255 self.FindChild(symbol.dotted_name).value = value 256 257 @property 258 def _import_as_name(self): 259 try: 260 import_as_names = self.FindChild(symbol.import_as_names) 261 except ValueError: 262 return None 263 264 if len(import_as_names.children) != 1: 265 raise NotImplementedError( 266 'This method only works if the statement has one import.') 267 268 return import_as_names.children[0] 269 270 @property 271 def module(self): 272 import_as_name = self._import_as_name 273 if import_as_name: 274 return import_as_name.name 275 else: 276 return '*' 277 278 @module.setter 279 def module(self, value): 280 if keyword.iskeyword(value): 281 raise ValueError('%s is a reserved keyword.' % value) 282 283 import_as_name = self._import_as_name 284 if value == '*': 285 # TODO: Implement this. 286 raise NotImplementedError() 287 else: 288 if import_as_name: 289 import_as_name.name = value 290 else: 291 # TODO: Implement this. 292 raise NotImplementedError() 293 294 @property 295 def path(self): 296 return '.'.join((self.root, self.module)) 297 298 @path.setter 299 def path(self, value): # pylint: disable=arguments-differ 300 self.root, _, self.module = value.rpartition('.') 301 302 @property 303 def alias(self): 304 import_as_name = self._import_as_name 305 if import_as_name: 306 return import_as_name.alias 307 else: 308 return None 309 310 @alias.setter 311 def alias(self, value): # pylint: disable=arguments-differ 312 import_as_name = self._import_as_name 313 if not import_as_name: 314 raise NotImplementedError('Cannot change alias for "import *".') 315 import_as_name.alias = value 316 317 @property 318 def name(self): 319 if self.alias: 320 return self.alias 321 else: 322 return self.module 323