@@ -57,7 +57,79 @@ function* extractDuplicateNode(node, expression) {
57
57
}
58
58
break
59
59
}
60
+ case 'BinaryExpression' : {
61
+ if ( nodeExpression . operator === '+' ) {
62
+ yield * extractDuplicateNode ( node , nodeExpression . left )
63
+ yield * extractDuplicateNode ( node , nodeExpression . right )
64
+ }
65
+ break
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @param {string } raw - raw class names string including quotes
72
+ * @returns {string }
73
+ */
74
+ function dedupePreserveSpaces ( raw ) {
75
+ const inner = raw . slice ( 1 , - 1 )
76
+ const tokens = inner . split ( / ( \s + ) / )
77
+
78
+ /** @type {string[] } */
79
+ const kept = [ ]
80
+ const used = new Set ( )
81
+
82
+ for ( let i = 0 ; i < tokens . length ; i ++ ) {
83
+ const token = tokens [ i ]
84
+ if ( ! token ) continue
85
+
86
+ const isWhitespace = / ^ \s + $ / . test ( token )
87
+
88
+ if ( isWhitespace ) {
89
+ // add whitespace to the last kept item or as leading whitespace
90
+ if ( kept . length > 0 ) {
91
+ kept [ kept . length - 1 ] += token
92
+ } else {
93
+ kept . push ( token )
94
+ }
95
+ } else if ( used . has ( token ) ) {
96
+ // handle duplicate class name
97
+ const nextToken = tokens [ i + 1 ]
98
+ const hasNextWhitespace =
99
+ kept . length > 0 && i + 1 < tokens . length && / ^ \s + $ / . test ( nextToken )
100
+
101
+ if ( hasNextWhitespace ) {
102
+ // update spaces of the last non-whitespace item
103
+ for ( let j = kept . length - 1 ; j >= 0 ; j -- ) {
104
+ const isNotWhitespace = ! / ^ \s + $ / . test ( kept [ j ] )
105
+ if ( isNotWhitespace ) {
106
+ const parts = kept [ j ] . split ( / ( \s + ) / )
107
+ kept [ j ] = parts [ 0 ] + nextToken
108
+ break
109
+ }
110
+ }
111
+ i ++ // skip the whitespace token
112
+ }
113
+ } else {
114
+ kept . push ( token )
115
+ used . add ( token )
116
+ }
60
117
}
118
+
119
+ // remove trailing whitespace from the last item if it's not purely whitespace
120
+ // unless the original string ended with whitespace
121
+ const endsWithSpace = / \s $ / . test ( inner )
122
+ if ( kept . length > 0 && ! endsWithSpace ) {
123
+ const lastItem = kept [ kept . length - 1 ]
124
+ const isLastWhitespace = / ^ \s + $ / . test ( lastItem )
125
+ if ( ! isLastWhitespace ) {
126
+ const parts = lastItem . split ( / ( \s + ) / )
127
+ kept [ kept . length - 1 ] = parts [ 0 ]
128
+ }
129
+ }
130
+
131
+ const quote = raw [ 0 ]
132
+ return quote + kept . join ( '' ) + quote
61
133
}
62
134
63
135
module . exports = {
@@ -113,11 +185,9 @@ module.exports = {
113
185
messageId : 'duplicateClassName' ,
114
186
data : { name : duplicates . join ( ', ' ) } ,
115
187
fix : ( fixer ) => {
116
- const unique = [ ...seen ] . join ( ' ' )
117
188
const sourceCode = context . getSourceCode ( )
118
189
const raw = sourceCode . text . slice ( node . range [ 0 ] , node . range [ 1 ] )
119
- const quote = raw [ 0 ]
120
- return fixer . replaceText ( node , `${ quote } ${ unique } ${ quote } ` )
190
+ return fixer . replaceText ( node , dedupePreserveSpaces ( raw ) )
121
191
}
122
192
} )
123
193
}
0 commit comments