OLD | NEW |
(Empty) | |
| 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 |
OLD | NEW |