diff --git a/python/build/pycharm_build.gant b/python/build/pycharm_build.gant index 7c17d12ad023..677d932d0f29 100644 --- a/python/build/pycharm_build.gant +++ b/python/build/pycharm_build.gant @@ -349,7 +349,7 @@ private layoutWin(Map args, String target) { } dir("skeletons") { - fileset(dir: "$home/python/skeletons") { + fileset(dir: "$pythonCommunityHome/skeletons") { include(name: "skeletons-win*.zip") } } @@ -390,7 +390,7 @@ private layoutMac(Map _args, String target) { } dir("skeletons") { - fileset(dir: "$home/python/skeletons") { + fileset(dir: "$pythonCommunityHome/skeletons") { include(name: "skeletons-mac*.zip") } } diff --git a/python/helpers/generator3.py b/python/helpers/generator3.py index 2c76d0f5902f..1b32313354af 100644 --- a/python/helpers/generator3.py +++ b/python/helpers/generator3.py @@ -24,7 +24,7 @@ but seemingly no one uses them in C extensions yet anyway. # * re.search-bound, ~30% time, in likes of builtins and _gtk with complex docstrings. # None of this can seemingly be easily helped. Maybe there's a simpler and faster parser library? -VERSION = "1.127" # Must be a number-dot-number string, updated with each change that affects generated skeletons +VERSION = "1.128" # Must be a number-dot-number string, updated with each change that affects generated skeletons # Note: DON'T FORGET TO UPDATE! import sys @@ -736,8 +736,29 @@ class ModuleRedeclarator(object): self._defined = {} # stores True for every name defined so far, to break circular refs in values self.doing_builtins = doing_builtins self.ret_type_cache = {} - self.used_imports = emptylistdict() # qual_mod_name -> [imported_names,..]: actullay used imported names + self.used_imports = emptylistdict() # qual_mod_name -> [imported_names,..]: actually used imported names + def initializeQApp(self): + try: # QtGui should be imported _before_ QtCore package. + # This is done for the QWidget references from QtCore (such as QSignalMapper). Known bug in PyQt 4.7+ + # Causes "TypeError: C++ type 'QWidget*' is not supported as a native Qt signal type" + import PyQt4.QtGui + except ImportError: + pass + + # manually instantiate and keep reference to singleton QCoreApplication (we don't want it to be deleted during the introspection) + # use QCoreApplication instead of QApplication to avoid blinking app in Dock on Mac OS + try: + from PyQt4.QtCore import QCoreApplication + self.app = QCoreApplication([]) + return + except ImportError: + pass + try: + from PyQt5.QtCore import QCoreApplication + self.app = QCoreApplication([]) + except ImportError: + pass def indent(self, level): "Return indentation whitespace for given level." @@ -809,6 +830,7 @@ class ModuleRedeclarator(object): REPLACE_MODULE_VALUES = { ("numpy.core.multiarray", "typeinfo"): "{}", ("psycopg2._psycopg", "string_types"): "{}", # badly mangled __eq__ breaks fmtValue + ("PyQt5.QtWidgets", "qApp") : "QApplication()", # instead of None } if version[0] <= 2: REPLACE_MODULE_VALUES[(BUILTIN_MOD_NAME, "None")] = "object()" @@ -988,7 +1010,7 @@ class ModuleRedeclarator(object): # known properties of modules # {{"module": {"class", "property" : ("letters", ("getter", "type"))}}, # where letters is any set of r,w,d (read, write, del) and "getter" is a source of typed getter. - # if vlue is None, the property should be omitted. + # if value is None, the property should be omitted. # read-only properties that return an object are not listed. G_OBJECT = ("lambda self: object()", None) G_TYPE = ("lambda self: type(object)", "type") @@ -1979,6 +2001,10 @@ class ModuleRedeclarator(object): @param p_name name of module """ action("redoing header of module %r %r", p_name, str(self.module)) + + if "pyqt" in p_name.lower(): # qt specific patch + self.initializeQApp() + self.redoSimpleHeader(p_name) # find whatever other self.imported_modules the module knows; effectively these are imports diff --git a/python/helpers/python-skeletons/README.md b/python/helpers/python-skeletons/README.md new file mode 100644 index 000000000000..e557a519e22e --- /dev/null +++ b/python/helpers/python-skeletons/README.md @@ -0,0 +1,139 @@ +Python Skeletons +================ + +_This proposal is a draft._ + +Python skeletons are Python files that contain API definitions of existing +libraries extended for static analysis tools. + +Rationale +--------- + +Python is a dynamic language less suitable for static code analysis than static +languages like C or Java. Although Python static analysis tools can extract +some information from Python source code without executing it, this information +is often very shallow and incomplete. + +Dynamic features of Python are very useful for user code. But using these +features in APIs of third-party libraries and the standard library is not +always a good idea. Tools (and users, in fact) need clear definitions of APIs. +Often library API definitions are quite static and easy to grasp (defined +using `class`, `def`), but types of function parameters and return values +usually are not specified. Sometimes API definitions involve metaprogramming. + +As there is not enough information in API definition code of libraries, +developers of static analysis tools collect extended API data themselves and +store it in their own formats. For example, PyLint uses imperative AST +transformations of API modules in order to extend them with hard-coded data. +PyCharm extends APIs via its proprietary database of declarative type +annotations. The absence of a common extended API information format makes it +hard for developers and users of tools to collect and share data. + + +Proposal +-------- + +The proposal is to create a common database of extended API definitions as a +collection of Python files called skeletons. Static analysis tools already +understand Python code, so it should be easy to start extracting API +definitions from these Python skeleton files. Regular function and class +definitions can be extended with additional docstrings and decorators, e.g. for +providing types of function parameters and return values. Static analysis tools +may use a subset of information contained in skeleton files needed for their +operation. Using Python files instead of a custom API definition format will +also make it easier for users to populate the skeletons database. + +Declarative Python API definitions for static analysis tools cannot cover all +dynamic tricks used in real APIs of libraries: some of them still require +library-specific code analysis. Nevertheless the skeletons database is enough +for many libraries. + +The proposed [python-skeletons](https://github.com/JetBrains/python-skeletons) +repository is hosted on GitHub. + + +Conventions +----------- + +Skeletons should respect PEP-8 and PEP-257 style guides. + +The most simple way of specifying types in skeletons is Sphinx docstrings. +Function annotations could be used for specifying types, but they are +available only for Python 3. + +There is no standard notation for specifying types in Python code. We propose +the following notation: + + Foo # Class Foo visible in the current scope + x.y.Bar # Class Bar from x.y module + Foo | Bar # Foo or Bar + (Foo, Bar) # Tuple of Foo and Bar + list[Foo] # List of Foo elements + dict[Foo, Bar] # Dict from Foo to Bar + T # Generic type (T-Z are reserved for generics) + T <= Foo # Generic type with upper bound Foo + Foo[T] # Foo parameterized with T + (Foo, Bar) -> Baz # Function of Foo and Bar that returns Baz + +The formal syntax is defined in `pytypes` library (work in progress). + +There are several shortcuts available: + + unknown # Unknown type + None # type(None) + string # Py2: str | unicode, Py3: str + bytestring # Py2: str | unicode, Py3: bytes + bytes # Py2: str, Py3: bytes + unicode # Py2: unicode, Py3: str + +The syntax is a subject to change. It is almost compatible to Python (except +function types), but its semantics differs from Python (no `|`, no implicitly +visible names, no generic types). So you cannot use these expressions in +Python 3 function annotations. See also `python-righarrow`, `typeannotations`. + +The recommended way of checking the version of Python is: + + import sys + + if sys.version_info >= (2, 7) and sys.version_info < (3,): + def from_27_until_30(): + pass + + +PyCharm +------- + +PyCharm 3 can extract the following information from the skeletons: + +* Parameters of functions and methods +* Return types and parameter types of functions and methods +* Types of assignment targets +* Extra module members +* TODO + +PyCharm 3 comes with a snapshot of the Python skeletons repository. You +should not modify it, because it will be updated with the PyCharm +installation. If you want to change the skeletons, clone the skeletons GitHub +repository into your PyCharm config directory: + + cd + git clone https://github.com/JetBrains/python-skeletons.git + +where `` is: + +* Mac OS X: `~/Library/Preferences/PyCharmXX/config` +* Linux: `~/.PyCharmXX/config` +* Windows: `\.PyCharmXX\config` + +Please send your PyCharm-related bug reports and feature requests to +[PyCharm issue tracker](http://youtrack.jetbrains.com/issues/PY). + + +Feedback +-------- + +If you want to contribute, send your pull requests to the Python skeletons +repository on GitHub. Please make sure, that you follow the conventions above. + +Use [code-quality](http://mail.python.org/mailman/listinfo/code-quality) +mailing list to discuss Python skeletons. diff --git a/python/helpers/python-skeletons/__builtin__.py b/python/helpers/python-skeletons/__builtin__.py new file mode 100644 index 000000000000..682c491c7367 --- /dev/null +++ b/python/helpers/python-skeletons/__builtin__.py @@ -0,0 +1,377 @@ +"""Skeletons for built-in symbols.""" + +import sys as __sys + + +def abs(number): + """Return the absolute value of the argument. + + :type number: T + :rtype: T | unknown + """ + pass + + +def all(iterable): + """Return True if bool(x) is True for all values x in the iterable. + + :type iterable: collections.Iterable + :rtype: bool + """ + pass + + +def any(iterable): + """Return True if bool(x) is True for any x in the iterable. + + :type iterable: collections.Iterable + :rtype: bool + """ + pass + + +def bin(number): + """Return the binary representation of an integer or long integer. + + :type number: numbers.Number + :rtype: bytes + """ + pass + + +def callable(object): + """Return whether the object is callable (i.e., some kind of function). + Note that classes are callable, as are instances with a __call__() method. + + :rtype: bool + """ + pass + + +def chr(i): + """Return a string of one character with ordinal i; 0 <= i < 256. + + :type i: int + :rtype: string + """ + pass + + +def cmp(x, y): + """Return negative if xy. + + :rtype: int + """ + pass + + +def dir(object=None): + """If called without an argument, return the names in the current scope. + Else, return an alphabetized list of names comprising (some of) the attributes + of the given object, and of attributes reachable from it. + + :rtype: list[string] + """ + pass + + +def divmod(x, y): + """Return the tuple ((x-x%y)/y, x%y). + + :type x: numbers.Number + :type y: numbers.Number + :rtype: (int | long | float | unknown, int | long | float | unknown) + """ + pass + + +def filter(function_or_none, sequence): + """Return those items of sequence for which function(item) is true. If + function is None, return the items that are true. If sequence is a tuple + or string, return the same type, else return a list. + + :type function_or_none: collections.Callable | None + :type sequence: T <= list | collections.Iterable | bytes | unicode + :rtype: T + """ + pass + + +def getattr(object, name, default=None): + """Get a named attribute from an object; getattr(x, 'y') is equivalent to + x.y. When a default argument is given, it is returned when the attribute + doesn't exist; without it, an exception is raised in that case. + + :type name: string + :rtype: object | unknown + """ + pass + + +def globals(): + """Return the dictionary containing the current scope's global variables. + + :rtype: dict[string, unknown] + """ + pass + + +def hasattr(object, name): + """Return whether the object has an attribute with the given name. + + :type name: string + :rtype: bool + """ + pass + + +def hash(object): + """Return a hash value for the object. + + :rtype: int + """ + pass + + +def hex(number): + """Return the hexadecimal representation of an integer or long integer. + + :type number: numbers.Integral + :rtype: string + """ + pass + + +def id(object): + """Return the identity of an object. + + :rtype: int + """ + pass + + +def isinstance(object, class_or_type_or_tuple): + """Return whether an object is an instance of a class or of a subclass + thereof. + + :rtype: bool + """ + pass + + +def issubclass(C, B): + """Return whether class C is a subclass (i.e., a derived class) of class B. + + :rtype: bool + """ + pass + + +def iter(source, sentinel=None): + """Get an iterator from an object. In the first form, the argument must + supply its own iterator, or be a sequence. In the second form, the callable + is called until it returns the sentinel. + + :type source: collections.Iterable[T] + :rtype: collections.Iterator[T] + """ + pass + + +def len(object): + """Return the number of items of a sequence or mapping. + + :type object: collections.Sized + :rtype: int + """ + pass + + +def locals(): + """Update and return a dictionary containing the current scope's local + variables. + + :rtype: dict[string, unknown] + """ + pass + + +def map(function, sequence, *sequence_1): + """Return a list of the results of applying the function to the items of + the argument sequence(s). + + :type function: ((T) -> V) | None + :type sequence: collections.Iterable[T] + :rtype: list[V] | bytes | unicode + """ + pass + + +def next(iterator, default=None): + """Return the next item from the iterator. + + :type iterator: collections.Iterator[T] + :rtype: T + """ + pass + + +def oct(number): + """Return the octal representation of an integer or long integer. + + :type number: numbers.Integral + :rtype: string + """ + pass + + +def open(name, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=None, opener=None): + """Open a file, returns a file object. + + :type name: string + :type mode: string + :type buffering: int + :type encoding: string | None + :type errors: string | None + :rtype: file + """ + pass + + +def ord(c): + """Return the integer ordinal of a one-character string. + + :type c: string + :rtype: int + """ + pass + + +def pow(x, y, z=None): + """With two arguments, equivalent to x**y. With three arguments, + equivalent to (x**y) % z, but may be more efficient (e.g. for longs). + + :type x: numbers.Number + :type y: numbers.Number + :type z: numbers.Number | None + :rtype: int | long | float | complex + """ + pass + + +if __sys.version_info < (3,): + def range(start, stop=None, step=None): + """Return a list containing an arithmetic progression of integers. + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + :rtype: list[int] + """ + pass + + +def reduce(function, sequence, initial=None): + """Apply a function of two arguments cumulatively to the items of a sequence, + from left to right, so as to reduce the sequence to a single value. + + :type function: collections.Callable + :type sequence: collections.Iterable + :type initial: T + :rtype: T | unknown + """ + pass + + +def repr(object): + """ + Return the canonical string representation of the object. + + :rtype: string + """ + pass + + +def round(number, ndigits=None): + """Round a number to a given precision in decimal digits (default 0 digits). + + :type number: numbers.Real + :type ndigits: numbers.Real | None + :rtype: float + """ + pass + + +class slice(object): + def __init__(self, start, stop=None, step=None): + """Create a slice object. This is used for extended slicing (e.g. a[0:10:2]). + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + """ + return + + +def vars(object=None): + """Without arguments, equivalent to locals(). With an argument, equivalent + to object.__dict__. + + :rtype: dict[string, unknown] + """ + pass + + +class object: + """ The most base type.""" + + @staticmethod + def __new__(cls, *more): + """Create a new object. + + :type cls: T + :rtype: T + """ + pass + + +class enumerate(object): + """enumerate object.""" + + def __init__(self, iterable, start=0): + """Create an enumerate object. + + :type iterable: collections.Iterable[T] + :type start: int | long + :rtype: enumerate[int, T] + """ + pass + + def next(self): + """Return the next value, or raise StopIteration. + + :rtype: (int, T) + """ + pass + + def __iter__(self): + """x.__iter__() <==> iter(x). + + :rtype: enumerate[int, T] + """ + pass + + +if __sys.version_info < (3,): + class xrange(object): + """xrange object.""" + + def __init__(self, start, stop=None, step=None): + """Create an xrange object. + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + :rtype: xrange[int] + """ + pass diff --git a/python/helpers/python-skeletons/nose/__init__.py b/python/helpers/python-skeletons/nose/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/helpers/python-skeletons/nose/tools/__init__.py b/python/helpers/python-skeletons/nose/tools/__init__.py new file mode 100644 index 000000000000..143355fe3dbb --- /dev/null +++ b/python/helpers/python-skeletons/nose/tools/__init__.py @@ -0,0 +1,177 @@ +"""Skeleton for 'nose.tools' module.""" + + +import sys + + +def assert_equal(first, second, msg=None): + """Fail if the two objects are unequal as determined by the '==' operator. + """ + pass + + +def assert_not_equal(first, second, msg=None): + """Fail if the two objects are equal as determined by the '==' operator. + """ + pass + + +def assert_true(expr, msg=None): + """Check that the expression is true.""" + pass + + +def assert_false(expr, msg=None): + """Check that the expression is false.""" + pass + + +if sys.version_info >= (2, 7): + def assert_is(expr1, expr2, msg=None): + """Just like assert_true(a is b), but with a nicer default message.""" + pass + + def assert_is_not(expr1, expr2, msg=None): + """Just like assert_true(a is not b), but with a nicer default message. + """ + pass + + def assert_is_none(obj, msg=None): + """Same as assert_true(obj is None), with a nicer default message. + """ + pass + + def assert_is_not_none(obj, msg=None): + """Included for symmetry with assert_is_none.""" + pass + + def assert_in(member, container, msg=None): + """Just like assert_true(a in b), but with a nicer default message.""" + pass + + def assert_not_in(member, container, msg=None): + """Just like assert_true(a not in b), but with a nicer default message. + """ + pass + + def assert_is_instance(obj, cls, msg=None): + """Same as assert_true(isinstance(obj, cls)), with a nicer default + message. + """ + pass + + def assert_not_is_instance(obj, cls, msg=None): + """Included for symmetry with assert_is_instance.""" + pass + + +def assert_raises(excClass, callableObj=None, *args, **kwargs): + """Fail unless an exception of class excClass is thrown by callableObj when + invoked with arguments args and keyword arguments kwargs. + + If called with callableObj omitted or None, will return a + context object used like this:: + + with assert_raises(SomeException): + do_something() + + :rtype: unittest.case._AssertRaisesContext | None + """ + pass + + +if sys.version_info >= (2, 7): + def assert_raises_regexp(expected_exception, expected_regexp, + callable_obj=None, *args, **kwargs): + """Asserts that the message in a raised exception matches a regexp. + + :rtype: unittest.case._AssertRaisesContext | None + """ + pass + + +def assert_almost_equal(first, second, places=None, msg=None, delta=None): + """Fail if the two objects are unequal as determined by their difference + rounded to the given number of decimal places (default 7) and comparing to + zero, or by comparing that the between the two objects is more than the + given delta. + """ + pass + + +def assert_not_almost_equal(first, second, places=None, msg=None, delta=None): + """Fail if the two objects are equal as determined by their difference + rounded to the given number of decimal places (default 7) and comparing to + zero, or by comparing that the between the two objects is less than the + given delta. + """ + pass + + +if sys.version_info >= (2, 7): + def assert_greater(a, b, msg=None): + """Just like assert_true(a > b), but with a nicer default message.""" + pass + + def assert_greater_equal(a, b, msg=None): + """Just like assert_true(a >= b), but with a nicer default message.""" + pass + + def assert_less(a, b, msg=None): + """Just like assert_true(a < b), but with a nicer default message.""" + pass + + def assert_less_equal(a, b, msg=None): + """Just like self.assertTrue(a <= b), but with a nicer default + message. + """ + pass + + def assert_regexp_matches(text, expected_regexp, msg=None): + """Fail the test unless the text matches the regular expression.""" + pass + + def assert_not_regexp_matches(text, unexpected_regexp, msg=None): + """Fail the test if the text matches the regular expression.""" + pass + + def assert_items_equal(expected_seq, actual_seq, msg=None): + """An unordered sequence specific comparison. It asserts that + actual_seq and expected_seq have the same element counts. + """ + pass + + def assert_dict_contains_subset(expected, actual, msg=None): + """Checks whether actual is a superset of expected.""" + pass + + def assert_multi_line_equal(first, second, msg=None): + """Assert that two multi-line strings are equal.""" + pass + + def assert_sequence_equal(seq1, seq2, msg=None, seq_type=None): + """An equality assertion for ordered sequences (like lists and tuples). + """ + pass + + def assert_list_equal(list1, list2, msg=None): + """A list-specific equality assertion.""" + pass + + def assert_tuple_equal(tuple1, tuple2, msg=None): + """A tuple-specific equality assertion.""" + pass + + def assert_set_equal(set1, set2, msg=None): + """A set-specific equality assertion.""" + pass + + def assert_dict_equal(d1, d2, msg=None): + """A dict-specific equality assertion.""" + pass + + +assert_equals = assert_equal +assert_not_equals = assert_not_equal +assert_almost_equals = assert_almost_equal +assert_not_almost_equals = assert_not_almost_equal diff --git a/python/helpers/python-skeletons/re.py b/python/helpers/python-skeletons/re.py new file mode 100644 index 000000000000..151ddc9b7257 --- /dev/null +++ b/python/helpers/python-skeletons/re.py @@ -0,0 +1,277 @@ +"""Skeleton for 're' stdlib module.""" + + +def compile(pattern, flags=0): + """Compile a regular expression pattern, returning a pattern object. + + :type pattern: bytes | unicode + :type flags: int + :rtype: __Regex + """ + pass + + +def search(pattern, string, flags=0): + """Scan through string looking for a match, and return a corresponding + match instance. Return None if no position in the string matches. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: __Match[T] | None + """ + pass + + +def match(pattern, string, flags=0): + """Matches zero or more characters at the beginning of the string. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: __Match[T] | None + """ + pass + + +def split(pattern, string, maxsplit=0, flags=0): + """Split string by the occurrences of pattern. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type maxsplit: int + :type flags: int + :rtype: list[T] + """ + pass + + +def findall(pattern, string, flags=0): + """Return a list of all non-overlapping matches of pattern in string. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: list[T] + """ + pass + + +def finditer(pattern, string, flags=0): + """Return an iterator over all non-overlapping matches for the pattern in + string. For each match, the iterator returns a match object. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: collections.Iterable[__Match[T]] + """ + pass + + +def sub(pattern, repl, string, count=0, flags=0): + """Return the string obtained by replacing the leftmost non-overlapping + occurrences of pattern in string by the replacement repl. + + :type pattern: bytes | unicode | __Regex + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :type flags: int + :rtype: T + """ + pass + + +def subn(pattern, repl, string, count=0, flags=0): + """Return the tuple (new_string, number_of_subs_made) found by replacing + the leftmost non-overlapping occurrences of pattern with the + replacement repl. + + :type pattern: bytes | unicode | __Regex + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :type flags: int + :rtype: (T, int) + """ + pass + + +def escape(string): + """Escape all the characters in pattern except ASCII letters and numbers. + + :type string: T <= bytes | unicode + :type: T + """ + pass + + +class __Regex(object): + """Mock class for a regular expression pattern object.""" + + def __init__(self, flags, groups, groupindex, pattern): + """Create a new pattern object. + + :type flags: int + :type groups: int + :type groupindex: dict[bytes | unicode, int] + :type pattern: bytes | unicode + """ + self.flags = flags + self.groups = groups + self.groupindex = groupindex + self.pattern = pattern + + def search(self, string, pos=0, endpos=-1): + """Scan through string looking for a match, and return a corresponding + match instance. Return None if no position in the string matches. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: __Match[T] | None + """ + pass + + def match(self, string, pos=0, endpos=-1): + """Matches zero | more characters at the beginning of the string. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: __Match[T] | None + """ + pass + + def split(self, string, maxsplit=0): + """Split string by the occurrences of pattern. + + :type string: T <= bytes | unicode + :type maxsplit: int + :rtype: list[T] + """ + pass + + def findall(self, string, pos=0, endpos=-1): + """Return a list of all non-overlapping matches of pattern in string. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: list[T] + """ + pass + + def finditer(self, string, pos=0, endpos=-1): + """Return an iterator over all non-overlapping matches for the + pattern in string. For each match, the iterator returns a + match object. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: collections.Iterable[__Match[T]] + """ + pass + + def sub(self, repl, string, count=0): + """Return the string obtained by replacing the leftmost non-overlapping + occurrences of pattern in string by the replacement repl. + + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :rtype: T + """ + pass + + def subn(self, repl, string, count=0): + """Return the tuple (new_string, number_of_subs_made) found by replacing + the leftmost non-overlapping occurrences of pattern with the + replacement repl. + + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :rtype: (T, int) + """ + pass + + +class __Match(object): + """Mock class for a match object.""" + + def __init__(self, pos, endpos, lastindex, lastgroup, re, string): + """Create a new match object. + + :type pos: int + :type endpos: int + :type lastindex: int | None + :type lastgroup: int | bytes | unicode | None + :type re: __Regex + :type string: bytes | unicode + :rtype: __Match[T] + """ + self.pos = pos + self.endpos = endpos + self.lastindex = lastindex + self.lastgroup = lastgroup + self.re = re + self.string = string + + def expand(self, template): + """Return the string obtained by doing backslash substitution on the + template string template. + + :type template: T + :rtype: T + """ + pass + + def group(self, *args): + """Return one or more subgroups of the match. + + :rtype: T | tuple + """ + pass + + def groups(self, default=None): + """Return a tuple containing all the subgroups of the match, from 1 up + to however many groups are in the pattern. + + :rtype: tuple + """ + pass + + def groupdict(self, default=None): + """Return a dictionary containing all the named subgroups of the match, + keyed by the subgroup name. + + :rtype: dict[bytes | unicode, T] + """ + pass + + def start(self, group=0): + """Return the index of the start of the substring matched by group. + + :type group: int | bytes | unicode + :rtype: int + """ + pass + + def end(self, group=0): + """Return the index of the end of the substring matched by group. + + :type group: int | bytes | unicode + :rtype: int + """ + pass + + def span(self, group=0): + """Return a 2-tuple (start, end) for the substring matched by group. + + :type group: int | bytes | unicode + :rtype: (int, int) + """ + pass diff --git a/python/src/META-INF/python-plugin-core.xml b/python/src/META-INF/python-plugin-core.xml index 681304de2d11..4edcb7367bda 100644 --- a/python/src/META-INF/python-plugin-core.xml +++ b/python/src/META-INF/python-plugin-core.xml @@ -4,6 +4,7 @@ com.jetbrains.python.PyBundle + @@ -324,6 +325,7 @@ + @@ -570,6 +572,9 @@ + + + diff --git a/python/src/com/jetbrains/pyqt/PyQtTypeProvider.java b/python/src/com/jetbrains/pyqt/PyQtTypeProvider.java new file mode 100644 index 000000000000..c97f2347c428 --- /dev/null +++ b/python/src/com/jetbrains/pyqt/PyQtTypeProvider.java @@ -0,0 +1,54 @@ +package com.jetbrains.pyqt; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiPolyVariantReference; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyQualifiedName; +import com.jetbrains.python.psi.stubs.PyClassNameIndex; +import com.jetbrains.python.psi.types.*; + +/** + * User : ktisha + */ +public class PyQtTypeProvider extends PyTypeProviderBase { + private static final String ourQtBoundSignal = "QtCore.pyqtBoundSignal"; + private static final String ourQt4Signal = "pyqtSignal"; + + @Override + public PyType getReferenceExpressionType(PyReferenceExpression referenceExpression, TypeEvalContext context) { + final PsiPolyVariantReference reference = referenceExpression.getReference(); + final PsiElement element = reference.resolve(); + if (element instanceof PyTypedElement) { + final PyType type = context.getType((PyTypedElement)element); + if (type instanceof PyClassType) { + final String classQName = ((PyClassType)type).getClassQName(); + if (classQName != null && classQName.startsWith("PyQt")) { + final PyQualifiedName name = PyQualifiedName.fromDottedString(classQName); + final String qtVersion = name.getComponents().get(0); + if (ourQt4Signal.equals(name.getLastComponent())) { + final PyClass aClass = PyClassNameIndex.findClass(qtVersion + "." + ourQtBoundSignal, referenceExpression.getProject()); + if (aClass != null) + return new PyClassTypeImpl(aClass, false); + } + } + } + else if (type instanceof PyFunctionType) { + final Callable callable = ((PyFunctionType)type).getCallable(); + if (callable instanceof PyFunction) { + final String qualifiedName = callable.getQualifiedName(); + if (qualifiedName != null && qualifiedName.startsWith("PyQt")){ + final PyQualifiedName name = PyQualifiedName.fromDottedString(qualifiedName); + final String qtVersion = name.getComponents().get(0); + final String docstring = ((PyFunction)callable).getDocStringValue(); + if (docstring != null && docstring.contains("[signal]")) { + final PyClass aClass = PyClassNameIndex.findClass(qtVersion + "." + ourQtBoundSignal, referenceExpression.getProject()); + if (aClass != null) + return new PyClassTypeImpl(aClass, false); + } + } + } + } + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java b/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java index 3857991591d1..db8df782ac60 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java @@ -1,5 +1,6 @@ package com.jetbrains.python.codeInsight.imports; +import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; @@ -47,8 +48,9 @@ public class AddImportHelper { boolean skippedOverImports = false; boolean skippedOverDoc = false; PsiElement seeker = feeler; + final boolean isInjected = InjectedLanguageManager.getInstance(feeler.getProject()).isInjectedFragment(feeler.getContainingFile()); do { - if (feeler instanceof PyImportStatementBase) { + if (feeler instanceof PyImportStatementBase && !isInjected) { if (nameToImport != null && priority != null && shouldInsertBefore(file, (PyImportStatementBase)feeler, nameToImport, priority)) { break; } @@ -172,17 +174,21 @@ public class AddImportHelper { * @param asName optional name for 'as' clause */ public static void addImportFromStatement(PsiFile file, String from, String name, @Nullable String asName, ImportPriority priority) { - String asClause; - if (asName == null) { - asClause = ""; - } - else { - asClause = " as " + asName; - } + String asClause = asName == null ? "" : " as " + asName; + final PyFromImportStatement importNodeToInsert = PyElementGenerator.getInstance(file.getProject()).createFromText( LanguageLevel.forElement(file), PyFromImportStatement.class, "from " + from + " import " + name + asClause); try { - file.addBefore(importNodeToInsert, getInsertPosition(file, from, priority)); + if (InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) { + final PsiElement element = file.addBefore(importNodeToInsert, getInsertPosition(file, from, priority)); + PsiElement whitespace = element.getNextSibling(); + if (!(whitespace instanceof PsiWhiteSpace)) + whitespace = PsiParserFacade.SERVICE.getInstance(file.getProject()).createWhiteSpaceFromText(" >>> "); + file.addBefore(whitespace, element); + } + else { + file.addBefore(importNodeToInsert, getInsertPosition(file, from, priority)); + } } catch (IncorrectOperationException e) { LOG.error(e); diff --git a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java index 676b86f83ae6..6aa350224a38 100644 --- a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java @@ -27,6 +27,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -34,21 +35,28 @@ import java.util.List; * @author vlan */ public class PyUserSkeletonsUtil { + public static final String USER_SKELETONS_DIR = "python-skeletons"; @Nullable private static VirtualFile ourUserSkeletonsDirectory; @NotNull - public static String getUserSkeletonsPath() { - if (ApplicationManager.getApplication().isInternal()) { - return StringUtil.join(new String[] {PathManager.getHomePath(), "python", "helpers", "user-skeletons"}, File.separator); - } - // TODO: Add the possibility to put skeletons into PathManager.getSystemPath() + "/user-skeletons" - return PythonHelpersLocator.getHelperPath("user-skeletons"); + private static List getPossibleUserSkeletonsPaths() { + final List result = new ArrayList(); + result.add(PathManager.getConfigPath() + File.separator + USER_SKELETONS_DIR); + result.add(ApplicationManager.getApplication().isInternal() + ? StringUtil.join(new String[]{PathManager.getHomePath(), "python", "helpers", USER_SKELETONS_DIR}, File.separator) + : PythonHelpersLocator.getHelperPath(USER_SKELETONS_DIR)); + return result; } @Nullable public static VirtualFile getUserSkeletonsDirectory() { if (ourUserSkeletonsDirectory == null) { - ourUserSkeletonsDirectory = LocalFileSystem.getInstance().findFileByPath(getUserSkeletonsPath()); + for (String path : getPossibleUserSkeletonsPaths()) { + ourUserSkeletonsDirectory = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); + if (ourUserSkeletonsDirectory != null) { + break; + } + } } return ourUserSkeletonsDirectory; } @@ -75,7 +83,7 @@ public class PyUserSkeletonsUtil { if (sdk != null) { final Project project = foothold.getProject(); final PythonSdkPathCache cache = PythonSdkPathCache.getInstance(project, sdk); - final PyQualifiedName cacheQName = PyQualifiedName.fromDottedString("user-skeletons." + qName); + final PyQualifiedName cacheQName = PyQualifiedName.fromDottedString(USER_SKELETONS_DIR + "." + qName); final List results = cache.get(cacheQName); if (results != null) { final PsiElement element = results.isEmpty() ? null : results.get(0); @@ -101,7 +109,7 @@ public class PyUserSkeletonsUtil { } public static void addUserSkeletonsRoot(@NotNull SdkModificator sdkModificator) { - final VirtualFile root = LocalFileSystem.getInstance().refreshAndFindFileByPath(getUserSkeletonsPath()); + final VirtualFile root = getUserSkeletonsDirectory(); if (root != null) { sdkModificator.addRoot(root, OrderRootType.CLASSES); } diff --git a/python/src/com/jetbrains/python/documentation/doctest/PyDocReferenceExpression.java b/python/src/com/jetbrains/python/documentation/doctest/PyDocReferenceExpression.java index 60b25d3b579b..4464ec2f03f8 100644 --- a/python/src/com/jetbrains/python/documentation/doctest/PyDocReferenceExpression.java +++ b/python/src/com/jetbrains/python/documentation/doctest/PyDocReferenceExpression.java @@ -1,10 +1,12 @@ package com.jetbrains.python.documentation.doctest; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyImportStatement; +import com.jetbrains.python.psi.PyFromImportStatement; +import com.jetbrains.python.psi.PyImportElement; import com.jetbrains.python.psi.impl.PyReferenceExpressionImpl; import com.jetbrains.python.psi.impl.references.PyImportReference; import com.jetbrains.python.psi.impl.references.PyQualifiedReference; @@ -27,8 +29,10 @@ public class PyDocReferenceExpression extends PyReferenceExpressionImpl { if (qualifier != null) { return new PyQualifiedReference(this, context); } - else if (PsiTreeUtil.getParentOfType(this, PyImportStatement.class) != null) - return new PyImportReference(this, context); + final PsiElement importParent = PsiTreeUtil.getParentOfType(this, PyImportElement.class, PyFromImportStatement.class); + if (importParent != null) { + return PyImportReference.forElement(this, importParent, context); + } return new PyDocReference(this, context); } } diff --git a/python/src/com/jetbrains/python/documentation/doctest/PyDocstringTokenSetContributor.java b/python/src/com/jetbrains/python/documentation/doctest/PyDocstringTokenSetContributor.java index 86f284b70a30..326e246ae822 100644 --- a/python/src/com/jetbrains/python/documentation/doctest/PyDocstringTokenSetContributor.java +++ b/python/src/com/jetbrains/python/documentation/doctest/PyDocstringTokenSetContributor.java @@ -11,4 +11,9 @@ public class PyDocstringTokenSetContributor extends PythonTokenSetContributor { public TokenSet getExpressionTokens() { return TokenSet.orSet(super.getExpressionTokens(), TokenSet.create(PyDocstringTokenTypes.DOC_REFERENCE)); } + + @Override + public TokenSet getReferenceExpressionTokens() { + return TokenSet.orSet(super.getExpressionTokens(), TokenSet.create(PyDocstringTokenTypes.DOC_REFERENCE)); + } } diff --git a/python/src/com/jetbrains/python/inspections/PyPep8NamingInspection.java b/python/src/com/jetbrains/python/inspections/PyPep8NamingInspection.java new file mode 100644 index 000000000000..f1a26b0c954d --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyPep8NamingInspection.java @@ -0,0 +1,110 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyQualifiedName; +import org.jetbrains.annotations.NotNull; + +import java.util.regex.Pattern; + +/** + * + * User : ktisha + */ +public class PyPep8NamingInspection extends PyInspection { + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, + boolean isOnTheFly, + @NotNull LocalInspectionToolSession session) { + return new Visitor(holder, session); + } + + public static class Visitor extends PyInspectionVisitor { + Pattern LOWERCASE_REGEX = Pattern.compile("[_a-z][_a-z0-9]*"); + Pattern UPPERCASE_REGEX = Pattern.compile("[_A-Z][_A-Z0-9]*"); + Pattern MIXEDCASE_REGEX = Pattern.compile("_?[A-Z][a-zA-Z0-9]*"); + + public Visitor(@NotNull final ProblemsHolder holder, LocalInspectionToolSession session) { + super(holder, session); + } + + @Override + public void visitPyAssignmentStatement(PyAssignmentStatement node) { + final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class, true, PyClass.class); + if (function == null) return; + for (PyExpression expression : node.getTargets()) { + final String name = expression.getName(); + if (name == null) continue; + if (!LOWERCASE_REGEX.matcher(name).matches() && !name.startsWith("_")) { + registerProblem(expression, "Variable in function should be lowercase", new PyRenameElementQuickFix()); + } + } + } + + @Override + public void visitPyParameter(PyParameter node) { + final String name = node.getName(); + if (name == null) return; + if (!LOWERCASE_REGEX.matcher(name).matches()) { + registerProblem(node, "Argument name should be lowercase", new PyRenameElementQuickFix()); + } + } + + @Override + public void visitPyFunction(PyFunction node) { + final PyClass containingClass = node.getContainingClass(); + final String name = node.getName(); + if (name == null) return; + if (containingClass != null && name.startsWith("__") && name.endsWith("__")) { + return; + } + if (!LOWERCASE_REGEX.matcher(name).matches()) { + final ASTNode nameNode = node.getNameNode(); + if (nameNode != null) + registerProblem(nameNode.getPsi(), "Function name should be lowercase", new PyRenameElementQuickFix()); + } + } + + @Override + public void visitPyClass(PyClass node) { + final String name = node.getName(); + if (name == null) return; + if (!MIXEDCASE_REGEX.matcher(name).matches()) { + final ASTNode nameNode = node.getNameNode(); + if (nameNode != null) + registerProblem(nameNode.getPsi(), "Class names should use CamelCase convention", new PyRenameElementQuickFix()); + } + } + + @Override + public void visitPyImportElement(PyImportElement node) { + final String asName = node.getAsName(); + final PyQualifiedName importedQName = node.getImportedQName(); + if (importedQName == null) return; + final String name = importedQName.toString(); + + if (asName == null || name == null) return; + if (UPPERCASE_REGEX.matcher(name).matches()) { + if (!UPPERCASE_REGEX.matcher(asName).matches()) { + registerProblem(node.getAsNameElement(), "Constant variable imported as non constant", new PyRenameElementQuickFix()); + } + } + else if (LOWERCASE_REGEX.matcher(name).matches()) { + if (!LOWERCASE_REGEX.matcher(asName).matches()) { + registerProblem(node.getAsNameElement(), "Lowercase variable imported as non lowercase", new PyRenameElementQuickFix()); + } + } + else if (LOWERCASE_REGEX.matcher(asName).matches()) { + registerProblem(node.getAsNameElement(), "CamelCase variable imported as lowercase", new PyRenameElementQuickFix()); + } + else if (UPPERCASE_REGEX.matcher(asName).matches()) { + registerProblem(node.getAsNameElement(), "CamelCase variable imported as constant", new PyRenameElementQuickFix()); + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyRenameElementQuickFix.java b/python/src/com/jetbrains/python/inspections/PyRenameElementQuickFix.java new file mode 100644 index 000000000000..55941fbcd1c9 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyRenameElementQuickFix.java @@ -0,0 +1,86 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiNameIdentifierOwner; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.rename.PsiElementRenameHandler; +import com.intellij.refactoring.rename.RenameProcessor; +import com.intellij.refactoring.rename.RenamePsiElementProcessor; +import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.psi.PyNamedParameter; +import com.jetbrains.python.psi.PyTargetExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: ktisha + */ +public class PyRenameElementQuickFix implements LocalQuickFix { + @NotNull + @Override + public String getName() { + return "Rename element"; + } + + @NotNull + @Override + public String getFamilyName() { + return "Rename element"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PsiNameIdentifierOwner nameOwner = element instanceof PsiNameIdentifierOwner ? + (PsiNameIdentifierOwner)element : + PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner.class, true); + if (nameOwner != null) { + final VirtualFile virtualFile = nameOwner.getContainingFile().getVirtualFile(); + if (virtualFile != null) { + final Editor editor = FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, virtualFile), true); + if (ApplicationManager.getApplication().isUnitTestMode()) { + renameInUnitTestMode(project, nameOwner, editor); + } + else { + if (checkLocalScope(element) != null && (nameOwner instanceof PyNamedParameter || nameOwner instanceof PyTargetExpression)) { + new VariableInplaceRenamer(nameOwner, editor).performInplaceRename(); + } + else { + PsiElementRenameHandler.invoke(nameOwner, project, ScopeUtil.getScopeOwner(nameOwner), editor); + } + } + } + } + } + + @Nullable + protected PsiElement checkLocalScope(PsiElement element) { + final SearchScope searchScope = PsiSearchHelper.SERVICE.getInstance(element.getProject()).getUseScope(element); + if (searchScope instanceof LocalSearchScope) { + final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope(); + return PsiTreeUtil.findCommonParent(elements); + } + + return null; + } + + private static void renameInUnitTestMode(@NotNull Project project, @NotNull PsiNameIdentifierOwner nameOwner, + @Nullable Editor editor) { + final PsiElement substitution = RenamePsiElementProcessor.forElement(nameOwner).substituteElementToRename(nameOwner, editor); + if (substitution != null) { + new RenameProcessor(project, substitution, "a", false, false).run(); + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java b/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java index 64c3ff48a5c3..3c917efe9c07 100644 --- a/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java @@ -1,24 +1,10 @@ package com.jetbrains.python.inspections; import com.intellij.codeInspection.LocalInspectionToolSession; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiNameIdentifierOwner; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.refactoring.rename.PsiElementRenameHandler; -import com.intellij.refactoring.rename.RenameProcessor; -import com.intellij.refactoring.rename.RenamePsiElementProcessor; -import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; -import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import org.jetbrains.annotations.NotNull; @@ -81,52 +67,5 @@ public class PyShadowingBuiltinsInspection extends PyInspection { } } } - - private static class PyRenameElementQuickFix implements LocalQuickFix { - @NotNull - @Override - public String getName() { - return "Rename element"; - } - - @NotNull - @Override - public String getFamilyName() { - return "Rename element"; - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - final PsiNameIdentifierOwner nameOwner = element instanceof PsiNameIdentifierOwner ? - (PsiNameIdentifierOwner)element : - PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner.class, true); - if (nameOwner != null) { - final VirtualFile virtualFile = nameOwner.getContainingFile().getVirtualFile(); - if (virtualFile != null) { - final Editor editor = FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, virtualFile), true); - if (ApplicationManager.getApplication().isUnitTestMode()) { - renameInUnitTestMode(project, nameOwner, editor); - } - else { - if (nameOwner instanceof PyNamedParameter || nameOwner instanceof PyTargetExpression) { - new VariableInplaceRenamer(nameOwner, editor).performInplaceRename(); - } - else { - PsiElementRenameHandler.invoke(nameOwner, project, ScopeUtil.getScopeOwner(nameOwner), editor); - } - } - } - } - } - - private static void renameInUnitTestMode(@NotNull Project project, @NotNull PsiNameIdentifierOwner nameOwner, - @Nullable Editor editor) { - final PsiElement substitution = RenamePsiElementProcessor.forElement(nameOwner).substituteElementToRename(nameOwner, editor); - if (substitution != null) { - new RenameProcessor(project, substitution, "a", false, false).run(); - } - } - } } } diff --git a/python/src/com/jetbrains/python/refactoring/PyNameSuggestionProvider.java b/python/src/com/jetbrains/python/refactoring/PyNameSuggestionProvider.java new file mode 100644 index 000000000000..875ecf1c9a77 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/PyNameSuggestionProvider.java @@ -0,0 +1,57 @@ +/* + * @author max + */ +package com.jetbrains.python.refactoring; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.codeStyle.SuggestedNameInfo; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.rename.NameSuggestionProvider; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Set; + +/** + * User : ktisha + */ +public class PyNameSuggestionProvider implements NameSuggestionProvider { + public SuggestedNameInfo getSuggestedNames(PsiElement element, PsiElement nameSuggestionContext, Set result) { + if (!(element instanceof PyElement)) return null; + final String name = ((PyElement)element).getName(); + if (name == null) return null; + + if (element instanceof PyClass) { + result.add(toCamelCase(name, true)); + } + else if (element instanceof PyFunction || element instanceof PyParameter) { + result.add(name.toLowerCase()); + } + else { + result.add(name.toLowerCase()); + final PyAssignmentStatement assignmentStatement = PsiTreeUtil.getParentOfType(element, PyAssignmentStatement.class); + if (assignmentStatement != null) return null; + result.add(name.toUpperCase()); + result.add(toCamelCase(name, false)); + } + return SuggestedNameInfo.NULL_INFO; + } + + @NotNull + protected String toCamelCase(@NotNull final String name, boolean uppercaseFirstLetter) { + final List strings = StringUtil.split(name, "_"); + if (strings.size() > 0) { + final StringBuilder buf = new StringBuilder(); + String str = strings.get(0).toLowerCase(); + if (uppercaseFirstLetter) str = StringUtil.capitalize(str); + buf.append(str); + for (int i = 1; i < strings.size(); i++) { + buf.append(StringUtil.capitalize(strings.get(i).toLowerCase())); + } + return buf.toString(); + } + return name; + } +} diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java index b22c41ddf1f7..2b0e5bb63d65 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java @@ -1,5 +1,6 @@ package com.jetbrains.python.refactoring.classes; +import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; @@ -348,7 +349,11 @@ public class PyClassRefactoringUtil { } if (deleteImportElement) { if (importStatement.getImportElements().length == 1) { - importStatement.delete(); + final boolean isInjected = InjectedLanguageManager.getInstance(importElement.getProject()).isInjectedFragment(importElement.getContainingFile()); + if (!isInjected) + importStatement.delete(); + else + deleteImportStatementFromInjected(importStatement); } else { importElement.delete(); @@ -358,6 +363,13 @@ public class PyClassRefactoringUtil { } } + private static void deleteImportStatementFromInjected(@NotNull final PyImportStatementBase importStatement) { + final PsiElement sibling = importStatement.getPrevSibling(); + importStatement.delete(); + if (sibling instanceof PsiWhiteSpace) + sibling.delete(); + } + @Nullable public static String getOriginalName(@NotNull PsiNamedElement element) { if (element instanceof PyFile) { diff --git a/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java b/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java index 85b24086454b..900e83129e7a 100644 --- a/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java +++ b/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java @@ -77,8 +77,6 @@ public class PyMoveFileHandler extends MoveFileHandler { for (UsageInfo usage : usages) { final PsiElement element = usage.getElement(); if (element != null) { - if (InjectedLanguageManager.getInstance(element.getProject()).isInjectedFragment(element.getContainingFile())) - continue; final PsiNamedElement newElement = element.getCopyableUserData(REFERENCED_ELEMENT); element.putCopyableUserData(REFERENCED_ELEMENT, null); if (newElement != null) { @@ -111,7 +109,9 @@ public class PyMoveFileHandler extends MoveFileHandler { if (!updatedFiles.isEmpty()) { final PyImportOptimizer optimizer = new PyImportOptimizer(); for (PsiFile file : updatedFiles) { - optimizer.processFile(file).run(); + final boolean injectedFragment = InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file); + if (!injectedFragment) + optimizer.processFile(file).run(); } } } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java index 69cfc3f42229..15dae82bf84f 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java @@ -126,7 +126,10 @@ public class PythonSdkUpdater implements StartupActivity { private static void updateSysPath(final Sdk sdk) throws InvalidSdkException { long start_time = System.currentTimeMillis(); final List sysPath = PythonSdkType.getSysPath(sdk.getHomePath()); - sysPath.add(PyUserSkeletonsUtil.getUserSkeletonsPath()); + final VirtualFile file = PyUserSkeletonsUtil.getUserSkeletonsDirectory(); + if (file != null) { + sysPath.add(file.getPath()); + } ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index 6dcca4ca6720..d4f40cf21e94 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -422,16 +422,18 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } @Override - public void setupUIComponentPresentation(String[] texts, EnumSet[] flags, Color background) { + public String setupUIComponentPresentation(String[] texts, EnumSet[] flags, Color background) { assert texts.length == flags.length; myTexts = texts; myFlags = flags; + return StringUtil.join(texts, ""); } @Override - public void setupUIComponentPresentation(String text, int highlightStartOffset, int highlightEndOffset, boolean isDisabled, - boolean strikeout, boolean isDisabledBeforeHighlight, Color background) { + public String setupUIComponentPresentation(String text, int highlightStartOffset, int highlightEndOffset, boolean isDisabled, + boolean strikeout, boolean isDisabledBeforeHighlight, Color background) { // nothing, we don't use it + return text; } @Override