From 27c430aaf004a95fcc5db9d6fde27bdd10c478ba Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Wed, 22 Jun 2016 20:10:23 +0100 Subject: [PATCH 01/16] :white_check_mark: Add no-color-hex tests --- tests/rules/no-color-hex.js | 35 +++++++++++++++++++++++++++++++++++ tests/sass/no-color-hex.sass | 25 +++++++++++++++++++++++++ tests/sass/no-color-hex.scss | 26 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 tests/rules/no-color-hex.js create mode 100644 tests/sass/no-color-hex.sass create mode 100644 tests/sass/no-color-hex.scss diff --git a/tests/rules/no-color-hex.js b/tests/rules/no-color-hex.js new file mode 100644 index 00000000..2cfe53e5 --- /dev/null +++ b/tests/rules/no-color-hex.js @@ -0,0 +1,35 @@ +'use strict'; + +var lint = require('./_lint'); + +////////////////////////////// +// SCSS syntax tests +////////////////////////////// +describe('no color hex - scss', function () { + var file = lint.file('no-color-hex.scss'); + + it('enforce', function (done) { + lint.test(file, { + 'no-color-hex': 1 + }, function (data) { + lint.assert.equal(9, data.warningCount); + done(); + }); + }); +}); + +////////////////////////////// +// Sass syntax tests +////////////////////////////// +describe('no color hex - sass', function () { + var file = lint.file('no-color-hex.sass'); + + it('enforce', function (done) { + lint.test(file, { + 'no-color-hex': 1 + }, function (data) { + lint.assert.equal(9, data.warningCount); + done(); + }); + }); +}); diff --git a/tests/sass/no-color-hex.sass b/tests/sass/no-color-hex.sass new file mode 100644 index 00000000..69c26e65 --- /dev/null +++ b/tests/sass/no-color-hex.sass @@ -0,0 +1,25 @@ +$foo-color: #123 + +.foo + background: linear-gradient(top, #cc2, #44d) + color: #fff + + +$bar-color: #112233 + +.bar + background: linear-gradient(top, #cccc22, #4444dd) + color: #ffffff + +.baz + border-color: #123456 + + +// color literals, rgb and hsl values currently don't get returned +// by the AST's color type + +$qux-color: red +$rgb-color: rgb(255, 255, 255) +$rgba-color: rgba(0, 0, 0, .1) +$hsl-color: hsl(40, 50%, 50%) +$hsla-color: hsla(40, 50%, 50%, .3) diff --git a/tests/sass/no-color-hex.scss b/tests/sass/no-color-hex.scss new file mode 100644 index 00000000..58c383d4 --- /dev/null +++ b/tests/sass/no-color-hex.scss @@ -0,0 +1,26 @@ +$foo-color: #123; + +.foo { + background: linear-gradient(top, #cc2, #44d); + color: #fff; +} + +$bar-color: #112233; + +.bar { + background: linear-gradient(top, #cccc22, #4444dd); + color: #ffffff; +} + +.baz { + border-color: #123456; +} + +// color literals, rgb and hsl values currently don't get returned +// by the AST's color type + +$qux-color: red; +$rgb-color: rgb(255, 255, 255); +$rgba-color: rgba(0, 0, 0, .1); +$hsl-color: hsl(40, 50%, 50%); +$hsla-color: hsla(40, 50%, 50%, .3); From 254c42e2c2f86873fdeb4b735052c0c91410a5b0 Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Wed, 22 Jun 2016 20:11:22 +0100 Subject: [PATCH 02/16] :mag: Add no-color-hex rule --- lib/rules/no-color-hex.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 lib/rules/no-color-hex.js diff --git a/lib/rules/no-color-hex.js b/lib/rules/no-color-hex.js new file mode 100644 index 00000000..d5190056 --- /dev/null +++ b/lib/rules/no-color-hex.js @@ -0,0 +1,22 @@ +'use strict'; + +var helpers = require('../helpers'); + +module.exports = { + 'name': 'no-color-hex', + 'defaults': {}, + 'detect': function (ast, parser) { + var result = []; + + ast.traverseByType('color', function (value) { + result = helpers.addUnique(result, { + 'ruleId': parser.rule.name, + 'line': value.start.line, + 'column': value.start.column, + 'message': 'Hexadecimal colors should not be used', + 'severity': parser.severity + }); + }); + return result; + } +}; From e96f4e1d06550da540c420fc4f99bfc893aaee56 Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Wed, 22 Jun 2016 20:11:48 +0100 Subject: [PATCH 03/16] :memo: Add no-color-hex documentation --- docs/rules/no-color-hex.md | 33 +++++++++++++++++++++++++++++++++ lib/config/sass-lint.yml | 1 + 2 files changed, 34 insertions(+) create mode 100644 docs/rules/no-color-hex.md diff --git a/docs/rules/no-color-hex.md b/docs/rules/no-color-hex.md new file mode 100644 index 00000000..9d55d95e --- /dev/null +++ b/docs/rules/no-color-hex.md @@ -0,0 +1,33 @@ +# No Color Hex + +Rule `no-color-hex` will disallow the use of hexadecimal colors + +## Examples + +When enabled the following are disallowed. + +```scss +$foo-color: #456; + +.bar { + background: linear-gradient(top, #3ff, #ddd); +} + +.baz { + color: #fff; +} +``` + +When enabled the following are allowed: + +```scss +$foo-color: red; + +.bar { + background: linear-gradient(top, blue, green); +} + +.baz { + color: white; +} +``` diff --git a/lib/config/sass-lint.yml b/lib/config/sass-lint.yml index dd3e5113..1b3ed511 100644 --- a/lib/config/sass-lint.yml +++ b/lib/config/sass-lint.yml @@ -18,6 +18,7 @@ rules: # Disallows no-attribute-selectors: 0 + no-color-hex: 0 no-color-keywords: 1 no-color-literals: 1 no-combinators: 0 From fede768599002a1a1cffd9b1f95768ae8d7d0000 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Fri, 1 Jul 2016 10:38:57 -0400 Subject: [PATCH 04/16] chore(package): update gonzales-pe to version 3.3.6 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 306f01f6..2b46c599 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "fs-extra": "^0.30.0", "glob": "^7.0.0", "globule": "^1.0.0", - "gonzales-pe": "3.3.4", + "gonzales-pe": "3.3.6", "js-yaml": "^3.5.4", "lodash.capitalize": "^4.1.0", "lodash.kebabcase": "^4.0.0", From a2deb23b7b839a2945510d0d04dcf383109a22fe Mon Sep 17 00:00:00 2001 From: Bastiaan van den Berg Date: Wed, 13 Jul 2016 20:25:38 +0200 Subject: [PATCH 05/16] Issue #787 Fix typos in no-vendor-prefixes rule documentation. --- docs/rules/no-vendor-prefixes.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/rules/no-vendor-prefixes.md b/docs/rules/no-vendor-prefixes.md index 22bd1567..957f9cf3 100644 --- a/docs/rules/no-vendor-prefixes.md +++ b/docs/rules/no-vendor-prefixes.md @@ -41,14 +41,14 @@ When enabled, the following are disallowed: ### Additional Identifiers -When `additional-identifiers` contains a custom prefix value of `test` as show below +When `additional-identifiers` contains a custom prefix value of `khtml` as show below ```yaml -no-vendor-prefix: +no-vendor-prefixes: - 1 - - 'additional-identifiers': - - 'khtml' + additional-identifiers: + - khtml ``` The following would now also be disallowed @@ -64,12 +64,12 @@ The following would now also be disallowed When `excluded-identifiers` contains currently disallowed prefix values such as `webkit` and `moz` as show below ```yaml -no-vendor-prefix: +no-vendor-prefixes: - 1 - - 'excluded-identifiers': - - 'webkit' - - 'moz' + excluded-identifiers: + - webkit + - moz ``` The following would now be allowed From 962ba56d30fe9316ace737d70aa9959e51635f9d Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Thu, 4 Aug 2016 00:18:36 -0700 Subject: [PATCH 06/16] chore(package): update mocha to version 3.0.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2b46c599..af153c93 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "coveralls": "^2.11.4", "deep-equal": "^1.0.1", "istanbul": "^0.4.0", - "mocha": "^2.2.5", + "mocha": "^3.0.1", "sinon": "^1.17.4" } } From 1e1ca9a453564667a32739dcd800ac7b03318c8f Mon Sep 17 00:00:00 2001 From: Glen Date: Mon, 8 Aug 2016 12:07:36 +0200 Subject: [PATCH 07/16] Add VS Code extension link --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 555a4a75..8052517b 100644 --- a/README.md +++ b/README.md @@ -194,3 +194,4 @@ Our AST is [Gonzales-PE](https://github.com/tonyganch/gonzales-pe/tree/dev). Eac * [Sublime Text](https://github.com/skovhus/SublimeLinter-contrib-sass-lint) * [Brackets](https://github.com/petetnt/brackets-sass-lint) * [IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm](https://github.com/idok/sass-lint-plugin) +* [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=glen-84.sass-lint) From daed6f4e43fa171b38573d87a782e19e608d344f Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Mon, 8 Aug 2016 12:28:13 -0400 Subject: [PATCH 08/16] chore(package): update gonzales-pe to version 3.4.3 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af153c93..0ff73405 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "fs-extra": "^0.30.0", "glob": "^7.0.0", "globule": "^1.0.0", - "gonzales-pe": "3.3.6", + "gonzales-pe": "3.4.3", "js-yaml": "^3.5.4", "lodash.capitalize": "^4.1.0", "lodash.kebabcase": "^4.0.0", From 119580a00c40ea4d1fd57f2f7078c93fcdaa5af6 Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Sat, 6 Aug 2016 16:38:35 +0100 Subject: [PATCH 09/16] :bug: Fix issue with import at-rule and maps --- lib/rules/indentation.js | 7 ++++++- tests/sass/indentation/indentation-spaces.scss | 15 +++++++++++++++ tests/sass/indentation/indentation-tabs.scss | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/rules/indentation.js b/lib/rules/indentation.js index 7ef79b16..fd821872 100644 --- a/lib/rules/indentation.js +++ b/lib/rules/indentation.js @@ -171,6 +171,11 @@ module.exports = { inBlock = false; } + // We should move out of the at-rule if it's an import as we dont want to look for multi level arguments etc + if (inAtRule && n.is('ident') && n.content === 'import') { + inAtRule = false; + } + // if a delimeter is encountered we check if it's directly after a parenthesis node // if it is we know next node will be the same level of indentation if (n.is('operator')) { @@ -204,7 +209,7 @@ module.exports = { processNode(n, level); } }; - + helpers.log(ast); processNode(ast); return result; } diff --git a/tests/sass/indentation/indentation-spaces.scss b/tests/sass/indentation/indentation-spaces.scss index 52d83606..beb50cac 100644 --- a/tests/sass/indentation/indentation-spaces.scss +++ b/tests/sass/indentation/indentation-spaces.scss @@ -143,3 +143,18 @@ $colors: ( margin: 16px; } } + +// Issue #783 and issue #779 - Import rule throws off indentation of maps etc + +@import 'echo-base/defaults/breakpoints'; + +$textsizes: ( + xs: ( + s: 10px, + m: 10px + ), + s: ( + s: 12px, + m: 13px + ), +); diff --git a/tests/sass/indentation/indentation-tabs.scss b/tests/sass/indentation/indentation-tabs.scss index d0bf98fa..bdcab940 100644 --- a/tests/sass/indentation/indentation-tabs.scss +++ b/tests/sass/indentation/indentation-tabs.scss @@ -143,3 +143,18 @@ $colors: ( margin: 16px; } } + +// Issue #783 and issue #779 - Import rule throws off indentation of maps etc + +@import 'echo-base/defaults/breakpoints'; + +$textsizes: ( + xs: ( + s: 10px, + m: 10px + ), + s: ( + s: 12px, + m: 13px + ), +); From 056fec82170f5f3b64a746871ad56f62cb2ee4bd Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Mon, 8 Aug 2016 21:51:54 +0100 Subject: [PATCH 10/16] :bug: Fix for at-rules not cotaining blocks --- lib/rules/indentation.js | 9 ++------- tests/rules/indentation.js | 4 ++-- tests/sass/indentation/indentation-spaces.scss | 17 ++++++++++++++++- tests/sass/indentation/indentation-tabs.scss | 17 ++++++++++++++++- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/rules/indentation.js b/lib/rules/indentation.js index fd821872..83b688b1 100644 --- a/lib/rules/indentation.js +++ b/lib/rules/indentation.js @@ -166,16 +166,11 @@ module.exports = { mixedWarning = false; } // if we're in an atrule make we need to possibly handle multiline arguments - if (n.is('atrule')) { + if (n.is('atrule') && n.contains('block')) { inAtRule = true; inBlock = false; } - // We should move out of the at-rule if it's an import as we dont want to look for multi level arguments etc - if (inAtRule && n.is('ident') && n.content === 'import') { - inAtRule = false; - } - // if a delimeter is encountered we check if it's directly after a parenthesis node // if it is we know next node will be the same level of indentation if (n.is('operator')) { @@ -209,7 +204,7 @@ module.exports = { processNode(n, level); } }; - helpers.log(ast); + processNode(ast); return result; } diff --git a/tests/rules/indentation.js b/tests/rules/indentation.js index 7ccf15f3..43968f51 100644 --- a/tests/rules/indentation.js +++ b/tests/rules/indentation.js @@ -13,7 +13,7 @@ describe('indentation - scss', function () { lint.test(spaceFile, { 'indentation': 1 }, function (data) { - lint.assert.equal(12, data.warningCount); + lint.assert.equal(14, data.warningCount); done(); }); }); @@ -27,7 +27,7 @@ describe('indentation - scss', function () { } ] }, function (data) { - lint.assert.equal(12, data.warningCount); + lint.assert.equal(14, data.warningCount); done(); }); }); diff --git a/tests/sass/indentation/indentation-spaces.scss b/tests/sass/indentation/indentation-spaces.scss index beb50cac..15d8ea6b 100644 --- a/tests/sass/indentation/indentation-spaces.scss +++ b/tests/sass/indentation/indentation-spaces.scss @@ -144,7 +144,7 @@ $colors: ( } } -// Issue #783 and issue #779 - Import rule throws off indentation of maps etc +// Issue #783 and issue #779 - at-rule throws off indentation of maps etc @import 'echo-base/defaults/breakpoints'; @@ -153,6 +153,21 @@ $textsizes: ( s: 10px, m: 10px ), + s: ( + s: 12px, + m: 13px + ), +); + +@function em($pixels, $context: $font-size-base) { + @return #{($pixels / $context)}rem; +} + +$textsizes: ( + xs: ( + s: 10px, + m: 10px + ), s: ( s: 12px, m: 13px diff --git a/tests/sass/indentation/indentation-tabs.scss b/tests/sass/indentation/indentation-tabs.scss index bdcab940..c34f5d5a 100644 --- a/tests/sass/indentation/indentation-tabs.scss +++ b/tests/sass/indentation/indentation-tabs.scss @@ -144,7 +144,7 @@ $colors: ( } } -// Issue #783 and issue #779 - Import rule throws off indentation of maps etc +// Issue #783 and issue #779 - at-rule throws off indentation of maps etc @import 'echo-base/defaults/breakpoints'; @@ -153,6 +153,21 @@ $textsizes: ( s: 10px, m: 10px ), + s: ( + s: 12px, + m: 13px + ), +); + +@function em($pixels, $context: $font-size-base) { + @return #{($pixels / $context)}rem; +} + +$textsizes: ( + xs: ( + s: 10px, + m: 10px + ), s: ( s: 12px, m: 13px From c993574a7366d31cce9fcfe1b0391b54922d8615 Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Mon, 8 Aug 2016 22:58:37 +0100 Subject: [PATCH 11/16] :bug: Fix comments warning as selectors #789 --- lib/rules/single-line-per-selector.js | 47 +++++++++++++++++------- tests/sass/single-line-per-selector.sass | 9 +++++ tests/sass/single-line-per-selector.scss | 10 +++++ 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/lib/rules/single-line-per-selector.js b/lib/rules/single-line-per-selector.js index 9ba9c7ca..53cdf902 100644 --- a/lib/rules/single-line-per-selector.js +++ b/lib/rules/single-line-per-selector.js @@ -2,6 +2,31 @@ var helpers = require('../helpers'); +/** + * Checks a ruleset for selectors or EOL characters. If a selector is found before an EOL + * then it returns the selector node for reporting or returns false + * + * @param {Object} ruleset - The ruleset node + * @param {number} index - The current index of the delimiter + * @returns {Object|boolean} Either the selector node or false + */ +var checkLineForSelector = function (ruleset, index) { + var curIndex = index += 1; + if (ruleset.content[curIndex]) { + for (; curIndex < ruleset.content.length; curIndex++) { + var curType = ruleset.content[curIndex].type; + if (curType === 'space' && helpers.hasEOL(ruleset.content[curIndex])) { + return false; + } + if (curType === 'selector' || curType === 'typeSelector') { + return ruleset.content[curIndex]; + } + } + } + + return false; +}; + module.exports = { 'name': 'single-line-per-selector', 'defaults': {}, @@ -10,22 +35,16 @@ module.exports = { ast.traverseByType('ruleset', function (ruleset) { ruleset.forEach('delimiter', function (delimiter, j) { - var next = ruleset.content[j + 1] || false; + var next = checkLineForSelector(ruleset, j); if (next) { - if (next.is('selector')) { - next = next.content[0]; - } - - if (!(next.is('space') && helpers.hasEOL(next.content))) { - result = helpers.addUnique(result, { - 'ruleId': parser.rule.name, - 'line': next.start.line, - 'column': next.start.column, - 'message': 'Selectors must be placed on new lines', - 'severity': parser.severity - }); - } + result = helpers.addUnique(result, { + 'ruleId': parser.rule.name, + 'line': next.start.line, + 'column': next.start.column, + 'message': 'Selectors must be placed on new lines', + 'severity': parser.severity + }); } }); }); diff --git a/tests/sass/single-line-per-selector.sass b/tests/sass/single-line-per-selector.sass index 72210d79..f2b94b0f 100644 --- a/tests/sass/single-line-per-selector.sass +++ b/tests/sass/single-line-per-selector.sass @@ -48,3 +48,12 @@ .foo .bar &, .baz & content: 'foo' + +// Issue #789 - Issue with comments being warned as selector content on single lines +button, +html, +html input[type='button'], // 6 +input[type='reset'], +input[type='submit'] + -webkit-appearance: button; // 7 + cursor: pointer; // 8 diff --git a/tests/sass/single-line-per-selector.scss b/tests/sass/single-line-per-selector.scss index 65ed18ef..18fcb5df 100644 --- a/tests/sass/single-line-per-selector.scss +++ b/tests/sass/single-line-per-selector.scss @@ -54,3 +54,13 @@ content: 'foo'; } } + +// Issue #789 - Issue with comments being warned as selector content on single lines +button, +html, +html input[type='button'], // 6 +input[type='reset'], +input[type='submit'] { + -webkit-appearance: button; // 7 + cursor: pointer; // 8 +} From dae875c5ca4fdd87f29ad2d94381c6ed3c9b41b8 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Fri, 12 Aug 2016 06:22:40 -0400 Subject: [PATCH 12/16] chore(package): update gonzales-pe to version 3.4.4 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ff73405..bf208fd3 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "fs-extra": "^0.30.0", "glob": "^7.0.0", "globule": "^1.0.0", - "gonzales-pe": "3.4.3", + "gonzales-pe": "3.4.4", "js-yaml": "^3.5.4", "lodash.capitalize": "^4.1.0", "lodash.kebabcase": "^4.0.0", From 22c7c321b259f5ddfef0810ac7c3a23892d7cf49 Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Mon, 8 Aug 2016 23:59:23 +0100 Subject: [PATCH 13/16] :bug: Fix interpolation in placeholders - bem depth --- lib/rules/bem-depth.js | 26 ++++---- lib/selector-helpers.js | 128 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 lib/selector-helpers.js diff --git a/lib/rules/bem-depth.js b/lib/rules/bem-depth.js index 9910b765..356efa25 100644 --- a/lib/rules/bem-depth.js +++ b/lib/rules/bem-depth.js @@ -1,6 +1,7 @@ 'use strict'; var helpers = require('../helpers'); +var selectorHelpers = require('../selector-helpers'); /** * Get number of BEM elements in @@ -32,18 +33,19 @@ module.exports = { maxDepth = parser.options['max-depth']; if (node.is('placeholder')) { - name = node.first('ident') && node.first('ident').content; - depth = bemDepth(name); - - if (name && depth > maxDepth) { - result = helpers.addUnique(result, { - 'ruleId': parser.rule.name, - 'line': node.start.line, - 'column': node.start.column, - 'message': ['Placeholder \'%', name, '\' should have ', maxDepth, ' or fewer BEM elements, but ', - depth, ' were found.'].join(''), - 'severity': parser.severity - }); + name = selectorHelpers.constructSelector(node); + if (name) { + depth = bemDepth(name); + if (depth > maxDepth) { + result = helpers.addUnique(result, { + 'ruleId': parser.rule.name, + 'line': node.start.line, + 'column': node.start.column, + 'message': ['Placeholder \'%', name, '\' should have ', maxDepth, ' or fewer BEM elements, but ', + depth, ' were found.'].join(''), + 'severity': parser.severity + }); + } } } else { diff --git a/lib/selector-helpers.js b/lib/selector-helpers.js new file mode 100644 index 00000000..1e78ddcb --- /dev/null +++ b/lib/selector-helpers.js @@ -0,0 +1,128 @@ +'use strict'; + +// ============================================================================== +// Helpers +// ============================================================================== + +var simpleIdents = [ + 'ident', + 'number', + 'operator', + 'combinator', + 'string', + 'parentSelector', + 'delimiter', + 'typeSelector', + 'attributeMatch' +]; + +/** + * Adds grammar around our content blocks to construct selectors with + * more readable formats. + * + * @param {object} val - The current value node + * @param {string} prefix - The grammar to prefix the value with + * @param {string} suffix - The grammar to add after the value + * @returns {string} The correct readable format + */ +var addGrammar = function (val, prefix, suffix) { + return prefix + val.content + suffix; +}; + +/** + * Adds grammar around our content blocks to construct selectors with + * more readable formats and loops the content as they're within sub blocks. + * + * @param {object} val - The current value node + * @param {string} prefix - The grammar to prefix the value with + * @param {string} suffix - The grammar to add after the value + * @param {function} constructSelector - The callback we wish to use which means constructSelector in this instance + * @returns {string} The correct readable format + */ +var constructSubSelector = function (val, prefix, suffix, constructSelector) { + var content = prefix; + val.forEach(function (subItem) { + content += constructSelector(subItem); + }); + + return content + suffix; +}; + +// ============================================================================== +// Public Methods +// ============================================================================== + +/** + * Constructs a syntax complete selector for our selector matching and warning output + * + * @param {object} val - The current node / part of our selector + * @returns {string} - Content: The current node with correct syntax e.g. class my-class = '.my-class' + */ +var constructSelector = function (val) { + var content = null; + + if (val.is('id')) { + content = addGrammar(val, '#', ''); + } + + else if (val.is('class')) { + content = addGrammar(val, '.', ''); + } + + else if (simpleIdents.indexOf(val.type) !== -1) { + content = val.content; + } + + else if (val.is('attributeSelector')) { + content = constructSubSelector(val, '[', ']', constructSelector); + } + + else if (val.is('atkeyword')) { + content = constructSubSelector(val, '@', '', constructSelector); + } + + else if (val.is('placeholder')) { + content = constructSubSelector(val, '%', '', constructSelector); + } + + else if (val.is('variable')) { + content = constructSubSelector(val, '$', '', constructSelector); + } + + else if (val.is('pseudoClass')) { + content = addGrammar(val, ':', ''); + } + + else if (val.is('pseudoElement')) { + content = addGrammar(val, '::', ''); + } + + else if (val.is('nth')) { + content = addGrammar(val, '(', ')'); + } + + else if (val.is('nthSelector')) { + content = constructSubSelector(val, ':', '', constructSelector); + } + + else if (val.is('parentheses')) { + content = constructSubSelector(val, '(', ')', constructSelector); + } + + else if (val.is('space')) { + content = ' '; + } + + else if (val.is('parentSelectorExtension') || val.is('attributeName') || val.is('attributeValue') || val.is('dimension')) { + content = constructSubSelector(val, '', '', constructSelector); + } + + else if (val.is('interpolation')) { + content = constructSubSelector(val, '#{', '}', constructSelector); + } + return content; +}; + +module.exports = { + constructSelector: constructSelector +}; From 654bf72e573c85f7a8638e1980066fbefbd6764b Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Sun, 14 Aug 2016 00:01:22 +0100 Subject: [PATCH 14/16] :white_check_mark: Add constructSelector tests --- lib/selector-helpers.js | 6 +- .../selector-helpers/selector-helpers.scss | 61 ++++++++++ tests/selector-helpers/selectorHelpers.js | 114 ++++++++++++++++++ 3 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 tests/sass/selector-helpers/selector-helpers.scss create mode 100644 tests/selector-helpers/selectorHelpers.js diff --git a/lib/selector-helpers.js b/lib/selector-helpers.js index 1e78ddcb..f522c964 100644 --- a/lib/selector-helpers.js +++ b/lib/selector-helpers.js @@ -73,6 +73,10 @@ var constructSelector = function (val) { content = val.content; } + else if (val.is('arguments')) { + content = constructSubSelector(val, '(', ')', constructSelector); + } + else if (val.is('attributeSelector')) { content = constructSubSelector(val, '[', ']', constructSelector); } @@ -90,7 +94,7 @@ var constructSelector = function (val) { } else if (val.is('pseudoClass')) { - content = addGrammar(val, ':', ''); + content = constructSubSelector(val, ':', '', constructSelector); } else if (val.is('pseudoElement')) { diff --git a/tests/sass/selector-helpers/selector-helpers.scss b/tests/sass/selector-helpers/selector-helpers.scss new file mode 100644 index 00000000..7a449784 --- /dev/null +++ b/tests/sass/selector-helpers/selector-helpers.scss @@ -0,0 +1,61 @@ +.test{ + color: red; +} + +#test{ + color: red; +} + +%test { + color: red; +} + +.#{test} { + color: red +} + +.test, #test { + color: red; +} + +input[type="text"] { + color: red; +} + +.test > li { + color: red; +} + +span[lang~=en-us] { + color: red; +} + +.block__element-one { + color: red; +} + +@media (max-width: 200px) { + color: red; +} + +##{$id} { + color: red; +} + +.right-element::-ms-backdrop { + content: "right-prefixed-element"; +} + +.wrong-element:selection { + content: "wrong-element"; +} + +p:nth-of-type(2) { + margin: 0; +} + +.test { + &__test { + color: red; + } +} diff --git a/tests/selector-helpers/selectorHelpers.js b/tests/selector-helpers/selectorHelpers.js new file mode 100644 index 00000000..9a502ef9 --- /dev/null +++ b/tests/selector-helpers/selectorHelpers.js @@ -0,0 +1,114 @@ +'use strict'; + +var assert = require('assert'), + selectorHelpers = require('../../lib/selector-helpers'), + groot = require('../../lib/groot'), + path = require('path'), + fs = require('fs'), + equal = require('deep-equal'); + +describe('selectorHelpers - constructSelector', function () { + + var expectedSelectors = [ + '.test', + '#test', + '%test', + '.#{test}', + '.test', + '#test', + 'input[type="text"]', + '.test > li', + 'span[lang~=en-us]', + '.block__element-one', + '##{$id}', + '.right-element::-ms-backdrop', + '.wrong-element:selection', + 'p:nth-of-type(2)', + '.test', + '&__test' + ], + selectorList = []; + + before(function () { + var file = '../sass/selector-helpers/selector-helpers.scss', + ast = groot(fs.readFileSync(path.join(__dirname, file)), path.extname(file).replace('.', ''), file); + + ast.traverseByType('selector', function (value) { + var ruleSet = ''; + value.forEach(function (selectorContent) { + ruleSet += selectorHelpers.constructSelector(selectorContent); + }); + selectorList.push(ruleSet); + }); + }); + + ////////////////////////////// + // contructSelector + ////////////////////////////// + + it('should return the correct class name', function (done) { + assert(equal(selectorList[0], expectedSelectors[0])); + done(); + }); + + it('should return the correct ID name', function (done) { + assert(equal(selectorList[1], expectedSelectors[1])); + done(); + }); + + it('should return the correct placeholder name', function (done) { + assert(equal(selectorList[2], expectedSelectors[2])); + done(); + }); + + it('should return the correct interpolated selector name', function (done) { + assert(equal(selectorList[3], expectedSelectors[3])); + done(); + }); + + it('should return the correct type selector name', function (done) { + assert(equal(selectorList[6], expectedSelectors[6])); + done(); + }); + + it('should return the correct combinator selector name', function (done) { + assert(equal(selectorList[7], expectedSelectors[7])); + done(); + }); + + it('should return the correct attribute selector name', function (done) { + assert(equal(selectorList[8], expectedSelectors[8])); + done(); + }); + + it('should return the correct BEM selector name', function (done) { + assert(equal(selectorList[9], expectedSelectors[9])); + done(); + }); + + it('should return the correct interpolated ID selector name', function (done) { + assert(equal(selectorList[10], expectedSelectors[10])); + done(); + }); + + it('should return the correct pseudo element selector name', function (done) { + assert(equal(selectorList[11], expectedSelectors[11])); + done(); + }); + + it('should return the correct pseudo selector name', function (done) { + assert(equal(selectorList[12], expectedSelectors[12])); + done(); + }); + + it('should return the correct nth selector name', function (done) { + assert(equal(selectorList[13], expectedSelectors[13])); + done(); + }); + + it('should return the correct parent selector name', function (done) { + assert(equal(selectorList[16], expectedSelectors[16])); + done(); + }); + +}); From e43b349ab65abc7bd70e9bee417bc3748a52c931 Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Sun, 14 Aug 2016 00:22:22 +0100 Subject: [PATCH 15/16] :art: Remove duplicate functions --- lib/rules/no-mergeable-selectors.js | 113 ++-------------------------- 1 file changed, 5 insertions(+), 108 deletions(-) diff --git a/lib/rules/no-mergeable-selectors.js b/lib/rules/no-mergeable-selectors.js index 9bb70d48..d5752e06 100644 --- a/lib/rules/no-mergeable-selectors.js +++ b/lib/rules/no-mergeable-selectors.js @@ -1,10 +1,10 @@ 'use strict'; -var helpers = require('../helpers'); +var helpers = require('../helpers'), + selectorHelpers = require('../selector-helpers'); var mergeableNodes = ['atrule', 'include', 'ruleset'], validAtRules = ['media'], - simpleIdents = ['ident', 'number', 'operator', 'combinator', 'string', 'parentSelector', 'delimiter', 'typeSelector', 'attributeMatch'], curLevel = 0, curSelector = [], parentSelector = [], @@ -12,109 +12,6 @@ var mergeableNodes = ['atrule', 'include', 'ruleset'], syntax = ''; -/** - * Adds grammar around our content blocks to construct selectors with - * more readable formats. - * - * @param {object} val - The current value node - * @param {string} prefix - The grammar to prefix the value with - * @param {string} suffix - The grammar to add after the value - * @returns {string} The correct readable format - */ -var addGrammar = function (val, prefix, suffix) { - return prefix + val.content + suffix; -}; - -/** - * Adds grammar around our content blocks to construct selectors with - * more readable formats and loops the content as they're within sub blocks. - * - * @param {object} val - The current value node - * @param {string} prefix - The grammar to prefix the value with - * @param {string} suffix - The grammar to add after the value - * @param {function} constructSelector - The callback we wish to use which means constructSelector in this instance - * @returns {string} The correct readable format - */ -var constructSubSelector = function (val, prefix, suffix, constructSelector) { - var content = prefix; - val.forEach(function (subItem) { - content += constructSelector(subItem); - }); - - return content + suffix; -}; - -/** - * Constructs a syntax complete selector for our selector matching and warning output - * - * @param {object} val - The current node / part of our selector - * @returns {string} - Content: The current node with correct syntax e.g. class my-class = '.my-class' - */ -var constructSelector = function (val) { - var content = null; - - if (val.is('id')) { - content = addGrammar(val, '#', ''); - } - - else if (val.is('class')) { - content = addGrammar(val, '.', ''); - } - - else if (simpleIdents.indexOf(val.type) !== -1) { - content = val.content; - } - - else if (val.is('attributeSelector')) { - content = constructSubSelector(val, '[', ']', constructSelector); - } - - else if (val.is('atkeyword')) { - content = constructSubSelector(val, '@', '', constructSelector); - } - - else if (val.is('placeholder')) { - content = constructSubSelector(val, '%', '', constructSelector); - } - - else if (val.is('variable')) { - content = constructSubSelector(val, '$', '', constructSelector); - } - - else if (val.is('pseudoClass')) { - content = addGrammar(val, ':', ''); - } - - else if (val.is('pseudoElement')) { - content = addGrammar(val, '::', ''); - } - - else if (val.is('nth')) { - content = addGrammar(val, '(', ')'); - } - - else if (val.is('nthSelector')) { - content = constructSubSelector(val, ':', '', constructSelector); - } - - else if (val.is('parentheses')) { - content = constructSubSelector(val, '(', ')', constructSelector); - } - - else if (val.is('space')) { - content = ' '; - } - - else if (val.is('parentSelectorExtension') || val.is('attributeName') || val.is('attributeValue') || val.is('dimension')) { - content = constructSubSelector(val, '', '', constructSelector); - } - - else if (val.is('interpolation')) { - content = constructSubSelector(val, '#{', '}', constructSelector); - } - return content; -}; - /** * Traverses a block and calls our callback function for each block encountered * @@ -161,11 +58,11 @@ var checkRuleset = function (ruleNode) { if (!ruleNodeItem.is('block')) { if (ruleNodeItem.is('selector')) { ruleNodeItem.forEach(function (selectorContent) { - ruleSet += constructSelector(selectorContent); + ruleSet += selectorHelpers.constructSelector(selectorContent); }); } else if (ruleNodeItem.is('delimiter') || ruleNodeItem.is('space')) { - ruleSet += constructSelector(ruleNodeItem); + ruleSet += selectorHelpers.constructSelector(ruleNodeItem); } } }); @@ -184,7 +81,7 @@ var checkAtRule = function (atRule) { var test = ''; atRule.forEach(function (atRuleItem) { if (!atRuleItem.is('block')) { - test += constructSelector(atRuleItem); + test += selectorHelpers.constructSelector(atRuleItem); } }); updateList(test, true, atRule.start.line, atRule.start.column); From 9db9beaedcfb6fae4e4051b2fe6e4f391c32ef56 Mon Sep 17 00:00:00 2001 From: Dan Purdy Date: Thu, 18 Aug 2016 10:13:16 +0100 Subject: [PATCH 16/16] Prepare 1.9.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ package.json | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee6c74a5..755aa333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Sass Lint Changelog +## v1.9.0 + +**August 18, 2016** + +**Fixes** +* Fixed an issue with teh indentation rule when it encountered at-rules with no block immediately preceeding a map [#779](https://github.com/sasstools/sass-lint/issues/779) [#783](https://github.com/sasstools/sass-lint/issues/783) +* Fixed an issue in `single-lint-per-selector` where inline comments were seen as selectors [#789](https://github.com/sasstools/sass-lint/issues/789) +* Fixed an issue with interpolation in placeholders within the `bem-depth` rule [#782](https://github.com/sasstools/sass-lint/issues/782) +* Removed duplicated code from `no-mergeable-selectors` to helper methods + +**Documentation** +* Fixed typos in no-vendor-prefixes rule documentation [#787](https://github.com/sasstools/sass-lint/issues/787) +* Added link to Visual Studio extension [#815](https://github.com/sasstools/sass-lint/pull/815) + +**New Rules** +* Added the `no-color-hex` rule to disallow all hexadecimal colour definitions [#754](https://github.com/sasstools/sass-lint/issues/754) + +**Updates** +* Gonzales-pe updated to version 3.4.4 which fixes a lot of longstanding issues see the [Changelog](https://github.com/tonyganch/gonzales-pe/blob/dev/CHANGELOG.md) + ## v1.8.2 **June 23, 2016** diff --git a/package.json b/package.json index bf208fd3..4184e54c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sass-lint", - "version": "1.8.2", + "version": "1.9.0", "description": "All Node Sass linter!", "main": "index.js", "scripts": {