Repository: conda-forge/conda-smithy

Issue:
lint jinja2 variable spaces
Should we lint the jinja2 variable spaces?


Discussion:
As an example `{% set version="0.27.3" %}` has no spaces around `=` and probably should.

Resolving patch:
diff --git a/conda_smithy/lint_recipe.py b/conda_smithy/lint_recipe.py
--- a/conda_smithy/lint_recipe.py
+++ b/conda_smithy/lint_recipe.py
@@ -32,6 +32,7 @@
 TEST_KEYS = {'imports', 'commands'}
 
 sel_pat = re.compile(r'(.+?)\s*(#.*)?\[([^\[\]]+)\](?(2).*)$')
+jinja_pat = re.compile(r'\s*\{%\s*(set)\s+[^\s]+\s*=\s*[^\s]+\s*%\}')
 
 
 class NullUndefined(jinja2.Undefined):
@@ -264,6 +265,24 @@ def lintify(meta, recipe_dir=None, conda_forge=False):
             conda_build.conda_interface.VersionOrder(ver)
         except:
             lints.append("Package version {} doesn't match conda spec".format(ver))
+
+    # 20: Jinja2 variable definitions should be nice.
+    if recipe_dir is not None and os.path.exists(meta_fname):
+        bad_jinja = []
+        bad_lines = []
+        # Good Jinja2 variable definitions look like "{% set .+ = .+ %}"
+        good_jinja_pat = re.compile(r'\s*\{%\s(set)\s[^\s]+\s=\s[^\s]+\s%\}')
+        with io.open(meta_fname, 'rt') as fh:
+            for jinja_line, line_number in jinja_lines(fh):
+                if not good_jinja_pat.match(jinja_line):
+                    bad_jinja.append(jinja_line)
+                    bad_lines.append(line_number)
+        if bad_jinja:
+            lints.append('Jinja2 variable definitions are suggested to '
+                         'take a ``{{%<one space>set<one space>'
+                         '<variable name><one space>=<one space>'
+                         '<expression><one space>%}}`` form. See lines '
+                         '{}'.format(bad_lines))
     return lints
 
 
@@ -324,12 +343,26 @@ def is_selector_line(line):
     return False
 
 
+def is_jinja_line(line):
+    line = line.rstrip()
+    m = jinja_pat.match(line)
+    if m:
+        return True
+    return False
+
+
 def selector_lines(lines):
     for i, line in enumerate(lines):
         if is_selector_line(line):
             yield line, i
 
 
+def jinja_lines(lines):
+    for i, line in enumerate(lines):
+        if is_jinja_line(line):
+            yield line, i
+
+
 def main(recipe_dir, conda_forge=False):
     recipe_dir = os.path.abspath(recipe_dir)
     recipe_meta = os.path.join(recipe_dir, 'meta.yaml')


Test patch:
diff --git a/tests/test_lint_recipe.py b/tests/test_lint_recipe.py
--- a/tests/test_lint_recipe.py
+++ b/tests/test_lint_recipe.py
@@ -579,6 +579,38 @@ def test_unicode(self):
             # Just run it and make sure it does not raise.
             linter.main(recipe_dir)
 
+    def test_jinja_variable_def(self):
+        expected_message = ('Jinja2 variable definitions are suggested to '
+                            'take a ``{{%<one space>set<one space>'
+                            '<variable name><one space>=<one space>'
+                            '<expression><one space>%}}`` form. See lines '
+                            '{}'.format([2]))
+
+        with tmp_directory() as recipe_dir:
+            def assert_jinja(jinja_var, is_good=True):
+                with io.open(os.path.join(recipe_dir, 'meta.yaml'), 'w') as fh:
+                    fh.write("""
+                             {{% set name = "conda-smithy" %}}
+                             {}
+                             """.format(jinja_var))
+                lints = linter.lintify({}, recipe_dir)
+                if is_good:
+                    message = ("Found lints when there shouldn't have been a "
+                               "lint for '{}'.".format(jinja_var))
+                else:
+                    message = ("Expecting lints for '{}', but didn't get any."
+                               "".format(jinja_var))
+                self.assertEqual(not is_good,
+                                 any(lint.startswith(expected_message)
+                                     for lint in lints),
+                                 message)
+
+            assert_jinja('{% set version = "0.27.3" %}')
+            assert_jinja('{% set version="0.27.3" %}', is_good=False)
+            assert_jinja('{%set version = "0.27.3" %}', is_good=False)
+            assert_jinja('{% set version = "0.27.3"%}', is_good=False)
+            assert_jinja('{% set version= "0.27.3"%}', is_good=False)
+
 
 if __name__ == '__main__':
     unittest.main()