106 (self.__checkDictWithSortedKeys, ("M201",)), |
107 (self.__checkDictWithSortedKeys, ("M201",)), |
107 (self.__checkPep3101, ("M601",)), |
108 (self.__checkPep3101, ("M601",)), |
108 (self.__checkFormatString, ("M611", "M612", "M613", |
109 (self.__checkFormatString, ("M611", "M612", "M613", |
109 "M621", "M622", "M623", "M624", "M625", |
110 "M621", "M622", "M623", "M624", "M625", |
110 "M631", "M632")), |
111 "M631", "M632")), |
|
112 (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")), |
111 (self.__checkFuture, ("M701", "M702")), |
113 (self.__checkFuture, ("M701", "M702")), |
112 (self.__checkPrintStatements, ("M801",)), |
114 (self.__checkPrintStatements, ("M801",)), |
113 (self.__checkTuple, ("M811", )), |
115 (self.__checkTuple, ("M811", )), |
114 (self.__checkMutableDefault, ("M821", "M822")), |
116 (self.__checkMutableDefault, ("M821", "M822")), |
115 ] |
117 ] |
642 if isinstance(node, ast.Dict) and self.__dictShouldBeChecked(node): |
644 if isinstance(node, ast.Dict) and self.__dictShouldBeChecked(node): |
643 for key1, key2 in zip(node.keys, node.keys[1:]): |
645 for key1, key2 in zip(node.keys, node.keys[1:]): |
644 if key2.s < key1.s: |
646 if key2.s < key1.s: |
645 self.__error(key2.lineno - 1, key2.col_offset, |
647 self.__error(key2.lineno - 1, key2.col_offset, |
646 "M201", key2.s, key1.s) |
648 "M201", key2.s, key1.s) |
|
649 |
|
650 def __checkLogging(self): |
|
651 """ |
|
652 Private method to check logging statements. |
|
653 """ |
|
654 visitor = LoggingVisitor() |
|
655 visitor.visit(self.__tree) |
|
656 for node, reason in visitor.violations: |
|
657 self.__error(node.lineno - 1, node.col_offset, reason) |
647 |
658 |
648 |
659 |
649 class TextVisitor(ast.NodeVisitor): |
660 class TextVisitor(ast.NodeVisitor): |
650 """ |
661 """ |
651 Class implementing a node visitor for bytes and str instances. |
662 Class implementing a node visitor for bytes and str instances. |
782 node.func.value.id == 'str' and node.args and |
793 node.func.value.id == 'str' and node.args and |
783 self.__isBaseString(node.args[0])): |
794 self.__isBaseString(node.args[0])): |
784 self.calls[node.args[0]] = (node, True) |
795 self.calls[node.args[0]] = (node, True) |
785 super(TextVisitor, self).generic_visit(node) |
796 super(TextVisitor, self).generic_visit(node) |
786 |
797 |
|
798 |
|
799 class LoggingVisitor(ast.NodeVisitor): |
|
800 """ |
|
801 Class implementing a node visitor to check logging statements. |
|
802 """ |
|
803 LoggingLevels = { |
|
804 "debug", |
|
805 "critical", |
|
806 "error", |
|
807 "info", |
|
808 "warn", |
|
809 "warning", |
|
810 } |
|
811 |
|
812 def __init__(self): |
|
813 """ |
|
814 Constructor |
|
815 """ |
|
816 super(LoggingVisitor, self).__init__() |
|
817 |
|
818 self.__currentLoggingCall = None |
|
819 self.__currentLoggingArgument = None |
|
820 self.__currentLoggingLevel = None |
|
821 self.__currentExtraKeyword = None |
|
822 self.violations = [] |
|
823 |
|
824 def __withinLoggingStatement(self): |
|
825 """ |
|
826 Private method to check, if we are inside a logging statement. |
|
827 |
|
828 @return flag indicating we are inside a logging statement |
|
829 @rtype bool |
|
830 """ |
|
831 return self.__currentLoggingCall is not None |
|
832 |
|
833 def __withinLoggingArgument(self): |
|
834 """ |
|
835 Private method to check, if we are inside a logging argument. |
|
836 |
|
837 @return flag indicating we are inside a logging argument |
|
838 @rtype bool |
|
839 """ |
|
840 return self.__currentLoggingArgument is not None |
|
841 |
|
842 def __withinExtraKeyword(self, node): |
|
843 """ |
|
844 Private method to check, if we are inside the extra keyword. |
|
845 |
|
846 @param node reference to the node to be checked |
|
847 @type ast.keyword |
|
848 @return flag indicating we are inside the extra keyword |
|
849 @rtype bool |
|
850 """ |
|
851 return self.__currentExtraKeyword is not None and \ |
|
852 self.__currentExtraKeyword != node |
|
853 |
|
854 def __detectLoggingLevel(self, node): |
|
855 """ |
|
856 Private method to decide whether an AST Call is a logging call. |
|
857 |
|
858 @param node reference to the node to be processed |
|
859 @type ast.Call |
|
860 @return logging level |
|
861 @rtype str or None |
|
862 """ |
|
863 try: |
|
864 if node.func.value.id == "warnings": |
|
865 return None |
|
866 |
|
867 if node.func.attr in LoggingVisitor.LoggingLevels: |
|
868 return node.func.attr |
|
869 except AttributeError: |
|
870 pass |
|
871 |
|
872 return None |
|
873 |
|
874 def __isFormatCall(self, node): |
|
875 """ |
|
876 Private method to check if a function call uses format. |
|
877 |
|
878 @param node reference to the node to be processed |
|
879 @type ast.Call |
|
880 @return flag indicating the function call uses format |
|
881 @rtype bool |
|
882 """ |
|
883 try: |
|
884 return node.func.attr == "format" |
|
885 except AttributeError: |
|
886 return False |
|
887 |
|
888 def visit_Call(self, node): |
|
889 """ |
|
890 Public method to handle a function call. |
|
891 |
|
892 Every logging statement and string format is expected to be a function |
|
893 call. |
|
894 |
|
895 @param node reference to the node to be processed |
|
896 @type ast.Call |
|
897 """ |
|
898 # we are in a logging statement |
|
899 if self.__withinLoggingStatement(): |
|
900 if self.__withinLoggingArgument() and self.__isFormatCall(node): |
|
901 self.violations.append((node, "M651")) |
|
902 super(LoggingVisitor, self).generic_visit(node) |
|
903 return |
|
904 |
|
905 loggingLevel = self.__detectLoggingLevel(node) |
|
906 |
|
907 if loggingLevel and self.__currentLoggingLevel is None: |
|
908 self.__currentLoggingLevel = loggingLevel |
|
909 |
|
910 # we are in some other statement |
|
911 if loggingLevel is None: |
|
912 super(LoggingVisitor, self).generic_visit(node) |
|
913 return |
|
914 |
|
915 # we are entering a new logging statement |
|
916 self.__currentLoggingCall = node |
|
917 |
|
918 if loggingLevel == "warn": |
|
919 self.violations.append((node, "M655")) |
|
920 |
|
921 for index, child in enumerate(ast.iter_child_nodes(node)): |
|
922 if index == 1: |
|
923 self.__currentLoggingArgument = child |
|
924 if index > 1 and isinstance(child, ast.keyword) and \ |
|
925 child.arg == "extra": |
|
926 self.__currentExtraKeyword = child |
|
927 |
|
928 super(LoggingVisitor, self).visit(child) |
|
929 |
|
930 self.__currentLoggingArgument = None |
|
931 self.__currentExtraKeyword = None |
|
932 |
|
933 self.__currentLoggingCall = None |
|
934 self.__currentLoggingLevel = None |
|
935 |
|
936 def visit_BinOp(self, node): |
|
937 """ |
|
938 Public method to handle binary operations while processing the first |
|
939 logging argument. |
|
940 |
|
941 @param node reference to the node to be processed |
|
942 @type ast.BinOp |
|
943 """ |
|
944 if self.__withinLoggingStatement() and self.__withinLoggingArgument(): |
|
945 # handle percent format |
|
946 if isinstance(node.op, ast.Mod): |
|
947 self.violations.append((node, "M652")) |
|
948 |
|
949 # handle string concat |
|
950 if isinstance(node.op, ast.Add): |
|
951 self.violations.append((node, "M653")) |
|
952 |
|
953 super(LoggingVisitor, self).generic_visit(node) |
|
954 |
|
955 def visit_JoinedStr(self, node): |
|
956 """ |
|
957 Public method to handle f-string arguments. |
|
958 |
|
959 @param node reference to the node to be processed |
|
960 @type ast.JoinedStr |
|
961 """ |
|
962 if sys.version_info >= (3, 6): |
|
963 if self.__withinLoggingStatement(): |
|
964 if any(isinstance(i, ast.FormattedValue) for i in node.values): |
|
965 if self.__withinLoggingArgument(): |
|
966 self.violations.append((node, "M654")) |
|
967 |
|
968 super(LoggingVisitor, self).generic_visit(node) |
|
969 |
787 # |
970 # |
788 # eflag: noqa = M702 |
971 # eflag: noqa = M702 |