hacking/hacking/checks/localization.py
Eric Harney d62d6e029d Fix python 3.6 escape char warnings in strings
In python 3.6, escape sequences that are not
recognized in string literals issue DeprecationWarnings.

Convert these to raw strings.

Change-Id: I0e2bff9cb45974b159aed6a63913a63b1956aa32
2017-08-16 16:45:27 -04:00

142 lines
4.6 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
import tokenize
from hacking import core
FORMAT_RE = re.compile(r"%(?:"
r"%|" # Ignore plain percents
r"(\(\w+\))?" # mapping key
r"([#0 +-]?" # flag
r"(?:\d+|\*)?" # width
r"(?:\.\d+)?" # precision
r"[hlL]?" # length mod
r"\w))") # type
class LocalizationError(Exception):
pass
def check_i18n():
"""Generator that checks token stream for localization errors.
Expects tokens to be ``send``ed one by one.
Raises LocalizationError if some error is found.
"""
while True:
try:
token_type, text, _, _, line = yield
except GeneratorExit:
return
if text == "def" and token_type == tokenize.NAME:
# explicitly ignore function definitions, as oslo defines these
return
if (token_type == tokenize.NAME and
text in ["_", "_LI", "_LW", "_LE", "_LC"]):
while True:
token_type, text, start, _, _ = yield
if token_type != tokenize.NL:
break
if token_type != tokenize.OP or text != "(":
continue # not a localization call
format_string = ''
while True:
token_type, text, start, _, _ = yield
if token_type == tokenize.STRING:
format_string += eval(text)
elif token_type == tokenize.NL:
pass
else:
break
if not format_string:
raise LocalizationError(
start, "H701: Empty localization string")
if token_type != tokenize.OP:
raise LocalizationError(
start, "H701: Invalid localization call")
if text != ")":
if text == "%":
raise LocalizationError(
start,
"H702: Formatting operation should be outside"
" of localization method call")
elif text == "+":
raise LocalizationError(
start,
"H702: Use bare string concatenation instead of +")
else:
raise LocalizationError(
start, "H702: Argument to _, _LI, _LW, _LC, or _LE "
"must be just a string")
format_specs = FORMAT_RE.findall(format_string)
positional_specs = [(key, spec) for key, spec in format_specs
if not key and spec]
# not spec means %%, key means %(smth)s
if len(positional_specs) > 1:
raise LocalizationError(
start, "H703: Multiple positional placeholders")
@core.flake8ext
def hacking_localization_strings(logical_line, tokens, noqa):
r"""Check localization in line.
Okay: _("This is fine")
Okay: _LI("This is fine")
Okay: _LW("This is fine")
Okay: _LE("This is fine")
Okay: _LC("This is fine")
Okay: _("This is also fine %s")
Okay: _("So is this %s, %(foo)s") % {foo: 'foo'}
H701: _('')
Okay: def _(msg):\n pass
Okay: def _LE(msg):\n pass
H701: _LI('')
H701: _LW('')
H701: _LE('')
H701: _LC('')
Okay: _('') # noqa
H702: _("Bob" + " foo")
H702: _LI("Bob" + " foo")
H702: _LW("Bob" + " foo")
H702: _LE("Bob" + " foo")
H702: _LC("Bob" + " foo")
Okay: _("Bob" + " foo") # noqa
H702: _("Bob %s" % foo)
H702: _LI("Bob %s" % foo)
H702: _LW("Bob %s" % foo)
H702: _LE("Bob %s" % foo)
H702: _LC("Bob %s" % foo)
H702: _("%s %s" % (foo, bar))
H703: _("%s %s") % (foo, bar)
"""
if noqa:
return
gen = check_i18n()
next(gen)
try:
list(map(gen.send, tokens))
gen.close()
except LocalizationError as e:
yield e.args
# TODO(jogo) Dict and list objects