Repository: lesshint/lesshint
Pull request #336 (2017-02-04 16:54:54)

Add universalSelector linter
<!--
If you're submitting a PR for a new feature, please open an issue about it first so we can discuss it.
If you're submitting a PR for something else, for example a documentation fix, go right ahead!
-->

**Which issue, if any, does this resolve?**
#266 

**Is there anything in this PR that needs extra explaining or should something specific be focused on?**
Nothing in particular.


Patch:
diff --git a/lib/config/defaults.json b/lib/config/defaults.json
index eee960d..6f285bd 100644
--- a/lib/config/defaults.json
+++ b/lib/config/defaults.json
@@ -175,6 +175,10 @@
         "enabled": true
     },
 
+    "universalSelector": {
+        "enabled": true
+    },
+
     "urlFormat": {
         "enabled": true,
         "style": "relative"
diff --git a/lib/linter.js b/lib/linter.js
index c6f104a..129d98a 100644
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -8,7 +8,6 @@ const path = require('path');
 
 const defaultLinters = [
     require('./linters/attribute_quotes'),
-    require('./linters/newline_after_block'),
     require('./linters/border_zero'),
     require('./linters/comment'),
     require('./linters/decimal_zero'),
@@ -22,6 +21,9 @@ const defaultLinters = [
     require('./linters/id_selector'),
     require('./linters/import_path'),
     require('./linters/important_rule'),
+    require('./linters/max_char_per_line'),
+    require('./linters/max_char_per_line'),
+    require('./linters/newline_after_block'),
     require('./linters/property_ordering'),
     require('./linters/property_units'),
     require('./linters/qualifying_element'),
@@ -38,11 +40,11 @@ const defaultLinters = [
     require('./linters/string_quotes'),
     require('./linters/trailing_semicolon'),
     require('./linters/trailing_whitespace'),
+    require('./linters/universal_selector'),
     require('./linters/url_format'),
     require('./linters/url_quotes'),
-    require('./linters/zero_unit'),
-    require('./linters/max_char_per_line'),
-    require('./linters/variable_value')
+    require('./linters/variable_value'),
+    require('./linters/zero_unit')
 ];
 
 const parseRules = function (node) {
diff --git a/lib/linters/README.md b/lib/linters/README.md
index 8a646ad..c3dbc09 100644
--- a/lib/linters/README.md
+++ b/lib/linters/README.md
@@ -32,6 +32,7 @@
 * [stringQuotes](#stringquotes)
 * [trailingSemicolon](#trailingsemicolon)
 * [trailingWhitespace](#trailingwhitespace)
+* [universalSelector](#universalselector)
 * [urlFormat](#urlformat)
 * [urlQuotes](#urlquotes)
 * [variableValue](#variablevalue)
@@ -836,6 +837,32 @@ Option     | Description
 }
 ```
 
+## universalSelector
+Disallow the usage of the universal selector anywhere in a selector expression.
+The universal selector shouldn't be used since it'll cancel out any inheritance.
+
+### invalid
+```less
+* {
+    color: red;
+}
+
+.foo * {
+    color: red;
+}
+```
+
+### valid
+```less
+.foo {
+    color: red;
+}
+
+.foo .bar {
+    color: red;
+}
+```
+
 ## urlQuotes
 All URLs should be enclosed in quotes.
 Using quotes around URLs allows them to be treated as strings, making escaping of characters easier.
diff --git a/lib/linters/universal_selector.js b/lib/linters/universal_selector.js
new file mode 100644
index 0000000..3985bd9
--- /dev/null
+++ b/lib/linters/universal_selector.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const parser = require('postcss-selector-parser');
+
+module.exports = {
+    name: 'universalSelector',
+    nodeTypes: ['rule'],
+    message: "The universal selector shouldn't be used.",
+
+    lint: function universalSelectorLinter (config, node) {
+        let tree;
+
+        parser(function (selectors) {
+            tree = selectors;
+        })
+        .process(node.selector);
+
+        const results = [];
+
+        tree.each((selector) => {
+            selector.walkUniversals((uni) => {
+                results.push({
+                    column: node.source.start.column + uni.source.start.column - 1,
+                    line: node.source.start.line,
+                    message: this.message
+                });
+            });
+        });
+
+        if (results.length) {
+            return results;
+        }
+    }
+};


Test patch:
diff --git a/test/specs/linters/universal_selector.js b/test/specs/linters/universal_selector.js
new file mode 100644
index 0000000..8294777
--- /dev/null
+++ b/test/specs/linters/universal_selector.js
@@ -0,0 +1,67 @@
+'use strict';
+
+const expect = require('chai').expect;
+const spec = require('../util.js').setup();
+
+describe('lesshint', function () {
+    describe('#universalSelector()', function () {
+        it('should have the proper node types', function () {
+            const source = '* {}';
+
+            return spec.parse(source, function (ast) {
+                expect(spec.linter.nodeTypes).to.include(ast.root.first.type);
+            });
+        });
+
+        it('should allow non-universal selectors', function () {
+            const source = '.foo {}';
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint({}, ast.root.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
+        it('should not allow universal selectors', function () {
+            const source = '.foo * {}';
+            const expected = [{
+                column: 6,
+                line: 1,
+                message: "The universal selector shouldn't be used."
+            }];
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint({}, ast.root.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
+
+        it('should not allow universal selectors in multiple selectors for a rule', function () {
+            const source = '.foo *, .baz * {}';
+            const expected = [
+                {
+                    column: 6,
+                    line: 1,
+                    message: "The universal selector shouldn't be used."
+                },
+                {
+                    column: 14,
+                    line: 1,
+                    message: "The universal selector shouldn't be used."
+                }
+            ];
+
+            const options = {
+                exclude: []
+            };
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
+    });
+});