Skip to content

Commit

Permalink
Add support for tailwindConfig.separator
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-rogerson committed Sep 26, 2022
1 parent ed8d1e2 commit 92e902c
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 27 deletions.
4 changes: 4 additions & 0 deletions __fixtures__/separator/separator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @ts-nocheck
import tw from './macro'

tw`[&[data-foo][data-bar]:not([data-baz])]__underline`
3 changes: 3 additions & 0 deletions __fixtures__/separator/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
separator: '__',
}
19 changes: 19 additions & 0 deletions __snapshots__/plugin.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -64854,6 +64854,25 @@ tw\`snap-proximity\`
})


`;

exports[`twin.macro separator.tsx: separator.tsx 1`] = `

// @ts-nocheck
import tw from './macro'

tw\`[&[data-foo][data-bar]:not([data-baz])]__underline\`

↓ ↓ ↓ ↓ ↓ ↓

// @ts-nocheck
;({
'&[data-foo][data-bar]:not([data-baz])': {
textDecorationLine: 'underline',
},
})


`;

exports[`twin.macro sepia.tsx: sepia.tsx 1`] = `
Expand Down
23 changes: 17 additions & 6 deletions src/core/getStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
AssertContext,
TailwindMatch,
TailwindContext,
TailwindConfig,
} from './types'

const IMPORTANT_OUTSIDE_BRACKETS =
Expand Down Expand Up @@ -63,7 +64,10 @@ function multilineReplaceWith(

function validateClasses(
classes: string,
{ assert }: { assert: CoreContext['assert'] }
{
assert,
tailwindConfig,
}: { tailwindConfig: TailwindConfig; assert: CoreContext['assert'] }
): boolean {
assert(
(classes.match(/\[/g) ?? []).length === (classes.match(/]/g) ?? []).length,
Expand All @@ -78,7 +82,7 @@ function validateClasses(

for (const className of splitAtTopLevelOnly(classes, ' ')) {
assert(
!className.endsWith(':'),
!className.endsWith(tailwindConfig.separator ?? ':'),
({ color }: AssertContext) =>
`${color(
`✕ The variant ${String(
Expand All @@ -93,12 +97,15 @@ function validateClasses(
return true
}

const tasks: Array<(classes: string) => string> = [
const tasks: Array<
(classes: string, tailwindConfig: TailwindConfig) => string
> = [
(classes): string => classes.replace(CLASS_DIVIDER_PIPE, ' '),
(classes): string =>
classes.replace(COMMENTS_MULTI_LINE, multilineReplaceWith),
(classes): string => classes.replace(COMMENTS_SINGLE_LINE, ''),
expandVariantGroups, // Expand grouped variants to individual classes
(classes, tailwindConfig): string =>
expandVariantGroups(classes, { tailwindConfig }), // Expand grouped variants to individual classes
]

function bigSign(bigIntValue: bigint): number {
Expand Down Expand Up @@ -163,11 +170,14 @@ function getStyles(
)}\n\nRead more at https://twinredirect.page.link/template-literals`
)

const result = validateClasses(classes, { assert })
const result = validateClasses(classes, {
tailwindConfig: params.tailwindConfig,
assert,
})
if (!result) return { styles: undefined, matched: [], unmatched: [] }

for (const task of tasks) {
classes = task(classes)
classes = task(classes, params.tailwindConfig)
}

params.debug('classes after format', classes)
Expand All @@ -184,6 +194,7 @@ function getStyles(

const convertedClassNameContext = {
...commonContext,
tailwindConfig: params.tailwindConfig,
isShortCssOnly: params.isShortCssOnly,
disableShortCss: params.twinConfig.disableShortCss,
}
Expand Down
27 changes: 20 additions & 7 deletions src/core/lib/convertClassName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ const ALL_COMMAS = /,/g

type ConvertShortCssToArbitraryPropertyParameters = {
disableShortCss: CoreContext['twinConfig']['disableShortCss']
} & Pick<CoreContext, 'assert' | 'isShortCssOnly'>
} & Pick<CoreContext, 'tailwindConfig' | 'assert' | 'isShortCssOnly'>

function convertShortCssToArbitraryProperty(
className: string,
{
tailwindConfig,
assert,
disableShortCss,
isShortCssOnly,
}: ConvertShortCssToArbitraryPropertyParameters
): string {
const splitArray = [...splitAtTopLevelOnly(className, ':')]
const splitArray = [
...splitAtTopLevelOnly(className, tailwindConfig.separator ?? ':'),
]

const lastValue = splitArray.slice(-1)[0]

Expand All @@ -38,10 +41,10 @@ function convertShortCssToArbitraryProperty(
const template = `${preSelector}[${[
property,
value === '' ? "''" : value,
].join(':')}]`
].join(tailwindConfig.separator ?? ':')}]`
splitArray.splice(-1, 1, template)

const arbitraryProperty = splitArray.join(':')
const arbitraryProperty = splitArray.join(tailwindConfig.separator ?? ':')

const isShortCssDisabled = disableShortCss && !isShortCssOnly
assert(!isShortCssDisabled, ({ color }) =>
Expand All @@ -64,12 +67,16 @@ function convertShortCssToArbitraryProperty(

type ConvertClassNameParameters = {
disableShortCss: CoreContext['twinConfig']['disableShortCss']
} & Pick<CoreContext, 'theme' | 'assert' | 'debug' | 'isShortCssOnly'>
} & Pick<
CoreContext,
'tailwindConfig' | 'theme' | 'assert' | 'debug' | 'isShortCssOnly'
>

// Convert a twin class to a tailwindcss friendly class
function convertClassName(
className: string,
{
tailwindConfig,
theme,
isShortCssOnly,
disableShortCss,
Expand All @@ -84,17 +91,23 @@ function convertClassName(
if (className.endsWith('!')) {
debug('trailing bang found', className)

const splitArray = [...splitAtTopLevelOnly(className.slice(0, -1), ':')]
const splitArray = [
...splitAtTopLevelOnly(
className.slice(0, -1),
tailwindConfig.separator ?? ':'
),
]
// Place a ! before the class
splitArray.splice(-1, 1, `!${splitArray[splitArray.length - 1]}`)
className = splitArray.join(':')
className = splitArray.join(tailwindConfig.separator ?? ':')
}

// Convert short css to an arbitrary property, eg: `[display:block]`
// (Short css is deprecated)
if (isShortCss(className)) {
debug('short css found', className)
className = convertShortCssToArbitraryProperty(className, {
tailwindConfig,
assert,
disableShortCss,
isShortCssOnly,
Expand Down
4 changes: 3 additions & 1 deletion src/suggestions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ function getVariantSuggestions(
function getClassError(rawClass: string, context: ClassErrorContext): string {
const input = rawClass.replace(ALL_SPACE_IDS, ' ')

const classPieces = [...splitAtTopLevelOnly(input, ':')]
const classPieces = [
...splitAtTopLevelOnly(input, context.tailwindConfig.separator ?? ':'),
]

for (const validator of validators) {
const error = validator(classPieces, context)
Expand Down
27 changes: 19 additions & 8 deletions src/suggestions/lib/getClassSuggestions.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import stringSimilarity from 'string-similarity'
import type { ClassErrorContext } from 'suggestions/types'
import type { TailwindConfig, ClassErrorContext } from 'suggestions/types'

const RATING_MINIMUM = 0.2

type RateCandidate = [number, string, string]

function rateCandidate(
classData: [string, string],
className: string,
matchee: string,
threshold = 0.2
params: { tailwindConfig: TailwindConfig }
): RateCandidate | undefined {
const [classEnd, value] = classData

const candidate = `${[className, classEnd === 'DEFAULT' ? '' : classEnd]
.filter(Boolean)
.join('-')}`
.join(params.tailwindConfig.separator)}`

const rating = Number(stringSimilarity.compareTwoStrings(matchee, candidate))
if (rating < threshold) return
if (rating < RATING_MINIMUM) return

const classValue = `${String(
(typeof value === 'string' && (value.length === 0 ? `''` : value)) ??
Expand All @@ -28,7 +30,8 @@ function rateCandidate(

function extractCandidates(
candidates: ClassErrorContext['candidates'],
matchee: string
matchee: string,
tailwindConfig: TailwindConfig
): RateCandidate[] {
const results = [] as RateCandidate[]

Expand All @@ -38,13 +41,17 @@ function extractCandidates(
if (options?.values) {
// Dynamic classes like mt-xxx, bg-xxx
for (const value of Object.entries(options?.values)) {
const rated = rateCandidate(value, className, matchee)
const rated = rateCandidate(value, className, matchee, {
tailwindConfig,
})
// eslint-disable-next-line max-depth
if (rated) results.push(rated)
}
} else {
// Non-dynamic classes like fixed, block
const rated = rateCandidate(['', className], className, matchee)
const rated = rateCandidate(['', className], className, matchee, {
tailwindConfig,
})
if (rated) results.push(rated)
}
}
Expand All @@ -59,7 +66,11 @@ export function getClassSuggestions(
): string {
const { color } = context

const candidates = extractCandidates(context.candidates, matchee)
const candidates = extractCandidates(
context.candidates,
matchee,
context.tailwindConfig
)

const errorText = `${context.color(
`✕ ${context.color(matchee, 'errorLight')} was not found`,
Expand Down
10 changes: 5 additions & 5 deletions src/suggestions/lib/validateVariants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ export function validateVariants(
.filter(Boolean) as Array<[string, number]>

const errorText = `${context.color(
`✕ Variant ${context.color(
`${variantMatch}:`,
'errorLight'
)} was not found`,
`✕ Variant ${context.color(`${variantMatch}`, 'errorLight')} was not found`,
'error'
)}`

Expand All @@ -37,7 +34,10 @@ export function validateVariants(
const suggestions = results
.sort(([, a]: [string, number], [, b]: [string, number]) => b - a)
.slice(0, 4)
.map(([i]: [string, number]): string => `${i}:`)
.map(
([i]: [string, number]): string =>
`${i}${context.tailwindConfig.separator ?? ':'}`
)

const showMore = results.length > 2 && results[0][1] - results[1][1] < 0.1

Expand Down

0 comments on commit 92e902c

Please sign in to comment.