Skip to content

Commit ee9ea05

Browse files
committed
Add options to match attributes by prefix and suffix
1 parent 919349c commit ee9ea05

File tree

3 files changed

+73
-23
lines changed

3 files changed

+73
-23
lines changed

src/index.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ function createParser(
2828
) {
2929
let customizationDefaults: Customizations = {
3030
staticAttrs: new Set(meta.staticAttrs ?? []),
31+
prefixAttrs: new Set(meta.prefixAttrs ?? []),
32+
suffixAttrs: new Set(meta.suffixAttrs ?? []),
3133
dynamicAttrs: new Set(meta.dynamicAttrs ?? []),
3234
functions: new Set(meta.functions ?? []),
3335
}
@@ -273,11 +275,11 @@ function transformDynamicJsAttribute(attr: any, env: TransformerEnv) {
273275
}
274276

275277
function transformHtml(ast: any, { env, changes }: TransformerContext) {
276-
let { staticAttrs, dynamicAttrs } = env.customizations
278+
let { dynamicAttrs } = env.customizations
277279
let { parser } = env.options
278280

279281
for (let attr of ast.attrs ?? []) {
280-
if (staticAttrs.has(attr.name)) {
282+
if (isSortableAttribute(env.customizations, attr.name)) {
281283
attr.value = sortClasses(attr.value, { env })
282284
} else if (dynamicAttrs.has(attr.name)) {
283285
if (!/[`'"]/.test(attr.value)) {
@@ -298,11 +300,9 @@ function transformHtml(ast: any, { env, changes }: TransformerContext) {
298300
}
299301

300302
function transformGlimmer(ast: any, { env }: TransformerContext) {
301-
let { staticAttrs } = env.customizations
302-
303303
visit(ast, {
304304
AttrNode(attr, _path, meta) {
305-
if (staticAttrs.has(attr.name) && attr.value) {
305+
if (isSortableAttribute(env.customizations, attr.name) && attr.value) {
306306
meta.sortTextNodes = true
307307
}
308308
},
@@ -354,12 +354,10 @@ function transformGlimmer(ast: any, { env }: TransformerContext) {
354354
}
355355

356356
function transformLiquid(ast: any, { env }: TransformerContext) {
357-
let { staticAttrs } = env.customizations
358-
359357
function isClassAttr(node: { name: string | { type: string; value: string }[] }) {
360358
return Array.isArray(node.name)
361-
? node.name.every((n) => n.type === 'TextNode' && staticAttrs.has(n.value))
362-
: staticAttrs.has(node.name)
359+
? node.name.every((n) => n.type === 'TextNode' && isSortableAttribute(env.customizations, n.value))
360+
: isSortableAttribute(env.customizations, node.name)
363361
}
364362

365363
function hasSurroundingQuotes(str: string) {
@@ -566,6 +564,22 @@ function sortTemplateLiteral(
566564
return didChange
567565
}
568566

567+
function isSortableAttribute(customizations: Customizations, name: string) {
568+
const { staticAttrs, suffixAttrs, prefixAttrs } = customizations
569+
570+
if (staticAttrs.has(name)) return true
571+
572+
for (const prefix of prefixAttrs) {
573+
if (name.startsWith(prefix)) return true
574+
}
575+
576+
for (const suffix of suffixAttrs) {
577+
if (name.endsWith(suffix)) return true
578+
}
579+
580+
return false
581+
}
582+
569583
function isSortableTemplateExpression(
570584
node: import('@babel/types').TaggedTemplateExpression | import('ast-types').namedTypes.TaggedTemplateExpression,
571585
functions: Set<string>,
@@ -653,7 +667,7 @@ function canCollapseWhitespaceIn(path: Path<import('@babel/types').Node, any>) {
653667
// We cross several parsers that share roughly the same shape so things are
654668
// good enough. The actual AST we should be using is probably estree + ts.
655669
function transformJavaScript(ast: import('@babel/types').Node, { env }: TransformerContext) {
656-
let { staticAttrs, functions } = env.customizations
670+
let { functions } = env.customizations
657671

658672
function sortInside(ast: import('@babel/types').Node) {
659673
visit(ast, (node, path) => {
@@ -685,7 +699,7 @@ function transformJavaScript(ast: import('@babel/types').Node, { env }: Transfor
685699
return
686700
}
687701

688-
if (!staticAttrs.has(node.name.name)) {
702+
if (!isSortableAttribute(env.customizations, node.name.name)) {
689703
return
690704
}
691705

@@ -787,11 +801,11 @@ function transformCss(ast: any, { env }: TransformerContext) {
787801
}
788802

789803
function transformAstro(ast: any, { env, changes }: TransformerContext) {
790-
let { staticAttrs, dynamicAttrs } = env.customizations
804+
let { dynamicAttrs } = env.customizations
791805

792806
if (ast.type === 'element' || ast.type === 'custom-element' || ast.type === 'component') {
793807
for (let attr of ast.attributes ?? []) {
794-
if (staticAttrs.has(attr.name) && attr.type === 'attribute' && attr.kind === 'quoted') {
808+
if (isSortableAttribute(env.customizations, attr.name) && attr.type === 'attribute' && attr.kind === 'quoted') {
795809
attr.value = sortClasses(attr.value, {
796810
env,
797811
})
@@ -812,8 +826,6 @@ function transformAstro(ast: any, { env, changes }: TransformerContext) {
812826
}
813827

814828
function transformMarko(ast: any, { env }: TransformerContext) {
815-
let { staticAttrs } = env.customizations
816-
817829
const nodesToVisit = [ast]
818830
while (nodesToVisit.length > 0) {
819831
const currentNode = nodesToVisit.pop()
@@ -832,7 +844,7 @@ function transformMarko(ast: any, { env }: TransformerContext) {
832844
nodesToVisit.push(...currentNode.body)
833845
break
834846
case 'MarkoAttribute':
835-
if (!staticAttrs.has(currentNode.name)) break
847+
if (!isSortableAttribute(env.customizations, currentNode.name)) break
836848
switch (currentNode.value.type) {
837849
case 'ArrayExpression':
838850
const classList = currentNode.value.elements
@@ -862,7 +874,7 @@ function transformTwig(ast: any, { env, changes }: TransformerContext) {
862874

863875
visit(ast, {
864876
Attribute(node, _path, meta) {
865-
if (!staticAttrs.has(node.name.name)) return
877+
if (!isSortableAttribute(env.customizations, node.name.name)) return
866878

867879
meta.sortTextNodes = true
868880
},
@@ -893,16 +905,14 @@ function transformTwig(ast: any, { env, changes }: TransformerContext) {
893905
}
894906

895907
function transformPug(ast: any, { env }: TransformerContext) {
896-
let { staticAttrs } = env.customizations
897-
898908
// This isn't optimal
899909
// We should merge the classes together across class attributes and class tokens
900910
// And then we sort them
901911
// But this is good enough for now
902912

903913
// First sort the classes in attributes
904914
for (const token of ast.tokens) {
905-
if (token.type === 'attribute' && staticAttrs.has(token.name)) {
915+
if (token.type === 'attribute' && isSortableAttribute(env.customizations, token.name)) {
906916
token.val = [token.val.slice(0, 1), sortClasses(token.val.slice(1, -1), { env }), token.val.slice(-1)].join('')
907917
}
908918
}
@@ -947,10 +957,8 @@ function transformPug(ast: any, { env }: TransformerContext) {
947957
}
948958

949959
function transformSvelte(ast: any, { env, changes }: TransformerContext) {
950-
let { staticAttrs } = env.customizations
951-
952960
for (let attr of ast.attributes ?? []) {
953-
if (!staticAttrs.has(attr.name) || attr.type !== 'Attribute') {
961+
if (!isSortableAttribute(env.customizations, attr.name) || attr.type !== 'Attribute') {
954962
continue
955963
}
956964

@@ -1238,6 +1246,16 @@ export interface PluginOptions {
12381246
*/
12391247
tailwindAttributes?: string[]
12401248

1249+
/**
1250+
* List of prefixes to match attributes that contain classes.
1251+
*/
1252+
tailwindAttributesStartsWith?: string[]
1253+
1254+
/**
1255+
* List of suffixes to match attributes that contain classes.
1256+
*/
1257+
tailwindAttributesEndsWith?: string[]
1258+
12411259
/**
12421260
* Preserve whitespace around Tailwind classes when sorting.
12431261
*/

src/options.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ export const options: Record<string, SupportOption> = {
3131
description: 'List of attributes/props that contain sortable Tailwind classes',
3232
},
3333

34+
tailwindAttributesStartsWith: {
35+
type: 'string',
36+
array: true,
37+
default: [{ value: [] }],
38+
category: 'Tailwind CSS',
39+
description: 'List of prefixes for attributes that contain sortable Tailwind classes',
40+
},
41+
42+
tailwindAttributesEndsWith: {
43+
type: 'string',
44+
array: true,
45+
default: [{ value: [] }],
46+
category: 'Tailwind CSS',
47+
description: 'List of suffixes for attributes that contain sortable Tailwind classes',
48+
},
49+
3450
tailwindFunctions: {
3551
type: 'string',
3652
array: true,
@@ -63,6 +79,8 @@ export const options: Record<string, SupportOption> = {
6379

6480
export function getCustomizations(options: RequiredOptions, parser: string, defaults: Customizations): Customizations {
6581
let staticAttrs = new Set<string>(defaults.staticAttrs)
82+
let prefixAttrs = new Set<string>(defaults.prefixAttrs)
83+
let suffixAttrs = new Set<string>(defaults.suffixAttrs)
6684
let dynamicAttrs = new Set<string>(defaults.dynamicAttrs)
6785
let functions = new Set<string>(defaults.functions)
6886

@@ -96,9 +114,19 @@ export function getCustomizations(options: RequiredOptions, parser: string, defa
96114
functions.add(fn)
97115
}
98116

117+
for (let attr of options.tailwindAttributesStartsWith ?? []) {
118+
prefixAttrs.add(attr)
119+
}
120+
121+
for (let attr of options.tailwindAttributesEndsWith ?? []) {
122+
suffixAttrs.add(attr)
123+
}
124+
99125
return {
100126
functions,
101127
staticAttrs,
102128
dynamicAttrs,
129+
prefixAttrs,
130+
suffixAttrs,
103131
}
104132
}

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ export interface TransformerMetadata {
44
// Default customizations for a given transformer
55
functions?: string[]
66
staticAttrs?: string[]
7+
prefixAttrs?: string[]
8+
suffixAttrs?: string[]
79
dynamicAttrs?: string[]
810
}
911

1012
export interface Customizations {
1113
functions: Set<string>
1214
staticAttrs: Set<string>
15+
prefixAttrs: Set<string>
16+
suffixAttrs: Set<string>
1317
dynamicAttrs: Set<string>
1418
}
1519

0 commit comments

Comments
 (0)