diff --git a/__tests__/applyAtRule.test.js b/__tests__/applyAtRule.test.js index 478232ba7..18c0fe6df 100644 --- a/__tests__/applyAtRule.test.js +++ b/__tests__/applyAtRule.test.js @@ -887,6 +887,50 @@ describe('using apply with the prefix option', () => { }) }) + test('a "Did You Mean" suggestion is recommended if a similar class can be identified.', () => { + const input = ` + .foo { @apply anti-aliased; } + ` + + const config = resolveConfig([ + { + ...defaultConfig, + }, + ]) + + expect.assertions(1) + + return run(input, config).catch((e) => { + expect(e).toMatchObject({ + name: 'CssSyntaxError', + reason: + "The `anti-aliased` class does not exist, but `antialiased` does. If you're sure that `anti-aliased` exists, make sure that any `@import` statements are being properly processed before Tailwind CSS sees your CSS, as `@apply` can only be used for classes in the same CSS tree.", + }) + }) + }) + + test('a "Did You Mean" suggestion is omitted if a similar class cannot be identified.', () => { + const input = ` + .foo { @apply anteater; } + ` + + const config = resolveConfig([ + { + ...defaultConfig, + }, + ]) + + expect.assertions(1) + + return run(input, config).catch((e) => { + expect(e).toMatchObject({ + name: 'CssSyntaxError', + reason: + "The `anteater` class does not exist. If you're sure that `anteater` exists, make sure that any `@import` statements are being properly processed before Tailwind CSS sees your CSS, as `@apply` can only be used for classes in the same CSS tree.", + }) + }) + }) + test('you can apply classes with important and a prefix enabled', () => { const input = ` .foo { @apply tw-mt-4; } diff --git a/src/lib/substituteClassApplyAtRules.js b/src/lib/substituteClassApplyAtRules.js index 78e4544a7..8f0f1eee5 100644 --- a/src/lib/substituteClassApplyAtRules.js +++ b/src/lib/substituteClassApplyAtRules.js @@ -1,6 +1,7 @@ import _ from 'lodash' import selectorParser from 'postcss-selector-parser' import postcss from 'postcss' +import didYouMean from 'didyoumean' import substituteTailwindAtRules from './substituteTailwindAtRules' import evaluateTailwindFunctions from './evaluateTailwindFunctions' import substituteVariantsAtRules from './substituteVariantsAtRules' @@ -166,6 +167,8 @@ function makeExtractUtilityRules(css, lookupTree, config) { if (utilityMap[utilityName] === undefined) { // Look for prefixed utility in case the user has goofed const prefixedUtility = prefixSelector(config.prefix, `.${utilityName}`).slice(1) + const suggestedClass = didYouMean(utilityName, Object.keys(utilityMap)) + const suggestionMessage = suggestedClass ? `, but \`${suggestedClass}\` does` : '' if (utilityMap[prefixedUtility] !== undefined) { throw rule.error( @@ -174,7 +177,7 @@ function makeExtractUtilityRules(css, lookupTree, config) { } throw rule.error( - `The \`${utilityName}\` class does not exist. If you're sure that \`${utilityName}\` exists, make sure that any \`@import\` statements are being properly processed before Tailwind CSS sees your CSS, as \`@apply\` can only be used for classes in the same CSS tree.`, + `The \`${utilityName}\` class does not exist${suggestionMessage}. If you're sure that \`${utilityName}\` exists, make sure that any \`@import\` statements are being properly processed before Tailwind CSS sees your CSS, as \`@apply\` can only be used for classes in the same CSS tree.`, { word: utilityName } ) }