Class browsers: improved endline detection for the Python, IDL and ProtoBuf source code browsers and the Python module parser.

Fri, 11 Sep 2020 17:28:59 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 11 Sep 2020 17:28:59 +0200
changeset 7699
d338c533f5f0
parent 7698
12cb12380a6a
child 7700
a3cf077a8db3

Class browsers: improved endline detection for the Python, IDL and ProtoBuf source code browsers and the Python module parser.

eric6/APIs/Python3/eric6.api file | annotate | diff | comparison | revisions
eric6/Documentation/Help/source.qch file | annotate | diff | comparison | revisions
eric6/Documentation/Help/source.qhp file | annotate | diff | comparison | revisions
eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.idlclbr.html file | annotate | diff | comparison | revisions
eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.protoclbr.html file | annotate | diff | comparison | revisions
eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.pyclbr.html file | annotate | diff | comparison | revisions
eric6/Documentation/Source/eric6.Utilities.ModuleParser.html file | annotate | diff | comparison | revisions
eric6/Utilities/ClassBrowsers/idlclbr.py file | annotate | diff | comparison | revisions
eric6/Utilities/ClassBrowsers/protoclbr.py file | annotate | diff | comparison | revisions
eric6/Utilities/ClassBrowsers/pyclbr.py file | annotate | diff | comparison | revisions
eric6/Utilities/ModuleParser.py file | annotate | diff | comparison | revisions
--- a/eric6/APIs/Python3/eric6.api	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/APIs/Python3/eric6.api	Fri Sep 11 17:28:59 2020 +0200
@@ -9179,6 +9179,8 @@
 eric6.Utilities.ClassBrowsers.idlclbr._getnext?8
 eric6.Utilities.ClassBrowsers.idlclbr._modules?8
 eric6.Utilities.ClassBrowsers.idlclbr._normalize?8
+eric6.Utilities.ClassBrowsers.idlclbr.calculateEndline?4(lineno, lines)
+eric6.Utilities.ClassBrowsers.idlclbr.calculateMethodEndline?4(lineno, lines)
 eric6.Utilities.ClassBrowsers.idlclbr.readmodule_ex?4(module, path=None)
 eric6.Utilities.ClassBrowsers.idlclbr.scan?4(src, file, module)
 eric6.Utilities.ClassBrowsers.jsclbr.Attribute?1(module, name, file, lineno)
@@ -9206,6 +9208,7 @@
 eric6.Utilities.ClassBrowsers.protoclbr._getnext?8
 eric6.Utilities.ClassBrowsers.protoclbr._modules?8
 eric6.Utilities.ClassBrowsers.protoclbr._normalize?8
+eric6.Utilities.ClassBrowsers.protoclbr.calculateEndline?4(lineno, lines)
 eric6.Utilities.ClassBrowsers.protoclbr.readmodule_ex?4(module, path=None)
 eric6.Utilities.ClassBrowsers.protoclbr.scan?4(src, file, module)
 eric6.Utilities.ClassBrowsers.pyclbr.Attribute?1(module, name, file, lineno)
@@ -9269,6 +9272,7 @@
 eric6.Utilities.ModuleParser.Module.addModule?4(name, module)
 eric6.Utilities.ModuleParser.Module.addPathToHierarchy?4(path, result, fn)
 eric6.Utilities.ModuleParser.Module.assembleHierarchy?4(name, classes, path, result)
+eric6.Utilities.ModuleParser.Module.calculateEndline?4(lines, indent)
 eric6.Utilities.ModuleParser.Module.createHierarchy?4()
 eric6.Utilities.ModuleParser.Module.getFileName?4()
 eric6.Utilities.ModuleParser.Module.getName?4()
Binary file eric6/Documentation/Help/source.qch has changed
--- a/eric6/Documentation/Help/source.qhp	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Documentation/Help/source.qhp	Fri Sep 11 17:28:59 2020 +0200
@@ -9987,6 +9987,7 @@
       <keyword name="Module.addModule" id="Module.addModule" ref="eric6.Utilities.ModuleParser.html#Module.addModule" />
       <keyword name="Module.addPathToHierarchy" id="Module.addPathToHierarchy" ref="eric6.Utilities.ModuleParser.html#Module.addPathToHierarchy" />
       <keyword name="Module.assembleHierarchy" id="Module.assembleHierarchy" ref="eric6.Utilities.ModuleParser.html#Module.assembleHierarchy" />
+      <keyword name="Module.calculateEndline" id="Module.calculateEndline" ref="eric6.Utilities.ModuleParser.html#Module.calculateEndline" />
       <keyword name="Module.createHierarchy" id="Module.createHierarchy" ref="eric6.Utilities.ModuleParser.html#Module.createHierarchy" />
       <keyword name="Module.getFileName" id="Module.getFileName" ref="eric6.Utilities.ModuleParser.html#Module.getFileName" />
       <keyword name="Module.getName" id="Module.getName" ref="eric6.Utilities.ModuleParser.html#Module.getName" />
@@ -16978,7 +16979,10 @@
       <keyword name="blank_lines" id="blank_lines" ref="eric6.Plugins.CheckerPlugins.CodeStyleChecker.pycodestyle.html#blank_lines" />
       <keyword name="break_after_binary_operator" id="break_after_binary_operator" ref="eric6.Plugins.CheckerPlugins.CodeStyleChecker.pycodestyle.html#break_after_binary_operator" />
       <keyword name="break_before_binary_operator" id="break_before_binary_operator" ref="eric6.Plugins.CheckerPlugins.CodeStyleChecker.pycodestyle.html#break_before_binary_operator" />
+      <keyword name="calculateEndline" id="calculateEndline" ref="eric6.Utilities.ClassBrowsers.idlclbr.html#calculateEndline" />
+      <keyword name="calculateEndline" id="calculateEndline" ref="eric6.Utilities.ClassBrowsers.protoclbr.html#calculateEndline" />
       <keyword name="calculateEndline" id="calculateEndline" ref="eric6.Utilities.ClassBrowsers.pyclbr.html#calculateEndline" />
+      <keyword name="calculateMethodEndline" id="calculateMethodEndline" ref="eric6.Utilities.ClassBrowsers.idlclbr.html#calculateMethodEndline" />
       <keyword name="certificateValidation (Module)" id="certificateValidation (Module)" ref="eric6.Plugins.CheckerPlugins.CodeStyleChecker.Security.Checks.certificateValidation.html" />
       <keyword name="changeRememberedMaster" id="changeRememberedMaster" ref="eric6.Utilities.crypto.__init__.html#changeRememberedMaster" />
       <keyword name="checkAssertUsed" id="checkAssertUsed" ref="eric6.Plugins.CheckerPlugins.CodeStyleChecker.Security.Checks.assert.html#checkAssertUsed" />
--- a/eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.idlclbr.html	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.idlclbr.html	Fri Sep 11 17:28:59 2020 +0200
@@ -69,6 +69,14 @@
 <table>
 
 <tr>
+<td><a href="#calculateEndline">calculateEndline</a></td>
+<td>Function to calculate the end line.</td>
+</tr>
+<tr>
+<td><a href="#calculateMethodEndline">calculateMethodEndline</a></td>
+<td>Function to calculate the end line.</td>
+</tr>
+<tr>
 <td><a href="#readmodule_ex">readmodule_ex</a></td>
 <td>Read a CORBA IDL file and return a dictionary of classes, functions and modules.</td>
 </tr>
@@ -383,6 +391,72 @@
 <div align="right"><a href="#top">Up</a></div>
 <hr />
 <hr />
+<a NAME="calculateEndline" ID="calculateEndline"></a>
+<h2>calculateEndline</h2>
+<b>calculateEndline</b>(<i>lineno, lines</i>)
+
+<p>
+        Function to calculate the end line.
+</p>
+<dl>
+
+<dt><i>lineno</i> (int)</dt>
+<dd>
+line number to start at (one based)
+</dd>
+<dt><i>lines</i> (list of str)</dt>
+<dd>
+list of source lines
+</dd>
+</dl>
+<dl>
+<dt>Returns:</dt>
+<dd>
+end line (one based)
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+int
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="calculateMethodEndline" ID="calculateMethodEndline"></a>
+<h2>calculateMethodEndline</h2>
+<b>calculateMethodEndline</b>(<i>lineno, lines</i>)
+
+<p>
+        Function to calculate the end line.
+</p>
+<dl>
+
+<dt><i>lineno</i> (int)</dt>
+<dd>
+line number to start at (one based)
+</dd>
+<dt><i>lines</i> (list of str)</dt>
+<dd>
+list of source lines
+</dd>
+</dl>
+<dl>
+<dt>Returns:</dt>
+<dd>
+end line (one based)
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+int
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
 <a NAME="readmodule_ex" ID="readmodule_ex"></a>
 <h2>readmodule_ex</h2>
 <b>readmodule_ex</b>(<i>module, path=None</i>)
--- a/eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.protoclbr.html	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.protoclbr.html	Fri Sep 11 17:28:59 2020 +0200
@@ -64,6 +64,10 @@
 <table>
 
 <tr>
+<td><a href="#calculateEndline">calculateEndline</a></td>
+<td>Function to calculate the end line.</td>
+</tr>
+<tr>
 <td><a href="#readmodule_ex">readmodule_ex</a></td>
 <td>Read a ProtoBuf protocol file and return a dictionary of messages, enums, services and rpc methods.</td>
 </tr>
@@ -369,6 +373,39 @@
 <div align="right"><a href="#top">Up</a></div>
 <hr />
 <hr />
+<a NAME="calculateEndline" ID="calculateEndline"></a>
+<h2>calculateEndline</h2>
+<b>calculateEndline</b>(<i>lineno, lines</i>)
+
+<p>
+        Function to calculate the end line.
+</p>
+<dl>
+
+<dt><i>lineno</i> (int)</dt>
+<dd>
+line number to start at (one based)
+</dd>
+<dt><i>lines</i> (list of str)</dt>
+<dd>
+list of source lines
+</dd>
+</dl>
+<dl>
+<dt>Returns:</dt>
+<dd>
+end line (one based)
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+int
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
 <a NAME="readmodule_ex" ID="readmodule_ex"></a>
 <h2>readmodule_ex</h2>
 <b>readmodule_ex</b>(<i>module, path=None</i>)
--- a/eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.pyclbr.html	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Documentation/Source/eric6.Utilities.ClassBrowsers.pyclbr.html	Fri Sep 11 17:28:59 2020 +0200
@@ -637,7 +637,7 @@
 
 <dt><i>lineno</i> (int)</dt>
 <dd>
-line number to start at
+line number to start at (one based)
 </dd>
 <dt><i>lines</i> (list of str)</dt>
 <dd>
@@ -651,7 +651,7 @@
 <dl>
 <dt>Returns:</dt>
 <dd>
-end line of the class/method/function
+end line of the class/method/function (one based)
 </dd>
 </dl>
 <dl>
--- a/eric6/Documentation/Source/eric6.Utilities.ModuleParser.html	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Documentation/Source/eric6.Utilities.ModuleParser.html	Fri Sep 11 17:28:59 2020 +0200
@@ -581,6 +581,10 @@
 <td>Public method to assemble the inheritance hierarchy.</td>
 </tr>
 <tr>
+<td><a href="#Module.calculateEndline">calculateEndline</a></td>
+<td>Function to calculate the end line of a class or method/function.</td>
+</tr>
+<tr>
 <td><a href="#Module.createHierarchy">createHierarchy</a></td>
 <td>Public method to build the inheritance hierarchy for all classes of this module.</td>
 </tr>
@@ -816,6 +820,40 @@
 The resultant hierarchy
 </dd>
 </dl>
+<a NAME="Module.calculateEndline" ID="Module.calculateEndline"></a>
+<h4>Module.calculateEndline</h4>
+<b>calculateEndline</b>(<i>lines, indent</i>)
+
+<p>
+            Function to calculate the end line of a class or method/function.
+</p>
+<dl>
+
+<dt><i>lineno</i> (int)</dt>
+<dd>
+line number to start at (one based)
+</dd>
+<dt><i>lines</i> (list of str)</dt>
+<dd>
+list of source lines
+</dd>
+<dt><i>indent</i> (int)</dt>
+<dd>
+indent length the class/method/function definition
+</dd>
+</dl>
+<dl>
+<dt>Returns:</dt>
+<dd>
+end line of the class/method/function (one based)
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+int
+</dd>
+</dl>
 <a NAME="Module.createHierarchy" ID="Module.createHierarchy"></a>
 <h4>Module.createHierarchy</h4>
 <b>createHierarchy</b>(<i></i>)
--- a/eric6/Utilities/ClassBrowsers/idlclbr.py	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Utilities/ClassBrowsers/idlclbr.py	Fri Sep 11 17:28:59 2020 +0200
@@ -252,8 +252,58 @@
     @return dictionary containing the extracted data
     @rtype dict
     """
+    def calculateEndline(lineno, lines):
+        """
+        Function to calculate the end line.
+        
+        @param lineno line number to start at (one based)
+        @type int
+        @param lines list of source lines
+        @type list of str
+        @return end line (one based)
+        @rtype int
+        """
+        # convert lineno to be zero based
+        lineno -= 1
+        # 1. search for opening brace '{'
+        while lineno < len(lines) and not "{" in lines[lineno]:
+            lineno += 1
+        depth = lines[lineno].count("{") - lines[lineno].count("}")
+        # 2. search for ending line, i.e. matching closing brace '}'
+        while depth > 0 and lineno < len(lines) - 1:
+            lineno += 1
+            depth += lines[lineno].count("{") - lines[lineno].count("}")
+        if depth == 0:
+            # found a matching brace
+            return lineno + 1
+        else:
+            # nothing found
+            return -1
+    
+    def calculateMethodEndline(lineno, lines):
+        """
+        Function to calculate the end line.
+        
+        @param lineno line number to start at (one based)
+        @type int
+        @param lines list of source lines
+        @type list of str
+        @return end line (one based)
+        @rtype int
+        """
+        # convert lineno to be zero based
+        lineno -= 1
+        while lineno < len(lines) and not ";" in lines[lineno]:
+            lineno += 1
+        if ";" in lines[lineno]:
+            # found an end indicator, i.e. ';'
+            return lineno + 1
+        else:
+            return -1
+    
     # convert eol markers the Python style
     src = src.replace("\r\n", "\n").replace("\r", "\n")
+    srcLines = src.splitlines()
 
     dictionary = {}
     dict_counts = {}
@@ -262,8 +312,6 @@
     indent = 0
     
     lineno, last_lineno_pos = 1, 0
-    lastGlobalEntry = None
-    cur_obj = None
     i = 0
     while True:
         m = _getnext(src, i)
@@ -283,9 +331,6 @@
             last_lineno_pos = start
             # close all interfaces/modules indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             if classstack:
                 # it's an interface/module method
@@ -312,13 +357,9 @@
                 else:
                     dict_counts[meth_name] = 0
                 dictionary[meth_name] = f
-            if not classstack:
-                if lastGlobalEntry:
-                    lastGlobalEntry.setEndLine(lineno - 1)
-                lastGlobalEntry = f
-            if cur_obj and isinstance(cur_obj, Function):
-                cur_obj.setEndLine(lineno - 1)
-            cur_obj = f
+            if f:
+                endline = calculateMethodEndline(lineno, srcLines)
+                f.setEndLine(endline)
             classstack.append((f, thisindent))  # Marker for nested fns
 
         elif m.start("String") >= 0:
@@ -333,9 +374,6 @@
             indent += 1
             # close all interfaces/modules indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             lineno = lineno + src.count('\n', last_lineno_pos, start)
             last_lineno_pos = start
@@ -348,18 +386,13 @@
             # remember this interface
             cur_class = Interface(module, class_name, inherit,
                                   file, lineno)
+            endline = calculateEndline(lineno, srcLines)
+            cur_class.setEndLine(endline)
             if not classstack:
                 dictionary[class_name] = cur_class
             else:
                 cls = classstack[-1][0]
                 cls._addclass(class_name, cur_class)
-            if not classstack:
-                if lastGlobalEntry:
-                    lastGlobalEntry.setEndLine(lineno - 1)
-                lastGlobalEntry = cur_class
-            if cur_obj and isinstance(cur_obj, Function):
-                cur_obj.setEndLine(lineno - 1)
-            cur_obj = cur_class
             classstack.append((cur_class, thisindent))
 
         elif m.start("Module") >= 0:
@@ -368,23 +401,16 @@
             indent += 1
             # close all interfaces/modules indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             lineno = lineno + src.count('\n', last_lineno_pos, start)
             last_lineno_pos = start
             module_name = m.group("ModuleName")
             # remember this module
             cur_class = Module(module, module_name, file, lineno)
+            endline = calculateEndline(lineno, srcLines)
+            cur_class.setEndLine(endline)
             if not classstack:
                 dictionary[module_name] = cur_class
-                if lastGlobalEntry:
-                    lastGlobalEntry.setEndLine(lineno - 1)
-                lastGlobalEntry = cur_class
-            if cur_obj and isinstance(cur_obj, Function):
-                cur_obj.setEndLine(lineno - 1)
-            cur_obj = cur_class
             classstack.append((cur_class, thisindent))
 
         elif m.start("Attribute") >= 0:
@@ -407,9 +433,6 @@
                     break
                 else:
                     index -= 1
-                    if lastGlobalEntry:
-                        lastGlobalEntry.setEndLine(lineno - 1)
-                    lastGlobalEntry = None
 
         elif m.start("Begin") >= 0:
             # a begin of a block we are not interested in
--- a/eric6/Utilities/ClassBrowsers/protoclbr.py	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Utilities/ClassBrowsers/protoclbr.py	Fri Sep 11 17:28:59 2020 +0200
@@ -243,8 +243,37 @@
     @return dictionary containing the extracted data
     @rtype dict
     """
+    def calculateEndline(lineno, lines):
+        """
+        Function to calculate the end line.
+        
+        @param lineno line number to start at (one based)
+        @type int
+        @param lines list of source lines
+        @type list of str
+        @return end line (one based)
+        @rtype int
+        """
+        # convert lineno to be zero based
+        lineno -= 1
+        # 1. search for opening brace '{'
+        while lineno < len(lines) and not "{" in lines[lineno]:
+            lineno += 1
+        depth = lines[lineno].count("{") - lines[lineno].count("}")
+        # 2. search for ending line, i.e. matching closing brace '}'
+        while depth > 0 and lineno < len(lines) - 1:
+            lineno += 1
+            depth += lines[lineno].count("{") - lines[lineno].count("}")
+        if depth == 0:
+            # found a matching brace
+            return lineno + 1
+        else:
+            # nothing found
+            return -1
+    
     # convert eol markers the Python style
     src = src.replace("\r\n", "\n").replace("\r", "\n")
+    srcLines = src.splitlines()
 
     dictionary = {}
 
@@ -252,8 +281,6 @@
     indent = 0
 
     lineno, last_lineno_pos = 1, 0
-    lastGlobalEntry = None
-    cur_obj = None
     i = 0
     while True:
         m = _getnext(src, i)
@@ -277,9 +304,6 @@
             last_lineno_pos = start
             # close all interfaces/modules indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             if classstack:
                 # it's an interface/module method
@@ -295,13 +319,9 @@
             else:
                 # the file is incorrect, ignore the entry
                 continue
-            if not classstack:
-                if lastGlobalEntry:
-                    lastGlobalEntry.setEndLine(lineno - 1)
-                lastGlobalEntry = f
-            if cur_obj and isinstance(cur_obj, ServiceMethod):
-                cur_obj.setEndLine(lineno - 1)
-            cur_obj = f
+            if f:
+                endline = calculateEndline(lineno, srcLines)
+                f.setEndLine(endline)
             classstack.append((f, thisindent))  # Marker for nested fns
 
         elif m.start("String") >= 0:
@@ -319,22 +339,16 @@
             message_name = m.group("MessageName")
             # close all messages/services indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             # remember this message
             cur_class = Message(module, message_name, file, lineno)
+            endline = calculateEndline(lineno, srcLines)
+            cur_class.setEndLine(endline)
             if not classstack:
                 dictionary[message_name] = cur_class
             else:
                 msg = classstack[-1][0]
                 msg._addclass(message_name, cur_class)
-            if not classstack:
-                if lastGlobalEntry:
-                    lastGlobalEntry.setEndLine(lineno - 1)
-                lastGlobalEntry = cur_class
-            cur_obj = cur_class
             classstack.append((cur_class, thisindent))
 
         elif m.start("Enum") >= 0:
@@ -343,25 +357,19 @@
             indent += 1
             # close all messages/services indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             lineno = lineno + src.count('\n', last_lineno_pos, start)
             last_lineno_pos = start
             enum_name = m.group("EnumName")
             # remember this Enum
             cur_class = Enum(module, enum_name, file, lineno)
+            endline = calculateEndline(lineno, srcLines)
+            cur_class.setEndLine(endline)
             if not classstack:
                 dictionary[enum_name] = cur_class
             else:
                 enum = classstack[-1][0]
                 enum._addclass(enum_name, cur_class)
-            if not classstack:
-                if lastGlobalEntry:
-                    lastGlobalEntry.setEndLine(lineno - 1)
-                lastGlobalEntry = cur_class
-            cur_obj = cur_class
             classstack.append((cur_class, thisindent))
 
         elif m.start("Service") >= 0:
@@ -370,25 +378,19 @@
             indent += 1
             # close all messages/services indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             lineno = lineno + src.count('\n', last_lineno_pos, start)
             last_lineno_pos = start
             service_name = m.group("ServiceName")
             # remember this Service
             cur_class = Service(module, service_name, file, lineno)
+            endline = calculateEndline(lineno, srcLines)
+            cur_class.setEndLine(endline)
             if not classstack:
                 dictionary[service_name] = cur_class
             else:
                 service = classstack[-1][0]
                 service._addclass(service_name, cur_class)
-            if not classstack:
-                if lastGlobalEntry:
-                    lastGlobalEntry.setEndLine(lineno - 1)
-                lastGlobalEntry = cur_class
-            cur_obj = cur_class
             classstack.append((cur_class, thisindent))
 
         elif m.start("Begin") >= 0:
--- a/eric6/Utilities/ClassBrowsers/pyclbr.py	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Utilities/ClassBrowsers/pyclbr.py	Fri Sep 11 17:28:59 2020 +0200
@@ -13,11 +13,11 @@
 
 import sys
 import re
+from functools import reduce
 
 import Utilities
 import Utilities.ClassBrowsers as ClassBrowsers
 from . import ClbrBaseClasses
-from functools import reduce
 
 TABWIDTH = 4
 
@@ -404,13 +404,13 @@
         """
         Function to calculate the end line of a class or method/function.
         
-        @param lineno line number to start at
+        @param lineno line number to start at (one based)
         @type int
         @param lines list of source lines
         @type list of str
         @param indent indent length the class/method/function definition
         @type int
-        @return end line of the class/method/function
+        @return end line of the class/method/function (one based)
         @rtype int
         """
         # start with zero based line after start line
@@ -440,7 +440,6 @@
     deltaindentcalculated = 0
     
     lineno, last_lineno_pos = 1, 0
-    cur_obj = None
     i = 0
     modifierType = ClbrBaseClasses.Function.General
     modifierIndent = -1
@@ -494,9 +493,6 @@
                     deltaindentcalculated = 0
             # close all classes indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             if classstack:
                 # it's a class method
@@ -507,6 +503,8 @@
                                  file, lineno, meth_sig, annotation=meth_ret,
                                  modifierType=modifier)
                     cur_class._addmethod(meth_name, f)
+                else:
+                    f = None
             else:
                 # it's a function
                 f = Function(module, meth_name,
@@ -519,12 +517,10 @@
                 else:
                     dict_counts[meth_name] = 0
                 dictionary[meth_name] = f
-            endlineno = calculateEndline(lineno, srcLines, thisindent)
-            f.setEndLine(endlineno)
-            if cur_obj and isinstance(cur_obj, Function):
-                cur_obj.setEndLine(lineno - 1)
-            cur_obj = f
-            classstack.append((f, thisindent))  # Marker for nested fns
+            if f:
+                endlineno = calculateEndline(lineno, srcLines, thisindent)
+                f.setEndLine(endlineno)
+                classstack.append((f, thisindent))  # Marker for nested fns
             
             # reset the modifier settings
             modifierType = ClbrBaseClasses.Function.General
@@ -538,9 +534,6 @@
             thisindent = _indent(m.group("ClassIndent"))
             # close all classes indented at least as much
             while classstack and classstack[-1][1] >= thisindent:
-                if classstack[-1][0] is not None:
-                    # record the end line
-                    classstack[-1][0].setEndLine(lineno - 1)
                 del classstack[-1]
             lineno = lineno + src.count('\n', last_lineno_pos, start)
             last_lineno_pos = start
--- a/eric6/Utilities/ModuleParser.py	Wed Sep 09 18:07:21 2020 +0200
+++ b/eric6/Utilities/ModuleParser.py	Fri Sep 11 17:28:59 2020 +0200
@@ -524,8 +524,35 @@
         
         @param src the source text to be scanned (string)
         """
+        def calculateEndline(lineno, lines, indent):
+            """
+            Function to calculate the end line of a class or method/function.
+            
+            @param lineno line number to start at (one based)
+            @type int
+            @param lines list of source lines
+            @type list of str
+            @param indent indent length the class/method/function definition
+            @type int
+            @return end line of the class/method/function (one based)
+            @rtype int
+            """
+            # start with zero based line after start line
+            while lineno < len(lines):
+                line = lines[lineno]
+                if line.strip():
+                    # line contains some text
+                    lineIndent = _indent(line.replace(line.lstrip(), ""))
+                    if lineIndent <= indent:
+                        return lineno
+                lineno += 1
+            
+            # nothing found
+            return -1
+        
+        srcLines = src.splitlines()
+        
         lineno, last_lineno_pos = 1, 0
-        lastGlobalEntry = None
         classstack = []  # stack of (class, indent) pairs
         conditionalsstack = []  # stack of indents of conditional defines
         deltastack = []
@@ -596,12 +623,6 @@
                         deltaindentcalculated = 0
                 # close all classes indented at least as much
                 while classstack and classstack[-1][1] >= thisindent:
-                    if (
-                        classstack[-1][0] is not None and
-                        isinstance(classstack[-1][0], (Class, Function))
-                    ):
-                        # record the end line of this class or function
-                        classstack[-1][0].setEndLine(lineno - 1)
                     del classstack[-1]
                 if classstack:
                     csi = -1
@@ -636,12 +657,8 @@
                                  annotation=meth_ret)
                     self.__py_setVisibility(f)
                     self.addFunction(meth_name, f)
-                if not classstack:
-                    if lastGlobalEntry:
-                        lastGlobalEntry.setEndLine(lineno - 1)
-                    lastGlobalEntry = f
-                if cur_obj and isinstance(cur_obj, Function):
-                    cur_obj.setEndLine(lineno - 1)
+                endlineno = calculateEndline(lineno, srcLines, thisindent)
+                f.setEndLine(endlineno)
                 cur_obj = f
                 classstack.append((None, thisindent))  # Marker for nested fns
                 
@@ -693,12 +710,6 @@
                 last_lineno_pos = start
                 # close all classes indented at least as much
                 while classstack and classstack[-1][1] >= thisindent:
-                    if (
-                        classstack[-1][0] is not None and
-                        isinstance(classstack[-1][0], (Class, Function))
-                    ):
-                        # record the end line of this class or function
-                        classstack[-1][0].setEndLine(lineno - 1)
                     del classstack[-1]
                 class_name = m.group("ClassName")
                 inherit = m.group("ClassSupers")
@@ -730,13 +741,11 @@
                 cur_class = Class(self.name, class_name, inherit,
                                   self.file, lineno)
                 self.__py_setVisibility(cur_class)
+                endlineno = calculateEndline(lineno, srcLines, thisindent)
+                cur_class.setEndLine(endlineno)
                 cur_obj = cur_class
-                # add nested classes to the module
                 self.addClass(class_name, cur_class)
-                if not classstack:
-                    if lastGlobalEntry:
-                        lastGlobalEntry.setEndLine(lineno - 1)
-                    lastGlobalEntry = cur_class
+                # add nested classes to the module
                 classstack.append((cur_class, thisindent))
             
             elif m.start("Attribute") >= 0:
@@ -773,9 +782,6 @@
                         isSignal=isSignal)
                     self.__py_setVisibility(attr)
                     self.addGlobal(variable_name, attr)
-                    if lastGlobalEntry:
-                        lastGlobalEntry.setEndLine(lineno - 1)
-                    lastGlobalEntry = None
                 else:
                     index = -1
                     while index >= -len(classstack):

eric ide

mercurial