398 "M625", |
392 "M625", |
399 "M631", |
393 "M631", |
400 "M632", |
394 "M632", |
401 ), |
395 ), |
402 ), |
396 ), |
403 (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")), |
|
404 (self.__checkFuture, ("M701", "M702")), |
397 (self.__checkFuture, ("M701", "M702")), |
405 (self.__checkGettext, ("M711",)), |
398 (self.__checkGettext, ("M711",)), |
406 (self.__checkPrintStatements, ("M801",)), |
399 (self.__checkPrintStatements, ("M801",)), |
407 (self.__checkTuple, ("M811",)), |
400 (self.__checkTuple, ("M811",)), |
408 (self.__checkMutableDefault, ("M821", "M822")), |
401 (self.__checkMutableDefault, ("M821", "M822")), |
1656 and node.args |
1640 and node.args |
1657 and AstUtilities.isBaseString(node.args[0]) |
1641 and AstUtilities.isBaseString(node.args[0]) |
1658 ): |
1642 ): |
1659 self.calls[node.args[0]] = (node, True) |
1643 self.calls[node.args[0]] = (node, True) |
1660 super().generic_visit(node) |
1644 super().generic_visit(node) |
1661 |
|
1662 |
|
1663 class LoggingVisitor(ast.NodeVisitor): |
|
1664 """ |
|
1665 Class implementing a node visitor to check logging statements. |
|
1666 """ |
|
1667 |
|
1668 LoggingLevels = { |
|
1669 "debug", |
|
1670 "critical", |
|
1671 "error", |
|
1672 "info", |
|
1673 "warn", |
|
1674 "warning", |
|
1675 } |
|
1676 |
|
1677 def __init__(self): |
|
1678 """ |
|
1679 Constructor |
|
1680 """ |
|
1681 super().__init__() |
|
1682 |
|
1683 self.__currentLoggingCall = None |
|
1684 self.__currentLoggingArgument = None |
|
1685 self.__currentLoggingLevel = None |
|
1686 self.__currentExtraKeyword = None |
|
1687 self.violations = [] |
|
1688 |
|
1689 def __withinLoggingStatement(self): |
|
1690 """ |
|
1691 Private method to check, if we are inside a logging statement. |
|
1692 |
|
1693 @return flag indicating we are inside a logging statement |
|
1694 @rtype bool |
|
1695 """ |
|
1696 return self.__currentLoggingCall is not None |
|
1697 |
|
1698 def __withinLoggingArgument(self): |
|
1699 """ |
|
1700 Private method to check, if we are inside a logging argument. |
|
1701 |
|
1702 @return flag indicating we are inside a logging argument |
|
1703 @rtype bool |
|
1704 """ |
|
1705 return self.__currentLoggingArgument is not None |
|
1706 |
|
1707 def __withinExtraKeyword(self, node): |
|
1708 """ |
|
1709 Private method to check, if we are inside the extra keyword. |
|
1710 |
|
1711 @param node reference to the node to be checked |
|
1712 @type ast.keyword |
|
1713 @return flag indicating we are inside the extra keyword |
|
1714 @rtype bool |
|
1715 """ |
|
1716 return ( |
|
1717 self.__currentExtraKeyword is not None |
|
1718 and self.__currentExtraKeyword != node |
|
1719 ) |
|
1720 |
|
1721 def __detectLoggingLevel(self, node): |
|
1722 """ |
|
1723 Private method to decide whether an AST Call is a logging call. |
|
1724 |
|
1725 @param node reference to the node to be processed |
|
1726 @type ast.Call |
|
1727 @return logging level |
|
1728 @rtype str or None |
|
1729 """ |
|
1730 with contextlib.suppress(AttributeError): |
|
1731 if node.func.value.id == "warnings": |
|
1732 return None |
|
1733 |
|
1734 if node.func.attr in LoggingVisitor.LoggingLevels: |
|
1735 return node.func.attr |
|
1736 |
|
1737 return None |
|
1738 |
|
1739 def __isFormatCall(self, node): |
|
1740 """ |
|
1741 Private method to check if a function call uses format. |
|
1742 |
|
1743 @param node reference to the node to be processed |
|
1744 @type ast.Call |
|
1745 @return flag indicating the function call uses format |
|
1746 @rtype bool |
|
1747 """ |
|
1748 try: |
|
1749 return node.func.attr == "format" |
|
1750 except AttributeError: |
|
1751 return False |
|
1752 |
|
1753 def visit_Call(self, node): |
|
1754 """ |
|
1755 Public method to handle a function call. |
|
1756 |
|
1757 Every logging statement and string format is expected to be a function |
|
1758 call. |
|
1759 |
|
1760 @param node reference to the node to be processed |
|
1761 @type ast.Call |
|
1762 """ |
|
1763 # we are in a logging statement |
|
1764 if ( |
|
1765 self.__withinLoggingStatement() |
|
1766 and self.__withinLoggingArgument() |
|
1767 and self.__isFormatCall(node) |
|
1768 ): |
|
1769 self.violations.append((node, "M651")) |
|
1770 super().generic_visit(node) |
|
1771 return |
|
1772 |
|
1773 loggingLevel = self.__detectLoggingLevel(node) |
|
1774 |
|
1775 if loggingLevel and self.__currentLoggingLevel is None: |
|
1776 self.__currentLoggingLevel = loggingLevel |
|
1777 |
|
1778 # we are in some other statement |
|
1779 if loggingLevel is None: |
|
1780 super().generic_visit(node) |
|
1781 return |
|
1782 |
|
1783 # we are entering a new logging statement |
|
1784 self.__currentLoggingCall = node |
|
1785 |
|
1786 if loggingLevel == "warn": |
|
1787 self.violations.append((node, "M655")) |
|
1788 |
|
1789 for index, child in enumerate(ast.iter_child_nodes(node)): |
|
1790 if index == 1: |
|
1791 self.__currentLoggingArgument = child |
|
1792 if index > 1 and isinstance(child, ast.keyword) and child.arg == "extra": |
|
1793 self.__currentExtraKeyword = child |
|
1794 |
|
1795 super().visit(child) |
|
1796 |
|
1797 self.__currentLoggingArgument = None |
|
1798 self.__currentExtraKeyword = None |
|
1799 |
|
1800 self.__currentLoggingCall = None |
|
1801 self.__currentLoggingLevel = None |
|
1802 |
|
1803 def visit_BinOp(self, node): |
|
1804 """ |
|
1805 Public method to handle binary operations while processing the first |
|
1806 logging argument. |
|
1807 |
|
1808 @param node reference to the node to be processed |
|
1809 @type ast.BinOp |
|
1810 """ |
|
1811 if self.__withinLoggingStatement() and self.__withinLoggingArgument(): |
|
1812 # handle percent format |
|
1813 if isinstance(node.op, ast.Mod): |
|
1814 self.violations.append((node, "M652")) |
|
1815 |
|
1816 # handle string concat |
|
1817 if isinstance(node.op, ast.Add): |
|
1818 self.violations.append((node, "M653")) |
|
1819 |
|
1820 super().generic_visit(node) |
|
1821 |
|
1822 def visit_JoinedStr(self, node): |
|
1823 """ |
|
1824 Public method to handle f-string arguments. |
|
1825 |
|
1826 @param node reference to the node to be processed |
|
1827 @type ast.JoinedStr |
|
1828 """ |
|
1829 if ( |
|
1830 self.__withinLoggingStatement() |
|
1831 and any(isinstance(i, ast.FormattedValue) for i in node.values) |
|
1832 and self.__withinLoggingArgument() |
|
1833 ): |
|
1834 self.violations.append((node, "M654")) |
|
1835 |
|
1836 super().generic_visit(node) |
|
1837 |
1645 |
1838 |
1646 |
1839 ####################################################################### |
1647 ####################################################################### |
1840 ## BugBearVisitor |
1648 ## BugBearVisitor |
1841 ## |
1649 ## |