Skip to content

Commit

Permalink
fix(postcss-merge-rules): do not merge unknown pseudo-selectors
Browse files Browse the repository at this point in the history
Do not merge if the pseudo-selector is not in the list of well-known pseudo-selectors
and does not start with a vendor prefix. We let through vendor prefixes as they are
handled elsewhere in the code.

Fix #999
  • Loading branch information
ludofischer committed May 11, 2021
1 parent 70091c6 commit 29f9de5
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 40 deletions.
5 changes: 5 additions & 0 deletions packages/postcss-merge-rules/src/__tests__/index.js
Expand Up @@ -627,6 +627,11 @@ test(
)
);

test(
'should not merge unknown and known selector',
passthroughCSS('p {color: blue}:nonsense {color: blue}')
);

test(
'should merge multiple media queries',
processCSS(
Expand Down
40 changes: 5 additions & 35 deletions packages/postcss-merge-rules/src/index.js
@@ -1,10 +1,10 @@
import browserslist from 'browserslist';
import vendors from 'vendors';
import { sameParent } from 'cssnano-utils';
import ensureCompatibility from './lib/ensureCompatibility';

/** @type {string[]} */
const prefixes = vendors.map((v) => `-${v}-`);
import {
ensureCompatibility,
sameVendor,
noVendor,
} from './lib/ensureCompatibility';

/**
* @param {postcss.Declaration} a
Expand Down Expand Up @@ -52,36 +52,6 @@ function sameDeclarationsAndOrder(a, b) {
return a.every((d, index) => declarationIsEqual(d, b[index]));
}

// Internet Explorer use :-ms-input-placeholder.
// Microsoft Edge use ::-ms-input-placeholder.
const findMsInputPlaceholder = (selector) =>
~selector.search(/-ms-input-placeholder/i);

/**
* @param {string} selector
* @return {string[]}
*/
function filterPrefixes(selector) {
return prefixes.filter((prefix) => selector.indexOf(prefix) !== -1);
}

function sameVendor(selectorsA, selectorsB) {
let same = (selectors) => selectors.map(filterPrefixes).join();
let findMsVendor = (selectors) => selectors.find(findMsInputPlaceholder);
return (
same(selectorsA) === same(selectorsB) &&
!(findMsVendor(selectorsA) && findMsVendor(selectorsB))
);
}

/**
* @param {string} selector
* @return {boolean}
*/
function noVendor(selector) {
return !filterPrefixes(selector).length;
}

/**
* @param {postcss.Rule} ruleA
* @param {postcss.Rule} ruleB
Expand Down
43 changes: 38 additions & 5 deletions packages/postcss-merge-rules/src/lib/ensureCompatibility.js
@@ -1,5 +1,6 @@
import { isSupported } from 'caniuse-api';
import selectorParser from 'postcss-selector-parser';
import vendors from 'vendors';

const simpleSelectorRe = /^#?[-._a-z0-9 ]+$/i;

Expand All @@ -10,6 +11,39 @@ const cssFirstLetter = 'css-first-letter';
const cssFirstLine = 'css-first-line';
const cssInOutOfRange = 'css-in-out-of-range';

/** @type {string[]} */
const prefixes = vendors.map((v) => `-${v}-`);

/**
* @param {string} selector
* @return {string[]}
*/
export function filterPrefixes(selector) {
return prefixes.filter((prefix) => selector.indexOf(prefix) !== -1);
}

// Internet Explorer use :-ms-input-placeholder.
// Microsoft Edge use ::-ms-input-placeholder.
const findMsInputPlaceholder = (selector) =>
~selector.search(/-ms-input-placeholder/i);

export function sameVendor(selectorsA, selectorsB) {
let same = (selectors) => selectors.map(filterPrefixes).join();
let findMsVendor = (selectors) => selectors.find(findMsInputPlaceholder);
return (
same(selectorsA) === same(selectorsB) &&
!(findMsVendor(selectorsA) && findMsVendor(selectorsB))
);
}

/**
* @param {string} selector
* @return {boolean}
*/
export function noVendor(selector) {
return !filterPrefixes(selector).length;
}

export const pseudoElements = {
':active': cssSel2,
':after': cssGencontent,
Expand Down Expand Up @@ -80,11 +114,7 @@ function isSupportedCached(feature, browsers) {
return result;
}

export default function ensureCompatibility(
selectors,
browsers,
compatibilityCache
) {
export function ensureCompatibility(selectors, browsers, compatibilityCache) {
// Should not merge mixins
if (selectors.some(isCssMixin)) {
return false;
Expand All @@ -107,6 +137,9 @@ export default function ensureCompatibility(
const { type, value } = node;
if (type === 'pseudo') {
const entry = pseudoElements[value];
if (!entry && noVendor(value)) {
compatible = false;
}
if (entry && compatible) {
compatible = isSupportedCached(entry, browsers);
}
Expand Down

0 comments on commit 29f9de5

Please sign in to comment.