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