@@ -1029,6 +1029,96 @@ function createMetaDescriptionRequireFix(
10291029 } ;
10301030}
10311031
1032+ /**
1033+ * Create auto-fix action for link-rel-canonical-require rule
1034+ */
1035+ function createLinkRelCanonicalRequireFix (
1036+ document : TextDocument ,
1037+ diagnostic : Diagnostic ,
1038+ ) : CodeAction | null {
1039+ if (
1040+ ! diagnostic . data ||
1041+ diagnostic . data . ruleId !== "link-rel-canonical-require"
1042+ ) {
1043+ return null ;
1044+ }
1045+
1046+ const text = document . getText ( ) ;
1047+ const headMatch = text . match ( / < h e a d ( \s [ ^ > ] * ) ? > ( [ \s \S ] * ?) < \/ h e a d > / i) ;
1048+
1049+ if ( ! headMatch ) {
1050+ return null ;
1051+ }
1052+
1053+ const headContent = headMatch [ 2 ] ;
1054+ const canonicalMatch = headContent . match (
1055+ / < l i n k \s + [ ^ > ] * r e l \s * = \s * [ " ' ] c a n o n i c a l [ " ' ] [ ^ > ] * > / i,
1056+ ) ;
1057+
1058+ if ( canonicalMatch ) {
1059+ return null ; // Canonical link tag already exists
1060+ }
1061+
1062+ // Find a good position to insert canonical link tag (after meta tags if they exist, otherwise at the beginning of head)
1063+ const headStart = headMatch . index ! + headMatch [ 0 ] . indexOf ( ">" ) + 1 ;
1064+ const metaCharsetMatch = headContent . match (
1065+ / < m e t a \s + c h a r s e t \s * = \s * [ " ' ] [ ^ " ' ] * [ " ' ] [ ^ > ] * > / i,
1066+ ) ;
1067+ const metaViewportMatch = headContent . match (
1068+ / < m e t a \s + n a m e \s * = \s * [ " ' ] v i e w p o r t [ " ' ] [ ^ > ] * > / i,
1069+ ) ;
1070+ const metaDescriptionMatch = headContent . match (
1071+ / < m e t a \s + n a m e \s * = \s * [ " ' ] d e s c r i p t i o n [ " ' ] [ ^ > ] * > / i,
1072+ ) ;
1073+
1074+ let insertPosition : number ;
1075+ const shouldSelfClose = isRuleEnabledForDocument ( document , "tag-self-close" ) ;
1076+ const canonicalSnippet =
1077+ '\n <link rel="canonical" href=""' +
1078+ ( shouldSelfClose ? " />" : ">" ) ;
1079+
1080+ if ( metaDescriptionMatch ) {
1081+ // Insert after description meta tag
1082+ const metaDescriptionEnd =
1083+ headStart + metaDescriptionMatch . index ! + metaDescriptionMatch [ 0 ] . length ;
1084+ insertPosition = metaDescriptionEnd ;
1085+ } else if ( metaViewportMatch ) {
1086+ // Insert after viewport meta tag
1087+ const metaViewportEnd =
1088+ headStart + metaViewportMatch . index ! + metaViewportMatch [ 0 ] . length ;
1089+ insertPosition = metaViewportEnd ;
1090+ } else if ( metaCharsetMatch ) {
1091+ // Insert after charset meta tag
1092+ const metaCharsetEnd =
1093+ headStart + metaCharsetMatch . index ! + metaCharsetMatch [ 0 ] . length ;
1094+ insertPosition = metaCharsetEnd ;
1095+ } else {
1096+ // Insert at the beginning of head
1097+ insertPosition = headStart ;
1098+ }
1099+
1100+ const edit : TextEdit = {
1101+ range : {
1102+ start : document . positionAt ( insertPosition ) ,
1103+ end : document . positionAt ( insertPosition ) ,
1104+ } ,
1105+ newText : canonicalSnippet ,
1106+ } ;
1107+
1108+ const workspaceEdit : WorkspaceEdit = {
1109+ changes : {
1110+ [ document . uri ] : [ edit ] ,
1111+ } ,
1112+ } ;
1113+
1114+ return {
1115+ title : 'Add <link rel="canonical"> tag' ,
1116+ kind : CodeActionKind . QuickFix ,
1117+ edit : workspaceEdit ,
1118+ isPreferred : true ,
1119+ } ;
1120+ }
1121+
10321122/**
10331123 * Create auto-fix action for alt-require rule
10341124 */
@@ -2432,6 +2522,10 @@ async function createAutoFixes(
24322522 trace ( `[DEBUG] Calling createMetaDescriptionRequireFix` ) ;
24332523 fix = await createMetaDescriptionRequireFix ( document , diagnostic ) ;
24342524 break ;
2525+ case "link-rel-canonical-require" :
2526+ trace ( `[DEBUG] Calling createLinkRelCanonicalRequireFix` ) ;
2527+ fix = await createLinkRelCanonicalRequireFix ( document , diagnostic ) ;
2528+ break ;
24352529 case "alt-require" :
24362530 trace ( `[DEBUG] Calling createAltRequireFix` ) ;
24372531 fix = createAltRequireFix ( document , diagnostic ) ;
0 commit comments