Repository: sasstools/sass-lint (js)

Issue:
New Rule: Empty Args
With Sass, if a function or mixin doesn't have any arguments, the parenthesis can be left off of the definition and, in the case of mixins, the invocation as well

Pull request description:
Add Empty Args rule
This PR adds a rule for Empty Args which enforces whether or not parenthesis should be included if no arguments are defined or used, when declaring or invoking a mixin.

Closes #38 

DCO 1.1 Signed-off-by: Ben Griffith <gt11687@gmail.com>

Patch:
diff --git a/docs/rules/empty-args.md b/docs/rules/empty-args.md
new file mode 100644
index 0000000..07736fc
--- /dev/null
+++ b/docs/rules/empty-args.md
@@ -0,0 +1,33 @@
+# Empty Args
+
+Rule `empty-args` will enforce whether or not parenthesis should be included if no arguments are defined or used, when declaring or invoking a mixin.
+
+## Options
+
+* `include`: `true`/`false` (defaults to `false`)
+
+## Examples
+
+When `include: false`, the following are allowed. When `include: true`, the following are disallowed:
+
+```scss
+@mixin bar {
+  padding: 10px;
+}
+
+.bar {
+  @include bar;
+}
+```
+
+When `include: true`, the following are allowed. When `include: false`, the following are disallowed:
+
+```scss
+@mixin foo() {
+  padding: 10px;
+}
+
+.foo {
+  @include foo();
+}
+```
diff --git a/lib/config/sass-lint.yml b/lib/config/sass-lint.yml
index 7bf601f..ec5052e 100644
--- a/lib/config/sass-lint.yml
+++ b/lib/config/sass-lint.yml
@@ -28,6 +28,7 @@ rules:
   border-zero: 1
   clean-import-paths: 1
   comment: 1
+  empty-args: 1
   hex-length: 1
   hex-notation: 1
   hex-validation: 1
diff --git a/lib/rules/empty-args.js b/lib/rules/empty-args.js
new file mode 100644
index 0000000..06179bd
--- /dev/null
+++ b/lib/rules/empty-args.js
@@ -0,0 +1,46 @@
+'use strict';
+
+var helpers = require('../helpers');
+
+module.exports = {
+  'name': 'no-empty-args',
+  'defaults': {
+    'include': false
+  },
+  'detect': function (ast, parser) {
+    var result = [];
+
+    ast.traverseByTypes(['mixin', 'include'], function (item) {
+      if (item.contains('arguments')) {
+        item.traverse(function (node) {
+          if (node.type === 'arguments') {
+            if (node.content.length === 0) {
+              if (!parser.options.include) {
+                result = helpers.addUnique(result, {
+                  'ruleId': parser.rule.name,
+                  'line': node.start.line,
+                  'column': node.start.column,
+                  'message': 'Parenthesis should be removed.',
+                  'severity': parser.severity
+                });
+              }
+            }
+          }
+        });
+      }
+      else {
+        if (parser.options.include) {
+          result = helpers.addUnique(result, {
+            'ruleId': parser.rule.name,
+            'line': item.start.line,
+            'column': item.start.column,
+            'message': 'Parenthesis are required.',
+            'severity': parser.severity
+          });
+        }
+      }
+    });
+
+    return result;
+  }
+};


Test patch:
diff --git a/tests/main.js b/tests/main.js
index 7920294..a21a2b9 100644
--- a/tests/main.js
+++ b/tests/main.js
@@ -499,6 +499,29 @@ describe('rule', function () {
     });
   });
 
+  //////////////////////////////
+  // Empty Args
+  //////////////////////////////
+
+  // Default
+  it('empty args - [include: false]', function (done) {
+    lintFile('empty-args.scss', function (data) {
+      assert.equal(2, data.warningCount);
+      done();
+    });
+  });
+
+  it('empty args - [include: true]', function (done) {
+    lintFile('empty-args.scss', {
+      'rules': {
+        'empty-args': [1, { include: true }]
+      }
+    }, function (data) {
+      assert.equal(3, data.warningCount);
+      done();
+    });
+  });
+
   //////////////////////////////
   // Border Zero
   //////////////////////////////
diff --git a/tests/sass/empty-args.scss b/tests/sass/empty-args.scss
new file mode 100644
index 0000000..048f919
--- /dev/null
+++ b/tests/sass/empty-args.scss
@@ -0,0 +1,43 @@
+// No arguments with parenthesis
+@mixin foo() {
+  color: red;
+}
+
+.foo {
+  @include foo();
+}
+
+
+
+// No arguments with no parenthesis
+@mixin bar {
+  color: orange;
+}
+
+.bar {
+  @include bar;
+}
+
+
+
+// Arguments with parenthesis
+@mixin baz($color) {
+  color: $color;
+}
+
+$foo: blue;
+
+.baz {
+  @include baz($foo);
+}
+
+
+
+// Parenthesis only where required
+@mixin qux($color: lime) {
+  color: $color;
+}
+
+.qux {
+  @include qux;
+}