blob: e598f5aa9a22b2ae3c5ddfe5a37c6adfcd4c46f1 [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import itertools
import keyword
import symbol
import token
from catapult_base.refactor.annotated_symbol import base_symbol
from catapult_base.refactor import snippet
__all__ = [
'AsName',
'DottedName',
'Import',
'ImportFrom',
'ImportName',
]
class DottedName(base_symbol.AnnotatedSymbol):
@classmethod
def Annotate(cls, symbol_type, children):
if symbol_type != symbol.dotted_name:
return None
return cls(symbol_type, children)
@property
def value(self):
return ''.join(token_snippet.value for token_snippet in self._children)
@value.setter
def value(self, value):
value_parts = value.split('.')
for value_part in value_parts:
if keyword.iskeyword(value_part):
raise ValueError('%s is a reserved keyword.' % value_part)
# If we have too many children, cut the list down to size.
self._children = self._children[:len(value_parts)*2-1]
# Update child nodes.
for child, value_part in itertools.izip_longest(
self._children[::2], value_parts):
if child:
# Modify existing children. This helps preserve comments and spaces.
child.value = value_part
else:
# Add children as needed.
self._children.append(snippet.TokenSnippet.Create(token.DOT, '.'))
self._children.append(
snippet.TokenSnippet.Create(token.NAME, value_part))
class AsName(base_symbol.AnnotatedSymbol):
@classmethod
def Annotate(cls, symbol_type, children):
if (symbol_type != symbol.dotted_as_name and
symbol_type != symbol.import_as_name):
return None
return cls(symbol_type, children)
@property
def name(self):
return self.children[0].value
@name.setter
def name(self, value):
self.children[0].value = value
@property
def alias(self):
if len(self.children) < 3:
return None
return self.children[2].value
@alias.setter
def alias(self, value):
if keyword.iskeyword(value):
raise ValueError('%s is a reserved keyword.' % value)
if value:
if len(self.children) < 3:
# If we currently have no alias, add one.
self.children.append(
snippet.TokenSnippet.Create(token.NAME, 'as', (0, 1)))
self.children.append(
snippet.TokenSnippet.Create(token.NAME, value, (0, 1)))
else:
# We already have an alias. Just update the value.
self.children[2].value = value
else:
# Removing the alias. Strip the "as foo".
self.children = [self.children[0]]
class Import(base_symbol.AnnotatedSymbol):
"""An import statement.
Example:
import a.b.c as d
from a.b import c as d
In these examples,
path == 'a.b.c'
alias == 'd'
root == 'a.b' (only for "from" imports)
module == 'c' (only for "from" imports)
name (read-only) == the name used by references to the module, which is the
alias if there is one, the full module path in "full" imports, and the
module name in "from" imports.
"""
@property
def has_from(self):
"""Returns True iff the import statment is of the form "from x import y"."""
raise NotImplementedError()
@property
def values(self):
raise NotImplementedError()
@property
def paths(self):
raise NotImplementedError()
@property
def aliases(self):
raise NotImplementedError()
@property
def path(self):
"""The full dotted path of the module."""
raise NotImplementedError()
@path.setter
def path(self, value):
raise NotImplementedError()
@property
def alias(self):
"""The alias, if the module is renamed with "as". None otherwise."""
raise NotImplementedError()
@alias.setter
def alias(self, value):
raise NotImplementedError()
@property
def name(self):
"""The name used to reference this import's module."""
raise NotImplementedError()
class ImportName(Import):
@classmethod
def Annotate(cls, symbol_type, children):
if symbol_type != symbol.import_stmt:
return None
if children[0].type != symbol.import_name:
return None
assert len(children) == 1
return cls(symbol_type, children[0].children)
@property
def has_from(self):
return False
@property
def values(self):
dotted_as_names = self.children[1]
return tuple((dotted_as_name.name, dotted_as_name.alias)
for dotted_as_name in dotted_as_names.children[::2])
@property
def paths(self):
return tuple(path for path, _ in self.values)
@property
def aliases(self):
return tuple(alias for _, alias in self.values)
@property
def _dotted_as_name(self):
dotted_as_names = self.children[1]
if len(dotted_as_names.children) != 1:
raise NotImplementedError(
'This method only works if the statement has one import.')
return dotted_as_names.children[0]
@property
def path(self):
return self._dotted_as_name.name
@path.setter
def path(self, value): # pylint: disable=arguments-differ
self._dotted_as_name.name = value
@property
def alias(self):
return self._dotted_as_name.alias
@alias.setter
def alias(self, value): # pylint: disable=arguments-differ
self._dotted_as_name.alias = value
@property
def name(self):
if self.alias:
return self.alias
else:
return self.path
class ImportFrom(Import):
@classmethod
def Annotate(cls, symbol_type, children):
if symbol_type != symbol.import_stmt:
return None
if children[0].type != symbol.import_from:
return None
assert len(children) == 1
return cls(symbol_type, children[0].children)
@property
def has_from(self):
return True
@property
def values(self):
try:
import_as_names = self.FindChild(symbol.import_as_names)
except ValueError:
return (('*', None),)
return tuple((import_as_name.name, import_as_name.alias)
for import_as_name in import_as_names.children[::2])
@property
def paths(self):
module = self.module
return tuple('.'.join((module, name)) for name, _ in self.values)
@property
def aliases(self):
return tuple(alias for _, alias in self.values)
@property
def root(self):
return self.FindChild(symbol.dotted_name).value
@root.setter
def root(self, value):
self.FindChild(symbol.dotted_name).value = value
@property
def _import_as_name(self):
try:
import_as_names = self.FindChild(symbol.import_as_names)
except ValueError:
return None
if len(import_as_names.children) != 1:
raise NotImplementedError(
'This method only works if the statement has one import.')
return import_as_names.children[0]
@property
def module(self):
import_as_name = self._import_as_name
if import_as_name:
return import_as_name.name
else:
return '*'
@module.setter
def module(self, value):
if keyword.iskeyword(value):
raise ValueError('%s is a reserved keyword.' % value)
import_as_name = self._import_as_name
if value == '*':
# TODO: Implement this.
raise NotImplementedError()
else:
if import_as_name:
import_as_name.name = value
else:
# TODO: Implement this.
raise NotImplementedError()
@property
def path(self):
return '.'.join((self.root, self.module))
@path.setter
def path(self, value): # pylint: disable=arguments-differ
self.root, _, self.module = value.rpartition('.')
@property
def alias(self):
import_as_name = self._import_as_name
if import_as_name:
return import_as_name.alias
else:
return None
@alias.setter
def alias(self, value): # pylint: disable=arguments-differ
import_as_name = self._import_as_name
if not import_as_name:
raise NotImplementedError('Cannot change alias for "import *".')
import_as_name.alias = value
@property
def name(self):
if self.alias:
return self.alias
else:
return self.module