diff --git a/__tests__/applyAtRule.test.js b/__tests__/applyAtRule.test.js index 8ad5af4dc..f07e90ff3 100644 --- a/__tests__/applyAtRule.test.js +++ b/__tests__/applyAtRule.test.js @@ -80,6 +80,22 @@ test('it fails if the class does not exist', () => { }) }) +test('applying classes that are defined in a media query is not supported', () => { + const input = ` + @media (min-width: 300px) { + .a { color: blue; } + } + + .b { + @apply .a; + } + ` + expect.assertions(1) + return run(input).catch(e => { + expect(e).toMatchObject({ name: 'CssSyntaxError' }) + }) +}) + test('applying classes that are ever used in a media query is not supported', () => { const input = ` .a { diff --git a/src/lib/substituteClassApplyAtRules.js b/src/lib/substituteClassApplyAtRules.js index 35a5b4bf0..cea7167fb 100644 --- a/src/lib/substituteClassApplyAtRules.js +++ b/src/lib/substituteClassApplyAtRules.js @@ -2,23 +2,25 @@ import _ from 'lodash' import postcss from 'postcss' import escapeClassName from '../util/escapeClassName' +function buildClassTable(css) { + const classTable = {} + + css.walkRules(rule => { + if (!_.has(classTable, rule.selector)) { + classTable[rule.selector] = [] + } + classTable[rule.selector].push(rule) + }) + + return classTable +} + function normalizeClassName(className) { return `.${escapeClassName(_.trimStart(className, '.'))}` } -function findMixin(css, mixin, onError) { - const matches = [] - - css.walkRules(rule => { - if (rule.selectors.includes(mixin)) { - if (rule.parent.type !== 'root') { - // prettier-ignore - onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is nested inside of an at-rule (@${rule.parent.name}).`) - } - - matches.push(rule) - } - }) +function findMixin(classTable, mixin, onError) { + const matches = _.get(classTable, mixin, []) if (_.isEmpty(matches)) { // prettier-ignore @@ -30,11 +32,20 @@ function findMixin(css, mixin, onError) { onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is included in multiple rulesets.`) } - return _.flatten(matches.map(match => match.clone().nodes)) + const [match] = matches + + if (match.parent.type !== 'root') { + // prettier-ignore + onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is nested inside of an at-rule (@${match.parent.name}).`) + } + + return match.clone().nodes } export default function() { return function(css) { + const classLookup = buildClassTable(css) + css.walkRules(rule => { rule.walkAtRules('apply', atRule => { const mixins = postcss.list.space(atRule.params) @@ -53,7 +64,7 @@ export default function() { const decls = _(classes) .reject(mixin => mixin === '!important') .flatMap(mixin => { - return findMixin(css, normalizeClassName(mixin), message => { + return findMixin(classLookup, normalizeClassName(mixin), message => { throw atRule.error(message) }) })