diff --git a/lib/rules/unit-no-unknown/__tests__/index.js b/lib/rules/unit-no-unknown/__tests__/index.js index a2e8d6bec7..41319c467c 100644 --- a/lib/rules/unit-no-unknown/__tests__/index.js +++ b/lib/rules/unit-no-unknown/__tests__/index.js @@ -220,6 +220,22 @@ testRule(rule, { code: "@import 'foo.css'", description: 'ignore non-media and non-variable at-rule', }, + { + code: "a { background-image: image-set('img-1x.jpg' 1x, 'img-2x.jpg' 2x, 'img-3x.jpg' 3x) }", + description: 'ignore `x` unit in image-set', + }, + { + code: '@media (resolution: 2x) {}', + description: 'ignore `x` unit in @media with `resolution`', + }, + { + code: '@media ( resOLution: 2x) {}', + description: 'ignore `x` unit in @media with `resolution`', + }, + { + code: 'a { image-resolution: 1x; }', + description: 'ignore `x` unit in image-resolution', + }, ], reject: [ @@ -356,6 +372,50 @@ testRule(rule, { line: 1, column: 13, }, + { + code: 'a { width: 400x; }', + message: messages.rejected('x'), + description: '`x` is not allowed for non-resolution props', + line: 1, + column: 12, + }, + { + code: '@media (resolution: 2x) and (min-width: 200x) {}', + message: messages.rejected('x'), + description: '`x` rejected with inappropriate property', + line: 1, + column: 41, + }, + { + code: '@media ( resolution: /* comment */ 2x ) and (min-width: 200x) {}', + message: messages.rejected('x'), + description: '`x` rejected with inappropriate property', + line: 1, + column: 44, + }, + { + code: + "a { background: image-set('img1x.png' 1x, 'img2x.png' 2x) left 20x / 15% 60% repeat-x; }", + message: messages.rejected('x'), + description: '`x` rejected with inappropriate property', + line: 1, + column: 64, + }, + { + code: + "a { background: /* comment */ image-set('img1x.png' 1x, 'img2x.png' 2x) left 20x / 15% 60% repeat-x; }", + message: messages.rejected('x'), + description: '`x` rejected with inappropriate property', + line: 1, + column: 78, + }, + { + code: "a { background-image: image-set('img1x.png' 1pix, 'img2x.png' 2x); }", + message: messages.rejected('pix'), + description: '`pix` rejected with inappropriate property', + line: 1, + column: 45, + }, ], }); diff --git a/lib/rules/unit-no-unknown/index.js b/lib/rules/unit-no-unknown/index.js index 92bcfeabc8..763257bf33 100644 --- a/lib/rules/unit-no-unknown/index.js +++ b/lib/rules/unit-no-unknown/index.js @@ -5,6 +5,7 @@ const atRuleParamIndex = require('../../utils/atRuleParamIndex'); const declarationValueIndex = require('../../utils/declarationValueIndex'); const getUnitFromValueNode = require('../../utils/getUnitFromValueNode'); const keywordSets = require('../../reference/keywordSets'); +const mediaParser = require('postcss-media-query-parser').default; const optionsMatches = require('../../utils/optionsMatches'); const report = require('../../utils/report'); const ruleMessages = require('../../utils/ruleMessages'); @@ -41,7 +42,9 @@ const rule = function(actual, options) { // make sure multiplication operations (*) are divided - not handled // by postcss-value-parser value = value.replace(/\*/g, ','); - valueParser(value).walk(function(valueNode) { + const parsedValue = valueParser(value); + + parsedValue.walk(function(valueNode) { // Ignore wrong units within `url` function // and within functions listed in the `ignoreFunctions` option if ( @@ -62,10 +65,50 @@ const rule = function(actual, options) { return; } - if (keywordSets.units.has(unit.toLowerCase())) { + if (keywordSets.units.has(unit.toLowerCase()) && unit.toLowerCase() !== 'x') { return; } + if (unit.toLowerCase() === 'x') { + if ( + node.type === 'atrule' && + node.name === 'media' && + node.params.toLowerCase().includes('resolution') + ) { + let ignoreUnit = false; + + mediaParser(node.params).walk((mediaNode, i, mediaNodes) => { + if ( + mediaNode.value.toLowerCase().includes('resolution') && + _.last(mediaNodes).sourceIndex === valueNode.sourceIndex + ) { + ignoreUnit = true; + + return false; + } + }); + + if (ignoreUnit) { + return; + } + } + + if (node.type === 'decl') { + if (node.prop.toLowerCase() === 'image-resolution') { + return; + } + + if (/^image-set/i.test(value)) { + const imageSet = parsedValue.nodes.find((node) => node.value === 'image-set'); + const imageSetValueLastIndex = _.last(imageSet.nodes).sourceIndex; + + if (imageSetValueLastIndex >= valueNode.sourceIndex) { + return; + } + } + } + } + report({ index: getIndex(node) + valueNode.sourceIndex, message: messages.rejected(unit),