[Python-checkins] bpo-38307: Add end_lineno attribute to pyclbr Objects (GH-24348)

terryjreedy webhook-mailer at python.org
Mon Feb 1 12:39:17 EST 2021


https://github.com/python/cpython/commit/000cde59847beaf5fa7b73633e1f3c898fe5bf90
commit: 000cde59847beaf5fa7b73633e1f3c898fe5bf90
branch: master
author: Aviral Srivastava <avi.srivastava254084 at gmail.com>
committer: terryjreedy <tjreedy at udel.edu>
date: 2021-02-01T12:38:44-05:00
summary:

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 at Avirals-MacBook-Air.local
Co-authored-by: Terry Jan Reedy <tjreedy at udel.edu>

files:
A Misc/NEWS.d/next/Library/2020-03-16-03-03-21.bpo-38307.2cmw2i.rst
M Doc/whatsnew/3.10.rst
M Lib/idlelib/idle_test/test_browser.py
M Lib/pyclbr.py
M Lib/test/test_pyclbr.py

diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index 3dccb7c50019b..d80ceeca85a89 100644
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -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
 ------
 
diff --git a/Lib/idlelib/idle_test/test_browser.py b/Lib/idlelib/idle_test/test_browser.py
index 25d6dc6630364..03a50f22ca1e8 100644
--- a/Lib/idlelib/idle_test/test_browser.py
+++ b/Lib/idlelib/idle_test/test_browser.py
@@ -61,15 +61,15 @@ def test_close(self):
 # 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.
diff --git a/Lib/pyclbr.py b/Lib/pyclbr.py
index f0c8381946c61..ebcc23c29da21 100644
--- a/Lib/pyclbr.py
+++ b/Lib/pyclbr.py
@@ -21,6 +21,7 @@
     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 @@
 
 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 @@ def visit_ClassDef(self, node):
                 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 @@ def visit_ClassDef(self, node):
 
     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)
diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py
index 2c7afa994f305..82c1ebb5b070f 100644
--- a/Lib/test/test_pyclbr.py
+++ b/Lib/test/test_pyclbr.py
@@ -176,15 +176,15 @@ def F3(): return 1+1
         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 @@ def compare(parent1, children1, parent2, children2):
                 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)
diff --git a/Misc/NEWS.d/next/Library/2020-03-16-03-03-21.bpo-38307.2cmw2i.rst b/Misc/NEWS.d/next/Library/2020-03-16-03-03-21.bpo-38307.2cmw2i.rst
new file mode 100644
index 0000000000000..358089915fb6c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-03-16-03-03-21.bpo-38307.2cmw2i.rst
@@ -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.



More information about the Python-checkins mailing list