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:
Aviral Srivastava 2021-02-01 09:38:44 -08:00 committed by GitHub
parent b5931f1d9f
commit 000cde5984
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 56 additions and 35 deletions

View file

@ -435,6 +435,14 @@ py_compile
Added ``--quiet`` option to command-line interface of :mod:`py_compile`.
(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
------

View file

@ -61,15 +61,15 @@ class ModuleBrowserTest(unittest.TestCase):
# Nested tree same as in test_pyclbr.py except for supers on C0. C1.
mb = pyclbr
module, fname = 'test', 'test.py'
C0 = mb.Class(module, 'C0', ['base'], fname, 1)
F1 = mb._nest_function(C0, 'F1', 3)
C1 = mb._nest_class(C0, 'C1', 6, [''])
C2 = mb._nest_class(C1, 'C2', 7)
F3 = mb._nest_function(C2, 'F3', 9)
f0 = mb.Function(module, 'f0', fname, 11)
f1 = mb._nest_function(f0, 'f1', 12)
f2 = mb._nest_function(f1, 'f2', 13)
c1 = mb._nest_class(f0, 'c1', 15)
C0 = mb.Class(module, 'C0', ['base'], fname, 1, end_lineno=9)
F1 = mb._nest_function(C0, 'F1', 3, 5)
C1 = mb._nest_class(C0, 'C1', 6, 9, [''])
C2 = mb._nest_class(C1, 'C2', 7, 9)
F3 = mb._nest_function(C2, 'F3', 9, 9)
f0 = mb.Function(module, 'f0', fname, 11, end_lineno=15)
f1 = mb._nest_function(f0, 'f1', 12, 14)
f2 = mb._nest_function(f1, 'f2', 13, 13)
c1 = mb._nest_class(f0, 'c1', 15, 15)
mock_pyclbr_tree = {'C0': C0, 'f0': f0}
# Adjust C0.name, C1.name so tests do not depend on order.

View file

@ -21,6 +21,7 @@ has the following attributes:
name -- name of the object;
file -- file in which the object is defined;
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;
children -- nested objects contained in this object.
The 'children' attribute is a dictionary mapping names to objects.
@ -52,40 +53,50 @@ _modules = {} # Initialize cache of modules we've seen.
class _Object:
"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.name = name
self.file = file
self.lineno = lineno
self.end_lineno = end_lineno
self.parent = parent
self.children = {}
if parent is not None:
parent.children[name] = self
# Odd Function and Class signatures are for back-compatibility.
class Function(_Object):
"Information about a Python function, including methods."
def __init__(self, module, name, file, lineno, parent=None, is_async=False):
super().__init__(module, name, file, lineno, parent)
def __init__(self, module, name, file, lineno,
parent=None, is_async=False, *, end_lineno=None):
super().__init__(module, name, file, lineno, end_lineno, parent)
self.is_async = is_async
if isinstance(parent, Class):
parent.methods[name] = lineno
class Class(_Object):
"Information about a Python class."
def __init__(self, module, name, super_, file, lineno, parent=None):
super().__init__(module, name, file, lineno, parent)
def __init__(self, module, name, super_, file, lineno,
parent=None, *, end_lineno=None):
super().__init__(module, name, file, lineno, end_lineno, parent)
self.super = super_ or []
self.methods = {}
# These 2 functions are used in these tests
# 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 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 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):
"""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 [])
def _readmodule(module, path, inpackage=None):
"""Do the hard work for readmodule[_ex].
@ -198,9 +210,8 @@ class _ModuleBrowser(ast.NodeVisitor):
bases.append(name)
parent = self.stack[-1] if self.stack else None
class_ = Class(
self.module, node.name, bases, self.file, node.lineno, parent
)
class_ = Class(self.module, node.name, bases, self.file, node.lineno,
parent=parent, end_lineno=node.end_lineno)
if parent is None:
self.tree[node.name] = class_
self.stack.append(class_)
@ -209,9 +220,8 @@ class _ModuleBrowser(ast.NodeVisitor):
def visit_FunctionDef(self, node, *, is_async=False):
parent = self.stack[-1] if self.stack else None
function = Function(
self.module, node.name, self.file, node.lineno, parent, is_async
)
function = Function(self.module, node.name, self.file, node.lineno,
parent, is_async, end_lineno=node.end_lineno)
if parent is None:
self.tree[node.name] = function
self.stack.append(function)

View file

@ -176,15 +176,15 @@ class PyclbrTest(TestCase):
actual = mb._create_tree(m, p, f, source, t, i)
# Create descriptors, linked together, and expected dict.
f0 = mb.Function(m, 'f0', f, 1)
f1 = mb._nest_function(f0, 'f1', 2)
f2 = mb._nest_function(f1, 'f2', 3)
c1 = mb._nest_class(f0, 'c1', 5)
C0 = mb.Class(m, 'C0', None, f, 6)
F1 = mb._nest_function(C0, 'F1', 8)
C1 = mb._nest_class(C0, 'C1', 11)
C2 = mb._nest_class(C1, 'C2', 12)
F3 = mb._nest_function(C2, 'F3', 14)
f0 = mb.Function(m, 'f0', f, 1, end_lineno=5)
f1 = mb._nest_function(f0, 'f1', 2, 4)
f2 = mb._nest_function(f1, 'f2', 3, 3)
c1 = mb._nest_class(f0, 'c1', 5, 5)
C0 = mb.Class(m, 'C0', None, f, 6, end_lineno=14)
F1 = mb._nest_function(C0, 'F1', 8, 10)
C1 = mb._nest_class(C0, 'C1', 11, 14)
C2 = mb._nest_class(C1, 'C2', 12, 14)
F3 = mb._nest_function(C2, 'F3', 14, 14)
expected = {'f0':f0, 'C0':C0}
def compare(parent1, children1, parent2, children2):
@ -203,8 +203,8 @@ class PyclbrTest(TestCase):
self.assertIs(ob.parent, parent2)
for key in children1.keys():
o1, o2 = children1[key], children2[key]
t1 = type(o1), o1.name, o1.file, o1.module, o1.lineno
t2 = type(o2), o2.name, o2.file, o2.module, o2.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, o2.end_lineno
self.assertEqual(t1, t2)
if type(o1) is mb.Class:
self.assertEqual(o1.methods, o2.methods)

View file

@ -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.