@@ -1965,6 +1965,102 @@ function createAttrNoDuplicationFix(
19651965 } ;
19661966}
19671967
1968+ /**
1969+ * Create auto-fix action for attr-value-no-duplication rule
1970+ * Removes duplicate values within attribute values (e.g., class="btn btn primary" -> class="btn primary")
1971+ */
1972+ function createAttrValueNoDuplicationFix (
1973+ document : TextDocument ,
1974+ diagnostic : Diagnostic ,
1975+ ) : CodeAction | null {
1976+ trace (
1977+ `[DEBUG] createAttrValueNoDuplicationFix called with diagnostic: ${ JSON . stringify ( diagnostic ) } ` ,
1978+ ) ;
1979+
1980+ if ( ! diagnostic . data || diagnostic . data . ruleId !== "attr-value-no-duplication" ) {
1981+ trace (
1982+ `[DEBUG] createAttrValueNoDuplicationFix: Invalid diagnostic data or ruleId` ,
1983+ ) ;
1984+ return null ;
1985+ }
1986+
1987+ const text = document . getText ( ) ;
1988+ const diagnosticOffset = document . offsetAt ( diagnostic . range . start ) ;
1989+
1990+ // Use robust tag boundary detection
1991+ const tagBoundaries = findTagBoundaries ( text , diagnosticOffset ) ;
1992+ if ( ! tagBoundaries ) {
1993+ trace ( `[DEBUG] createAttrValueNoDuplicationFix: Could not find tag boundaries` ) ;
1994+ return null ;
1995+ }
1996+
1997+ const { tagStart, tagEnd } = tagBoundaries ;
1998+ const tagContent = text . substring ( tagStart , tagEnd + 1 ) ;
1999+ trace ( `[DEBUG] createAttrValueNoDuplicationFix: Found tag: ${ tagContent } ` ) ;
2000+
2001+ // Parse attributes from the tag to find the one with duplicate values
2002+ const attrPattern = / ( \w + (?: - \w + ) * ) \s * = \s * (?: " ( [ ^ " ] * ) " | ' ( [ ^ ' ] * ) ' | ( [ ^ \s > ] + ) ) / g;
2003+ let match ;
2004+ const edits : TextEdit [ ] = [ ] ;
2005+
2006+ while ( ( match = attrPattern . exec ( tagContent ) ) !== null ) {
2007+ const attrName = match [ 1 ] ;
2008+ const attrValue = match [ 2 ] || match [ 3 ] || match [ 4 ] || "" ;
2009+ const fullMatch = match [ 0 ] ;
2010+ const attrStartIndex = match . index ;
2011+ const attrEndIndex = match . index + fullMatch . length ;
2012+
2013+ // Check if this attribute contains duplicate values
2014+ if ( attrValue . trim ( ) ) {
2015+ const values = attrValue . trim ( ) . split ( / \s + / ) ;
2016+ const uniqueValues = [ ...new Set ( values ) ] ; // Remove duplicates while preserving order
2017+
2018+ if ( values . length !== uniqueValues . length ) {
2019+ // Found duplicates, create an edit to fix them
2020+ const newAttrValue = uniqueValues . join ( ' ' ) ;
2021+ const quote = match [ 2 ] !== undefined ? '"' : match [ 3 ] !== undefined ? "'" : '' ;
2022+ const newFullMatch = quote
2023+ ? `${ attrName } =${ quote } ${ newAttrValue } ${ quote } `
2024+ : `${ attrName } =${ newAttrValue } ` ;
2025+
2026+ const absoluteStart = tagStart + attrStartIndex ;
2027+ const absoluteEnd = tagStart + attrEndIndex ;
2028+
2029+ edits . push ( {
2030+ range : {
2031+ start : document . positionAt ( absoluteStart ) ,
2032+ end : document . positionAt ( absoluteEnd ) ,
2033+ } ,
2034+ newText : newFullMatch ,
2035+ } ) ;
2036+
2037+ trace (
2038+ `[DEBUG] createAttrValueNoDuplicationFix: Will replace "${ fullMatch } " with "${ newFullMatch } "` ,
2039+ ) ;
2040+ break ; // Only fix one attribute per diagnostic
2041+ }
2042+ }
2043+ }
2044+
2045+ if ( edits . length === 0 ) {
2046+ trace ( `[DEBUG] createAttrValueNoDuplicationFix: No edits created` ) ;
2047+ return null ;
2048+ }
2049+
2050+ const workspaceEdit : WorkspaceEdit = {
2051+ changes : {
2052+ [ document . uri ] : edits ,
2053+ } ,
2054+ } ;
2055+
2056+ return {
2057+ title : "Remove duplicate values from attribute" ,
2058+ kind : CodeActionKind . QuickFix ,
2059+ edit : workspaceEdit ,
2060+ isPreferred : true ,
2061+ } ;
2062+ }
2063+
19682064/**
19692065 * Create auto-fix action for form-method-require rule
19702066 */
@@ -2162,6 +2258,10 @@ async function createAutoFixes(
21622258 trace ( `[DEBUG] Calling createAttrNoDuplicationFix` ) ;
21632259 fix = createAttrNoDuplicationFix ( document , diagnostic ) ;
21642260 break ;
2261+ case "attr-value-no-duplication" :
2262+ trace ( `[DEBUG] Calling createAttrValueNoDuplicationFix` ) ;
2263+ fix = createAttrValueNoDuplicationFix ( document , diagnostic ) ;
2264+ break ;
21652265 case "form-method-require" :
21662266 trace ( `[DEBUG] Calling createFormMethodRequireFix` ) ;
21672267 fix = createFormMethodRequireFix ( document , diagnostic ) ;
0 commit comments