diff --git a/CHANGELOG.md b/CHANGELOG.md index 755aa333..e0c23689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,20 @@ # Sass Lint Changelog +## v1.9.1 + +**August 25, 2016** + +**Fixes** +* Fixed an issue with nth selectors in the `no-mergeable-selectors` rule [#834](https://github.com/sasstools/sass-lint/issues/834) +* Fixed an issue with atrule arguments containing functions in the `no-mergeable-selectors` rule [#826](https://github.com/sasstools/sass-lint/issues/826) +* Fixed an issue with hex colors being ignored in the `shorthand-values` rule [#836](https://github.com/sasstools/sass-lint/pull/836) + ## 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 with the indentation rule when it encountered at-rules with no block immediately preceding 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 diff --git a/bin/sass-lint.js b/bin/sass-lint.js index 787e9330..a6218b8f 100755 --- a/bin/sass-lint.js +++ b/bin/sass-lint.js @@ -6,15 +6,7 @@ var program = require('commander'), lint = require('../index'); var configPath, - ignores, - configOptions = {}, - exitCode = 0; - -var tooManyWarnings = function (detects) { - var warningCount = lint.warningCount(detects).count; - - return warningCount > 0 && warningCount > program.maxWarnings; -}; + configOptions = {}; var detectPattern = function (pattern) { var detects; @@ -25,13 +17,7 @@ var detectPattern = function (pattern) { lint.outputResults(detects, configOptions, configPath); } - if (lint.errorCount(detects).count || tooManyWarnings(detects)) { - exitCode = 1; - } - - if (program.exit) { - lint.failOnError(detects); - } + lint.failOnError(detects, configOptions, configPath); }; program @@ -47,21 +33,16 @@ program .option('--max-warnings [integer]', 'Number of warnings to trigger nonzero exit code') .parse(process.argv); +// Create "options" and "files" dictionaries if they don't exist +configOptions.files = configOptions.files || {}; +configOptions.options = configOptions.options || {}; if (program.config && program.config !== true) { configPath = program.config; } if (program.ignore && program.ignore !== true) { - ignores = program.ignore.split(', '); - if (configOptions.hasOwnProperty('files')) { - configOptions.files.ignore = ignores; - } - else { - configOptions.files = { - 'ignore': ignores - }; - } + configOptions.files.ignore = program.ignore.split(', '); } if (program.syntax && ['sass', 'scss'].indexOf(program.syntax) > -1) { @@ -69,25 +50,15 @@ if (program.syntax && ['sass', 'scss'].indexOf(program.syntax) > -1) { } if (program.format && program.format !== true) { - if (configOptions.hasOwnProperty('options')) { - configOptions.options.formatter = program.format; - } - else { - configOptions.options = { - 'formatter': program.format - }; - } + configOptions.options.formatter = program.format; } if (program.output && program.output !== true) { - if (configOptions.hasOwnProperty('options')) { - configOptions.options['output-file'] = program.output; - } - else { - configOptions.options = { - 'output-file': program.output - }; - } + configOptions.options['output-file'] = program.output; +} + +if (program.maxWarnings && program.maxWarnings !== true) { + configOptions.options['max-warnings'] = program.maxWarnings; } if (program.args.length === 0) { @@ -98,7 +69,3 @@ else { detectPattern(path); }); } - -process.on('exit', function () { - process.exit(exitCode); // eslint-disable-line no-process-exit -}); diff --git a/docs/options/max-warnings.md b/docs/options/max-warnings.md new file mode 100644 index 00000000..32d6e8ed --- /dev/null +++ b/docs/options/max-warnings.md @@ -0,0 +1,29 @@ +# Max warnings + +An error will be thrown if the total number of warnings exceeds the `max-warnings` setting. + +## Examples + +This can be set as a command-line option: + +``` bash +$ sass-lint --max-warnings 50 +``` + +In `.sass-lint.yml`: + +``` yaml +options: + max-warnings: 50 +``` + +Or inside a script: + +``` javascript +var sassLint = require('sass-lint'), + config = {options: {'max-warnings': 50}}; + +results = sassLint.lintFiles('sass/**/*.scss', config) +sassLint.failOnError(results, config); +``` + diff --git a/docs/rules/no-url-domains.md b/docs/rules/no-url-domains.md new file mode 100644 index 00000000..8c536b1a --- /dev/null +++ b/docs/rules/no-url-domains.md @@ -0,0 +1,37 @@ +# No Url Domains + +Rule `no-url-domains` will enforce that domains are not used within urls. + +## Examples + +When enabled, the following are allowed: + +```scss +.foo { + background-image: url('/img/bar.png'); +} + +.foo { + background-image: url('img/bar.png'); +} + +.foo { + background-image: url('bar.png'); +} +``` + +When enabled, the following are disallowed: + +```scss +.foo { + background-image: url('https://foo.com/img/bar.png'); +} + +.foo { + background-image: url('http://foo.com/img/bar.png'); +} + +.foo { + background-image: url('//foo.com/img/bar.png'); +} +``` diff --git a/docs/rules/no-url-protocols.md b/docs/rules/no-url-protocols.md index ebf620b3..50baa5ff 100644 --- a/docs/rules/no-url-protocols.md +++ b/docs/rules/no-url-protocols.md @@ -2,9 +2,17 @@ Rule `no-url-protocols` will enforce that protocols and domains are not used within urls. +## Options + +* `allow-protocol-relative-urls`: `true`/`false` (defaults to `false`) +> This option is scheduled to be deprecated in favour of the [no-url-domains](https://github.com/sasstools/sass-lint/blob/develop/docs/rules/no-url-domains.md) rule in sass-lint 2.0. + ## Examples -When enabled, the following are allowed: +### `allow-protocol-relative-urls` + + +When `allow-protocol-relative-urls: false`, the following are allowed: ```scss .foo { @@ -20,7 +28,7 @@ When enabled, the following are allowed: } ``` -When enabled, the following are disallowed: +When `allow-protocol-relative-urls: false`, the following are disallowed: ```scss .foo { @@ -35,3 +43,35 @@ When enabled, the following are disallowed: background-image: url('//foo.com/img/bar.png'); } ``` + +When `allow-protocol-relative-urls: true`, the following are allowed: + +```scss +.foo { + background-image: url('//foo.com/img/bar.png'); +} + +.foo { + background-image: url('/img/bar.png'); +} + +.foo { + background-image: url('img/bar.png'); +} + +.foo { + background-image: url('bar.png'); +} +``` + +When `allow-protocol-relative-urls: true`, the following are disallowed: + +```scss +.foo { + background-image: url('https://foo.com/img/bar.png'); +} + +.foo { + background-image: url('http://foo.com/img/bar.png'); +} +``` diff --git a/docs/sass-lint.yml b/docs/sass-lint.yml index 3de8a760..682ec6c5 100644 --- a/docs/sass-lint.yml +++ b/docs/sass-lint.yml @@ -9,6 +9,8 @@ options: formatter: html # Output file instead of logging results output-file: 'linters/sass-lint.html' + # Raise an error if more than 50 warnings are generated + max-warnings: 50 # File Options files: include: 'sass/**/*.s+(a|c)ss' diff --git a/index.js b/index.js index 4ffabd81..8f1fae84 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ var slConfig = require('./lib/config'), groot = require('./lib/groot'), + exceptions = require('./lib/exceptions'), helpers = require('./lib/helpers'), slRules = require('./lib/rules'), ruleToggler = require('./lib/ruleToggler'), @@ -294,14 +295,30 @@ sassLint.outputResults = function (results, options, configPath) { * Throws an error if there are any errors detected. The error includes a count of all errors * and a list of all files that include errors. * - * @param {object} results our results object + * @param {object} results - our results object + * @param {object} [options] - extra options to use when running failOnError, e.g. max-warnings + * @param {string} [configPath] - path to the config file * @returns {void} */ -sassLint.failOnError = function (results) { - var errorCount = this.errorCount(results); +sassLint.failOnError = function (results, options, configPath) { + // Default parameters + options = typeof options !== 'undefined' ? options : {}; + configPath = typeof configPath !== 'undefined' ? configPath : null; + + var errorCount = this.errorCount(results), + warningCount = this.warningCount(results), + configOptions = this.getConfig(options, configPath).options; if (errorCount.count > 0) { - throw new Error(errorCount.count + ' errors were detected in \n- ' + errorCount.files.join('\n- ')); + throw new exceptions.SassLintFailureError(errorCount.count + ' errors were detected in \n- ' + errorCount.files.join('\n- ')); + } + + if (!isNaN(configOptions['max-warnings']) && warningCount.count > configOptions['max-warnings']) { + throw new exceptions.MaxWarningsExceededError( + 'Number of warnings (' + warningCount.count + + ') exceeds the allowed maximum of ' + configOptions['max-warnings'] + + '.\n' + ); } }; diff --git a/lib/config/sass-lint.yml b/lib/config/sass-lint.yml index 1b3ed511..33dcf7dd 100644 --- a/lib/config/sass-lint.yml +++ b/lib/config/sass-lint.yml @@ -38,6 +38,7 @@ rules: no-trailing-zero: 1 no-transition-all: 1 no-universal-selectors: 0 + no-url-domains: 1 no-url-protocols: 1 no-vendor-prefixes: 1 no-warn: 1 diff --git a/lib/exceptions.js b/lib/exceptions.js new file mode 100644 index 00000000..77e15683 --- /dev/null +++ b/lib/exceptions.js @@ -0,0 +1,19 @@ +'use strict'; + +var util = require('util'); + +module.exports = { + SassLintFailureError: function (message) { + Error.captureStackTrace(this, this.constructor); + this.name = 'SassLintFailureError'; + this.message = message; + }, + MaxWarningsExceededError: function (message) { + Error.captureStackTrace(this, this.constructor); + this.name = 'MaxWarningsExceededError'; + this.message = message; + } +}; + +util.inherits(module.exports.SassLintFailureError, Error); +util.inherits(module.exports.MaxWarningsExceededError, Error); diff --git a/lib/rules/no-url-domains.js b/lib/rules/no-url-domains.js new file mode 100644 index 00000000..73b491fe --- /dev/null +++ b/lib/rules/no-url-domains.js @@ -0,0 +1,33 @@ +'use strict'; + +var helpers = require('../helpers'), + url = require('url'); + +module.exports = { + 'name': 'no-url-domains', + 'defaults': {}, + 'detect': function (ast, parser) { + var result = []; + + ast.traverseByType('uri', function (uri) { + uri.traverse(function (item) { + if (item.is('string')) { + var stripped = helpers.stripQuotes(item.content), + parsedUrl = url.parse(stripped, false, true); + + if (parsedUrl.host && parsedUrl.protocol !== 'data:') { + result = helpers.addUnique(result, { + 'ruleId': parser.rule.name, + 'severity': parser.severity, + 'line': item.end.line, + 'column': item.end.column, + 'message': 'Domains in URLs are disallowed' + }); + } + } + }); + }); + + return result; + } +}; diff --git a/lib/rules/no-url-protocols.js b/lib/rules/no-url-protocols.js index 389b4e7a..90080479 100644 --- a/lib/rules/no-url-protocols.js +++ b/lib/rules/no-url-protocols.js @@ -2,30 +2,34 @@ var helpers = require('../helpers'); -var isUrlRegex = /^(https?:)?\/\//; - -var stripQuotes = function (str) { - return str.substring(1, str.length - 1); -}; +var isUrlRegex = /^(https?:)?\/\//, + protocolRelativeRegex = /^(https?:)\/\//; module.exports = { 'name': 'no-url-protocols', - 'defaults': {}, + 'defaults': { + 'allow-protocol-relative-urls': false + }, 'detect': function (ast, parser) { var result = []; ast.traverseByType('uri', function (uri) { uri.traverse(function (item) { if (item.is('string')) { - var stripped = stripQuotes(item.content); + var stripped = helpers.stripQuotes(item.content), + regexSelector = !parser.options['allow-protocol-relative-urls'] ? + isUrlRegex : protocolRelativeRegex, + message = !parser.options['allow-protocol-relative-urls'] ? + 'Protocols and domains in URLs are disallowed' : + 'Protocols in URLS are disallowed'; - if (stripped.match(isUrlRegex)) { + if (stripped.match(regexSelector)) { result = helpers.addUnique(result, { 'ruleId': parser.rule.name, 'severity': parser.severity, 'line': item.end.line, 'column': item.end.column, - 'message': 'Protocols and domains in URLs are disallowed' + 'message': message }); } } diff --git a/lib/rules/shorthand-values.js b/lib/rules/shorthand-values.js index 48e9ecd9..232ae45b 100644 --- a/lib/rules/shorthand-values.js +++ b/lib/rules/shorthand-values.js @@ -89,6 +89,10 @@ var scanValue = function (node) { fullVal += '#{' + scanValue(val.content) + '}'; } + else if (val.is('color')) { + fullVal += '#' + val.content + ''; + } + else if (val.is('operator') || val.is('ident') || val.is('number') || val.is('unaryOperator')) { fullVal += val.content; } diff --git a/lib/selector-helpers.js b/lib/selector-helpers.js index f522c964..86b79b9b 100644 --- a/lib/selector-helpers.js +++ b/lib/selector-helpers.js @@ -16,6 +16,15 @@ var simpleIdents = [ 'attributeMatch' ]; +var subSelectors = [ + 'parentSelectorExtension', + 'attributeName', + 'attributeValue', + 'dimension', + 'selector', + 'function' +]; + /** * Adds grammar around our content blocks to construct selectors with * more readable formats. @@ -61,36 +70,44 @@ var constructSubSelector = function (val, prefix, suffix, constructSelector) { var constructSelector = function (val) { var content = null; - if (val.is('id')) { - content = addGrammar(val, '#', ''); + if (val.is('arguments')) { + content = constructSubSelector(val, '(', ')', constructSelector); + } + + else if (val.is('atkeyword')) { + content = constructSubSelector(val, '@', '', constructSelector); + } + + else if (val.is('attributeSelector')) { + content = constructSubSelector(val, '[', ']', constructSelector); } else if (val.is('class')) { content = addGrammar(val, '.', ''); } - else if (simpleIdents.indexOf(val.type) !== -1) { - content = val.content; + else if (val.is('id')) { + content = addGrammar(val, '#', ''); } - else if (val.is('arguments')) { - content = constructSubSelector(val, '(', ')', constructSelector); + else if (val.is('interpolation')) { + content = constructSubSelector(val, '#{', '}', constructSelector); } - else if (val.is('attributeSelector')) { - content = constructSubSelector(val, '[', ']', constructSelector); + else if (val.is('nth')) { + content = addGrammar(val, '(', ')'); } - else if (val.is('atkeyword')) { - content = constructSubSelector(val, '@', '', constructSelector); + else if (val.is('nthSelector')) { + content = constructSubSelector(val, ':', '', constructSelector); } - else if (val.is('placeholder')) { - content = constructSubSelector(val, '%', '', constructSelector); + else if (val.is('parentheses')) { + content = constructSubSelector(val, '(', ')', constructSelector); } - else if (val.is('variable')) { - content = constructSubSelector(val, '$', '', constructSelector); + else if (val.is('placeholder')) { + content = constructSubSelector(val, '%', '', constructSelector); } else if (val.is('pseudoClass')) { @@ -101,29 +118,22 @@ var constructSelector = function (val) { content = addGrammar(val, '::', ''); } - else if (val.is('nth')) { - content = addGrammar(val, '(', ')'); - } - - else if (val.is('nthSelector')) { - content = constructSubSelector(val, ':', '', constructSelector); + else if (val.is('space')) { + content = ' '; } - else if (val.is('parentheses')) { - content = constructSubSelector(val, '(', ')', constructSelector); + else if (val.is('variable')) { + content = constructSubSelector(val, '$', '', constructSelector); } - else if (val.is('space')) { - content = ' '; + else if (simpleIdents.indexOf(val.type) !== -1) { + content = val.content; } - else if (val.is('parentSelectorExtension') || val.is('attributeName') || val.is('attributeValue') || val.is('dimension')) { + else if (subSelectors.indexOf(val.type) !== -1) { content = constructSubSelector(val, '', '', constructSelector); } - else if (val.is('interpolation')) { - content = constructSubSelector(val, '#{', '}', constructSelector); - } return content; }; diff --git a/package.json b/package.json index 4184e54c..646c5944 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sass-lint", - "version": "1.9.0", + "version": "1.9.1", "description": "All Node Sass linter!", "main": "index.js", "scripts": { @@ -33,7 +33,7 @@ "fs-extra": "^0.30.0", "glob": "^7.0.0", "globule": "^1.0.0", - "gonzales-pe": "3.4.4", + "gonzales-pe": "3.4.5", "js-yaml": "^3.5.4", "lodash.capitalize": "^4.1.0", "lodash.kebabcase": "^4.0.0", diff --git a/tests/cli.js b/tests/cli.js index 4cc618c3..a3d6698d 100644 --- a/tests/cli.js +++ b/tests/cli.js @@ -383,27 +383,27 @@ describe('cli', function () { }); - it('should exit with exit code 1 when quiet', function (done) { + it('should exit with error when quiet', function (done) { var command = 'sass-lint -c tests/yml/.error-output.yml tests/cli/cli-error.scss --verbose --no-exit'; exec(command, function (err) { - if (err.code === 1) { + if (err) { return done(); } - return done(new Error('Error code not 1')); + return done(new Error('No error on exit')); }); }); - it('should exit with exit code 1 when more warnings than --max-warnings', function (done) { + it('should exit with error when more warnings than --max-warnings', function (done) { var command = 'sass-lint -c tests/yml/.color-keyword-errors.yml tests/cli/cli.scss --max-warnings 0'; exec(command, function (err) { - if (err && err.code === 1) { + if (err) { return done(); } - return done(new Error('Error code not 1')); + return done(new Error('No error on exit')); }); }); diff --git a/tests/failures.js b/tests/failures.js new file mode 100644 index 00000000..3127d451 --- /dev/null +++ b/tests/failures.js @@ -0,0 +1,90 @@ +'use strict'; + +var lint = require('../index'), + assert = require('assert'), + exceptions = require('../lib/exceptions'); + +describe('failures', function () { + it('should raise SassLintFailureError if indentation is set to error', function (done) { + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {rules: {indentation: 2}}); // 14 errors + lint.failOnError(results); // Set indentation to error + }, + exceptions.SassLintFailureError + ); + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}, 'tests/yml/.indentation-error.yml'); // 14 errors + lint.failOnError(results); // Set indentation to error + }, + exceptions.SassLintFailureError + ); + + done(); + }); + + it('should not raise error if indentation is only set to warn', function (done) { + // These should produce 55 warnings and 0 errors + var directResults = lint.lintFiles('sass/indentation/indentation-spaces.scss', {rules: {indentation: 1}}); + var configResults = lint.lintFiles('sass/indentation/indentation-spaces.scss', {}, 'yml/.indentation-warn.yml'); + lint.failOnError(directResults); + lint.failOnError(configResults); + + done(); + }); + + it('should raise MaxWarningsExceededError if warnings exceed `max-warnings` setting', function (done) { + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {options: {'max-warnings': 10}}); + }, + exceptions.MaxWarningsExceededError + ); + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {}, 'tests/yml/.max-10-warnings.yml'); + }, + exceptions.MaxWarningsExceededError + ); + + done(); + }); + + it('should raise MaxWarningsExceededError if warnings exceed `max-warnings` of zero', function (done) { + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {options: {'max-warnings': 0}}); + }, + exceptions.MaxWarningsExceededError + ); + assert.throws( + function () { + var results = lint.lintFiles('tests/sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {}, 'tests/yml/.max-0-warnings.yml'); + }, + exceptions.MaxWarningsExceededError + ); + + done(); + }); + + it('should not raise error if warnings do not exceed `max-warnings` setting', function (done) { + var results = lint.lintFiles('sass/indentation/indentation-spaces.scss', {}); // 55 warnings + lint.failOnError(results, {'max-warnings': 100}); // should succceed + lint.failOnError(results, {}, 'yml/.max-100-warnings.yml'); // should succeed + + done(); + }); + + it('should not raise error if no warnings even if `max-warnings` is zero', function (done) { + var results = lint.lintFiles('sass/success.scss', {}); // no warnings + lint.failOnError(results, {'max-warnings': 0}); // should still succceed + lint.failOnError(results, {}, 'yml/.max-0-warnings.yml'); // should still succeed + + done(); + }); +}); diff --git a/tests/rules/no-mergeable-selectors.js b/tests/rules/no-mergeable-selectors.js index 449ad0e5..4b4be905 100644 --- a/tests/rules/no-mergeable-selectors.js +++ b/tests/rules/no-mergeable-selectors.js @@ -9,7 +9,7 @@ describe('no mergeable selectors - scss', function () { lint.test(file, { 'no-mergeable-selectors': 1 }, function (data) { - lint.assert.equal(22, data.warningCount); + lint.assert.equal(24, data.warningCount); done(); }); }); @@ -25,7 +25,7 @@ describe('no mergeable selectors - scss', function () { } ] }, function (data) { - lint.assert.equal(21, data.warningCount); + lint.assert.equal(23, data.warningCount); done(); }); }); @@ -40,7 +40,7 @@ describe('no mergeable selectors - sass', function () { lint.test(file, { 'no-mergeable-selectors': 1 }, function (data) { - lint.assert.equal(20, data.warningCount); + lint.assert.equal(21, data.warningCount); done(); }); }); @@ -57,7 +57,7 @@ describe('no mergeable selectors - sass', function () { } ] }, function (data) { - lint.assert.equal(19, data.warningCount); + lint.assert.equal(20, data.warningCount); done(); }); }); diff --git a/tests/rules/no-url-domains.js b/tests/rules/no-url-domains.js new file mode 100644 index 00000000..2caabdb1 --- /dev/null +++ b/tests/rules/no-url-domains.js @@ -0,0 +1,35 @@ +'use strict'; + +var lint = require('./_lint'); + +////////////////////////////// +// SCSS syntax tests +////////////////////////////// +describe('no url domains - scss', function () { + var file = lint.file('no-url-domains.scss'); + + it('enforce', function (done) { + lint.test(file, { + 'no-url-domains': 1 + }, function (data) { + lint.assert.equal(3, data.warningCount); + done(); + }); + }); +}); + +////////////////////////////// +// Sass syntax tests +////////////////////////////// +describe('no url domains - sass', function () { + var file = lint.file('no-url-domains.sass'); + + it('enforce', function (done) { + lint.test(file, { + 'no-url-domains': 1 + }, function (data) { + lint.assert.equal(3, data.warningCount); + done(); + }); + }); +}); diff --git a/tests/rules/no-url-protocols.js b/tests/rules/no-url-protocols.js index 87b5422a..65b5b5f1 100644 --- a/tests/rules/no-url-protocols.js +++ b/tests/rules/no-url-protocols.js @@ -16,8 +16,23 @@ describe('no url protocols - scss', function () { done(); }); }); + + it('[allow-protocol-relative-urls: true]', function (done) { + lint.test(file, { + 'no-url-protocols': [ + 1, + { + 'allow-protocol-relative-urls': true + } + ] + }, function (data) { + lint.assert.equal(2, data.warningCount); + done(); + }); + }); }); + ////////////////////////////// // Sass syntax tests ////////////////////////////// @@ -32,4 +47,18 @@ describe('no url protocols - sass', function () { done(); }); }); + + it('[allow-protocol-relative-urls: true]', function (done) { + lint.test(file, { + 'no-url-protocols': [ + 1, + { + 'allow-protocol-relative-urls': true + } + ] + }, function (data) { + lint.assert.equal(2, data.warningCount); + done(); + }); + }); }); diff --git a/tests/rules/shorthand-values.js b/tests/rules/shorthand-values.js index 7db7bafa..e80cc21c 100644 --- a/tests/rules/shorthand-values.js +++ b/tests/rules/shorthand-values.js @@ -12,7 +12,7 @@ describe('shorthand values - scss', function () { lint.test(file, { 'shorthand-values': 1 }, function (data) { - lint.assert.equal(76, data.warningCount); + lint.assert.equal(77, data.warningCount); done(); }); }); @@ -44,7 +44,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(38, data.warningCount); + lint.assert.equal(39, data.warningCount); done(); }); }); @@ -60,7 +60,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(45, data.warningCount); + lint.assert.equal(46, data.warningCount); done(); }); }); @@ -92,7 +92,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(57, data.warningCount); + lint.assert.equal(58, data.warningCount); done(); }); }); @@ -109,7 +109,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(64, data.warningCount); + lint.assert.equal(65, data.warningCount); done(); }); }); @@ -126,7 +126,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(57, data.warningCount); + lint.assert.equal(58, data.warningCount); done(); }); }); @@ -144,7 +144,7 @@ describe('shorthand values - scss', function () { } ] }, function (data) { - lint.assert.equal(76, data.warningCount); + lint.assert.equal(77, data.warningCount); done(); }); }); @@ -161,7 +161,7 @@ describe('shorthand values - sass', function () { lint.test(file, { 'shorthand-values': 1 }, function (data) { - lint.assert.equal(76, data.warningCount); + lint.assert.equal(77, data.warningCount); done(); }); }); @@ -193,7 +193,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(38, data.warningCount); + lint.assert.equal(39, data.warningCount); done(); }); }); @@ -209,7 +209,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(45, data.warningCount); + lint.assert.equal(46, data.warningCount); done(); }); }); @@ -241,7 +241,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(57, data.warningCount); + lint.assert.equal(58, data.warningCount); done(); }); }); @@ -258,7 +258,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(64, data.warningCount); + lint.assert.equal(65, data.warningCount); done(); }); }); @@ -275,7 +275,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(57, data.warningCount); + lint.assert.equal(58, data.warningCount); done(); }); }); @@ -293,7 +293,7 @@ describe('shorthand values - sass', function () { } ] }, function (data) { - lint.assert.equal(76, data.warningCount); + lint.assert.equal(77, data.warningCount); done(); }); }); diff --git a/tests/sass/no-mergeable-selectors.sass b/tests/sass/no-mergeable-selectors.sass index 1d7c6d36..04aad20d 100644 --- a/tests/sass/no-mergeable-selectors.sass +++ b/tests/sass/no-mergeable-selectors.sass @@ -179,6 +179,20 @@ ul ~ p .bar content: '' +// Issue #834 - selectors/typeselectors not properly recognised +.fake-field + tbody + tr:nth-child(even) + background: lighten($theme-color-primary, 50%) + tr:nth-child(odd) + background: #FFFFFF + +.not-test + &:not(:first-child) + border-left: none + + &:not(:first-child) + border-left: 2px .bar @media (max-width: 40em) and (min-width: 20em) and (orientation: landscape) @@ -237,15 +251,3 @@ ul ~ p // opacity: 1 // Issue #703 - Interpolation in selector - ignored in Sass syntax for now due to gonzales issue - -.navigation - @media #{$media-query-lg-up} - .nav-item - display: inline-block - @media #{$media-query-md-down} - // should not merge with the media query above - .nav-item - display: block - // should merge with the ruleset directly above - .nav-item - color: $blue diff --git a/tests/sass/no-mergeable-selectors.scss b/tests/sass/no-mergeable-selectors.scss index a16e7dbc..335c8889 100644 --- a/tests/sass/no-mergeable-selectors.scss +++ b/tests/sass/no-mergeable-selectors.scss @@ -320,3 +320,43 @@ ul ~ p { } } } + +// issue 826 - media queries with functions +@media(min-width: break('large')) { + .test { + float: left; + } +} + +@media(min-width: break('small')) { + .test { + float: left; + } +} + +@media(min-width: break('small')) { + .test { + float: none; + } +} + +// Issue #834 - selectors/typeselectors not properly recognised +.fake-field { + tbody { + tr:nth-child(even) { + background: lighten($theme-color-primary, 50%); + } + tr:nth-child(odd) { + background: #FFFFFF; + } + } +} + +.pseudo-not { + &:not(:first-child) { + border-left: none; + } + &:not(:first-child) { + border-left: 2px; + } +} diff --git a/tests/sass/no-url-domains.sass b/tests/sass/no-url-domains.sass new file mode 100644 index 00000000..21a17035 --- /dev/null +++ b/tests/sass/no-url-domains.sass @@ -0,0 +1,25 @@ +.foo + background-image: url('https://foo.com/img/bar.png') + + +.foo + background-image: url('http://foo.com/img/bar.png') + + +.foo + background-image: url('//foo.com/img/bar.png') + + +.foo + background-image: url('/img/bar.png') + + +.foo + background-image: url('img/bar.png') + + +.foo + background-image: url('bar.png') + +.foo + background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7') diff --git a/tests/sass/no-url-domains.scss b/tests/sass/no-url-domains.scss new file mode 100644 index 00000000..98ba24c0 --- /dev/null +++ b/tests/sass/no-url-domains.scss @@ -0,0 +1,27 @@ +.foo { + background-image: url('https://foo.com/img/bar.png'); +} + +.foo { + background-image: url('http://foo.com/img/bar.png'); +} + +.foo { + background-image: url('//foo.com/img/bar.png'); +} + +.foo { + background-image: url('/img/bar.png'); +} + +.foo { + background-image: url('img/bar.png'); +} + +.foo { + background-image: url('bar.png'); +} + +.foo { + background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); +} diff --git a/tests/sass/selector-helpers/selector-helpers.scss b/tests/sass/selector-helpers/selector-helpers.scss index 7a449784..cd206336 100644 --- a/tests/sass/selector-helpers/selector-helpers.scss +++ b/tests/sass/selector-helpers/selector-helpers.scss @@ -59,3 +59,10 @@ p:nth-of-type(2) { color: red; } } + +tr:nth-child(even) { + background: lighten($theme-color-primary, 50%); +} +tr:nth-child(odd) { + background: #FFFFFF; +} diff --git a/tests/sass/shorthand-values.sass b/tests/sass/shorthand-values.sass index f21aa5b4..b766c6ce 100644 --- a/tests/sass/shorthand-values.sass +++ b/tests/sass/shorthand-values.sass @@ -297,3 +297,11 @@ .value-four-diff-four-interp-function-mixed margin: calc(#{$doc-header-height} + #{$global-whitespace--regular}) calc(#{$doc-header-height} + #{$global-whitespace--regular}) calc(#{$doc-header-height} - #{$global-whitespace--regular}) calc(#{$doc-header-height} - #{$global-whitespace--regular}) + +// issue #772 - Issue with colours not being correctly interpreted +// should be ignored; +.test + border-color: transparent transparent transparent #095b97 + +.test + border-color: transparent #095b97 transparent #095b97 diff --git a/tests/sass/shorthand-values.scss b/tests/sass/shorthand-values.scss index 925f5d3e..7ffbccfb 100644 --- a/tests/sass/shorthand-values.scss +++ b/tests/sass/shorthand-values.scss @@ -359,3 +359,13 @@ .value-four-diff-four-interp-function-mixed { margin: calc(#{$doc-header-height} + #{$global-whitespace--regular}) calc(#{$doc-header-height} + #{$global-whitespace--regular}) calc(#{$doc-header-height} - #{$global-whitespace--regular}) calc(#{$doc-header-height} - #{$global-whitespace--regular}); } + +// issue #772 - Issue with colours not being correctly interpreted +// should be ignored; +.test { + border-color: transparent transparent transparent #095b97; +} + +.test { + border-color: transparent #095b97 transparent #095b97; +} diff --git a/tests/sass/success.scss b/tests/sass/success.scss new file mode 100644 index 00000000..ae771e48 --- /dev/null +++ b/tests/sass/success.scss @@ -0,0 +1,3 @@ +.one { + margin-top: 0; +} diff --git a/tests/selector-helpers/selectorHelpers.js b/tests/selector-helpers/selectorHelpers.js index 9a502ef9..23ffe732 100644 --- a/tests/selector-helpers/selectorHelpers.js +++ b/tests/selector-helpers/selectorHelpers.js @@ -25,7 +25,11 @@ describe('selectorHelpers - constructSelector', function () { '.wrong-element:selection', 'p:nth-of-type(2)', '.test', - '&__test' + '&__test', + 'tr:nth-child(even)', + 'even', + 'tr:nth-child(odd)', + 'odd' ], selectorList = []; @@ -46,67 +50,86 @@ describe('selectorHelpers - constructSelector', function () { // contructSelector ////////////////////////////// + // .test it('should return the correct class name', function (done) { assert(equal(selectorList[0], expectedSelectors[0])); done(); }); + // #test it('should return the correct ID name', function (done) { assert(equal(selectorList[1], expectedSelectors[1])); done(); }); + // %test it('should return the correct placeholder name', function (done) { assert(equal(selectorList[2], expectedSelectors[2])); done(); }); + // .#{test} it('should return the correct interpolated selector name', function (done) { assert(equal(selectorList[3], expectedSelectors[3])); done(); }); + // input[type="text"] it('should return the correct type selector name', function (done) { assert(equal(selectorList[6], expectedSelectors[6])); done(); }); + // .test > li it('should return the correct combinator selector name', function (done) { assert(equal(selectorList[7], expectedSelectors[7])); done(); }); + // span[lang~=en-us] it('should return the correct attribute selector name', function (done) { assert(equal(selectorList[8], expectedSelectors[8])); done(); }); + // .block__element-one it('should return the correct BEM selector name', function (done) { assert(equal(selectorList[9], expectedSelectors[9])); done(); }); + // ##{$id} it('should return the correct interpolated ID selector name', function (done) { assert(equal(selectorList[10], expectedSelectors[10])); done(); }); + // .right-element::-ms-backdrop it('should return the correct pseudo element selector name', function (done) { assert(equal(selectorList[11], expectedSelectors[11])); done(); }); + // .wrong-element:selection it('should return the correct pseudo selector name', function (done) { assert(equal(selectorList[12], expectedSelectors[12])); done(); }); + // p:nth-of-type(2) it('should return the correct nth selector name', function (done) { assert(equal(selectorList[13], expectedSelectors[13])); done(); }); + // &__test it('should return the correct parent selector name', function (done) { + assert(equal(selectorList[15], expectedSelectors[15])); + done(); + }); + + // tr:nth-child(even) + it('should return the correct nth selector and typeselector', function (done) { assert(equal(selectorList[16], expectedSelectors[16])); done(); }); diff --git a/tests/yml/.indentation-error.yml b/tests/yml/.indentation-error.yml new file mode 100644 index 00000000..57dbbfe6 --- /dev/null +++ b/tests/yml/.indentation-error.yml @@ -0,0 +1,2 @@ +rules: + indentation: 2 diff --git a/tests/yml/.indentation-warn.yml b/tests/yml/.indentation-warn.yml new file mode 100644 index 00000000..71a1f08f --- /dev/null +++ b/tests/yml/.indentation-warn.yml @@ -0,0 +1,2 @@ +rules: + indentation: 1 diff --git a/tests/yml/.max-0-warnings.yml b/tests/yml/.max-0-warnings.yml new file mode 100644 index 00000000..0a3dc120 --- /dev/null +++ b/tests/yml/.max-0-warnings.yml @@ -0,0 +1,2 @@ +options: + max-warnings: 0 diff --git a/tests/yml/.max-10-warnings.yml b/tests/yml/.max-10-warnings.yml new file mode 100644 index 00000000..85b0f1c9 --- /dev/null +++ b/tests/yml/.max-10-warnings.yml @@ -0,0 +1,2 @@ +options: + max-warnings: 10 diff --git a/tests/yml/.max-100-warnings.yml b/tests/yml/.max-100-warnings.yml new file mode 100644 index 00000000..759639a6 --- /dev/null +++ b/tests/yml/.max-100-warnings.yml @@ -0,0 +1,2 @@ +options: + max-warnings: 100