Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions htmlhint-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1966,6 +1966,108 @@
};
}

/**
* Create auto-fix action for form-method-require rule
*/
function createFormMethodRequireFix(
document: TextDocument,
diagnostic: Diagnostic,
): CodeAction | null {
trace(
`[DEBUG] createFormMethodRequireFix called with diagnostic: ${JSON.stringify(diagnostic)}`,
);

if (!diagnostic.data || diagnostic.data.ruleId !== "form-method-require") {
trace(
`[DEBUG] createFormMethodRequireFix: Invalid diagnostic data or ruleId`,
);
return null;
}

const text = document.getText();
const diagnosticOffset = document.offsetAt(diagnostic.range.start);

// Use robust tag boundary detection to find the form tag
const tagBoundaries = findTagBoundaries(text, diagnosticOffset);
if (!tagBoundaries) {
trace(`[DEBUG] createFormMethodRequireFix: Could not find tag boundaries`);
return null;
}

const { tagStart, tagEnd } = tagBoundaries;
const tagContent = text.substring(tagStart, tagEnd + 1);
trace(`[DEBUG] createFormMethodRequireFix: Found tag: ${tagContent}`);

// Verify this is a form tag
const formTagMatch = tagContent.match(/^<\s*form\b/i);
if (!formTagMatch) {
trace(`[DEBUG] createFormMethodRequireFix: Not a form tag`);
return null;
}

// Check if method attribute already exists
const methodAttrMatch = tagContent.match(/\bmethod\s*=/i);
if (methodAttrMatch) {
trace(
`[DEBUG] createFormMethodRequireFix: Method attribute already exists`,
);
return null;
}

// Find the best position to insert the method attribute
// We'll add it after the opening form tag name but before the closing >
const formMatch = tagContent.match(/^(<\s*form)(\s+[^>]*?)?(\/?\s*>)$/i);
if (!formMatch) {
trace(
`[DEBUG] createFormMethodRequireFix: Could not parse form tag structure`,
);
return null;
}

const beforeAttrs = formMatch[1]; // "<form"
const existingAttrs = formMatch[2] || ""; // existing attributes
const tagClose = formMatch[3]; // ">" or "/>"

// Calculate insertion position
let insertPosition: number;
let newText: string;

if (existingAttrs.trim()) {
// There are existing attributes, add method after them
insertPosition = tagStart + beforeAttrs.length + existingAttrs.length;
newText = ' method=""';
} else {
// No existing attributes, add method right after "form"
insertPosition = tagStart + beforeAttrs.length;
newText = ' method=""';
}
Comment on lines 2032 to 2040
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The newText variable is assigned the same value ' method=""' in both branches of the if-else statement. You can avoid this duplication by declaring and initializing newText as a constant before the conditional block. This improves code readability and maintainability by making it clear that the text to be inserted is always the same.

  // Calculate insertion position
  const newText = ' method=""';
  let insertPosition: number;

  if (existingAttrs.trim()) {
    // There are existing attributes, add method after them
    insertPosition = tagStart + beforeAttrs.length + existingAttrs.length;
  } else {
    // No existing attributes, add method right after "form"
    insertPosition = tagStart + beforeAttrs.length;
  }


const edit: TextEdit = {
range: {
start: document.positionAt(insertPosition),
end: document.positionAt(insertPosition),
},
newText: newText,
};

trace(
`[DEBUG] createFormMethodRequireFix: Will insert "${newText}" at position ${insertPosition}`,
);

const workspaceEdit: WorkspaceEdit = {
changes: {
[document.uri]: [edit],
},
};

return {
title: 'Add method="" attribute to form',
kind: CodeActionKind.QuickFix,
edit: workspaceEdit,
isPreferred: true,
};
}

/**
* Create auto-fix actions for supported rules
*/
Expand Down Expand Up @@ -2064,6 +2166,10 @@
trace(`[DEBUG] Calling createAttrNoDuplicationFix`);
fix = createAttrNoDuplicationFix(document, diagnostic);
break;
case "form-method-require":
trace(`[DEBUG] Calling createFormMethodRequireFix`);
fix = createFormMethodRequireFix(document, diagnostic);
break;
default:
trace(`[DEBUG] No autofix function found for rule: ${ruleId}`);
break;
Expand Down
1 change: 1 addition & 0 deletions htmlhint/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All notable changes to the "vscode-htmlhint" extension will be documented in thi
### v1.14.0 (2025-11-26)

- Add autofix for the `attr-no-duplication` rule
- Add autofix for the `form-method-require` rule

### v1.13.0 (2025-11-25)

Expand Down
1 change: 1 addition & 0 deletions htmlhint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The extension provides automatic fixes for many common HTML issues. Currently su
- **`button-type-require`** - Adds type attribute to buttons
- **`doctype-first`** - Adds DOCTYPE declaration at the beginning
- **`doctype-html5`** - Updates DOCTYPE to HTML5
- **`form-method-require`** - Adds empty method attribute to forms
- **`html-lang-require`** - Adds `lang` attribute to `<html>` tag
- **`meta-charset-require`** - Adds charset meta tag
- **`meta-description-require`** - Adds description meta tag
Expand Down
Loading