mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-38307: Add end_lineno attribute to pyclbr Objects (GH-24348)
For back-compatibility, make the new constructor parameter for public classes Function and Class keyword-only with a default of None. Co-authored-by: Aviral Srivastava <aviralsrivastava@Avirals-MacBook-Air.local Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
This commit is contained in:
parent
b5931f1d9f
commit
000cde5984
5 changed files with 56 additions and 35 deletions
|
@ -435,6 +435,14 @@ py_compile
|
||||||
Added ``--quiet`` option to command-line interface of :mod:`py_compile`.
|
Added ``--quiet`` option to command-line interface of :mod:`py_compile`.
|
||||||
(Contributed by Gregory Schevchenko in :issue:`38731`.)
|
(Contributed by Gregory Schevchenko in :issue:`38731`.)
|
||||||
|
|
||||||
|
pyclbr
|
||||||
|
------
|
||||||
|
|
||||||
|
Added an ``end_lineno`` attribute to the ``Function`` and ``Class``
|
||||||
|
objects in the tree returned by :func:`pyclbr.readline` and
|
||||||
|
:func:`pyclbr.readline_ex`. It matches the existing (start) ``lineno``.
|
||||||
|
(Contributed by Aviral Srivastava in :issue:`38307`.)
|
||||||
|
|
||||||
shelve
|
shelve
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -61,15 +61,15 @@ class ModuleBrowserTest(unittest.TestCase):
|
||||||
# Nested tree same as in test_pyclbr.py except for supers on C0. C1.
|
# Nested tree same as in test_pyclbr.py except for supers on C0. C1.
|
||||||
mb = pyclbr
|
mb = pyclbr
|
||||||
module, fname = 'test', 'test.py'
|
module, fname = 'test', 'test.py'
|
||||||
C0 = mb.Class(module, 'C0', ['base'], fname, 1)
|
C0 = mb.Class(module, 'C0', ['base'], fname, 1, end_lineno=9)
|
||||||
F1 = mb._nest_function(C0, 'F1', 3)
|
F1 = mb._nest_function(C0, 'F1', 3, 5)
|
||||||
C1 = mb._nest_class(C0, 'C1', 6, [''])
|
C1 = mb._nest_class(C0, 'C1', 6, 9, [''])
|
||||||
C2 = mb._nest_class(C1, 'C2', 7)
|
C2 = mb._nest_class(C1, 'C2', 7, 9)
|
||||||
F3 = mb._nest_function(C2, 'F3', 9)
|
F3 = mb._nest_function(C2, 'F3', 9, 9)
|
||||||
f0 = mb.Function(module, 'f0', fname, 11)
|
f0 = mb.Function(module, 'f0', fname, 11, end_lineno=15)
|
||||||
f1 = mb._nest_function(f0, 'f1', 12)
|
f1 = mb._nest_function(f0, 'f1', 12, 14)
|
||||||
f2 = mb._nest_function(f1, 'f2', 13)
|
f2 = mb._nest_function(f1, 'f2', 13, 13)
|
||||||
c1 = mb._nest_class(f0, 'c1', 15)
|
c1 = mb._nest_class(f0, 'c1', 15, 15)
|
||||||
mock_pyclbr_tree = {'C0': C0, 'f0': f0}
|
mock_pyclbr_tree = {'C0': C0, 'f0': f0}
|
||||||
|
|
||||||
# Adjust C0.name, C1.name so tests do not depend on order.
|
# Adjust C0.name, C1.name so tests do not depend on order.
|
||||||
|
|
|
@ -21,6 +21,7 @@ has the following attributes:
|
||||||
name -- name of the object;
|
name -- name of the object;
|
||||||
file -- file in which the object is defined;
|
file -- file in which the object is defined;
|
||||||
lineno -- line in the file where the object's definition starts;
|
lineno -- line in the file where the object's definition starts;
|
||||||
|
end_lineno -- line in the file where the object's definition ends;
|
||||||
parent -- parent of this object, if any;
|
parent -- parent of this object, if any;
|
||||||
children -- nested objects contained in this object.
|
children -- nested objects contained in this object.
|
||||||
The 'children' attribute is a dictionary mapping names to objects.
|
The 'children' attribute is a dictionary mapping names to objects.
|
||||||
|
@ -52,40 +53,50 @@ _modules = {} # Initialize cache of modules we've seen.
|
||||||
|
|
||||||
class _Object:
|
class _Object:
|
||||||
"Information about Python class or function."
|
"Information about Python class or function."
|
||||||
def __init__(self, module, name, file, lineno, parent):
|
def __init__(self, module, name, file, lineno, end_lineno, parent):
|
||||||
self.module = module
|
self.module = module
|
||||||
self.name = name
|
self.name = name
|
||||||
self.file = file
|
self.file = file
|
||||||
self.lineno = lineno
|
self.lineno = lineno
|
||||||
|
self.end_lineno = end_lineno
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.children = {}
|
self.children = {}
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
parent.children[name] = self
|
parent.children[name] = self
|
||||||
|
|
||||||
|
|
||||||
|
# Odd Function and Class signatures are for back-compatibility.
|
||||||
class Function(_Object):
|
class Function(_Object):
|
||||||
"Information about a Python function, including methods."
|
"Information about a Python function, including methods."
|
||||||
def __init__(self, module, name, file, lineno, parent=None, is_async=False):
|
def __init__(self, module, name, file, lineno,
|
||||||
super().__init__(module, name, file, lineno, parent)
|
parent=None, is_async=False, *, end_lineno=None):
|
||||||
|
super().__init__(module, name, file, lineno, end_lineno, parent)
|
||||||
self.is_async = is_async
|
self.is_async = is_async
|
||||||
if isinstance(parent, Class):
|
if isinstance(parent, Class):
|
||||||
parent.methods[name] = lineno
|
parent.methods[name] = lineno
|
||||||
|
|
||||||
|
|
||||||
class Class(_Object):
|
class Class(_Object):
|
||||||
"Information about a Python class."
|
"Information about a Python class."
|
||||||
def __init__(self, module, name, super_, file, lineno, parent=None):
|
def __init__(self, module, name, super_, file, lineno,
|
||||||
super().__init__(module, name, file, lineno, parent)
|
parent=None, *, end_lineno=None):
|
||||||
|
super().__init__(module, name, file, lineno, end_lineno, parent)
|
||||||
self.super = super_ or []
|
self.super = super_ or []
|
||||||
self.methods = {}
|
self.methods = {}
|
||||||
|
|
||||||
|
|
||||||
# These 2 functions are used in these tests
|
# These 2 functions are used in these tests
|
||||||
# Lib/test/test_pyclbr, Lib/idlelib/idle_test/test_browser.py
|
# Lib/test/test_pyclbr, Lib/idlelib/idle_test/test_browser.py
|
||||||
def _nest_function(ob, func_name, lineno, is_async=False):
|
def _nest_function(ob, func_name, lineno, end_lineno, is_async=False):
|
||||||
"Return a Function after nesting within ob."
|
"Return a Function after nesting within ob."
|
||||||
return Function(ob.module, func_name, ob.file, lineno, ob, is_async)
|
return Function(ob.module, func_name, ob.file, lineno,
|
||||||
|
parent=ob, is_async=is_async, end_lineno=end_lineno)
|
||||||
|
|
||||||
def _nest_class(ob, class_name, lineno, super=None):
|
def _nest_class(ob, class_name, lineno, end_lineno, super=None):
|
||||||
"Return a Class after nesting within ob."
|
"Return a Class after nesting within ob."
|
||||||
return Class(ob.module, class_name, super, ob.file, lineno, ob)
|
return Class(ob.module, class_name, super, ob.file, lineno,
|
||||||
|
parent=ob, end_lineno=end_lineno)
|
||||||
|
|
||||||
|
|
||||||
def readmodule(module, path=None):
|
def readmodule(module, path=None):
|
||||||
"""Return Class objects for the top-level classes in module.
|
"""Return Class objects for the top-level classes in module.
|
||||||
|
@ -108,6 +119,7 @@ def readmodule_ex(module, path=None):
|
||||||
"""
|
"""
|
||||||
return _readmodule(module, path or [])
|
return _readmodule(module, path or [])
|
||||||
|
|
||||||
|
|
||||||
def _readmodule(module, path, inpackage=None):
|
def _readmodule(module, path, inpackage=None):
|
||||||
"""Do the hard work for readmodule[_ex].
|
"""Do the hard work for readmodule[_ex].
|
||||||
|
|
||||||
|
@ -198,9 +210,8 @@ class _ModuleBrowser(ast.NodeVisitor):
|
||||||
bases.append(name)
|
bases.append(name)
|
||||||
|
|
||||||
parent = self.stack[-1] if self.stack else None
|
parent = self.stack[-1] if self.stack else None
|
||||||
class_ = Class(
|
class_ = Class(self.module, node.name, bases, self.file, node.lineno,
|
||||||
self.module, node.name, bases, self.file, node.lineno, parent
|
parent=parent, end_lineno=node.end_lineno)
|
||||||
)
|
|
||||||
if parent is None:
|
if parent is None:
|
||||||
self.tree[node.name] = class_
|
self.tree[node.name] = class_
|
||||||
self.stack.append(class_)
|
self.stack.append(class_)
|
||||||
|
@ -209,9 +220,8 @@ class _ModuleBrowser(ast.NodeVisitor):
|
||||||
|
|
||||||
def visit_FunctionDef(self, node, *, is_async=False):
|
def visit_FunctionDef(self, node, *, is_async=False):
|
||||||
parent = self.stack[-1] if self.stack else None
|
parent = self.stack[-1] if self.stack else None
|
||||||
function = Function(
|
function = Function(self.module, node.name, self.file, node.lineno,
|
||||||
self.module, node.name, self.file, node.lineno, parent, is_async
|
parent, is_async, end_lineno=node.end_lineno)
|
||||||
)
|
|
||||||
if parent is None:
|
if parent is None:
|
||||||
self.tree[node.name] = function
|
self.tree[node.name] = function
|
||||||
self.stack.append(function)
|
self.stack.append(function)
|
||||||
|
|
|
@ -176,15 +176,15 @@ class PyclbrTest(TestCase):
|
||||||
actual = mb._create_tree(m, p, f, source, t, i)
|
actual = mb._create_tree(m, p, f, source, t, i)
|
||||||
|
|
||||||
# Create descriptors, linked together, and expected dict.
|
# Create descriptors, linked together, and expected dict.
|
||||||
f0 = mb.Function(m, 'f0', f, 1)
|
f0 = mb.Function(m, 'f0', f, 1, end_lineno=5)
|
||||||
f1 = mb._nest_function(f0, 'f1', 2)
|
f1 = mb._nest_function(f0, 'f1', 2, 4)
|
||||||
f2 = mb._nest_function(f1, 'f2', 3)
|
f2 = mb._nest_function(f1, 'f2', 3, 3)
|
||||||
c1 = mb._nest_class(f0, 'c1', 5)
|
c1 = mb._nest_class(f0, 'c1', 5, 5)
|
||||||
C0 = mb.Class(m, 'C0', None, f, 6)
|
C0 = mb.Class(m, 'C0', None, f, 6, end_lineno=14)
|
||||||
F1 = mb._nest_function(C0, 'F1', 8)
|
F1 = mb._nest_function(C0, 'F1', 8, 10)
|
||||||
C1 = mb._nest_class(C0, 'C1', 11)
|
C1 = mb._nest_class(C0, 'C1', 11, 14)
|
||||||
C2 = mb._nest_class(C1, 'C2', 12)
|
C2 = mb._nest_class(C1, 'C2', 12, 14)
|
||||||
F3 = mb._nest_function(C2, 'F3', 14)
|
F3 = mb._nest_function(C2, 'F3', 14, 14)
|
||||||
expected = {'f0':f0, 'C0':C0}
|
expected = {'f0':f0, 'C0':C0}
|
||||||
|
|
||||||
def compare(parent1, children1, parent2, children2):
|
def compare(parent1, children1, parent2, children2):
|
||||||
|
@ -203,8 +203,8 @@ class PyclbrTest(TestCase):
|
||||||
self.assertIs(ob.parent, parent2)
|
self.assertIs(ob.parent, parent2)
|
||||||
for key in children1.keys():
|
for key in children1.keys():
|
||||||
o1, o2 = children1[key], children2[key]
|
o1, o2 = children1[key], children2[key]
|
||||||
t1 = type(o1), o1.name, o1.file, o1.module, o1.lineno
|
t1 = type(o1), o1.name, o1.file, o1.module, o1.lineno, o1.end_lineno
|
||||||
t2 = type(o2), o2.name, o2.file, o2.module, o2.lineno
|
t2 = type(o2), o2.name, o2.file, o2.module, o2.lineno, o2.end_lineno
|
||||||
self.assertEqual(t1, t2)
|
self.assertEqual(t1, t2)
|
||||||
if type(o1) is mb.Class:
|
if type(o1) is mb.Class:
|
||||||
self.assertEqual(o1.methods, o2.methods)
|
self.assertEqual(o1.methods, o2.methods)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Add an 'end_lineno' attribute to the Class and Function objects that appear in the
|
||||||
|
tree returned by pyclbr functions. This and the existing 'lineno'
|
||||||
|
attribute define the extent of class and def statements. Patch by Aviral Srivastava.
|
Loading…
Add table
Add a link
Reference in a new issue