Skip to content

Linter: Implement html-no-nested-forms rule - #1

Closed
irinanazarova wants to merge 1 commit into
mainfrom
html-no-nested-forms
Closed

Linter: Implement html-no-nested-forms rule#1
irinanazarova wants to merge 1 commit into
mainfrom
html-no-nested-forms

Conversation

@irinanazarova

@irinanazarova irinanazarova commented Aug 2, 2026

Copy link
Copy Markdown
Owner

This pull request implements a new html-no-nested-forms rule that disallows placing one <form> element inside another <form> element, including forms rendered by Action View helpers.

<%= form_with model: @mission do |form| %>
  <%= form.submit "Update" %>
  <%= button_to "Delete", mission_path(@mission), method: :delete %>
<% end %>
`button_to` renders its own `<form>` element and cannot be nested inside another form. Move it outside of the enclosing form.

HTML does not support nested forms: browsers silently drop or re-parent the inner form, leading to broken submissions and buttons that submit the wrong form. The helper case is particularly easy to miss because the nested <form> never appears in the template. In the example above, the form rendered by button_to is dropped during parsing and clicking "Delete" submits the outer form instead, performing an update.

The rule detects literal <form> elements with a depth counter (same approach as html-no-nested-links) and form-rendering helpers via Prism (same approach as actionview-no-silent-helper). The helper list comes from the Action View helper registry via getHelpersForTag("form"), filtered to helpers the parser does not transform yet: once a form helper becomes supported, the element branch catches its synthesized <form> element and the Prism branch stops covering it automatically. button_to is included per the discussion in marcoroth#166, since it is effectively a hidden form. The offense is reported on the inner form's tag name, or on the helper call for helper-rendered forms.

Nesting composed across partials (an outer form in one template, an inner form in a rendered partial) is not detectable with per-file linting. tag.form and content_tag(:form) are not detected yet; the registry's detectStyle metadata encodes how to find these. This could be a follow-up.

Resolves marcoroth#166

@irinanazarova
irinanazarova force-pushed the html-no-nested-forms branch 2 times, most recently from a08c298 to f2da265 Compare August 2, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linter Rule: Disallow nested forms

1 participant