Skip to content

Commit

Permalink
Ignore map property name in unit-no-unknown (#4450)
Browse files Browse the repository at this point in the history
* Ignore map property name (#4375)

* Update isMap, add comments

* Update types

* Update lib/utils/isMap.js

Co-Authored-By: Aleks Hudochenkov <aleks@hudochenkov.com>

* Update lib/utils/isMap.js

Co-Authored-By: Aleks Hudochenkov <aleks@hudochenkov.com>

* Clarify const purpose
  • Loading branch information
fanich37 authored and hudochenkov committed Dec 1, 2019
1 parent cbfb82d commit a3a74a3
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 0 deletions.
26 changes: 26 additions & 0 deletions lib/rules/unit-no-unknown/__tests__/index.js
Expand Up @@ -437,6 +437,19 @@ testRule(rule, {
code: 'a { margin: calc(100% - #{$margin * 2}); }',
description: 'work with interpolation',
},
{
code: '$foo: ( 1prop: 10px, 2prop: 12px, 3prop: /* comment */ 14px )',
description: 'ignore map property name',
},
{
code: '$breakpoints: ( small: /* comment */ 767px, 1medium: 992px, large: 1200px );',
description: 'ignore map property name',
},
{
code:
'$breakpoints: ( small: /* comment */ 767px, medium: ( 1prop: 992px, 2prop: ( 1prop: 1200px ) ) );',
description: 'ignore map property name in nested maps',
},
],

reject: [
Expand Down Expand Up @@ -470,6 +483,19 @@ testRule(rule, {
line: 1,
column: 39,
},
{
code: '$breakpoints: ( small: 767px, 1medium: 992pix, large: 1200px );',
message: messages.rejected('pix'),
line: 1,
column: 40,
},
{
code:
'$breakpoints: ( small: /* comment */ 767px, medium: ( 1prop: 992pix, 2prop: ( 1prop: 1200px ) ) );',
message: messages.rejected('pix'),
line: 1,
column: 49,
},
{
code: 'a { font: (italic bold 10px/8pix) }',
message: messages.rejected('pix'),
Expand Down
18 changes: 18 additions & 0 deletions lib/rules/unit-no-unknown/index.js
Expand Up @@ -4,6 +4,7 @@ const _ = require('lodash');
const atRuleParamIndex = require('../../utils/atRuleParamIndex');
const declarationValueIndex = require('../../utils/declarationValueIndex');
const getUnitFromValueNode = require('../../utils/getUnitFromValueNode');
const isMap = require('../../utils/isMap');
const keywordSets = require('../../reference/keywordSets');
const mediaParser = require('postcss-media-query-parser').default;
const optionsMatches = require('../../utils/optionsMatches');
Expand All @@ -18,6 +19,10 @@ const messages = ruleMessages(ruleName, {
rejected: (unit) => `Unexpected unknown unit "${unit}"`,
});

// The map property name (in map cleared from comments and spaces) always
// has index that being divided by 4 gives remainder equals 0
const mapPropertyNameIndexOffset = 4;

const rule = function(actual, options) {
return (root, result) => {
const validOptions = validateOptions(
Expand All @@ -43,6 +48,7 @@ const rule = function(actual, options) {
// by postcss-value-parser
value = value.replace(/\*/g, ',');
const parsedValue = valueParser(value);
const ignoredMapProperties = [];

parsedValue.walk(function(valueNode) {
// Ignore wrong units within `url` function
Expand All @@ -55,6 +61,18 @@ const rule = function(actual, options) {
return false;
}

if (isMap(valueNode)) {
valueNode.nodes.forEach((node, index) => {
if (!(index % mapPropertyNameIndexOffset)) {
ignoredMapProperties.push(node.sourceIndex);
}
});
}

if (ignoredMapProperties.includes(valueNode.sourceIndex)) {
return;
}

const unit = getUnitFromValueNode(valueNode);

if (!unit) {
Expand Down
66 changes: 66 additions & 0 deletions lib/utils/__tests__/isMap.test.js
@@ -0,0 +1,66 @@
'use strict';

const isMap = require('../isMap');
const sass = require('postcss-sass');
const scss = require('postcss-scss');
const valueParser = require('postcss-value-parser');

describe('isMap', () => {
const simpleMaps = [
['$map: (prop: "flex");', true],
['$font: (italic bold 10px/8pix)', false],
['$map: (prop: /* comment */ 0);', true],
['$calc: calc(100% / 2px);', false],
['$url: url();', false],
];
const nestedMaps = [
['$map: (prop: 0, prop2: (prop3: "normal"), prop4: 2px);', [0, 17]],
['$map: (prop: 0, prop2: (prop3: "normal", prop4: (prop5: "grid")), prop6: 2px);', [0, 17, 42]],
];

test.each(simpleMaps)('simple maps', (css, expected) => {
runTests(css, (decl) => {
const parsedValue = valueParser(decl.value).nodes[0];

expect(isMap(parsedValue)).toBe(expected);
});
});

test.each(nestedMaps)('nested maps', (css, expected) => {
runTests(css, (decl) => {
const parsedValue = valueParser(decl.value);

parsedValue.walk(function(valueNode) {
if (expected.includes(valueNode.sourceIndex)) {
expect(isMap(valueNode)).toBeTruthy();
} else {
expect(isMap(valueNode)).toBeFalsy();
}
});
});
});

it('empty map returns `false`', () => {
const emptyMap = '$map: ();';

runTests(emptyMap, (decl) => {
const parsedValue = valueParser(decl.value);

expect(isMap(parsedValue)).toBeFalsy();
});
});
});

function sassDecls(css, cb) {
sass.parse(css).walkDecls(cb);
}

function scssDecls(css, cb) {
scss.parse(css).walkDecls(cb);
}

function runTests(css, cb) {
[sassDecls, scssDecls].forEach((fn) => {
fn(css, cb);
});
}
44 changes: 44 additions & 0 deletions lib/utils/isMap.js
@@ -0,0 +1,44 @@
'use strict';

/** @typedef {import('postcss-value-parser').Node} ValueNode */

/**
* @param {ValueNode | undefined} valueNode
* @returns {boolean}
*/
module.exports = function(valueNode) {
if (!valueNode) {
return false;
}

if (valueNode.type !== 'function' || !valueNode.nodes || valueNode.value) {
return false;
}

// It's necessary to remove comments and spaces if they are present
const cleanNodes = valueNode.nodes.filter(
(node) => node.type !== 'comment' && node.type !== 'space',
);

// Map without comments and spaces will have the structure like $map (prop: value, prop2: value)
// ↑ ↑ ↑ ↑
// 0 1 2 3
if (cleanNodes[0] && cleanNodes[0].type !== 'word' && cleanNodes[0].type !== 'string') {
return false;
}

if (cleanNodes[1] && cleanNodes[1].value !== ':') {
return false;
}

// There is no need to check type or value of this node since it could be anything
if (!cleanNodes[2]) {
return false;
}

if (cleanNodes[3] && cleanNodes[3].value !== ',') {
return false;
}

return true;
};

0 comments on commit a3a74a3

Please sign in to comment.