diff --git a/dennis/linter.py b/dennis/linter.py index 5a76ac5..df6ec9c 100644 --- a/dennis/linter.py +++ b/dennis/linter.py @@ -102,9 +102,7 @@ class MalformedNoTypeLintRule(LintRule): def lint(self, vartok, linted_entry): msgs = [] - # This only applies if one of the variable tokenizers - # is python-format. - # FIXME: Generalize this. + # This only applies if one of the variable tokenizers is python-format. if not vartok.contains('python-format'): return msgs @@ -143,9 +141,7 @@ class MalformedMissingRightBraceLintRule(LintRule): def lint(self, vartok, linted_entry): msgs = [] - # This only applies if one of the variable tokenizers - # is python-brace-format. - # FIXME: Generalize this. + # This only applies if one of the variable tokenizers is python-brace-format. if not vartok.contains('python-brace-format'): return [] @@ -179,9 +175,7 @@ class MalformedMissingLeftBraceLintRule(LintRule): def lint(self, vartok, linted_entry): msgs = [] - # This only applies if one of the variable tokenizers - # is python-brace-format. - # FIXME: Generalize this. + # This only applies if one of the variable tokenizers is python-brace-format. if not vartok.contains('python-brace-format'): return [] @@ -253,6 +247,11 @@ class MissingVarsLintRule(LintRule): def lint(self, vartok, linted_entry): msgs = [] + + # If there are no variable formats, skip this rule. + if not vartok.formats: + return [] + for trstr in linted_entry.strs: if not trstr.msgstr_string: continue @@ -443,6 +442,10 @@ class InvalidVarsLintRule(LintRule): desc = 'Checks for variables not in msgid, but in msgstr' def lint(self, vartok, linted_entry): + # If there are no variable formats, skip this rule. + if not vartok.formats: + return [] + msgs = [] for trstr in linted_entry.strs: if not trstr.msgstr_string: diff --git a/dennis/tools.py b/dennis/tools.py index 3d0a473..8a7101e 100644 --- a/dennis/tools.py +++ b/dennis/tools.py @@ -108,22 +108,30 @@ def __init__(self, formats=None): if formats is None: formats = all_formats.keys() - # Convert names to classes - self.formats = [] - - for fmt in formats: - try: - self.formats.append(all_formats[fmt]) - except KeyError: - raise UnknownFormat( - '{0} is not a known variable format'.format(fmt)) - - # Generate variable regexp - self.vars_re = re.compile( - r'(' + - '|'.join([vt.regexp for vt in self.formats]) + - r')' - ) + formats = [fmt for fmt in formats if fmt] + + # If they don't want variable tokenizing at all + if not formats: + self.formats = [] + self.vars_re = None + + else: + # Convert names to classes + self.formats = [] + + for fmt in formats: + try: + self.formats.append(all_formats[fmt]) + except KeyError: + raise UnknownFormat( + '{0} is not a known variable format'.format(fmt)) + + # Generate variable regexp + self.vars_re = re.compile( + r'(' + + '|'.join([vt.regexp for vt in self.formats]) + + r')' + ) def contains(self, fmt): """Does this tokenizer contain specified variable format?""" @@ -139,10 +147,15 @@ def tokenize(self, text): :returns: list of tokens---every even one is a Python variable """ + if not self.vars_re: + return [text] return [token for token in self.vars_re.split(text) if token] def extract_tokens(self, text, unique=True): """Returns the set of variable in the text""" + if not self.vars_re: + return set() + try: tokens = self.vars_re.findall(text) if unique: @@ -153,6 +166,8 @@ def extract_tokens(self, text, unique=True): def is_token(self, text): """Is this text a variable?""" + if not self.vars_re: + return False return self.vars_re.match(text) is not None def extract_variable_name(self, text): diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index a3d5f61..63d56fc 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -263,7 +263,21 @@ def test_excludes(self, runner, tmpdir): # The rule that generates this error is excluded, so this error shouldn't show up. assert 'W501: one character variable name "o"' not in result.output - # FIXME: test --varformat on .po file + def test_varformat_no_value(self, runner, tmpdir): + po_file = build_po_string( + '#: foo/foo.py:5\n' + 'msgid "Foo %(o)s baz"\n' + 'msgstr ""\n') + fn = tmpdir.join('messages.pot') + fn.write(po_file) + + result = runner.invoke(cli, ('lint', '--varformat', '', str(fn))) + + assert result.exit_code == 0 + # We're not looking at any variables, so we should never have a variable related warning. + assert 'W501: one character variable name "o"' not in result.output + + # FIXME: test --varformat with values # FIXME: test --reporter diff --git a/tests/test_linter.py b/tests/test_linter.py index 9ebdcab..5fc9f7a 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -161,6 +161,16 @@ def test_ignore_non_formatting_tokens(self): msgs = self.lintrule.lint(self.vartok, linted_entry) assert msgs == [] + def test_varformat_empty(self): + vartok = VariableTokenizer([]) + linted_entry = build_linted_entry( + '#: foo/foo.py:5\n' + 'msgid "%s foo"\n' + 'msgstr "%a FOO"\n' + ) + msgs = self.lintrule.lint(vartok, linted_entry) + assert msgs == [] + class TestMalformedNoTypeLintRule(LintRuleTestCase): lintrule = MalformedNoTypeLintRule() @@ -180,7 +190,7 @@ def test_fine(self): 'msgstr "Oof: {foo}"\n') msgs = self.lintrule.lint(self.vartok, linted_entry) - assert len(msgs) == 0 + assert msgs == [] def test_python_var_with_space(self): linted_entry = build_linted_entry( @@ -228,7 +238,19 @@ def test_python_var_not_malformed(self): 'msgstr "%(stars)s de %(user)s el %(date)s (%(locale)s)"\n') msgs = self.lintrule.lint(self.vartok, linted_entry) - assert len(msgs) == 0 + assert msgs == [] + + def test_varformat_empty(self): + vartok = VariableTokenizer([]) + + linted_entry = build_linted_entry( + '#: kitsune/questions/templates/questions/answers.html:56\n' + 'msgid "%(count)s view"\n' + 'msgid_plural "%(count)s views"\n' + 'msgstr[0] "%(count) zoo"\n') + + msgs = self.lintrule.lint(vartok, linted_entry) + assert msgs == [] class TestMalformedMissingRightBraceLintRule(LintRuleTestCase): @@ -292,6 +314,16 @@ def test_python_var_missing_right_curly_brace_two_vars(self): 'missing right curly-brace: {0]" excede el tamano de {' ) + def test_varformat_empty(self): + vartok = VariableTokenizer([]) + + linted_entry = build_linted_entry( + 'msgid "Value for key \\"{0}\\" exceeds the length of {1}"\n' + 'msgstr "Valor para la clave \\"{0}\\" excede el tamano de {1]"\n') + + msgs = self.lintrule.lint(vartok, linted_entry) + assert msgs == [] + class TestMalformedMissingLeftBraceLintRuleTest(LintRuleTestCase): lintrule = MalformedMissingLeftBraceLintRule() @@ -339,6 +371,17 @@ def test_python_var_missing_left_curly_brace(self): 'missing left curly-brace: }}' ) + def test_varformat_empty(self): + vartok = VariableTokenizer([]) + + linted_entry = build_linted_entry( + '#: kitsune/questions/templates/questions/question_details.html:14\n' + 'msgid "{q} | {product} Support Forum"\n' + 'msgstr "{q} | {product}} foo bar"\n') + + msgs = self.lintrule.lint(vartok, linted_entry) + assert msgs == [] + class TestMissingVarsLintRule(LintRuleTestCase): lintrule = MissingVarsLintRule() @@ -471,6 +514,17 @@ def test_python_format_are_errors_unnamed(self): 'missing variables: %s' ) + def test_varformat_empty(self): + vartok = VariableTokenizer([]) + + linted_entry = build_linted_entry( + '#: kitsune/kbforums/feeds.py:23\n' + 'msgid "Recently updated threads about %s"\n' + 'msgstr "RECENTLY UPDATED THREADS"\n' + ) + msgs = self.lintrule.lint(vartok, linted_entry) + assert msgs == [] + class TestInvalidVarsLintRule(LintRuleTestCase): lintrule = InvalidVarsLintRule() @@ -482,7 +536,7 @@ def test_fine(self): 'msgstr "Oof"\n') msgs = self.lintrule.lint(self.vartok, linted_entry) - assert len(msgs) == 0 + assert msgs == [] linted_entry = build_linted_entry( '#: foo/foo.py:5\n' @@ -490,7 +544,7 @@ def test_fine(self): 'msgstr "Oof: {foo}"\n') msgs = self.lintrule.lint(self.vartok, linted_entry) - assert len(msgs) == 0 + assert msgs == [] def test_invalid(self): linted_entry = build_linted_entry( @@ -539,7 +593,7 @@ def test_plurals(self): 'msgstr[0] "{n} mooo"\n') msgs = self.lintrule.lint(self.vartok, linted_entry) - assert len(msgs) == 0 + assert msgs == [] def test_double_percent(self): # Double-percent shouldn't be picked up as a variable. @@ -550,7 +604,7 @@ def test_double_percent(self): 'msgstr "more than 50%% of the traffic"\n') msgs = self.lintrule.lint(self.vartok, linted_entry) - assert len(msgs) == 0 + assert msgs == [] def test_urlencoded_urls(self): # urlencoding uses % and that shouldn't get picked up @@ -562,7 +616,7 @@ def test_urlencoded_urls(self): 'msgstr "http://example.com/foo%20%E5%B4%A9%E6%BA%83 is best"\n') msgs = self.lintrule.lint(self.vartok, linted_entry) - assert len(msgs) == 0 + assert msgs == [] def test_msgid_no_vars(self): linted_entry = build_linted_entry( @@ -571,7 +625,17 @@ def test_msgid_no_vars(self): 'msgstr "http://it.wikipedia.org/wiki/Canvas_%28elemento_HTML%29"\n') msgs = self.lintrule.lint(self.vartok, linted_entry) - assert len(msgs) == 0 + assert msgs == [] + + def test_varformat_empty(self): + vartok = VariableTokenizer([]) + linted_entry = build_linted_entry( + '#: foo/foo.py:5\n' + 'msgid "Foo {bar}"\n' + 'msgstr "Oof: {foo}"\n') + + msgs = self.lintrule.lint(vartok, linted_entry) + assert msgs == [] class TestBlankLintRule(LintRuleTestCase): diff --git a/tests/test_tools.py b/tests/test_tools.py index 51eac76..9d52f72 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -6,6 +6,15 @@ ) +def test_empty_tokenizer(): + vartok = VariableTokenizer([]) + assert vartok.contains('python-format') is False + assert vartok.tokenize('a b c d e') == ['a b c d e'] + assert vartok.extract_tokens('a b c d e') == set() + assert vartok.is_token('{0}') is False + assert vartok.extract_variable_name('{0}') is None + + def test_python_tokenizing(): vartok = VariableTokenizer(['python-format', 'python-brace-format']) data = [