diff -r e9e7eca7efee -r bf71ee032bb4 src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/DocStyle/DocStyleChecker.py --- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/DocStyle/DocStyleChecker.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/DocStyle/DocStyleChecker.py Wed Jul 13 14:55:47 2022 +0200 @@ -18,7 +18,7 @@ import contextlib try: - ast.AsyncFunctionDef # __IGNORE_EXCEPTION__ + ast.AsyncFunctionDef # __IGNORE_EXCEPTION__ except AttributeError: ast.AsyncFunctionDef = ast.FunctionDef @@ -27,10 +27,11 @@ """ Class implementing the source context. """ + def __init__(self, source, startLine, contextType): """ Constructor - + @param source source code of the context (list of string or string) @param startLine line number the context starts in the source (integer) @param contextType type of the context object (string) @@ -43,74 +44,73 @@ self.__indent = "" self.__type = contextType self.__special = "" - + # ensure first line is left justified if self.__source: - self.__indent = self.__source[0].replace( - self.__source[0].lstrip(), "") + self.__indent = self.__source[0].replace(self.__source[0].lstrip(), "") self.__source[0] = self.__source[0].lstrip() - + def source(self): """ Public method to get the source. - + @return source (list of string) """ return self.__source - + def ssource(self): """ Public method to get the joined source lines. - + @return source (string) """ return "".join(self.__source) - + def start(self): """ Public method to get the start line number. - + @return start line number (integer) """ return self.__start - + def end(self): """ Public method to get the end line number. - + @return end line number (integer) """ return self.__start + len(self.__source) - 1 - + def indent(self): """ Public method to get the indentation of the first line. - + @return indentation string (string) """ return self.__indent - + def contextType(self): """ Public method to get the context type. - + @return context type (string) """ return self.__type - + def setSpecial(self, special): """ Public method to set a special attribute for the context. - + @param special attribute string @type str """ self.__special = special - + def special(self): """ Public method to get the special context attribute string. - + @return attribute string @rtype str """ @@ -121,27 +121,75 @@ """ Class implementing a checker for documentation string conventions. """ + Codes = [ - "D101", "D102", "D103", "D104", "D105", - "D111", "D112", - "D121", "D122", - "D130", "D131", "D132", "D133", "D134", - "D141", "D142", "D143", "D144", "D145", - - "D201", "D202.1", "D202.2", "D203", "D205", "D206", - "D221", "D222", - "D231", "D232", "D234r", "D234y", "D235r", "D235y", "D236", "D237", - "D238", "D239", - "D242", "D243", "D244", "D245", "D246", "D247", - "D250", "D251", "D252", "D253", - "D260", "D261", "D262", "D263", + "D101", + "D102", + "D103", + "D104", + "D105", + "D111", + "D112", + "D121", + "D122", + "D130", + "D131", + "D132", + "D133", + "D134", + "D141", + "D142", + "D143", + "D144", + "D145", + "D201", + "D202.1", + "D202.2", + "D203", + "D205", + "D206", + "D221", + "D222", + "D231", + "D232", + "D234r", + "D234y", + "D235r", + "D235y", + "D236", + "D237", + "D238", + "D239", + "D242", + "D243", + "D244", + "D245", + "D246", + "D247", + "D250", + "D251", + "D252", + "D253", + "D260", + "D261", + "D262", + "D263", ] - def __init__(self, source, filename, select, ignore, expected, repeat, - maxLineLength=88, docType="pep257"): + def __init__( + self, + source, + filename, + select, + ignore, + expected, + repeat, + maxLineLength=88, + docType="pep257", + ): """ Constructor - + @param source source code to be checked (list of string) @param filename name of the source file (string) @param select list of selected codes (list of string) @@ -154,45 +202,46 @@ (string, one of 'eric' or 'pep257') """ self.__select = tuple(select) - self.__ignore = ('',) if select else tuple(ignore) + self.__ignore = ("",) if select else tuple(ignore) self.__expected = expected[:] self.__repeat = repeat self.__maxLineLength = maxLineLength self.__docType = docType self.__filename = filename self.__source = source[:] - + # statistics counters self.counters = {} - + # collection of detected errors self.errors = [] - + self.__lineNumber = 0 - + # caches self.__functionsCache = None self.__classesCache = None self.__methodsCache = None - + self.__keywords = [ - 'moduleDocstring', 'functionDocstring', - 'classDocstring', 'methodDocstring', - 'defDocstring', 'docstring' + "moduleDocstring", + "functionDocstring", + "classDocstring", + "methodDocstring", + "defDocstring", + "docstring", ] if self.__docType == "pep257": checkersWithCodes = { "moduleDocstring": [ (self.__checkModulesDocstrings, ("D101",)), ], - "functionDocstring": [ - ], + "functionDocstring": [], "classDocstring": [ (self.__checkClassDocstring, ("D104", "D105")), (self.__checkBlankBeforeAndAfterClass, ("D142", "D143")), ], - "methodDocstring": [ - ], + "methodDocstring": [], "defDocstring": [ (self.__checkFunctionDocstring, ("D102", "D103")), (self.__checkImperativeMood, ("D132",)), @@ -216,30 +265,36 @@ "moduleDocstring": [ (self.__checkModulesDocstrings, ("D101", "D201")), ], - "functionDocstring": [ - ], + "functionDocstring": [], "classDocstring": [ (self.__checkClassDocstring, ("D104", "D205", "D206")), - (self.__checkEricNoBlankBeforeAndAfterClassOrFunction, - ("D242", "D243")), + ( + self.__checkEricNoBlankBeforeAndAfterClassOrFunction, + ("D242", "D243"), + ), (self.__checkEricSignal, ("D260", "D261", "D262", "D263")), ], "methodDocstring": [ (self.__checkEricSummary, ("D232")), ], "defDocstring": [ - (self.__checkFunctionDocstring, - ("D102", "D202.1", "D202.2", "D203")), + ( + self.__checkFunctionDocstring, + ("D102", "D202.1", "D202.2", "D203"), + ), (self.__checkImperativeMood, ("D132",)), (self.__checkNoSignature, ("D133",)), (self.__checkEricReturn, ("D234r", "D235r")), (self.__checkEricYield, ("D234y", "D235y")), - (self.__checkEricFunctionArguments, - ("D236", "D237", "D238", "D239")), - (self.__checkEricNoBlankBeforeAndAfterClassOrFunction, - ("D244", "D245")), - (self.__checkEricException, - ("D250", "D251", "D252", "D253")), + ( + self.__checkEricFunctionArguments, + ("D236", "D237", "D238", "D239"), + ), + ( + self.__checkEricNoBlankBeforeAndAfterClassOrFunction, + ("D244", "D245"), + ), + (self.__checkEricException, ("D250", "D251", "D252", "D253")), ], "docstring": [ (self.__checkTripleDoubleQuotes, ("D111",)), @@ -249,19 +304,18 @@ (self.__checkEricEndsWithPeriod, ("D231",)), (self.__checkEricBlankAfterSummary, ("D246",)), (self.__checkEricNBlankAfterLastParagraph, ("D247",)), - (self.__checkEricQuotesOnSeparateLines, ("D222", "D223")) + (self.__checkEricQuotesOnSeparateLines, ("D222", "D223")), ], } - + self.__checkers = {} for key, checkers in checkersWithCodes.items(): for checker, codes in checkers: - if any(not (code and self.__ignoreCode(code)) - for code in codes): + if any(not (code and self.__ignoreCode(code)) for code in codes): if key not in self.__checkers: self.__checkers[key] = [] self.__checkers[key].append(checker) - + def __ignoreCode(self, code): """ Private method to check if the error code should be ignored. @@ -269,13 +323,12 @@ @param code message code to check for (string) @return flag indicating to ignore the given code (boolean) """ - return (code.startswith(self.__ignore) and - not code.startswith(self.__select)) - + return code.startswith(self.__ignore) and not code.startswith(self.__select) + def __error(self, lineNumber, offset, code, *args): """ Private method to record an issue. - + @param lineNumber line number of the issue (integer) @param offset position within line of the issue (integer) @param code message code (string) @@ -283,16 +336,16 @@ """ if self.__ignoreCode(code): return - + if code in self.counters: self.counters[code] += 1 else: self.counters[code] = 1 - + # Don't care about expected codes if code in self.__expected: return - + if code and (self.counters[code] == 1 or self.__repeat): # record the issue with one based line number self.errors.append( @@ -304,24 +357,24 @@ "args": args, } ) - + def __resetReadline(self): """ Private method to reset the internal readline function. """ self.__lineNumber = 0 - + def __readline(self): """ Private method to get the next line from the source. - + @return next line of source (string) """ self.__lineNumber += 1 if self.__lineNumber > len(self.__source): - return '' + return "" return self.__source[self.__lineNumber - 1] - + def run(self): """ Public method to check the given source for violations of doc string @@ -330,68 +383,72 @@ if not self.__filename: # don't do anything, if essential data is missing return - + if not self.__checkers: # don't do anything, if no codes were selected return - + for keyword in self.__keywords: if keyword in self.__checkers: for check in self.__checkers[keyword]: for context in self.__parseContexts(keyword): docstring = self.__parseDocstring(context, keyword) check(docstring, context) - + def __getSummaryLine(self, docstringContext): """ Private method to extract the summary line. - + @param docstringContext docstring context (DocStyleContext) @return summary line (string) and the line it was found on (integer) """ lines = docstringContext.source() - - line = (lines[0] - .replace('r"""', "", 1) - .replace('u"""', "", 1) - .replace('"""', "") - .replace("r'''", "", 1) - .replace("u'''", "", 1) - .replace("'''", "") - .strip()) - + + line = ( + lines[0] + .replace('r"""', "", 1) + .replace('u"""', "", 1) + .replace('"""', "") + .replace("r'''", "", 1) + .replace("u'''", "", 1) + .replace("'''", "") + .strip() + ) + if len(lines) == 1 or len(line) > 0: return line, 0 return lines[1].strip().replace('"""', "").replace("'''", ""), 1 - + def __getSummaryLines(self, docstringContext): """ Private method to extract the summary lines. - + @param docstringContext docstring context (DocStyleContext) @return summary lines (list of string) and the line it was found on (integer) """ summaries = [] lines = docstringContext.source() - - line0 = (lines[0] - .replace('r"""', "", 1) - .replace('u"""', "", 1) - .replace('"""', "") - .replace("r'''", "", 1) - .replace("u'''", "", 1) - .replace("'''", "") - .strip()) + + line0 = ( + lines[0] + .replace('r"""', "", 1) + .replace('u"""', "", 1) + .replace('"""', "") + .replace("r'''", "", 1) + .replace("u'''", "", 1) + .replace("'''", "") + .strip() + ) line1 = ( lines[1].strip().replace('"""', "").replace("'''", "") - if len(lines) > 1 else - "" + if len(lines) > 1 + else "" ) line2 = ( lines[2].strip().replace('"""', "").replace("'''", "") - if len(lines) > 2 else - "" + if len(lines) > 2 + else "" ) if line0: lineno = 0 @@ -409,11 +466,11 @@ lineno = 2 summaries.append(line2) return summaries, lineno - + def __getArgNames(self, node): """ Private method to get the argument names of a function node. - + @param node AST node to extract arguments names from @return tuple of two list of argument names, one for arguments and one for keyword arguments (tuple of list of string) @@ -422,66 +479,65 @@ arguments.extend([arg.arg for arg in node.args.args]) if node.args.vararg is not None: arguments.append(node.args.vararg.arg) - + kwarguments = [] kwarguments.extend([arg.arg for arg in node.args.kwonlyargs]) if node.args.kwarg is not None: kwarguments.append(node.args.kwarg.arg) return arguments, kwarguments - + ################################################################## ## Parsing functionality below ################################################################## - + def __parseModuleDocstring(self, source): """ Private method to extract a docstring given a module source. - + @param source source to parse (list of string) @return context of extracted docstring (DocStyleContext) """ for kind, value, (line, _char), _, _ in tokenize.generate_tokens( - StringIO("".join(source)).readline): + StringIO("".join(source)).readline + ): if kind in [tokenize.COMMENT, tokenize.NEWLINE, tokenize.NL]: continue elif kind == tokenize.STRING: # first STRING should be docstring return DocStyleContext(value, line - 1, "docstring") else: return None - + return None - def __parseDocstring(self, context, what=''): + def __parseDocstring(self, context, what=""): """ Private method to extract a docstring given `def` or `class` source. - + @param context context data to get the docstring from (DocStyleContext) @param what string denoting what is being parsed (string) @return context of extracted docstring (DocStyleContext) """ moduleDocstring = self.__parseModuleDocstring(context.source()) - if what.startswith('module') or context.contextType() == "module": + if what.startswith("module") or context.contextType() == "module": return moduleDocstring if moduleDocstring: return moduleDocstring - - tokenGenerator = tokenize.generate_tokens( - StringIO(context.ssource()).readline) + + tokenGenerator = tokenize.generate_tokens(StringIO(context.ssource()).readline) with contextlib.suppress(StopIteration): kind = None while kind != tokenize.INDENT: kind, _, _, _, _ = next(tokenGenerator) kind, value, (line, char), _, _ = next(tokenGenerator) if kind == tokenize.STRING: # STRING after INDENT is a docstring - return DocStyleContext( - value, context.start() + line - 1, "docstring") - + return DocStyleContext(value, context.start() + line - 1, "docstring") + return None - + def __parseTopLevel(self, keyword): """ Private method to extract top-level functions or classes. - + @param keyword keyword signaling what to extract (string) @return extracted function or class contexts (list of DocStyleContext) """ @@ -492,45 +548,42 @@ try: while True: start, end = None, None - while not (kind == tokenize.NAME and - value == keyword and - char == 0): + while not (kind == tokenize.NAME and value == keyword and char == 0): kind, value, (line, char), _, _ = next(tokenGenerator) start = line - 1, char - while not (kind == tokenize.DEDENT and - value == '' and - char == 0): + while not (kind == tokenize.DEDENT and value == "" and char == 0): kind, value, (line, char), _, _ = next(tokenGenerator) end = line - 1, char - contexts.append(DocStyleContext( - self.__source[start[0]:end[0]], start[0], keyword)) + contexts.append( + DocStyleContext(self.__source[start[0] : end[0]], start[0], keyword) + ) except StopIteration: return contexts - + def __parseFunctions(self): """ Private method to extract top-level functions. - + @return extracted function contexts (list of DocStyleContext) """ if not self.__functionsCache: - self.__functionsCache = self.__parseTopLevel('def') + self.__functionsCache = self.__parseTopLevel("def") return self.__functionsCache - + def __parseClasses(self): """ Private method to extract top-level classes. - + @return extracted class contexts (list of DocStyleContext) """ if not self.__classesCache: - self.__classesCache = self.__parseTopLevel('class') + self.__classesCache = self.__parseTopLevel("class") return self.__classesCache - + def __skipIndentedBlock(self, tokenGenerator): """ Private method to skip over an indented block of source code. - + @param tokenGenerator token generator @return last token of the indented block """ @@ -545,78 +598,73 @@ indent -= 1 if indent == 0: return kind, value, start, end, raw - + return None - + def __parseMethods(self): """ Private method to extract methods of all classes. - + @return extracted method contexts (list of DocStyleContext) """ if not self.__methodsCache: contexts = [] for classContext in self.__parseClasses(): tokenGenerator = tokenize.generate_tokens( - StringIO(classContext.ssource()).readline) + StringIO(classContext.ssource()).readline + ) kind, value, char = None, None, None with contextlib.suppress(StopIteration): while True: start, end = None, None - while not (kind == tokenize.NAME and value == 'def'): - kind, value, (line, char), _, _ = ( - next(tokenGenerator) - ) + while not (kind == tokenize.NAME and value == "def"): + kind, value, (line, char), _, _ = next(tokenGenerator) start = line - 1, char - kind, value, (line, char), _, _ = ( - self.__skipIndentedBlock(tokenGenerator) + kind, value, (line, char), _, _ = self.__skipIndentedBlock( + tokenGenerator ) end = line - 1, char startLine = classContext.start() + start[0] endLine = classContext.start() + end[0] context = DocStyleContext( - self.__source[startLine:endLine], - startLine, "def") + self.__source[startLine:endLine], startLine, "def" + ) if startLine > 0: - if ( - self.__source[startLine - 1].strip() == - "@staticmethod" - ): + if self.__source[startLine - 1].strip() == "@staticmethod": context.setSpecial("staticmethod") - elif ( - self.__source[startLine - 1].strip() == - "@classmethod" - ): + elif self.__source[startLine - 1].strip() == "@classmethod": context.setSpecial("classmethod") contexts.append(context) self.__methodsCache = contexts - + return self.__methodsCache def __parseContexts(self, kind): """ Private method to extract a context from the source. - + @param kind kind of context to extract (string) @return requested contexts (list of DocStyleContext) """ - if kind == 'moduleDocstring': + if kind == "moduleDocstring": return [DocStyleContext(self.__source, 0, "module")] - if kind == 'functionDocstring': + if kind == "functionDocstring": return self.__parseFunctions() - if kind == 'classDocstring': + if kind == "classDocstring": return self.__parseClasses() - if kind == 'methodDocstring': + if kind == "methodDocstring": return self.__parseMethods() - if kind == 'defDocstring': + if kind == "defDocstring": return self.__parseFunctions() + self.__parseMethods() - if kind == 'docstring': - return ([DocStyleContext(self.__source, 0, "module")] + - self.__parseFunctions() + - self.__parseClasses() + - self.__parseMethods()) - return [] # fall back - + if kind == "docstring": + return ( + [DocStyleContext(self.__source, 0, "module")] + + self.__parseFunctions() + + self.__parseClasses() + + self.__parseMethods() + ) + return [] # fall back + ################################################################## ## Checking functionality below (PEP-257) ################################################################## @@ -624,150 +672,140 @@ def __checkModulesDocstrings(self, docstringContext, context): """ Private method to check, if the module has a docstring. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: self.__error(context.start(), 0, "D101") return - + docstring = docstringContext.ssource() - if (not docstring or not docstring.strip() or - not docstring.strip('\'"')): + if not docstring or not docstring.strip() or not docstring.strip("'\""): self.__error(context.start(), 0, "D101") - + if ( - self.__docType == "eric" and - docstring.strip('\'"').strip() == - "Module documentation goes here." + self.__docType == "eric" + and docstring.strip("'\"").strip() == "Module documentation goes here." ): self.__error(docstringContext.end(), 0, "D201") return - + def __checkFunctionDocstring(self, docstringContext, context): """ Private method to check, that all public functions and methods have a docstring. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ functionName = context.source()[0].lstrip().split()[1].split("(")[0] - if functionName.startswith('_') and not functionName.endswith('__'): + if functionName.startswith("_") and not functionName.endswith("__"): if self.__docType == "eric": code = "D203" else: code = "D103" else: code = "D102" - + if docstringContext is None: self.__error(context.start(), 0, code) return - + docstring = docstringContext.ssource() - if (not docstring or not docstring.strip() or - not docstring.strip('\'"')): + if not docstring or not docstring.strip() or not docstring.strip("'\""): self.__error(context.start(), 0, code) - + if self.__docType == "eric": - if ( - docstring.strip('\'"').strip() == - "Function documentation goes here." - ): + if docstring.strip("'\"").strip() == "Function documentation goes here.": self.__error(docstringContext.end(), 0, "D202.1") return - - if ( - "DESCRIPTION" in docstring or - "TYPE" in docstring - ): + + if "DESCRIPTION" in docstring or "TYPE" in docstring: self.__error(docstringContext.end(), 0, "D202.2") return - + def __checkClassDocstring(self, docstringContext, context): """ Private method to check, that all public functions and methods have a docstring. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ className = context.source()[0].lstrip().split()[1].split("(")[0] - if className.startswith('_'): + if className.startswith("_"): if self.__docType == "eric": code = "D205" else: code = "D105" else: code = "D104" - + if docstringContext is None: self.__error(context.start(), 0, code) return - + docstring = docstringContext.ssource() - if (not docstring or not docstring.strip() or - not docstring.strip('\'"')): + if not docstring or not docstring.strip() or not docstring.strip("'\""): self.__error(context.start(), 0, code) return - + if ( - self.__docType == "eric" and - docstring.strip('\'"').strip() == "Class documentation goes here." + self.__docType == "eric" + and docstring.strip("'\"").strip() == "Class documentation goes here." ): self.__error(docstringContext.end(), 0, "D206") return - + def __checkTripleDoubleQuotes(self, docstringContext, context): """ Private method to check, that all docstrings are surrounded by triple double quotes. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + docstring = docstringContext.ssource().strip() if not docstring.startswith(('"""', 'r"""', 'u"""')): self.__error(docstringContext.start(), 0, "D111") - + def __checkBackslashes(self, docstringContext, context): """ Private method to check, that all docstrings containing backslashes are surrounded by raw triple double quotes. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + docstring = docstringContext.ssource().strip() if "\\" in docstring and not docstring.startswith('r"""'): self.__error(docstringContext.start(), 0, "D112") - + def __checkOneLiner(self, docstringContext, context): """ Private method to check, that one-liner docstrings fit on one line with quotes. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + lines = docstringContext.source() if len(lines) > 1: - nonEmptyLines = [line for line in lines - if line.strip().strip('\'"')] + nonEmptyLines = [line for line in lines if line.strip().strip("'\"")] if len(nonEmptyLines) == 1: - modLen = len(context.indent() + '"""' + - nonEmptyLines[0].strip() + '"""') + modLen = len( + context.indent() + '"""' + nonEmptyLines[0].strip() + '"""' + ) if context.contextType() != "module": modLen += 4 if not nonEmptyLines[0].strip().endswith("."): @@ -775,228 +813,221 @@ modLen += 1 if modLen <= self.__maxLineLength: self.__error(docstringContext.start(), 0, "D121") - + def __checkIndent(self, docstringContext, context): """ Private method to check, that docstrings are properly indented. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + lines = docstringContext.source() if len(lines) == 1: return - + nonEmptyLines = [line.rstrip() for line in lines[1:] if line.strip()] if not nonEmptyLines: return - + indent = min(len(line) - len(line.strip()) for line in nonEmptyLines) expectedIndent = ( - 0 - if context.contextType() == "module" else - len(context.indent()) + 4 + 0 if context.contextType() == "module" else len(context.indent()) + 4 ) if indent != expectedIndent: self.__error(docstringContext.start(), 0, "D122") - + def __checkSummary(self, docstringContext, context): """ Private method to check, that docstring summaries contain some text. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + summary, lineNumber = self.__getSummaryLine(docstringContext) if summary == "": self.__error(docstringContext.start() + lineNumber, 0, "D130") - + def __checkEndsWithPeriod(self, docstringContext, context): """ Private method to check, that docstring summaries end with a period. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + summary, lineNumber = self.__getSummaryLine(docstringContext) if not summary.endswith("."): self.__error(docstringContext.start() + lineNumber, 0, "D131") - + def __checkImperativeMood(self, docstringContext, context): """ Private method to check, that docstring summaries are in imperative mood. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + summary, lineNumber = self.__getSummaryLine(docstringContext) if summary: firstWord = summary.strip().split()[0] if firstWord.endswith("s") and not firstWord.endswith("ss"): self.__error(docstringContext.start() + lineNumber, 0, "D132") - + def __checkNoSignature(self, docstringContext, context): """ Private method to check, that docstring summaries don't repeat the function's signature. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + functionName = context.source()[0].lstrip().split()[1].split("(")[0] summary, lineNumber = self.__getSummaryLine(docstringContext) - if ( - functionName + "(" in summary.replace(" ", "") and - functionName + "()" not in summary.replace(" ", "") - ): + if functionName + "(" in summary.replace( + " ", "" + ) and functionName + "()" not in summary.replace(" ", ""): # report only, if it is not an abbreviated form (i.e. function() ) self.__error(docstringContext.start() + lineNumber, 0, "D133") - + def __checkReturnType(self, docstringContext, context): """ Private method to check, that docstrings mention the return value type. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + if "return" not in docstringContext.ssource().lower(): tokens = list( - tokenize.generate_tokens(StringIO(context.ssource()).readline)) - return_ = [tokens[i + 1][0] for i, token in enumerate(tokens) - if token[1] == "return"] - if (set(return_) - - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != - set()): + tokenize.generate_tokens(StringIO(context.ssource()).readline) + ) + return_ = [ + tokens[i + 1][0] + for i, token in enumerate(tokens) + if token[1] == "return" + ] + if ( + set(return_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} + != set() + ): self.__error(docstringContext.end(), 0, "D134") - + def __checkNoBlankLineBefore(self, docstringContext, context): """ Private method to check, that function/method docstrings are not preceded by a blank line. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + contextLines = context.source() cti = 0 - while ( - cti < len(contextLines) and - not contextLines[cti].strip().startswith( - ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")) + while cti < len(contextLines) and not contextLines[cti].strip().startswith( + ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''") ): cti += 1 if cti == len(contextLines): return - + if not contextLines[cti - 1].strip(): self.__error(docstringContext.start(), 0, "D141") - + def __checkBlankBeforeAndAfterClass(self, docstringContext, context): """ Private method to check, that class docstrings have one blank line around them. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + contextLines = context.source() cti = 0 - while ( - cti < len(contextLines) and - not contextLines[cti].strip().startswith( - ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")) + while cti < len(contextLines) and not contextLines[cti].strip().startswith( + ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''") ): cti += 1 if cti == len(contextLines): return - + start = cti - if contextLines[cti].strip() in ( - '"""', 'r"""', 'u"""', "'''", "r'''", "u'''"): + if contextLines[cti].strip() in ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''"): # it is a multi line docstring cti += 1 - - while ( - cti < len(contextLines) and - not contextLines[cti].strip().endswith(('"""', "'''")) + + while cti < len(contextLines) and not contextLines[cti].strip().endswith( + ('"""', "'''") ): cti += 1 end = cti if cti >= len(contextLines) - 1: return - + if contextLines[start - 1].strip(): self.__error(docstringContext.start(), 0, "D142") if contextLines[end + 1].strip(): self.__error(docstringContext.end(), 0, "D143") - + def __checkBlankAfterSummary(self, docstringContext, context): """ Private method to check, that docstring summaries are followed by a blank line. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + docstrings = docstringContext.source() if len(docstrings) <= 3: # correct/invalid one-liner return - + summary, lineNumber = self.__getSummaryLine(docstringContext) - if ( - len(docstrings) > 2 and - docstrings[lineNumber + 1].strip() - ): + if len(docstrings) > 2 and docstrings[lineNumber + 1].strip(): self.__error(docstringContext.start() + lineNumber, 0, "D144") - + def __checkBlankAfterLastParagraph(self, docstringContext, context): """ Private method to check, that the last paragraph of docstrings is followed by a blank line. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + docstrings = docstringContext.source() if len(docstrings) <= 3: # correct/invalid one-liner return - + if docstrings[-2].strip(): self.__error(docstringContext.end(), 0, "D145") - + ################################################################## ## Checking functionality below (eric specific ones) ################################################################## @@ -1005,127 +1036,130 @@ """ Private method to check, that leading and trailing quotes are on a line by themselves. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + lines = docstringContext.source() - if lines[0].strip().strip('ru"\''): + if lines[0].strip().strip("ru\"'"): self.__error(docstringContext.start(), 0, "D221") - if lines[-1].strip().strip('"\''): + if lines[-1].strip().strip("\"'"): self.__error(docstringContext.end(), 0, "D222") - + def __checkEricEndsWithPeriod(self, docstringContext, context): """ Private method to check, that docstring summaries end with a period. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + summaryLines, lineNumber = self.__getSummaryLines(docstringContext) if summaryLines: if summaryLines[-1].lstrip().startswith("@"): summaryLines.pop(-1) summary = " ".join([s.strip() for s in summaryLines if s]) if ( - summary and - not summary.endswith(".") and - summary.split(None, 1)[0].lower() != "constructor" + summary + and not summary.endswith(".") + and summary.split(None, 1)[0].lower() != "constructor" ): self.__error( - docstringContext.start() + lineNumber + - len(summaryLines) - 1, - 0, "D231") - + docstringContext.start() + lineNumber + len(summaryLines) - 1, + 0, + "D231", + ) + def __checkEricReturn(self, docstringContext, context): """ Private method to check, that docstrings contain an @return line if they return anything and don't otherwise. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - - tokens = list( - tokenize.generate_tokens(StringIO(context.ssource()).readline)) - return_ = [tokens[i + 1][0] for i, token in enumerate(tokens) - if token[1] == "return"] + + tokens = list(tokenize.generate_tokens(StringIO(context.ssource()).readline)) + return_ = [ + tokens[i + 1][0] for i, token in enumerate(tokens) if token[1] == "return" + ] if "@return" not in docstringContext.ssource(): - if (set(return_) - - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != - set()): + if ( + set(return_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} + != set() + ): self.__error(docstringContext.end(), 0, "D234r") else: - if (set(return_) - - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} == - set()): + if ( + set(return_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} + == set() + ): self.__error(docstringContext.end(), 0, "D235r") - + def __checkEricYield(self, docstringContext, context): """ Private method to check, that docstrings contain an @yield line if they return anything and don't otherwise. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - - tokens = list( - tokenize.generate_tokens(StringIO(context.ssource()).readline)) - yield_ = [tokens[i + 1][0] for i, token in enumerate(tokens) - if token[1] == "yield"] + + tokens = list(tokenize.generate_tokens(StringIO(context.ssource()).readline)) + yield_ = [ + tokens[i + 1][0] for i, token in enumerate(tokens) if token[1] == "yield" + ] if "@yield" not in docstringContext.ssource(): - if (set(yield_) - - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != - set()): + if set(yield_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != set(): self.__error(docstringContext.end(), 0, "D234y") else: - if (set(yield_) - - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} == - set()): + if set(yield_) - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} == set(): self.__error(docstringContext.end(), 0, "D235y") - + def __checkEricFunctionArguments(self, docstringContext, context): """ Private method to check, that docstrings contain an @param and/or @keyparam line for each argument. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + try: tree = ast.parse(context.ssource()) except (SyntaxError, TypeError): return - if (isinstance(tree, ast.Module) and len(tree.body) == 1 and - isinstance(tree.body[0], - (ast.FunctionDef, ast.AsyncFunctionDef))): + if ( + isinstance(tree, ast.Module) + and len(tree.body) == 1 + and isinstance(tree.body[0], (ast.FunctionDef, ast.AsyncFunctionDef)) + ): functionDef = tree.body[0] argNames, kwNames = self.__getArgNames(functionDef) if "self" in argNames: argNames.remove("self") if "cls" in argNames: argNames.remove("cls") - + docstring = docstringContext.ssource() - if (docstring.count("@param") + docstring.count("@keyparam") < - len(argNames + kwNames)): + if docstring.count("@param") + docstring.count("@keyparam") < len( + argNames + kwNames + ): self.__error(docstringContext.end(), 0, "D236") - elif (docstring.count("@param") + docstring.count("@keyparam") > - len(argNames + kwNames)): + elif docstring.count("@param") + docstring.count("@keyparam") > len( + argNames + kwNames + ): self.__error(docstringContext.end(), 0, "D237") else: # extract @param and @keyparam from docstring @@ -1139,7 +1173,7 @@ if at == "@keyparam": kwargs.append(name.lstrip("*")) args.append(name.lstrip("*")) - + # do the checks for name in kwNames: if name not in kwargs: @@ -1147,24 +1181,23 @@ return if argNames + kwNames != args: self.__error(docstringContext.end(), 0, "D239") - + def __checkEricException(self, docstringContext, context): """ Private method to check, that docstrings contain an @exception line if they raise an exception and don't otherwise. - + Note: This method also checks the raised and documented exceptions for completeness (i.e. raised exceptions that are not documented or documented exceptions that are not raised) - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - - tokens = list( - tokenize.generate_tokens(StringIO(context.ssource()).readline)) + + tokens = list(tokenize.generate_tokens(StringIO(context.ssource()).readline)) exceptions = set() raisedExceptions = set() tokensLen = len(tokens) @@ -1172,28 +1205,22 @@ if token[1] == "raise": exceptions.add(tokens[i + 1][0]) if tokens[i + 1][0] == tokenize.NAME: - if ( - tokensLen > (i + 2) and - tokens[i + 2][1] == "." - ): - raisedExceptions.add("{0}.{1}".format( - tokens[i + 1][1], tokens[i + 3][1])) + if tokensLen > (i + 2) and tokens[i + 2][1] == ".": + raisedExceptions.add( + "{0}.{1}".format(tokens[i + 1][1], tokens[i + 3][1]) + ) else: raisedExceptions.add(tokens[i + 1][1]) - + if ( - "@exception" not in docstringContext.ssource() and - "@throws" not in docstringContext.ssource() and - "@raise" not in docstringContext.ssource() + "@exception" not in docstringContext.ssource() + and "@throws" not in docstringContext.ssource() + and "@raise" not in docstringContext.ssource() ): - if (exceptions - - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != - set()): + if exceptions - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} != set(): self.__error(docstringContext.end(), 0, "D250") else: - if (exceptions - - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} == - set()): + if exceptions - {tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE} == set(): self.__error(docstringContext.end(), 0, "D251") else: # step 1: extract documented exceptions @@ -1204,36 +1231,33 @@ exceptionTokens = line.split(None, 2) if len(exceptionTokens) >= 2: documentedExceptions.add(exceptionTokens[1]) - + # step 2: report undocumented exceptions for exception in raisedExceptions: if exception not in documentedExceptions: - self.__error(docstringContext.end(), 0, "D252", - exception) - + self.__error(docstringContext.end(), 0, "D252", exception) + # step 3: report undefined signals for exception in documentedExceptions: if exception not in raisedExceptions: - self.__error(docstringContext.end(), 0, "D253", - exception) - + self.__error(docstringContext.end(), 0, "D253", exception) + def __checkEricSignal(self, docstringContext, context): """ Private method to check, that docstrings contain an @signal line if they define signals and don't otherwise. - + Note: This method also checks the defined and documented signals for completeness (i.e. defined signals that are not documented or documented signals that are not defined) - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - - tokens = list( - tokenize.generate_tokens(StringIO(context.ssource()).readline)) + + tokens = list(tokenize.generate_tokens(StringIO(context.ssource()).readline)) definedSignals = set() for i, token in enumerate(tokens): if token[1] in ("pyqtSignal", "Signal"): @@ -1241,7 +1265,7 @@ definedSignals.add(tokens[i - 4][1]) elif tokens[i - 1][1] == "=": definedSignals.add(tokens[i - 2][1]) - + if "@signal" not in docstringContext.ssource() and definedSignals: self.__error(docstringContext.end(), 0, "D260") elif "@signal" in docstringContext.ssource(): @@ -1259,79 +1283,76 @@ if "(" in signal: signal = signal.split("(", 1)[0] documentedSignals.add(signal) - + # step 2: report undocumented signals for signal in definedSignals: if signal not in documentedSignals: self.__error(docstringContext.end(), 0, "D262", signal) - + # step 3: report undefined signals for signal in documentedSignals: if signal not in definedSignals: self.__error(docstringContext.end(), 0, "D263", signal) - + def __checkEricBlankAfterSummary(self, docstringContext, context): """ Private method to check, that docstring summaries are followed by a blank line. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + docstrings = docstringContext.source() if len(docstrings) <= 3: # correct/invalid one-liner return - + summaryLines, lineNumber = self.__getSummaryLines(docstringContext) if ( - len(docstrings) - 2 > lineNumber + len(summaryLines) - 1 and - docstrings[lineNumber + len(summaryLines)].strip() + len(docstrings) - 2 > lineNumber + len(summaryLines) - 1 + and docstrings[lineNumber + len(summaryLines)].strip() ): self.__error(docstringContext.start() + lineNumber, 0, "D246") - + def __checkEricNoBlankBeforeAndAfterClassOrFunction( - self, docstringContext, context): + self, docstringContext, context + ): """ Private method to check, that class and function/method docstrings have no blank line around them. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + contextLines = context.source() isClassContext = contextLines[0].lstrip().startswith("class ") cti = 0 - while ( - cti < len(contextLines) and - not contextLines[cti].strip().startswith( - ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''")) + while cti < len(contextLines) and not contextLines[cti].strip().startswith( + ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''") ): cti += 1 if cti == len(contextLines): return - + start = cti - if contextLines[cti].strip() in ( - '"""', 'r"""', 'u"""', "'''", "r'''", "u'''"): + if contextLines[cti].strip() in ('"""', 'r"""', 'u"""', "'''", "r'''", "u'''"): # it is a multi line docstring cti += 1 - - while ( - cti < len(contextLines) and - not contextLines[cti].strip().endswith(('"""', "'''")) + + while cti < len(contextLines) and not contextLines[cti].strip().endswith( + ('"""', "'''") ): cti += 1 end = cti if cti >= len(contextLines) - 1: return - + if isClassContext: if not contextLines[start - 1].strip(): self.__error(docstringContext.start(), 0, "D242") @@ -1344,44 +1365,44 @@ self.__error(docstringContext.start(), 0, "D244") if not contextLines[end + 1].strip(): if ( - self.__docType == "eric_black" and - len(contextLines) > end + 2 and - contextLines[end + 2].strip().startswith("def ") + self.__docType == "eric_black" + and len(contextLines) > end + 2 + and contextLines[end + 2].strip().startswith("def ") ): return - + self.__error(docstringContext.end(), 0, "D245") - + def __checkEricNBlankAfterLastParagraph(self, docstringContext, context): """ Private method to check, that the last paragraph of docstrings is not followed by a blank line. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + docstrings = docstringContext.source() if len(docstrings) <= 3: # correct/invalid one-liner return - + if not docstrings[-2].strip(): self.__error(docstringContext.end(), 0, "D247") - + def __checkEricSummary(self, docstringContext, context): """ Private method to check, that method docstring summaries start with specific words. - + @param docstringContext docstring context (DocStyleContext) @param context context of the docstring (DocStyleContext) """ if docstringContext is None: return - + summary, lineNumber = self.__getSummaryLine(docstringContext) if summary: # check, if the first word is 'Constructor', 'Public', @@ -1390,74 +1411,93 @@ context.source()[0].lstrip().split()[1].split("(", 1) ) firstWord = summary.strip().split(None, 1)[0].lower() - if functionName == '__init__': - if firstWord != 'constructor': - self.__error(docstringContext.start() + lineNumber, 0, - "D232", 'constructor') - elif ( - functionName.startswith('__') and - functionName.endswith('__') - ): - if firstWord != 'special': - self.__error(docstringContext.start() + lineNumber, 0, - "D232", 'special') + if functionName == "__init__": + if firstWord != "constructor": + self.__error( + docstringContext.start() + lineNumber, 0, "D232", "constructor" + ) + elif functionName.startswith("__") and functionName.endswith("__"): + if firstWord != "special": + self.__error( + docstringContext.start() + lineNumber, 0, "D232", "special" + ) elif context.special() == "staticmethod": secondWord = summary.strip().split(None, 2)[1].lower() - if firstWord != 'static' and secondWord != 'static': - self.__error(docstringContext.start() + lineNumber, 0, - "D232", 'static') - elif secondWord == 'static': - if functionName.startswith(('__', 'on_')): - if firstWord != 'private': - self.__error(docstringContext.start() + lineNumber, - 0, "D232", 'private static') - elif ( - functionName.startswith('_') or - functionName.endswith('Event') - ): - if firstWord != 'protected': - self.__error(docstringContext.start() + lineNumber, - 0, "D232", 'protected static') + if firstWord != "static" and secondWord != "static": + self.__error( + docstringContext.start() + lineNumber, 0, "D232", "static" + ) + elif secondWord == "static": + if functionName.startswith(("__", "on_")): + if firstWord != "private": + self.__error( + docstringContext.start() + lineNumber, + 0, + "D232", + "private static", + ) + elif functionName.startswith("_") or functionName.endswith("Event"): + if firstWord != "protected": + self.__error( + docstringContext.start() + lineNumber, + 0, + "D232", + "protected static", + ) else: - if firstWord != 'public': - self.__error(docstringContext.start() + lineNumber, - 0, "D232", 'public static') + if firstWord != "public": + self.__error( + docstringContext.start() + lineNumber, + 0, + "D232", + "public static", + ) elif ( - arguments.startswith(('cls,', 'cls)')) or - context.special() == "classmethod" + arguments.startswith(("cls,", "cls)")) + or context.special() == "classmethod" ): secondWord = summary.strip().split(None, 2)[1].lower() - if firstWord != 'class' and secondWord != 'class': - self.__error(docstringContext.start() + lineNumber, 0, - "D232", 'class') - elif secondWord == 'class': - if functionName.startswith(('__', 'on_')): - if firstWord != 'private': - self.__error(docstringContext.start() + lineNumber, - 0, "D232", 'private class') - elif ( - functionName.startswith('_') or - functionName.endswith('Event') - ): - if firstWord != 'protected': - self.__error(docstringContext.start() + lineNumber, - 0, "D232", 'protected class') + if firstWord != "class" and secondWord != "class": + self.__error( + docstringContext.start() + lineNumber, 0, "D232", "class" + ) + elif secondWord == "class": + if functionName.startswith(("__", "on_")): + if firstWord != "private": + self.__error( + docstringContext.start() + lineNumber, + 0, + "D232", + "private class", + ) + elif functionName.startswith("_") or functionName.endswith("Event"): + if firstWord != "protected": + self.__error( + docstringContext.start() + lineNumber, + 0, + "D232", + "protected class", + ) else: - if firstWord != 'public': - self.__error(docstringContext.start() + lineNumber, - 0, "D232", 'public class') - elif functionName.startswith(('__', 'on_')): - if firstWord != 'private': - self.__error(docstringContext.start() + lineNumber, 0, - "D232", 'private') - elif ( - functionName.startswith('_') or - functionName.endswith('Event') - ): - if firstWord != 'protected': - self.__error(docstringContext.start() + lineNumber, 0, - "D232", 'protected') + if firstWord != "public": + self.__error( + docstringContext.start() + lineNumber, + 0, + "D232", + "public class", + ) + elif functionName.startswith(("__", "on_")): + if firstWord != "private": + self.__error( + docstringContext.start() + lineNumber, 0, "D232", "private" + ) + elif functionName.startswith("_") or functionName.endswith("Event"): + if firstWord != "protected": + self.__error( + docstringContext.start() + lineNumber, 0, "D232", "protected" + ) else: - if firstWord != 'public': - self.__error(docstringContext.start() + lineNumber, 0, - "D232", 'public') + if firstWord != "public": + self.__error( + docstringContext.start() + lineNumber, 0, "D232", "public" + )