--- a/src/eric7/Utilities/ModuleParser.py Thu Jan 04 10:43:29 2024 +0100 +++ b/src/eric7/Utilities/ModuleParser.py Thu Jan 04 17:07:38 2024 +0100 @@ -23,6 +23,8 @@ from functools import reduce +from PyQt6.QtCore import QRegularExpression + from eric7 import Utilities __all__ = [ @@ -66,7 +68,7 @@ return -1 -_py_getnext = re.compile( +_py_getnext = QRegularExpression( r""" (?P<Comment> \# .*? $ # ignore everything in comments @@ -150,7 +152,7 @@ ^ (?P<MethodIndent> [ \t]* ) (?: async [ \t]+ )? (?: cdef | cpdef | def) [ \t]+ - (?P<MethodName> \w+ ) + (?P<MethodName> [\pL_] \w* ) (?: [ \t]* \[ [^\]]+ \] )? [ \t]* \( (?P<MethodSignature> (?: [^)] | \)[ \t]*,? )*? ) @@ -164,7 +166,7 @@ (?P<ClassIndent> [ \t]* ) (?: cdef [ \t]+ )? class [ \t]+ - (?P<ClassName> \w+ ) + (?P<ClassName> [\pL_] \w* ) (?: [ \t]* \[ [^\]]+ \] )? [ \t]* (?P<ClassSupers> \( [^)]* \) )? @@ -175,23 +177,30 @@ ^ (?P<AttributeIndent> [ \t]* ) self [ \t]* \. [ \t]* - (?P<AttributeName> \w+ ) - (?: [ \t]* : [^=\n]+ )? + (?P<AttributeName> [\pL_] \w* ) [ \t]* = ) +| (?P<TypedAttribute> + ^ + (?P<TypedAttributeIndent> [ \t]* ) + self [ \t]* \. [ \t]* + (?P<TypedAttributeName> [\pL_] \w* ) + [ \t]* : [ \t]+ + ) + | (?P<Variable> ^ (?P<VariableIndent> [ \t]* ) - (?P<VariableName> \w+ ) + (?P<VariableName> [\pL_] \w* ) [ \t]* = [ \t]* (?P<VariableSignal> (?:pyqtSignal)? ) ) | (?P<TypedVariable> ^ (?P<TypedVariableIndent> [ \t]* ) - (?P<TypedVariableName> \w+ ) - [ \t]* : + (?P<TypedVariableName> [\pL_] \w* ) + [ \t]* : [ \t]+ ) | (?P<Main> @@ -226,10 +235,12 @@ (?: (?: if | elif ) [ \t]+ [^:]* | else [ \t]* ) : (?= \s* (?: async [ \t]+ )? def) )""", - re.VERBOSE | re.MULTILINE, -).search + QRegularExpression.PatternOption.MultilineOption + | QRegularExpression.PatternOption.ExtendedPatternSyntaxOption + | QRegularExpression.PatternOption.UseUnicodePropertiesOption, +).match -_rb_getnext = re.compile( +_rb_getnext = QRegularExpression( r""" (?P<Docstring> =begin [ \t]+ edoc (?P<DocstringContents> .*? ) =end @@ -364,8 +375,10 @@ end \b [^_] ) )""", - re.VERBOSE | re.MULTILINE, -).search + QRegularExpression.PatternOption.MultilineOption + | QRegularExpression.PatternOption.ExtendedPatternSyntaxOption + | QRegularExpression.PatternOption.UseUnicodePropertiesOption, +).match _hashsub = re.compile(r"""^([ \t]*)#[ \t]?""", re.MULTILINE).sub @@ -613,25 +626,25 @@ modifierIndent = -1 while True: m = self._getnext(src, i) - if not m: + if not m.hasMatch(): break - start, i = m.span() + start, i = m.capturedStart(), m.capturedEnd() - if m.start("MethodModifier") >= 0: - modifierIndent = _indent(m.group("MethodModifierIndent")) - modifierType = m.group("MethodModifierType") + if m.captured("MethodModifier"): + modifierIndent = _indent(m.captured("MethodModifierIndent")) + modifierType = m.captured("MethodModifierType") - elif m.start("Method") >= 0: + elif m.captured("Method"): # found a method definition or function - thisindent = _indent(m.group("MethodIndent")) - meth_name = m.group("MethodName") - meth_sig = m.group("MethodSignature") + thisindent = _indent(m.captured("MethodIndent")) + meth_name = m.captured("MethodName") + meth_sig = m.captured("MethodSignature") meth_sig = meth_sig.replace("\\\n", "") - meth_ret = m.group("MethodReturnAnnotation") + meth_ret = m.captured("MethodReturnAnnotation") meth_ret = meth_ret.replace("\\\n", "") - if m.group("MethodPyQtSignature") is not None: + if m.captured("MethodPyQtSignature"): meth_pyqtSig = ( - m.group("MethodPyQtSignature") + m.captured("MethodPyQtSignature") .replace("\\\n", "") .split("result")[0] .split("name")[0] @@ -640,6 +653,13 @@ else: meth_pyqtSig = None lineno += src.count("\n", last_lineno_pos, start) + def_lineno = ( + lineno + src.count( + "\n", start, m.capturedStart("MethodIndent") + ) + if m.capturedStart("MethodIndent") > start + else lineno + ) last_lineno_pos = start if modifierType and modifierIndent == thisindent: if modifierType == "@staticmethod": @@ -719,7 +739,7 @@ ) self.__py_setVisibility(f) self.addFunction(meth_name, f) - endlineno = calculateEndline(lineno, srcLines, thisindent) + endlineno = calculateEndline(def_lineno, srcLines, thisindent) f.setEndLine(endlineno) cur_obj = f classstack.append((None, thisindent)) # Marker for nested fns @@ -728,49 +748,49 @@ modifierType = Function.General modifierIndent = -1 - elif m.start("Docstring") >= 0: - contents = m.group("DocstringContents3") - if contents is not None: + elif m.captured("Docstring"): + contents = m.captured("DocstringContents3") + if contents: contents = _hashsub(r"\1", contents) else: if self.file.lower().endswith(".ptl"): contents = "" else: - contents = m.group("DocstringContents1") or m.group( + contents = m.captured("DocstringContents1") or m.captured( "DocstringContents2" ) if cur_obj: cur_obj.addDescription(contents) - elif m.start("String") >= 0: + elif m.captured("String"): if modulelevel and ( src[start - len("\r\n") : start] == "\r\n" or src[start - len("\n") : start] == "\n" or src[start - len("\r") : start] == "\r" ): - contents = m.group("StringContents3") - if contents is not None: + contents = m.captured("StringContents3") + if contents: contents = _hashsub(r"\1", contents) else: if self.file.lower().endswith(".ptl"): contents = "" else: - contents = m.group("StringContents1") or m.group( + contents = m.captured("StringContents1") or m.captured( "StringContents2" ) if cur_obj: cur_obj.addDescription(contents) - elif m.start("Class") >= 0: + elif m.captured("Class"): # we found a class definition - thisindent = _indent(m.group("ClassIndent")) + thisindent = _indent(m.captured("ClassIndent")) lineno += src.count("\n", last_lineno_pos, start) last_lineno_pos = start # close all classes indented at least as much while classstack and classstack[-1][1] >= thisindent: del classstack[-1] - class_name = m.group("ClassName") - inherit = m.group("ClassSupers") + class_name = m.captured("ClassName") + inherit = m.captured("ClassSupers") if inherit: # the class inherits from other classes inherit = inherit[1:-1].strip() @@ -819,13 +839,16 @@ # add nested classes to the module classstack.append((cur_class, thisindent)) - elif m.start("Attribute") >= 0: + elif m.captured("Attribute") or m.captured("TypedAttribute"): + if m.captured("Attribute"): + attrName = m.captured("AttributeName") + else: + attrName = m.captured("TypedAttributeName") lineno += src.count("\n", last_lineno_pos, start) last_lineno_pos = start index = -1 while index >= -len(classstack): if classstack[index][0] is not None: - attrName = m.group("AttributeName") attr = Attribute(self.name, attrName, self.file, lineno) self.__py_setVisibility(attr) classstack[index][0].addAttribute(attrName, attr) @@ -833,19 +856,28 @@ else: index -= 1 - elif m.start("Main") >= 0: + elif m.captured("Main"): # 'main' part of the script, reset class stack lineno += src.count("\n", last_lineno_pos, start) last_lineno_pos = start classstack = [] - elif m.start("Variable") >= 0: - thisindent = _indent(m.group("VariableIndent")) - variable_name = m.group("VariableName") - isSignal = m.group("VariableSignal") != "" + elif m.captured("Variable") or m.captured("TypedVariable"): + if m.captured("Variable"): + thisindent = _indent(m.captured("VariableIndent")) + variable_name = m.captured("VariableName") + isSignal = m.captured("VariableSignal") != "" + else: + thisindent = _indent(m.captured("TypedVariableIndent")) + variable_name = m.captured("TypedVariableName") + isSignal = False + if keyword.iskeyword(variable_name): + # only if the determined name is not a keyword + # (e.g. else, except) + continue lineno += src.count("\n", last_lineno_pos, start) last_lineno_pos = start - if thisindent == 0: + if thisindent == 0 or not classstack: # global variable attr = Attribute( self.name, variable_name, self.file, lineno, isSignal=isSignal @@ -872,39 +904,11 @@ classstack[index][0].addGlobal(variable_name, attr) break - elif m.start("TypedVariable") >= 0: - thisindent = _indent(m.group("TypedVariableIndent")) - variable_name = m.group("TypedVariableName") - if not keyword.iskeyword(variable_name): - # only if the determined name is not a keyword (e.g. else, except) - lineno += src.count("\n", last_lineno_pos, start) - last_lineno_pos = start - if thisindent == 0: - # global variable - attr = Attribute(self.name, variable_name, self.file, lineno) - self.__py_setVisibility(attr) - self.addGlobal(variable_name, attr) - else: - index = -1 - while index >= -len(classstack): - if classstack[index][1] >= thisindent: - index -= 1 - else: - if classstack[index][0] is not None and isinstance( - classstack[index][0], Class - ): - attr = Attribute( - self.name, variable_name, self.file, lineno - ) - self.__py_setVisibility(attr) - classstack[index][0].addGlobal(variable_name, attr) - break - - elif m.start("Import") >= 0: + elif m.captured("Import"): # - import module names = [ n.strip() - for n in "".join(m.group("ImportList").splitlines()) + for n in "".join(m.captured("ImportList").splitlines()) .replace("\\", "") .split(",") ] @@ -912,11 +916,11 @@ [name for name in names if name not in self.imports] ) - elif m.start("ImportFrom") >= 0: + elif m.captured("ImportFrom"): # - from module import stuff - mod = m.group("ImportFromPath") + mod = m.captured("ImportFromPath") namesLines = ( - m.group("ImportFromList") + m.captured("ImportFromList") .replace("(", "") .replace(")", "") .replace("\\", "") @@ -931,9 +935,9 @@ [name for name in names if name not in self.from_imports[mod]] ) - elif m.start("ConditionalDefine") >= 0: + elif m.captured("ConditionalDefine"): # a conditional function/method definition - thisindent = _indent(m.group("ConditionalDefineIndent")) + thisindent = _indent(m.captured("ConditionalDefineIndent")) while conditionalsstack and conditionalsstack[-1] >= thisindent: del conditionalsstack[-1] if deltastack: @@ -941,7 +945,7 @@ conditionalsstack.append(thisindent) deltaindentcalculated = 0 - elif m.start("Comment") >= 0 and modulelevel: + elif m.captured("Comment") and modulelevel: continue modulelevel = False @@ -967,16 +971,16 @@ break start, i = m.span() - if m.start("Method") >= 0: + if m.captured("Method"): # found a method definition or function thisindent = indent indent += 1 meth_name = ( - m.group("MethodName") - or m.group("MethodName2") - or m.group("MethodName3") + m.captured("MethodName") + or m.captured("MethodName2") + or m.captured("MethodName3") ) - meth_sig = m.group("MethodSignature") + meth_sig = m.captured("MethodSignature") meth_sig = meth_sig and meth_sig.replace("\\\n", "") or "" lineno += src.count("\n", last_lineno_pos, start) last_lineno_pos = start @@ -1034,14 +1038,14 @@ cur_obj = f classstack.append((None, thisindent)) # Marker for nested fns - elif m.start("Docstring") >= 0: - contents = m.group("DocstringContents") - if contents is not None: + elif m.captured("Docstring"): + contents = m.captured("DocstringContents") + if contents: contents = _hashsub(r"\1", contents) if cur_obj: cur_obj.addDescription(contents) - elif m.start("Class") >= 0: + elif m.captured("Class"): # we found a class definition thisindent = indent indent += 1 @@ -1055,8 +1059,8 @@ # record the end line of this class, function or module classstack[-1][0].setEndLine(lineno - 1) del classstack[-1] - class_name = m.group("ClassName") or m.group("ClassName2") - inherit = m.group("ClassSupers") + class_name = m.captured("ClassName") or m.captured("ClassName2") + inherit = m.captured("ClassSupers") if inherit: # the class inherits from other classes inherit = inherit[1:].strip() @@ -1089,7 +1093,7 @@ acstack.append(["public", thisindent]) # default access control is 'public' - elif m.start("Module") >= 0: + elif m.captured("Module"): # we found a module definition thisindent = indent indent += 1 @@ -1103,7 +1107,7 @@ # record the end line of this class, function or module classstack[-1][0].setEndLine(lineno - 1) del classstack[-1] - module_name = m.group("ModuleName") + module_name = m.captured("ModuleName") # remember this class cur_class = RbModule(self.name, module_name, self.file, lineno) # add nested Ruby modules to the file @@ -1122,21 +1126,9 @@ acstack.append(["public", thisindent]) # default access control is 'public' - elif m.start("AccessControl") >= 0: - aclist = m.group("AccessControlList") - if aclist is None: - index = -1 - while index >= -len(acstack): - if acstack[index][1] < indent: - actype = ( - m.group("AccessControlType") - or m.group("AccessControlType2").split("_")[0] - ) - acstack[index][0] = actype.lower() - break - else: - index -= 1 - else: + elif m.captured("AccessControl"): + aclist = m.captured("AccessControlList") + if aclist: index = -1 while index >= -len(classstack): if ( @@ -1146,8 +1138,8 @@ ): parent = classstack[index][0] actype = ( - m.group("AccessControlType") - or m.group("AccessControlType2").split("_")[0] + m.captured("AccessControlType") + or m.captured("AccessControlType2").split("_")[0] ) actype = actype.lower() for name in aclist.split(","): @@ -1165,8 +1157,20 @@ break else: index -= 1 + else: + index = -1 + while index >= -len(acstack): + if acstack[index][1] < indent: + actype = ( + m.captured("AccessControlType") + or m.captured("AccessControlType2").split("_")[0] + ) + acstack[index][0] = actype.lower() + break + else: + index -= 1 - elif m.start("Attribute") >= 0: + elif m.captured("Attribute"): lineno += src.count("\n", last_lineno_pos, start) last_lineno_pos = start index = -1 @@ -1176,7 +1180,7 @@ and not isinstance(classstack[index][0], Function) and classstack[index][1] < indent ): - attrName = m.group("AttributeName") + attrName = m.captured("AttributeName") attr = Attribute(self.name, attrName, self.file, lineno) if attrName.startswith("@@") or attrName[0].isupper(): classstack[index][0].addGlobal(attrName, attr) @@ -1186,7 +1190,7 @@ else: index -= 1 else: - attrName = m.group("AttributeName") + attrName = m.captured("AttributeName") if attrName[0] != "@": attr = Attribute(self.name, attrName, self.file, lineno) self.addGlobal(attrName, attr) @@ -1194,7 +1198,7 @@ lastGlobalEntry.setEndLine(lineno - 1) lastGlobalEntry = None - elif m.start("Attr") >= 0: + elif m.captured("Attr"): lineno += src.count("\n", last_lineno_pos, start) last_lineno_pos = start index = -1 @@ -1205,25 +1209,9 @@ and classstack[index][1] < indent ): parent = classstack[index][0] - if m.group("AttrType") is None: - nv = m.group("AttrList").split(",") - if not nv: - break - # get rid of leading ':' - name = nv[0].strip()[1:] - attr = ( - parent.getAttribute("@" + name) - or parent.getAttribute("@@" + name) - or Attribute(self.name, "@" + name, self.file, lineno) - ) - if len(nv) == 1 or nv[1].strip() == "false": - attr.setProtected() - elif nv[1].strip() == "true": - attr.setPublic() - parent.addAttribute(attr.name, attr) - else: - access = m.group("AttrType") - for name in m.group("AttrList").split(","): + if m.captured("AttrType"): + access = m.captured("AttrType") + for name in m.captured("AttrList").split(","): # get rid of leading ':' name = name.strip()[1:] attr = ( @@ -1241,15 +1229,31 @@ elif attr.isProtected(): attr.setPublic() parent.addAttribute(attr.name, attr) + else: + nv = m.captured("AttrList").split(",") + if not nv: + break + # get rid of leading ':' + name = nv[0].strip()[1:] + attr = ( + parent.getAttribute("@" + name) + or parent.getAttribute("@@" + name) + or Attribute(self.name, "@" + name, self.file, lineno) + ) + if len(nv) == 1 or nv[1].strip() == "false": + attr.setProtected() + elif nv[1].strip() == "true": + attr.setPublic() + parent.addAttribute(attr.name, attr) break else: index -= 1 - elif m.start("Begin") >= 0: + elif m.captured("Begin"): # a begin of a block we are not interested in indent += 1 - elif m.start("End") >= 0: + elif m.captured("End"): # an end of a block indent -= 1 if indent < 0: @@ -1261,10 +1265,10 @@ indent = 0 elif ( - m.start("String") >= 0 - or m.start("Comment") >= 0 - or m.start("ClassIgnored") >= 0 - or m.start("BeginEnd") >= 0 + m.captured("String") + or m.captured("Comment") + or m.captured("ClassIgnored") + or m.captured("BeginEnd") ): pass