Skip to content

Commit 85adfe3

Browse files
committed
WIP
1 parent bc0f59e commit 85adfe3

File tree

3 files changed

+121
-46
lines changed

3 files changed

+121
-46
lines changed

src/lib/generateRules.js

+88-33
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import selectorParser from 'postcss-selector-parser'
33
import parseObjectStyles from '../util/parseObjectStyles'
44
import isPlainObject from '../util/isPlainObject'
55
import prefixSelector from '../util/prefixSelector'
6-
import { updateAllClasses } from '../util/pluginUtils'
6+
import { updateAllClasses, typeMap } from '../util/pluginUtils'
77
import log from '../util/log'
88
import * as sharedState from './sharedState'
99
import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
@@ -535,15 +535,28 @@ function* resolveMatches(candidate, context, original = candidate) {
535535
}
536536

537537
if (matchesPerPlugin.length > 0) {
538-
typesByMatches.set(matchesPerPlugin, sort.options?.type)
538+
let matchingTypes = (sort.options?.types ?? [])
539+
.map(({ type }) => type)
540+
.filter((type) => {
541+
return Boolean(
542+
typeMap[type](modifier, sort.options, {
543+
tailwindConfig: context.tailwindConfig,
544+
})
545+
)
546+
})
547+
548+
if (matchingTypes.length > 0) {
549+
typesByMatches.set(matchesPerPlugin, matchingTypes)
550+
}
551+
539552
matches.push(matchesPerPlugin)
540553
}
541554
}
542555

543556
if (isArbitraryValue(modifier)) {
544-
// When generated arbitrary values are ambiguous, we can't know
545-
// which to pick so don't generate any utilities for them
546557
if (matches.length > 1) {
558+
// When generated arbitrary values are ambiguous, we can't know which to pick so don't
559+
// generate any utilities for them
547560
let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
548561

549562
// Remove duplicates, so that we can detect proper unique types for each plugin.
@@ -564,39 +577,81 @@ function* resolveMatches(candidate, context, original = candidate) {
564577
}
565578
}
566579

567-
let messages = []
568-
569-
for (let [idx, group] of typesPerPlugin.entries()) {
570-
for (let type of group) {
571-
let rules = matches[idx]
572-
.map(([, rule]) => rule)
573-
.flat()
574-
.map((rule) =>
575-
rule
576-
.toString()
577-
.split('\n')
578-
.slice(1, -1) // Remove selector and closing '}'
579-
.map((line) => line.trim())
580-
.map((x) => ` ${x}`) // Re-indent
581-
.join('\n')
582-
)
583-
.join('\n\n')
584-
585-
messages.push(
586-
` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
580+
// Partition
581+
let [withAny, withoutAny] = matches.reduce(
582+
(group, plugin) => {
583+
let hasAnyType = plugin.some(([{ options }]) =>
584+
options.types.some(({ type }) => type === 'any')
587585
)
588-
break
586+
587+
if (hasAnyType) {
588+
group[0].push(plugin)
589+
} else {
590+
group[1].push(plugin)
591+
}
592+
return group
593+
},
594+
[[], []]
595+
)
596+
597+
function findFallback(matches) {
598+
if (matches.length === 1) {
599+
return matches[0]
589600
}
601+
602+
return matches.find((rules) => {
603+
let matchingTypes = typesByMatches.get(rules)
604+
return rules.some(([{ options }, rule]) => {
605+
if (!isParsableNode(rule)) {
606+
return false
607+
}
608+
609+
return options.types.some(
610+
({ type, disambiguate }) => matchingTypes.includes(type) && disambiguate
611+
)
612+
})
613+
})
590614
}
591615

592-
log.warn([
593-
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
594-
...messages,
595-
`If this is content and not a class, replace it with \`${candidate
596-
.replace('[', '[')
597-
.replace(']', ']')}\` to silence this warning.`,
598-
])
599-
continue
616+
let fallback = findFallback(withoutAny) ?? findFallback(withAny)
617+
618+
if (fallback) {
619+
matches = [fallback]
620+
} else {
621+
let messages = []
622+
623+
for (let [idx, group] of typesPerPlugin.entries()) {
624+
for (let type of group) {
625+
let rules = matches[idx]
626+
.map(([, rule]) => rule)
627+
.flat()
628+
.map((rule) =>
629+
rule
630+
.toString()
631+
.split('\n')
632+
.slice(1, -1) // Remove selector and closing '}'
633+
.map((line) => line.trim())
634+
.map((x) => ` ${x}`) // Re-indent
635+
.join('\n')
636+
)
637+
.join('\n\n')
638+
639+
messages.push(
640+
` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
641+
)
642+
break
643+
}
644+
}
645+
646+
log.warn([
647+
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
648+
...messages,
649+
`If this is content and not a class, replace it with \`${candidate
650+
.replace('[', '[')
651+
.replace(']', ']')}\` to silence this warning.`,
652+
])
653+
continue
654+
}
600655
}
601656

602657
matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))

src/lib/setupContextUtils.js

+31-11
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ function prefix(context, selector) {
3030
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
3131
}
3232

33+
function normalizeOptionTypes({ type = 'any', ...options }) {
34+
let types = [].concat(type)
35+
36+
return {
37+
...options,
38+
types: types.map((type) => {
39+
if (Array.isArray(type)) {
40+
return { type: type[0], ...type[1] }
41+
}
42+
return { type, disambiguate: false }
43+
}),
44+
}
45+
}
46+
3347
function parseVariantFormatString(input) {
3448
if (input.includes('{')) {
3549
if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
@@ -346,7 +360,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
346360
respectImportant: true,
347361
}
348362

349-
options = { ...defaultOptions, ...options }
363+
options = normalizeOptionTypes({ ...defaultOptions, ...options })
350364

351365
let offset = offsets.create('utilities')
352366

@@ -357,16 +371,24 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
357371
classList.add([prefixedIdentifier, options])
358372

359373
function wrapped(modifier, { isOnlyPlugin }) {
360-
let { type = 'any' } = options
361-
type = [].concat(type)
362-
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
374+
let [value, coercedType] = coerceValue(options.types, modifier, options, tailwindConfig)
363375

364376
if (value === undefined) {
365377
return []
366378
}
367379

368-
if (!type.includes(coercedType) && !isOnlyPlugin) {
369-
return []
380+
if (!options.types.some(({ type }) => type === coercedType)) {
381+
if (isOnlyPlugin) {
382+
log.warn([
383+
`Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
384+
`You can safely update it to \`${identifier}-${modifier.replace(
385+
coercedType + ':',
386+
''
387+
)}\`.`,
388+
])
389+
} else {
390+
return []
391+
}
370392
}
371393

372394
if (!isValidArbitraryValue(value)) {
@@ -398,7 +420,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
398420
respectImportant: false,
399421
}
400422

401-
options = { ...defaultOptions, ...options }
423+
options = normalizeOptionTypes({ ...defaultOptions, ...options })
402424

403425
let offset = offsets.create('components')
404426

@@ -409,15 +431,13 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
409431
classList.add([prefixedIdentifier, options])
410432

411433
function wrapped(modifier, { isOnlyPlugin }) {
412-
let { type = 'any' } = options
413-
type = [].concat(type)
414-
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
434+
let [value, coercedType] = coerceValue(options.types, modifier, options, tailwindConfig)
415435

416436
if (value === undefined) {
417437
return []
418438
}
419439

420-
if (!type.includes(coercedType)) {
440+
if (!options.types.some(({ type }) => type === coercedType)) {
421441
if (isOnlyPlugin) {
422442
log.warn([
423443
`Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,

src/util/pluginUtils.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ function guess(validate) {
146146
}
147147
}
148148

149-
let typeMap = {
149+
export let typeMap = {
150150
any: asValue,
151151
color: asColor,
152152
url: guess(url),
@@ -195,7 +195,7 @@ export function coerceValue(types, modifier, options, tailwindConfig) {
195195
}
196196

197197
// Find first matching type
198-
for (let type of [].concat(types)) {
198+
for (let { type } of types) {
199199
let result = typeMap[type](modifier, options, { tailwindConfig })
200200
if (result !== undefined) return [result, type]
201201
}

0 commit comments

Comments
 (0)