eric6/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py

changeset 7045
c2bf08f87a1d
parent 7042
2be5b245e1b8
child 7057
0e8d3b0c4889
equal deleted inserted replaced
7044:03fca68ab2c1 7045:c2bf08f87a1d
51 "M191", "M192", "M193", "M194", 51 "M191", "M192", "M193", "M194",
52 "M195", "M196", "M197", "M198", 52 "M195", "M196", "M197", "M198",
53 53
54 ## Dictionaries with sorted keys 54 ## Dictionaries with sorted keys
55 "M201", 55 "M201",
56
57 ## Naive datetime usage
58 "M301", "M302", "M303", "M304", "M305", "M306", "M307", "M308",
59 "M311", "M312", "M313", "M314", "M315",
60 "M321",
56 61
57 ## Bugbear 62 ## Bugbear
58 "M501", "M502", "M503", "M504", "M505", "M506", "M507", "M508", 63 "M501", "M502", "M503", "M504", "M505", "M506", "M507", "M508",
59 "M509", 64 "M509",
60 "M511", "M512", "M513", 65 "M511", "M512", "M513",
174 (self.__checkTuple, ("M811",)), 179 (self.__checkTuple, ("M811",)),
175 (self.__checkMutableDefault, ("M821", "M822")), 180 (self.__checkMutableDefault, ("M821", "M822")),
176 (self.__checkReturn, ("M831", "M832", "M833", "M834")), 181 (self.__checkReturn, ("M831", "M832", "M833", "M834")),
177 (self.__checkLineContinuation, ("M841",)), 182 (self.__checkLineContinuation, ("M841",)),
178 (self.__checkCommentedCode, ("M891",)), 183 (self.__checkCommentedCode, ("M891",)),
184 (self.__checkDateTime, ("M301", "M302", "M303", "M304", "M305",
185 "M306", "M307", "M308", "M311", "M312",
186 "M313", "M314", "M315", "M321")),
179 ] 187 ]
180 188
181 self.__defaultArgs = { 189 self.__defaultArgs = {
182 "BuiltinsChecker": { 190 "BuiltinsChecker": {
183 "chr": ["unichr", ], 191 "chr": ["unichr", ],
253 else: 261 else:
254 offset = (1, 0) 262 offset = (1, 0)
255 self.__error(offset[0] - 1, offset[1] or 0, 263 self.__error(offset[0] - 1, offset[1] or 0,
256 'M901', exc_type.__name__, exc.args[0]) 264 'M901', exc_type.__name__, exc.args[0])
257 265
258 def run(self): 266 def __generateTree(self):
259 """ 267 """
260 Public method to check the given source against miscellaneous 268 Private method to generate an AST for our source.
261 conditions. 269
262 """ 270 @return generated AST
263 if not self.__filename: 271 @rtype ast.AST
264 # don't do anything, if essential data is missing 272 """
265 return
266
267 if not self.__checkers:
268 # don't do anything, if no codes were selected
269 return
270
271 source = "".join(self.__source) 273 source = "".join(self.__source)
272 # Check type for py2: if not str it's unicode 274 # Check type for py2: if not str it's unicode
273 if sys.version_info[0] == 2: 275 if sys.version_info[0] == 2:
274 try: 276 try:
275 source = source.encode('utf-8') 277 source = source.encode('utf-8')
276 except UnicodeError: 278 except UnicodeError:
277 pass 279 pass
280
281 return compile(source, self.__filename, 'exec', ast.PyCF_ONLY_AST)
282
283 def run(self):
284 """
285 Public method to check the given source against miscellaneous
286 conditions.
287 """
288 if not self.__filename:
289 # don't do anything, if essential data is missing
290 return
291
292 if not self.__checkers:
293 # don't do anything, if no codes were selected
294 return
295
278 try: 296 try:
279 self.__tree = compile(source, self.__filename, 'exec', 297 self.__tree = self.__generateTree()
280 ast.PyCF_ONLY_AST)
281 except (SyntaxError, TypeError): 298 except (SyntaxError, TypeError):
282 self.__reportInvalidSyntax() 299 self.__reportInvalidSyntax()
283 return 300 return
284 301
285 for check in self.__checkers: 302 for check in self.__checkers:
369 source, aggressive=aggressive): 386 source, aggressive=aggressive):
370 self.__error(markedLine - 1, 0, "M891") 387 self.__error(markedLine - 1, 0, "M891")
371 388
372 def __checkLineContinuation(self): 389 def __checkLineContinuation(self):
373 """ 390 """
374 Private method to check öine continuation using '\'. 391 Private method to check line continuation using backslash.
375 """ 392 """
376 # generate source lines without comments 393 # generate source lines without comments
377 linesIterator = iter(self.__source) 394 linesIterator = iter(self.__source)
378 tokens = tokenize.generate_tokens(lambda: next(linesIterator)) 395 tokens = tokenize.generate_tokens(lambda: next(linesIterator))
379 comments = [token for token in tokens if token[0] == tokenize.COMMENT] 396 comments = [token for token in tokens if token[0] == tokenize.COMMENT]
830 visitor.visit(self.__tree) 847 visitor.visit(self.__tree)
831 for violation in visitor.violations: 848 for violation in visitor.violations:
832 node = violation[0] 849 node = violation[0]
833 reason = violation[1] 850 reason = violation[1]
834 self.__error(node.lineno - 1, node.col_offset, reason) 851 self.__error(node.lineno - 1, node.col_offset, reason)
852
853 def __checkDateTime(self):
854 """
855 Private method to check use of naive datetime functions.
856 """
857 if sys.version_info[0] == 3:
858 # this check is only performed for Python 3
859
860 # step 1: generate an augmented node tree containing parent info
861 # for each child node
862 tree = self.__generateTree()
863 for node in ast.walk(tree):
864 for childNode in ast.iter_child_nodes(node):
865 childNode._dtCheckerParent = node
866
867 # step 2: perform checks and report issues
868 visitor = DateTimeVisitor()
869 visitor.visit(tree)
870 for violation in visitor.violations:
871 node = violation[0]
872 reason = violation[1]
873 self.__error(node.lineno - 1, node.col_offset, reason)
835 874
836 875
837 class TextVisitor(ast.NodeVisitor): 876 class TextVisitor(ast.NodeVisitor):
838 """ 877 """
839 Class implementing a node visitor for bytes and str instances. 878 Class implementing a node visitor for bytes and str instances.
1733 1772
1734 elif beforeAssign < lineno: 1773 elif beforeAssign < lineno:
1735 return True 1774 return True
1736 1775
1737 return False 1776 return False
1777
1778
1779 class DateTimeVisitor(ast.NodeVisitor):
1780 """
1781 Class implementing a node visitor to check datetime function calls.
1782
1783 Note: This class is modelled after flake8_datetimez checker.
1784 """
1785 def __init__(self):
1786 """
1787 Constructor
1788 """
1789 super(DateTimeVisitor, self).__init__()
1790
1791 self.violations = []
1792
1793 def __getFromKeywords(self, keywords, name):
1794 """
1795 Private method to get a keyword node given its name.
1796
1797 @param keywords list of keyword argument nodes
1798 @type list of ast.AST
1799 @param name name of the keyword node
1800 @type str
1801 @return keyword node
1802 @rtype ast.AST
1803 """
1804 for keyword in keywords:
1805 if keyword.arg == name:
1806 return keyword
1807
1808 return None
1809
1810 def visit_Call(self, node):
1811 """
1812 Public method to handle a function call.
1813
1814 Every datetime related function call is check for use of the naive
1815 variant (i.e. use without TZ info).
1816
1817 @param node reference to the node to be processed
1818 @type ast.Call
1819 """
1820 # datetime.something()
1821 isDateTimeClass = (
1822 isinstance(node.func, ast.Attribute) and
1823 isinstance(node.func.value, ast.Name) and
1824 node.func.value.id == 'datetime')
1825
1826 # datetime.datetime.something()
1827 isDateTimeModuleAndClass = (
1828 isinstance(node.func, ast.Attribute) and
1829 isinstance(node.func.value, ast.Attribute) and
1830 node.func.value.attr == 'datetime' and
1831 isinstance(node.func.value.value, ast.Name) and
1832 node.func.value.value.id == 'datetime')
1833
1834 if isDateTimeClass:
1835 if node.func.attr == 'datetime':
1836 # datetime.datetime(2000, 1, 1, 0, 0, 0, 0,
1837 # datetime.timezone.utc)
1838 isCase1 = (len(node.args) >= 8 and
1839 not (isinstance(node.args[7], ast.NameConstant) and
1840 node.args[7].value is None))
1841
1842 # datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
1843 tzinfoKeyword = self.__getFromKeywords(node.keywords, 'tzinfo')
1844 isCase2 = (tzinfoKeyword is not None and
1845 not (isinstance(tzinfoKeyword.value,
1846 ast.NameConstant) and
1847 tzinfoKeyword.value.value is None))
1848
1849 if not (isCase1 or isCase2):
1850 self.violations.append((node, "M301"))
1851
1852 elif node.func.attr == 'time':
1853 # time(12, 10, 45, 0, datetime.timezone.utc)
1854 isCase1 = (len(node.args) >= 5 and
1855 not (isinstance(node.args[4], ast.NameConstant) and
1856 node.args[4].value is None))
1857
1858 # datetime.time(12, 10, 45, tzinfo=datetime.timezone.utc)
1859 tzinfoKeyword = self.__getFromKeywords(node.keywords, 'tzinfo')
1860 isCase2 = (tzinfoKeyword is not None and
1861 not (isinstance(tzinfoKeyword.value,
1862 ast.NameConstant) and
1863 tzinfoKeyword.value.value is None))
1864
1865 if not (isCase1 or isCase2):
1866 self.violations.append((node, "M321"))
1867
1868 elif node.func.attr == 'date':
1869 self.violations.append((node, "M311"))
1870
1871 if isDateTimeClass or isDateTimeModuleAndClass:
1872 if node.func.attr == 'today':
1873 self.violations.append((node, "M302"))
1874
1875 elif node.func.attr == 'utcnow':
1876 self.violations.append((node, "M303"))
1877
1878 elif node.func.attr == 'utcfromtimestamp':
1879 self.violations.append((node, "M304"))
1880
1881 elif node.func.attr in 'now':
1882 # datetime.now(UTC)
1883 isCase1 = (len(node.args) == 1 and
1884 len(node.keywords) == 0 and
1885 not (isinstance(node.args[0], ast.NameConstant) and
1886 node.args[0].value is None))
1887
1888 # datetime.now(tz=UTC)
1889 tzKeyword = self.__getFromKeywords(node.keywords, 'tz')
1890 isCase2 = (tzKeyword is not None and
1891 not (isinstance(tzKeyword.value,
1892 ast.NameConstant) and
1893 tzKeyword.value.value is None))
1894
1895 if not (isCase1 or isCase2):
1896 self.violations.append((node, "M305"))
1897
1898 elif node.func.attr == 'fromtimestamp':
1899 # datetime.fromtimestamp(1234, UTC)
1900 isCase1 = (len(node.args) == 2 and
1901 len(node.keywords) == 0 and
1902 not (isinstance(node.args[1], ast.NameConstant) and
1903 node.args[1].value is None))
1904
1905 # datetime.fromtimestamp(1234, tz=UTC)
1906 tzKeyword = self.__getFromKeywords(node.keywords, 'tz')
1907 isCase2 = (tzKeyword is not None and
1908 not (isinstance(tzKeyword.value,
1909 ast.NameConstant) and
1910 tzKeyword.value.value is None))
1911
1912 if not (isCase1 or isCase2):
1913 self.violations.append((node, "M306"))
1914
1915 elif node.func.attr == 'strptime':
1916 # datetime.strptime(...).replace(tzinfo=UTC)
1917 parent = getattr(node, '_dtCheckerParent', None)
1918 pparent = getattr(parent, '_dtCheckerParent', None)
1919 if not (isinstance(parent, ast.Attribute) and
1920 parent.attr == 'replace'):
1921 isCase1 = False
1922 elif not isinstance(pparent, ast.Call):
1923 isCase1 = False
1924 else:
1925 tzinfoKeyword = self.__getFromKeywords(pparent.keywords,
1926 'tzinfo')
1927 isCase1 = (tzinfoKeyword is not None and
1928 not (isinstance(tzinfoKeyword.value,
1929 ast.NameConstant) and
1930 tzinfoKeyword.value.value is None))
1931
1932 if not isCase1:
1933 self.violations.append((node, "M307"))
1934
1935 elif node.func.attr == 'fromordinal':
1936 self.violations.append((node, "M308"))
1937
1938 # date.something()
1939 isDateClass = (isinstance(node.func, ast.Attribute) and
1940 isinstance(node.func.value, ast.Name) and
1941 node.func.value.id == 'date')
1942
1943 # datetime.date.something()
1944 isDateModuleAndClass = (isinstance(node.func, ast.Attribute) and
1945 isinstance(node.func.value, ast.Attribute) and
1946 node.func.value.attr == 'date' and
1947 isinstance(node.func.value.value, ast.Name) and
1948 node.func.value.value.id == 'datetime')
1949
1950 if isDateClass or isDateModuleAndClass:
1951 if node.func.attr == 'today':
1952 self.violations.append((node, "M312"))
1953
1954 elif node.func.attr == 'fromtimestamp':
1955 self.violations.append((node, "M313"))
1956
1957 elif node.func.attr == 'fromordinal':
1958 self.violations.append((node, "M314"))
1959
1960 elif node.func.attr == 'fromisoformat':
1961 self.violations.append((node, "M315"))
1962
1963 self.generic_visit(node)
1964
1738 # 1965 #
1739 # eflag: noqa = M702 1966 # eflag: noqa = M702

eric ide

mercurial