Michael Johnson | cc8f89b | 2021-11-30 00:31:24 +0000 | [diff] [blame] | 1 | # Copyright 2014 Hewlett-Packard Development Company, L.P. |
| 2 | # Copyright (c) 2012, Cloudscaling |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 5 | # not use this file except in compliance with the License. You may obtain |
| 6 | # a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 13 | # License for the specific language governing permissions and limitations |
| 14 | # under the License. |
| 15 | import re |
| 16 | |
| 17 | from hacking import core |
| 18 | import pycodestyle |
| 19 | |
| 20 | # D701: Default parameter value is a mutable type |
| 21 | # D702: Log messages require translation |
| 22 | # D703: Found use of _() without explicit import of _! |
| 23 | # D704: Found import of %s. This oslo library has been graduated! |
| 24 | # D705: timeutils.utcnow() must be used instead of datetime.%s() |
| 25 | # D706: Don't translate debug level logs |
| 26 | # D707: basestring is not Python3-compatible, use str instead. |
| 27 | # D708: Do not use xrange. Use range for large loops. |
| 28 | # D709: LOG.audit is deprecated, please use LOG.info! |
| 29 | # D710: LOG.warn() is not allowed. Use LOG.warning() |
| 30 | # D711: Don't use backslashes for line continuation. |
| 31 | |
| 32 | UNDERSCORE_IMPORT_FILES = [] |
| 33 | |
| 34 | |
| 35 | mutable_default_argument_check = re.compile( |
| 36 | r"^\s*def .+\((.+=\{\}|.+=\[\])") |
| 37 | string_translation = re.compile(r"[^_]*_\(\s*('|\")") |
| 38 | translated_log = re.compile( |
| 39 | r"(.)*LOG\.(audit|error|info|warn|warning|critical|exception)" |
| 40 | r"\(\s*_\(\s*('|\")") |
| 41 | underscore_import_check = re.compile(r"(.)*import _(.)*") |
| 42 | # We need this for cases where they have created their own _ function. |
| 43 | custom_underscore_check = re.compile(r"(.)*_\s*=\s*(.)*") |
| 44 | graduated_oslo_libraries_import_re = re.compile( |
| 45 | r"^\s*(?:import|from) designate\.openstack\.common\.?.*?" |
| 46 | r"(gettextutils|rpc)" |
| 47 | r".*?") |
| 48 | no_line_continuation_backslash_re = re.compile(r'.*(\\)\n') |
| 49 | |
| 50 | |
| 51 | @core.flake8ext |
| 52 | def mutable_default_arguments(physical_line, logical_line, filename): |
| 53 | if pycodestyle.noqa(physical_line): |
| 54 | return |
| 55 | |
| 56 | if mutable_default_argument_check.match(logical_line): |
| 57 | yield (0, "D701: Default parameter value is a mutable type") |
| 58 | |
| 59 | |
| 60 | @core.flake8ext |
| 61 | def no_translate_debug_logs(logical_line, filename): |
| 62 | """Check for 'LOG.debug(_(' |
| 63 | As per our translation policy, |
| 64 | https://wiki.openstack.org/wiki/LoggingStandards#Log_Translation |
| 65 | we shouldn't translate debug level logs. |
| 66 | * This check assumes that 'LOG' is a logger. |
| 67 | * Use filename so we can start enforcing this in specific folders instead |
| 68 | of needing to do so all at once. |
| 69 | N319 |
| 70 | """ |
| 71 | if logical_line.startswith("LOG.debug(_("): |
| 72 | yield(0, "D706: Don't translate debug level logs") |
| 73 | |
| 74 | |
| 75 | @core.flake8ext |
| 76 | def check_explicit_underscore_import(logical_line, filename): |
| 77 | """Check for explicit import of the _ function |
| 78 | |
| 79 | We need to ensure that any files that are using the _() function |
| 80 | to translate logs are explicitly importing the _ function. We |
| 81 | can't trust unit test to catch whether the import has been |
| 82 | added so we need to check for it here. |
| 83 | """ |
| 84 | # Build a list of the files that have _ imported. No further |
| 85 | # checking needed once it is found. |
| 86 | if filename in UNDERSCORE_IMPORT_FILES: |
| 87 | pass |
| 88 | elif (underscore_import_check.match(logical_line) or |
| 89 | custom_underscore_check.match(logical_line)): |
| 90 | UNDERSCORE_IMPORT_FILES.append(filename) |
| 91 | elif (translated_log.match(logical_line) or |
| 92 | string_translation.match(logical_line)): |
| 93 | yield(0, "D703: Found use of _() without explicit import of _!") |
| 94 | |
| 95 | |
| 96 | @core.flake8ext |
| 97 | def no_import_graduated_oslo_libraries(logical_line, filename): |
| 98 | """Check that we don't continue to use o.c. oslo libraries after graduation |
| 99 | |
| 100 | After a library graduates from oslo-incubator, as we make the switch, we |
| 101 | should ensure we don't continue to use the oslo-incubator versions. |
| 102 | |
| 103 | In many cases, it's not possible to immediately remove the code from the |
| 104 | openstack/common folder due to dependency issues. |
| 105 | """ |
| 106 | # We can't modify oslo-incubator code, so ignore it here. |
| 107 | if "designate/openstack/common" in filename: |
| 108 | return |
| 109 | |
| 110 | matches = graduated_oslo_libraries_import_re.match(logical_line) |
| 111 | if matches: |
| 112 | yield(0, "D704: Found import of %s. This oslo library has been " |
| 113 | "graduated!" % matches.group(1)) |
| 114 | |
| 115 | |
| 116 | @core.flake8ext |
| 117 | def use_timeutils_utcnow(logical_line, filename): |
| 118 | # tools are OK to use the standard datetime module |
| 119 | if "/tools/" in filename: |
| 120 | return |
| 121 | |
| 122 | msg = "D705: timeutils.utcnow() must be used instead of datetime.%s()" |
| 123 | |
| 124 | datetime_funcs = ['now', 'utcnow'] |
| 125 | for f in datetime_funcs: |
| 126 | pos = logical_line.find('datetime.%s' % f) |
| 127 | if pos != -1: |
| 128 | yield (pos, msg % f) |
| 129 | |
| 130 | |
| 131 | @core.flake8ext |
| 132 | def check_no_basestring(logical_line): |
| 133 | if re.search(r"\bbasestring\b", logical_line): |
| 134 | msg = ("D707: basestring is not Python3-compatible, use " |
| 135 | "str instead.") |
| 136 | yield(0, msg) |
| 137 | |
| 138 | |
| 139 | @core.flake8ext |
| 140 | def check_python3_xrange(logical_line): |
| 141 | if re.search(r"\bxrange\s*\(", logical_line): |
| 142 | yield(0, "D708: Do not use xrange. Use range for " |
| 143 | "large loops.") |
| 144 | |
| 145 | |
| 146 | @core.flake8ext |
| 147 | def check_no_log_audit(logical_line): |
| 148 | """Ensure that we are not using LOG.audit messages |
| 149 | Plans are in place going forward as discussed in the following |
| 150 | spec (https://review.opendev.org/#/c/132552/) to take out |
| 151 | LOG.audit messages. Given that audit was a concept invented |
| 152 | for OpenStack we can enforce not using it. |
| 153 | """ |
| 154 | if "LOG.audit(" in logical_line: |
| 155 | yield(0, "D709: LOG.audit is deprecated, please use LOG.info!") |
| 156 | |
| 157 | |
| 158 | @core.flake8ext |
| 159 | def check_no_log_warn(logical_line): |
| 160 | """Disallow 'LOG.warn(' |
| 161 | |
| 162 | D710 |
| 163 | """ |
| 164 | if logical_line.startswith('LOG.warn('): |
| 165 | yield(0, "D710:Use LOG.warning() rather than LOG.warn()") |
| 166 | |
| 167 | |
| 168 | @core.flake8ext |
| 169 | def check_line_continuation_no_backslash(logical_line, tokens): |
| 170 | """D711 - Don't use backslashes for line continuation. |
| 171 | |
| 172 | :param logical_line: The logical line to check. Not actually used. |
| 173 | :param tokens: List of tokens to check. |
| 174 | :returns: None if the tokens don't contain any issues, otherwise a tuple |
| 175 | is yielded that contains the offending index in the logical |
| 176 | line and a message describe the check validation failure. |
| 177 | """ |
| 178 | backslash = None |
| 179 | for token_type, text, start, end, orig_line in tokens: |
| 180 | m = no_line_continuation_backslash_re.match(orig_line) |
| 181 | if m: |
| 182 | backslash = (start[0], m.start(1)) |
| 183 | break |
| 184 | |
| 185 | if backslash is not None: |
| 186 | msg = 'D711 Backslash line continuations not allowed' |
| 187 | yield backslash, msg |