src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py

branch
eric7
changeset 10048
df836ff707fd
parent 9653
e67609152c5e
child 10085
b5808c3a9967
equal deleted inserted replaced
10047:cc9ead6d1c46 10048:df836ff707fd
126 "M523", 126 "M523",
127 "M524", 127 "M524",
128 "M525", 128 "M525",
129 "M526", 129 "M526",
130 "M527", 130 "M527",
131 "M528",
132 "M529",
133 "M530",
134 "M531",
135 "M532",
136 "M533",
131 ## Bugbear++ 137 ## Bugbear++
132 "M581", 138 "M581",
133 "M582", 139 "M582",
134 ## Format Strings 140 ## Format Strings
135 "M601", 141 "M601",
311 "M523", 317 "M523",
312 "M524", 318 "M524",
313 "M525", 319 "M525",
314 "M526", 320 "M526",
315 "M527", 321 "M527",
322 "M528",
323 "M529",
324 "M530",
325 "M531",
326 "M532",
327 "M533",
316 "M581", 328 "M581",
317 "M582", 329 "M582",
318 ), 330 ),
319 ), 331 ),
320 (self.__checkPep3101, ("M601",)), 332 (self.__checkPep3101, ("M601",)),
1530 """ 1542 """
1531 Class implementing a node visitor to check for various topics. 1543 Class implementing a node visitor to check for various topics.
1532 """ 1544 """
1533 1545
1534 # 1546 #
1535 # This class was implemented along the BugBear flake8 extension (v 22.12.6). 1547 # This class was implemented along flake8-bugbear (v 22.12.6).
1536 # Original: Copyright (c) 2016 Łukasz Langa 1548 # Original: Copyright (c) 2016 Łukasz Langa
1537 # 1549 #
1538 1550
1539 CONTEXTFUL_NODES = ( 1551 CONTEXTFUL_NODES = (
1540 ast.Module, 1552 ast.Module,
1565 self.nodeWindow = [] 1577 self.nodeWindow = []
1566 self.violations = [] 1578 self.violations = []
1567 self.contexts = [] 1579 self.contexts = []
1568 1580
1569 self.__M523Seen = set() 1581 self.__M523Seen = set()
1582 self.__M505Imports = set()
1570 1583
1571 @property 1584 @property
1572 def nodeStack(self): 1585 def nodeStack(self):
1573 """ 1586 """
1574 Public method to get a reference to the most recent node stack. 1587 Public method to get a reference to the most recent node stack.
1705 yield node 1718 yield node
1706 if not isinstance(node, BugBearVisitor.FUNCTION_NODES): 1719 if not isinstance(node, BugBearVisitor.FUNCTION_NODES):
1707 for child in ast.iter_child_nodes(node): 1720 for child in ast.iter_child_nodes(node):
1708 yield from self.__childrenInScope(child) 1721 yield from self.__childrenInScope(child)
1709 1722
1710 def visit(self, node): 1723 def __flattenExcepthandler(self, node):
1711 """ 1724 """
1712 Public method to traverse a given AST node. 1725 Private method to flatten the list of exceptions handled by an except handler.
1713 1726
1714 @param node AST node to be traversed 1727 @param node reference to the node to be processed
1715 @type ast.Node 1728 @type ast.Node
1716 """ 1729 @yield reference to the exception type node
1717 isContextful = isinstance(node, BugBearVisitor.CONTEXTFUL_NODES) 1730 @ytype ast.Node
1718 1731 """
1719 if isContextful: 1732 if not isinstance(node, ast.Tuple):
1720 context = BugBearContext(node, []) 1733 yield node
1721 self.contexts.append(context) 1734 return
1722 1735
1723 self.nodeStack.append(node) 1736 exprList = node.elts.copy()
1724 self.nodeWindow.append(node) 1737 while len(exprList):
1725 self.nodeWindow = self.nodeWindow[-BugBearVisitor.NodeWindowSize :] 1738 expr = exprList.pop(0)
1726 1739 if isinstance(expr, ast.Starred) and isinstance(
1727 super().visit(node) 1740 expr.value, (ast.List, ast.Tuple)
1728 1741 ):
1729 self.nodeStack.pop() 1742 exprList.extend(expr.value.elts)
1730 1743 continue
1731 if isContextful: 1744 yield expr
1732 self.contexts.pop() 1745
1733 1746 def __checkRedundantExcepthandlers(self, names, node):
1734 def visit_ExceptHandler(self, node): 1747 """
1735 """ 1748 Private method to check for redundant exception types in an exception handler.
1736 Public method to handle exception handlers. 1749
1737 1750 @param names list of exception types to be checked
1738 @param node reference to the node to be processed 1751 @type list of ast.Name
1739 @type ast.ExceptHandler 1752 @param node reference to the exception handler node
1753 @type ast.ExceptionHandler
1754 @return tuple containing the error data
1755 @rtype tuple of (ast.Node, str, str, str, str)
1740 """ 1756 """
1741 redundantExceptions = { 1757 redundantExceptions = {
1742 "OSError": { 1758 "OSError": {
1743 # All of these are actually aliases of OSError since Python 3.3 1759 # All of these are actually aliases of OSError since Python 3.3
1744 "IOError", 1760 "IOError",
1751 "ValueError": { 1767 "ValueError": {
1752 "binascii.Error", 1768 "binascii.Error",
1753 }, 1769 },
1754 } 1770 }
1755 1771
1772 # See if any of the given exception names could be removed, e.g. from:
1773 # (MyError, MyError) # duplicate names
1774 # (MyError, BaseException) # everything derives from the Base
1775 # (Exception, TypeError) # builtins where one subclasses another
1776 # (IOError, OSError) # IOError is an alias of OSError since Python3.3
1777 # but note that other cases are impractical to handle from the AST.
1778 # We expect this is mostly useful for users who do not have the
1779 # builtin exception hierarchy memorised, and include a 'shadowed'
1780 # subtype without realising that it's redundant.
1781 good = sorted(set(names), key=names.index)
1782 if "BaseException" in good:
1783 good = ["BaseException"]
1784 # Remove redundant exceptions that the automatic system either handles
1785 # poorly (usually aliases) or can't be checked (e.g. it's not an
1786 # built-in exception).
1787 for primary, equivalents in redundantExceptions.items():
1788 if primary in good:
1789 good = [g for g in good if g not in equivalents]
1790
1791 for name, other in itertools.permutations(tuple(good), 2):
1792 if (
1793 self.__typesafeIssubclass(
1794 getattr(builtins, name, type), getattr(builtins, other, ())
1795 )
1796 and name in good
1797 ):
1798 good.remove(name)
1799 if good != names:
1800 desc = good[0] if len(good) == 1 else "({0})".format(", ".join(good))
1801 as_ = " as " + node.name if node.name is not None else ""
1802 return (node, "M514", ", ".join(names), as_, desc)
1803
1804 return None
1805
1806 def __walkList(self, nodes):
1807 """
1808 Private method to walk a given list of nodes.
1809
1810 @param nodes list of nodes to walk
1811 @type list of ast.Node
1812 @yield node references as determined by the ast.walk() function
1813 @ytype ast.Node
1814 """
1815 for node in nodes:
1816 yield from ast.walk(node)
1817
1818 def visit(self, node):
1819 """
1820 Public method to traverse a given AST node.
1821
1822 @param node AST node to be traversed
1823 @type ast.Node
1824 """
1825 isContextful = isinstance(node, BugBearVisitor.CONTEXTFUL_NODES)
1826
1827 if isContextful:
1828 context = BugBearContext(node, [])
1829 self.contexts.append(context)
1830
1831 self.nodeStack.append(node)
1832 self.nodeWindow.append(node)
1833 self.nodeWindow = self.nodeWindow[-BugBearVisitor.NodeWindowSize :]
1834
1835 super().visit(node)
1836
1837 self.nodeStack.pop()
1838
1839 if isContextful:
1840 self.contexts.pop()
1841
1842 def visit_ExceptHandler(self, node):
1843 """
1844 Public method to handle exception handlers.
1845
1846 @param node reference to the node to be processed
1847 @type ast.ExceptHandler
1848 """
1756 if node.type is None: 1849 if node.type is None:
1757 # bare except is handled by pycodestyle already 1850 # bare except is handled by pycodestyle already
1758 pass 1851 pass
1759 1852
1760 elif isinstance(node.type, ast.Tuple): 1853 else:
1761 names = [self.__toNameStr(e) for e in node.type.elts] 1854 handlers = self.__flattenExcepthandler(node.type)
1762 as_ = " as " + node.name if node.name is not None else "" 1855 names = []
1763 if len(names) == 0: 1856 badHandlers = []
1764 self.violations.append((node, "M501", as_)) 1857 ignoredHandlers = []
1765 elif len(names) == 1: 1858 for handler in handlers:
1859 if isinstance(handler, (ast.Name, ast.Attribute)):
1860 name = self.__toNameStr(handler)
1861 if name is None:
1862 ignoredHandlers.append(handler)
1863 else:
1864 names.append(name)
1865 elif isinstance(handler, (ast.Call, ast.Starred)):
1866 ignoredHandlers.append(handler)
1867 else:
1868 badHandlers.append(handler)
1869 if badHandlers:
1870 self.violations.append((node, "M530"))
1871 if len(names) == 0 and not badHandlers and not ignoredHandlers:
1872 self.violations.append((node, "M529"))
1873 elif (
1874 len(names) == 1
1875 and not badHandlers
1876 and not ignoredHandlers
1877 and isinstance(node.type, ast.Tuple)
1878 ):
1766 self.violations.append((node, "M513", *names)) 1879 self.violations.append((node, "M513", *names))
1767 else: 1880 else:
1768 # See if any of the given exception names could be removed, e.g. from: 1881 maybeError = self.__checkRedundantExcepthandlers(names, node)
1769 # (MyError, MyError) # duplicate names 1882 if maybeError is not None:
1770 # (MyError, BaseException) # everything derives from the Base 1883 self.violations.append(maybeError)
1771 # (Exception, TypeError) # builtins where one subclasses another
1772 # (IOError, OSError) # IOError is an alias of OSError since Python3.3
1773 # but note that other cases are impractical to handle from the AST.
1774 # We expect this is mostly useful for users who do not have the
1775 # builtin exception hierarchy memorised, and include a 'shadowed'
1776 # subtype without realising that it's redundant.
1777 good = sorted(set(names), key=names.index)
1778 if "BaseException" in good:
1779 good = ["BaseException"]
1780 # Remove redundant exceptions that the automatic system either handles
1781 # poorly (usually aliases) or can't be checked (e.g. it's not an
1782 # built-in exception).
1783 for primary, equivalents in redundantExceptions.items():
1784 if primary in good:
1785 good = [g for g in good if g not in equivalents]
1786
1787 for name, other in itertools.permutations(tuple(good), 2):
1788 if (
1789 self.__typesafeIssubclass(
1790 getattr(builtins, name, type), getattr(builtins, other, ())
1791 )
1792 and name in good
1793 ):
1794 good.remove(name)
1795 if good != names:
1796 desc = (
1797 good[0] if len(good) == 1 else "({0})".format(", ".join(good))
1798 )
1799 self.violations.append((node, "M514", ", ".join(names), as_, desc))
1800 1884
1801 self.generic_visit(node) 1885 self.generic_visit(node)
1802 1886
1803 def visit_UAdd(self, node): 1887 def visit_UAdd(self, node):
1804 """ 1888 """
1858 ): 1942 ):
1859 self.violations.append((node, "M510")) 1943 self.violations.append((node, "M510"))
1860 1944
1861 self.__checkForM526(node) 1945 self.__checkForM526(node)
1862 1946
1947 self.__checkForM528(node)
1948
1949 self.generic_visit(node)
1950
1951 def visit_Module(self, node):
1952 """
1953 Public method to handle a module node.
1954
1955 @param node reference to the node to be processed
1956 @type ast.Module
1957 """
1958 self.__checkForM518(node)
1959
1863 self.generic_visit(node) 1960 self.generic_visit(node)
1864 1961
1865 def visit_Assign(self, node): 1962 def visit_Assign(self, node):
1866 """ 1963 """
1867 Public method to handle assignments. 1964 Public method to handle assignments.
1888 @type ast.For 1985 @type ast.For
1889 """ 1986 """
1890 self.__checkForM507(node) 1987 self.__checkForM507(node)
1891 self.__checkForM520(node) 1988 self.__checkForM520(node)
1892 self.__checkForM523(node) 1989 self.__checkForM523(node)
1990 self.__checkForM531(node)
1893 1991
1894 self.generic_visit(node) 1992 self.generic_visit(node)
1895 1993
1896 def visit_AsyncFor(self, node): 1994 def visit_AsyncFor(self, node):
1897 """ 1995 """
1901 @type ast.AsyncFor 1999 @type ast.AsyncFor
1902 """ 2000 """
1903 self.__checkForM507(node) 2001 self.__checkForM507(node)
1904 self.__checkForM520(node) 2002 self.__checkForM520(node)
1905 self.__checkForM523(node) 2003 self.__checkForM523(node)
2004 self.__checkForM531(node)
1906 2005
1907 self.generic_visit(node) 2006 self.generic_visit(node)
1908 2007
1909 def visit_While(self, node): 2008 def visit_While(self, node):
1910 """ 2009 """
2059 if isinstance(value, ast.FormattedValue): 2158 if isinstance(value, ast.FormattedValue):
2060 return 2159 return
2061 2160
2062 self.violations.append((node, "M581")) 2161 self.violations.append((node, "M581"))
2063 2162
2163 def visit_AnnAssign(self, node):
2164 """
2165 Public method to check annotated assign statements.
2166
2167 @param node reference to the node to be processed
2168 @type ast.AnnAssign
2169 """
2170 self.__checkForM532(node)
2171
2172 self.generic_visit(node)
2173
2174 def visit_Import(self, node):
2175 """
2176 Public method to check imports.
2177
2178 @param node reference to the node to be processed
2179 @type ast.Import
2180 """
2181 self.__checkForM505(node)
2182
2183 self.generic_visit(node)
2184
2185 def visit_Set(self, node):
2186 """
2187 Public method to check a set.
2188
2189 @param node reference to the node to be processed
2190 @type ast.Set
2191 """
2192 self.__checkForM533(node)
2193
2194 self.generic_visit(node)
2195
2064 def __checkForM505(self, node): 2196 def __checkForM505(self, node):
2065 """ 2197 """
2066 Private method to check the use of *strip(). 2198 Private method to check the use of *strip().
2067 2199
2068 @param node reference to the node to be processed 2200 @param node reference to the node to be processed
2069 @type ast.Call 2201 @type ast.Call
2070 """ 2202 """
2071 if node.func.attr not in ("lstrip", "rstrip", "strip"): 2203 if isinstance(node, ast.Import):
2072 return # method name doesn't match 2204 for name in node.names:
2073 2205 self.__M505Imports.add(name.asname or name.name)
2074 if len(node.args) != 1 or not AstUtilities.isString(node.args[0]): 2206 elif isinstance(node, ast.Call):
2075 return # used arguments don't match the builtin strip 2207 if node.func.attr not in ("lstrip", "rstrip", "strip"):
2076 2208 return # method name doesn't match
2077 s = AstUtilities.getValue(node.args[0]) 2209
2078 if len(s) == 1: 2210 if (
2079 return # stripping just one character 2211 isinstance(node.func.value, ast.Name)
2080 2212 and node.func.value.id in self.__M505Imports
2081 if len(s) == len(set(s)): 2213 ):
2082 return # no characters appear more than once 2214 return # method is being run on an imported module
2083 2215
2084 self.violations.append((node, "M505")) 2216 if len(node.args) != 1 or not AstUtilities.isString(node.args[0]):
2217 return # used arguments don't match the builtin strip
2218
2219 s = AstUtilities.getValue(node.args[0])
2220 if len(s) == 1:
2221 return # stripping just one character
2222
2223 if len(s) == len(set(s)):
2224 return # no characters appear more than once
2225
2226 self.violations.append((node, "M505"))
2085 2227
2086 def __checkForM507(self, node): 2228 def __checkForM507(self, node):
2087 """ 2229 """
2088 Private method to check for unused loop variables. 2230 Private method to check for unused loop variables.
2089 2231
2150 self.violations.append((node, "M516")) 2292 self.violations.append((node, "M516"))
2151 2293
2152 def __checkForM517(self, node): 2294 def __checkForM517(self, node):
2153 """ 2295 """
2154 Private method to check for use of the evil syntax 2296 Private method to check for use of the evil syntax
2155 'with assertRaises(Exception):. 2297 'with assertRaises(Exception): or 'with pytest.raises(Exception):'.
2156 2298
2157 @param node reference to the node to be processed 2299 @param node reference to the node to be processed
2158 @type ast.With 2300 @type ast.With
2159 """ 2301 """
2160 item = node.items[0] 2302 item = node.items[0]
2161 itemContext = item.context_expr 2303 itemContext = item.context_expr
2162 if ( 2304 if (
2163 hasattr(itemContext, "func") 2305 hasattr(itemContext, "func")
2164 and hasattr(itemContext.func, "attr") 2306 and isinstance(itemContext.func, ast.Attribute)
2165 and itemContext.func.attr == "assertRaises" 2307 and (
2308 itemContext.func.attr == "assertRaises"
2309 or (
2310 itemContext.func.attr == "raises"
2311 and isinstance(itemContext.func.value, ast.Name)
2312 and itemContext.func.value.id == "pytest"
2313 and "match" not in [kwd.arg for kwd in itemContext.keywords]
2314 )
2315 )
2166 and len(itemContext.args) == 1 2316 and len(itemContext.args) == 1
2167 and isinstance(itemContext.args[0], ast.Name) 2317 and isinstance(itemContext.args[0], ast.Name)
2168 and itemContext.args[0].id == "Exception" 2318 and itemContext.args[0].id == "Exception"
2169 and not item.optional_vars 2319 and not item.optional_vars
2170 ): 2320 ):
2175 Private method to check for useless expressions. 2325 Private method to check for useless expressions.
2176 2326
2177 @param node reference to the node to be processed 2327 @param node reference to the node to be processed
2178 @type ast.FunctionDef 2328 @type ast.FunctionDef
2179 """ 2329 """
2180 subnodeClasses = (
2181 (
2182 ast.Constant,
2183 ast.List,
2184 ast.Set,
2185 ast.Dict,
2186 )
2187 if sys.version_info >= (3, 8, 0)
2188 else (
2189 ast.Num,
2190 ast.Bytes,
2191 ast.NameConstant,
2192 ast.List,
2193 ast.Set,
2194 ast.Dict,
2195 )
2196 )
2197 for subnode in node.body: 2330 for subnode in node.body:
2198 if not isinstance(subnode, ast.Expr): 2331 if not isinstance(subnode, ast.Expr):
2199 continue 2332 continue
2200 2333
2201 if isinstance(subnode.value, subnodeClasses) and not AstUtilities.isString( 2334 if (
2202 subnode.value 2335 sys.version_info < (3, 8, 0)
2336 and isinstance(
2337 subnode.value,
2338 (ast.Num, ast.Bytes, ast.NameConstant, ast.List, ast.Set, ast.Dict),
2339 )
2340 ) or (
2341 sys.version_info >= (3, 8, 0)
2342 and (
2343 isinstance(
2344 subnode.value,
2345 (ast.List, ast.Set, ast.Dict),
2346 )
2347 or (
2348 isinstance(subnode.value, ast.Constant)
2349 and (
2350 isinstance(
2351 subnode.value.value,
2352 (int, float, complex, bytes, bool),
2353 )
2354 or subnode.value.value is None
2355 )
2356 )
2357 )
2203 ): 2358 ):
2204 self.violations.append((subnode, "M518")) 2359 self.violations.append((subnode, "M518"))
2205 2360
2206 def __checkForM519(self, node): 2361 def __checkForM519(self, node):
2207 """ 2362 """
2419 2574
2420 # only check abstract classes 2575 # only check abstract classes
2421 if not any(map(isAbcClass, (*node.bases, *node.keywords))): 2576 if not any(map(isAbcClass, (*node.bases, *node.keywords))):
2422 return 2577 return
2423 2578
2579 hasMethod = False
2424 hasAbstractMethod = False 2580 hasAbstractMethod = False
2425 2581
2426 if not any(map(isAbcClass, (*node.bases, *node.keywords))): 2582 if not any(map(isAbcClass, (*node.bases, *node.keywords))):
2427 return 2583 return
2428 2584
2433 continue 2589 continue
2434 2590
2435 # only check function defs 2591 # only check function defs
2436 if not isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): 2592 if not isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
2437 continue 2593 continue
2594 hasMethod = True
2438 2595
2439 hasAbstractDecorator = any(map(isAbstractDecorator, stmt.decorator_list)) 2596 hasAbstractDecorator = any(map(isAbstractDecorator, stmt.decorator_list))
2440 2597
2441 hasAbstractMethod |= hasAbstractDecorator 2598 hasAbstractMethod |= hasAbstractDecorator
2442 2599
2445 and emptyBody(stmt.body) 2602 and emptyBody(stmt.body)
2446 and not any(map(isOverload, stmt.decorator_list)) 2603 and not any(map(isOverload, stmt.decorator_list))
2447 ): 2604 ):
2448 self.violations.append((stmt, "M527", stmt.name)) 2605 self.violations.append((stmt, "M527", stmt.name))
2449 2606
2450 if not hasAbstractMethod: 2607 if hasMethod and not hasAbstractMethod:
2451 self.violations.append((node, "M524", node.name)) 2608 self.violations.append((node, "M524", node.name))
2452 2609
2453 def __checkForM525(self, node): 2610 def __checkForM525(self, node):
2454 """ 2611 """
2455 Private method to check for exceptions being handled multiple times. 2612 Private method to check for exceptions being handled multiple times.
2495 if (starred.lineno, starred.col_offset) > ( 2652 if (starred.lineno, starred.col_offset) > (
2496 firstKeyword.lineno, 2653 firstKeyword.lineno,
2497 firstKeyword.col_offset, 2654 firstKeyword.col_offset,
2498 ): 2655 ):
2499 self.violations.append((node, "M526")) 2656 self.violations.append((node, "M526"))
2657
2658 def __checkForM528(self, node):
2659 """
2660 Private method to check for warn without stacklevel.
2661
2662 @param node reference to the node to be processed
2663 @type ast.Call
2664 """
2665 if (
2666 isinstance(node.func, ast.Attribute)
2667 and node.func.attr == "warn"
2668 and isinstance(node.func.value, ast.Name)
2669 and node.func.value.id == "warnings"
2670 and not any(kw.arg == "stacklevel" for kw in node.keywords)
2671 and len(node.args) < 3
2672 ):
2673 self.violations.append((node, "M528"))
2674
2675 def __checkForM531(self, loopNode):
2676 """
2677 Private method to check that 'itertools.groupby' isn't iterated over more than
2678 once.
2679
2680 A warning is emitted when the generator returned by 'groupby()' is used
2681 more than once inside a loop body or when it's used in a nested loop.
2682
2683 @param loopNode reference to the node to be processed
2684 @type ast.For or ast.AsyncFor
2685 """
2686 # for <loop_node.target> in <loop_node.iter>: ...
2687 if isinstance(loopNode.iter, ast.Call):
2688 node = loopNode.iter
2689 if (isinstance(node.func, ast.Name) and node.func.id in ("groupby",)) or (
2690 isinstance(node.func, ast.Attribute)
2691 and node.func.attr == "groupby"
2692 and isinstance(node.func.value, ast.Name)
2693 and node.func.value.id == "itertools"
2694 ):
2695 # We have an invocation of groupby which is a simple unpacking
2696 if isinstance(loopNode.target, ast.Tuple) and isinstance(
2697 loopNode.target.elts[1], ast.Name
2698 ):
2699 groupName = loopNode.target.elts[1].id
2700 else:
2701 # Ignore any 'groupby()' invocation that isn't unpacked
2702 return
2703
2704 numUsages = 0
2705 for node in self.__walkList(loopNode.body):
2706 # Handled nested loops
2707 if isinstance(node, ast.For):
2708 for nestedNode in self.__walkList(node.body):
2709 if (
2710 isinstance(nestedNode, ast.Name)
2711 and nestedNode.id == groupName
2712 ):
2713 self.violations.append((nestedNode, "M531"))
2714
2715 # Handle multiple uses
2716 if isinstance(node, ast.Name) and node.id == groupName:
2717 numUsages += 1
2718 if numUsages > 1:
2719 self.violations.append((nestedNode, "M531"))
2720
2721 def __checkForM532(self, node):
2722 """
2723 Private method to check for possible unintentional typing annotation.
2724
2725 @param node reference to the node to be processed
2726 @type ast.AnnAssign
2727 """
2728 if (
2729 node.value is None
2730 and hasattr(node.target, "value")
2731 and isinstance(node.target.value, ast.Name)
2732 and (
2733 isinstance(node.target, ast.Subscript)
2734 or (
2735 isinstance(node.target, ast.Attribute)
2736 and node.target.value.id != "self"
2737 )
2738 )
2739 ):
2740 self.violations.append((node, "M532"))
2741
2742 def __checkForM533(self, node):
2743 """
2744 Private method to check a set for duplicate items.
2745
2746 @param node reference to the node to be processed
2747 @type ast.Set
2748 """
2749 constants = [
2750 item.value
2751 for item in filter(lambda x: isinstance(x, ast.Constant), node.elts)
2752 ]
2753 if len(constants) != len(set(constants)):
2754 self.violations.append((node, "M533"))
2500 2755
2501 2756
2502 class NameFinder(ast.NodeVisitor): 2757 class NameFinder(ast.NodeVisitor):
2503 """ 2758 """
2504 Class to extract a name out of a tree of nodes. 2759 Class to extract a name out of a tree of nodes.

eric ide

mercurial