Skip to content

Changelog

What changed in each release of Ruby Templates, newest first. Every entry describes what you notice in the editor or on the command line: new checks and their messages, formatter and highlighting changes, Rails navigation and completion, and the fixes that came with them. The same notes ship with the extension as CHANGELOG.md.

0.14.1

  • A11y/GenericLinkText reports a Slim link with inline text (a href="/users" Read more) when a blank line follows it; before, the blank line was taken for a continuation line and the link was skipped.

0.14.0

  • Layout/AttributesPerLine is a formatter rule for ERB: a start tag that stands first on a line longer than max_line_length (default 100, counted like Layout/LineLength) gets one attribute per line, one indent level in, with the > or /> after the last attribute. It is off until enabled with [Layout.AttributesPerLine] and enabled = true in .ruby-templates.toml. Tags with fewer than two attributes, tags already over several lines or not first on their line, tags with an ERB tag between attributes or in a name, <% control flow in the tag, values over several lines and tags inside <pre> or <textarea> are left alone. Lines are measured after the re-indentation, the formatter reads every tag again and keeps the rewrite only when nothing but whitespace between attributes differs, and Format Selection leaves the rule out because it adds lines.
  • Rails/I18nMissingKey warns about a lazy translation key (t(".title"), translate, with or without parentheses) that the default locale has no entry for in config/locales, on the key, with the closest key of the view within two edits. The full key follows Rails (users/_form gives users.form.title); the default locale is config.i18n.default_locale or en. Absolute keys, keys with default:, scope: or locale:, interpolated keys, views named for a gem in the Gemfile.lock, templates with a locale in the name (show.de.html.erb) and the block of render layout: are not checked; with config.i18n.fallbacks a key in any locale counts. Nothing is reported without a config/application.rb or a locale file for the default locale, or when translations may come from elsewhere (Ruby locale files, I18n.load_path, I18n.backend, i18n-active_record). It runs in Rails projects under app/views, also in ruby-templates check, and is on by default.
  • Rails/UnknownRoute warns about a *_path or *_url call without a receiver that the app’s routes don’t define but that is within two edits of one (usr_path for user_path), offering that route; a name with no route near it may be a helper that can’t be seen. It needs the routes the editor loads with bin/rails runner, so ruby-templates check never reports it. Rails’ asset and polymorphic helpers, rails_*, turbo_*, omniauth_* and the like, Devise’s helpers with devise in the Gemfile.lock, views in a folder named for a gem, helpers the app defines or names with a symbol, routes declared with direct, calls under defined?, locals and calls on a receiver (main_app.users_path) are left alone; a bare name with no arguments is reported only when the template’s strict locals don’t list it. It runs in Rails projects under app/views and is on by default. Diagnostics are republished when the routes, helpers, controllers or route files change.
  • Stimulus/UnknownTargetOrAction warns about a static data-<controller>-target name that the controller’s static targets = [...] lacks and a data-action method (click->search#go, search#go, several descriptors, options such as :prevent, @window, key filters) that its class doesn’t define, on the name, with the closest one within two edits. It also reads data: { search_target: "…", action: "…" } in Haml and in the options of the tag, link, button and form helpers. A controller is read only when its identifier is exactly one file in app/javascript/controllers and the app registers its controllers where they can be seen (as for Stimulus/UnknownController); a base controller it extends is followed when imported by a relative path or controllers/…. Nothing is reported for a controller that extends a class that can’t be read (a package), has a static targets that is not a plain list of strings, defines members with computed names, decorators or Object.assign(…prototype…), has more than one class, or is only registered by name; connect, disconnect and initialize always count as methods. It runs in Rails projects under app/views, is on by default, and the controllers are read again when app/javascript or the importmap changes.
  • Html/NestedInteractive warns about an interactive element where HTML forbids it, on the inner element: a link or button inside a link or button (an <a> in an <a>, a <button>, <input> that isn’t hidden, <select>, <textarea>, <label>, <iframe>, <details>, <audio controls>, <video controls>, <img usemap>) and a <form> inside a <form>. link_to, tag.a, button_tag, form_with, form_for and form_tag blocks count as the element they make, and button_to is both a button and a form. ERB is read by matching start and end tags and do … end blocks, Haml and Slim by indentation. Elements started and ended in different branches of an if, partials rendered inside the element, a layout’s yield, content of <svg>, <math> and <template>, and a <label> around an <input> are left alone. It runs in any ERB, Haml or Slim template that outputs HTML and is on by default.

0.13.0

  • Html/ImgAlt and Html/DuplicateId run in Haml and Slim too. %img, img, and image_tag or tag.img without alt: are reported; so is a second static #id or id:. Ids in different branches of an - if/- else, and ids where only one sits in a conditional, a loop or a block, are left alone. ERB is unchanged.
  • Html/DuplicateAttribute warns about an attribute written twice on one tag: at the second one in ERB start tags, where the page gets both, and at the value that is dropped in Haml (%a{href: x}(href="y"), a key twice in the hash) and the literal options of Rails tag helpers, which render a key once. Slim’s own error for a repeated attribute stays Syntax/Template. class and id are left alone in Haml, Slim and the helpers, which merge them.
  • Html/AnchorHref warns about an <a> without href (ERB markup, %a, a), which is not a link and can’t be reached with the keyboard. link_to, an <a> with name, tabindex, :href/x-bind:href, an id and no text, an <a> inside <svg>, and one whose attributes may come from code that can’t be read are left alone.
  • A11y/IframeTitle warns about an <iframe> (also tag.iframe and content_tag :iframe) without title, aria-label or aria-labelledby (also as aria: { label: … }).
  • A11y/GenericLinkText warns about link text such as “Click here”, “here”, “read more” or “learn more” (any case, punctuation ignored; WCAG 2.4.9, technique F84) in an <a> that holds only text and in link_to "Click here", path. A link with aria-label, aria-labelledby or aria-describedby is left alone.
  • The new rules and the two above run in any ERB, Haml or Slim template that outputs HTML, in Rails projects or not, but not in .js.erb, .text.erb, .xml.haml and the like. They are on by default and appear in the disabledDiagnostics settings and under the new A11y department of .ruby-templates.toml.
  • Rails/UnsafeOutput warns about output that skips Rails’ HTML escaping: raw x, x.html_safe and safe_concat x on the method name, and ERB’s <%==, Haml’s != and Slim’s == on the marker, in Rails projects under app/views. It is on by default. A literal without interpolation, a t call with a _html key, and the result of sanitize, safe_join, strip_tags, json_escape, escape_javascript, link_to, content_tag, render and similar helpers are not reported, nor is <%== render … %>. raw x.to_json in a <script> is left alone; elsewhere (an attribute) it is reported. yield, content_for and provide are safe, escapers count only on ERB::Util, CGI, Rack::Utils and helpers, and text templates are skipped.
  • Style/ErbTrimMode warns when an ERB template uses both %> and -%>, at every closer of the kind used less (-%> when there are as many of each). Comments, <%% tags and closers with more on their line are not counted. It is off until enabled with [Style.ErbTrimMode] and enabled = true in .ruby-templates.toml.
  • Style/AttributeQuotes, Style/BooleanAttributes, Style/VoidTagClosing and Style/TagNameCase are formatter rules for the start and end tags of ERB templates (the last one for Slim too), each off until enabled. Style/AttributeQuotes writes attribute values in double quotes, or single with style = "single_quotes", and quotes bare values (data-controller=toggle); a value that holds the quote, spans lines or holds an output tag (<%=, whose helpers return text with quotes of their own), and a bare value with any ERB tag, stay. Style/BooleanAttributes turns disabled="disabled" and disabled="" into disabled for the boolean attributes of the HTML standard on the elements they belong to; disabled="false", hidden="until-found", values with ERB, custom elements and <svg> and <math> stay. Style/VoidTagClosing writes void elements as <br> (style = "no_slash") or <br /> ("slash"), and leaves a slash that belongs to an unquoted value or follows an ERB tag, and does not add one after a bare value that ends in an ERB tag. Style/TagNameCase lower-cases tag names (also in the end tags of <script>, <style>, <textarea> and <title>), with the camel-case SVG elements (linearGradient, foreignObject, clipPath) spelled as the SVG standard has them; in Slim a name that would become doctype or an embedded engine, and .xml.slim templates, stay. Like Turbo/PreferTurboAttributes, they run first and the formatter re-reads every tag (Slim: its parsed output), keeping the rewrite only when nothing but the intended change differs; the line count never changes, so Format Selection applies them to its lines.
  • Style/ImplicitDiv, Style/ClassesBeforeIds and Style/HashAttributes are formatter rules for the tags of Haml (Style/ImplicitDiv also for Slim), each off until enabled. Style/ImplicitDiv drops the div before a class or id (%div.card becomes .card, div#main becomes #main); a div with only attributes, %DIV, and a name in another case stay. Style/ClassesBeforeIds writes the classes of a Haml tag before its id (%p#intro.lead becomes %p.lead#intro), each kept in order; Haml renders the same either way. Style/HashAttributes writes attributes as a Ruby hash (style = "ruby_hash", the default: %a(href="/" checked) becomes %a{href: "/", checked: true}) or in HTML style (style = "html"), when every attribute is a string without escapes or #{}, a number, true or a variable; a tag with both syntaxes, a nested hash or splat, other expressions and attributes over several lines stay. They run before the other rules and re-read the result (Haml: the Ruby it generates and each tag’s attributes; Slim: its parsed output), keeping it only when nothing but the intended change differs; each rewrite stays on its line, so Format Selection applies them to its lines.
  • Layout/LineLength warns about a line longer than max characters (100 by default) in ERB, Haml and Slim, from the first character past the limit. Characters are counted, a tab is one, and trailing whitespace is not counted. A line with no space after its indentation (a long URL) and the # locals: comment of strict locals are skipped. It is off until enabled with [Layout.LineLength] and enabled = true in .ruby-templates.toml, and can be disabled in disabledDiagnostics like the other warnings.
  • Layout/ElementContentOnOwnLine is a formatter rule, off until enabled, that moves the content of an element written next to its tags to a line of its own: <div class="card"><%= name %></div> becomes three lines, Haml %section= x and %div text and Slim section = x and div text (written | text) put the content below the tag. The tags option lists the elements (["div", "section", "article", "header", "footer", "ul", "ol"] by default; the first list option a rule takes). It adds whitespace to the page, so pre, textarea, script, style and everything inside them or inside an inline element such as <span> stay as they are, and so do tags with ERB control flow, Haml’s <, > and /, Slim’s div: span x, content that would be read as something else on a line of its own, and tags with lines nested under them. The formatter reads the result again and keeps it only when nothing but whitespace is new: ERB must keep every character and element, Haml must generate the same Ruby with the content one level below its tag, Slim must compile to the same template. Format Selection leaves the rule out, since it adds lines.

0.12.0

  • Rails/SilentRender warns about a render in a tag that outputs nothing (<% render "x" %>, - render "x"), also in its block form, since the partial is thrown away. The quick fix “Output the result” makes the tag <%= (= in Haml and Slim) when the call is alone in its tag or line. The three rules run in Rails projects only, under app/views. Not reported when the result is assigned or passed on, or when the call is the last statement of a block whose value the helper around it may use.

  • Rails/SilentHelper warns about a Rails helper that only returns markup (link_to, button_to, image_tag, content_tag, tag.div, form_with, number_to_currency and the like) called in a tag that outputs nothing, with the same quick fix. Helpers with side effects (content_for, provide), helpers of your own and calls on a receiver (form.fields_for) are left alone.

  • Rails/DebugOutput warns about p, pp, puts and debug with arguments, byebug, debugger, binding.irb and binding.pry left in a view, in output and in statement tags. A local variable named p, form.p, a call used as an argument and a call under an if or unless that tests Rails.env are not reported.

  • Rails/OutputControlFlow warns about <%= if x %>, where <% if x %> is meant: an ERB output tag that opens if, unless, while, until, case, for or begin without closing it, or starts with else, elsif, when, end, rescue or ensure. The quick fix removes the =. The Ruby syntax errors it caused are no longer shown for it; other errors in the template are. <%= if a then b else c end %> and <%= x if y %> are not reported.

  • Rails/InstanceVariableInPartial warns about the first read of each @variable in a partial, which works only for the controllers that set it; pass it as a local. Not reported for variables the partial assigns itself, defined?(@x), ActionView’s own and partials under app/views/layouts.

  • Rails/UnusedStrictLocal warns about a local in the locals: comment (ERB, Haml and Slim) that the partial never uses, on the local’s name, with a quick fix that removes it. local_assigns[:x], local_assigns.fetch(:x), binding.local_variable_get(:x) and defined?(x) count as use, and nothing is reported when local_assigns, binding, local_variables or eval can reach every local.

  • Rails/UnusedBlockArgument warns about a plain block parameter the block never uses, however many tags the block spans, with a quick fix that prefixes it with _. Names starting with _, it, destructuring, *rest, keyword and &block parameters and blocks that call binding, local_variables or eval are left alone.

  • Rails/RenderInLoop warns about collection.each do |x| (or each_with_index) whose whole body is render "partial", x: x and suggests render partial: "partial", collection: collection, as: :x. A loop with a wrapping element, other code, more locals or another iterator is not reported.

  • Turbo/PermanentRequiresId warns about an element with data-turbo-permanent and no id, on the attribute, in ERB start tags, Haml and Slim tags, and in the literal options of tag.div, content_tag, link_to and the form and button helpers (data: { turbo_permanent: true }). An id of any kind (#id, %div[@user]) satisfies it. Nothing is reported where the attributes may come from code that can’t be read (ERB inside the start tag, **attrs, *attrs, data: attrs, a value that isn’t a literal).

  • Stimulus/UnknownController warns about a name in a static data-controller (also data: { controller: "…" } in Haml and in the options of the tag, link, button and form helpers) that no controller has, with the closest one within two edits (“did you mean search?”). Controller files in app/javascript/controllers (.js, .mjs, .jsx, .ts, .tsx) and the literal names that scripts in app/javascript register with application.register("name", …) count. Nothing is reported without an app/javascript/controllers folder, when controllers are loaded from another folder (eagerLoadControllersFrom("components", …), a context or glob outside the controllers folder, an importmap pin) or registered under a computed name, or for a name with ERB or interpolation.

  • Turbo/PreferTurboAttributes is a formatter rule, off until enabled, that turns the Rails UJS attributes into Turbo’s: data-method into data-turbo-method and data-confirm into data-turbo-confirm on links that send a method, and data-disable-with into data-turbo-submits-with on submit buttons, in markup and in data: { confirm:, method:, disable_with: } of link_to, tag, content_tag, submit_tag, button_tag, button_to and submit/button on a form builder (turbo_confirm:, turbo_method:, turbo_submits_with:). It is the one formatter rule that changes the page: the new attributes work only in an app that runs Turbo. A confirm on a link without a method, button_to’s confirm, link_to … method: and dynamic keys are left alone. The renames are made first, and a file is only changed when every element has the same attributes but the renamed ones; the formatter’s other checks then apply to the renamed text. Format Selection applies it to the lines it covers.

0.11.0

  • A .ruby-templates.toml configures the project: [Html.ImgAlt] enabled = false turns a rule off, include/exclude globs limit it to some files, a department table ([Html]) switches its rules, exclude leaves files out of every check and the formatter, and [erb], [haml], [slim] and [jbuilder] take the format.enabled, format.align_comments, diagnostics.enabled, disabled_diagnostics and extra_engines settings. The nearest file wins key by key, root = true stops the search, and the file wins over the VS Code settings. Problems in the file show on it in the Problems panel and in the output, and make ruby-templates check exit with 1.
  • ruby-templates check and format read the .ruby-templates.toml files too: rules turned off, excluded files, [erb] format.enabled = false and format.align_comments.
  • Diagnostic codes are rule names now: Html/ImgAlt instead of img-missing-alt, Rails/MissingPartial instead of unresolved-render, Syntax/Ruby instead of ruby-syntax, and so on (the diagnostics reference lists each earlier code). The old codes are still accepted in disabledDiagnostics, in the settings and in the file.
  • The ERB and Haml formatting providers are always registered; the server returns no edits where formatting is off by format.enabled or a .ruby-templates.toml, so a file can turn it on or off against the setting.
  • Layout/EmptyLines cuts runs of blank lines longer than max (1 by default) in ERB, Haml and Slim; it is off until [Layout.EmptyLines] enabled = true. Blank lines in <pre>, <textarea>, <script>, <style>, attribute values, comments and ERB tags, in Haml filters and in Slim text blocks and engine bodies stay, and Haml and Slim files are only changed when they compile to the same template without regard to line numbers.
  • Layout/TrailingEmptyLines is a rule now: on by default for Haml and Slim, as before, and [Layout.TrailingEmptyLines] enabled = false keeps the end of the file as it is. It is off for ERB, whose templates and partials Rails renders byte for byte, so a final line break or trailing blank lines would show in the page; enabled = true (with include for some files) gives ERB files exactly one final line break. Format Selection changes no blank lines.
  • Layout/EmptyLinesAroundBlockBody, Layout/EmptyLinesAroundElementBody and Layout/EmptyLinesAroundBranches set the blank lines at the start and end of an ERB/Ruby block body, of an element body, and before and after else, elsif, when, in, rescue and ensure, in ERB, Haml and Slim. Each is off until enabled and takes style = "no_empty_lines" (the default) or "empty_lines" (exactly one blank line there, added where missing). The end of a body is the closing tag or <% end %>, so Haml and Slim have only the start of a body and the branch lines; the blank lines before a dedent stay. An empty body loses its blank lines under no_empty_lines and gets none under empty_lines. Format Selection leaves all of them alone. Layout/EmptyLines max is applied afterwards, so max = 0 wins over empty_lines. The same places stay as they are as for Layout/EmptyLines, and Haml and Slim files are only changed when they compile to the same template.
  • Layout/SpaceInsideErbTag, Style/ErbCommentSyntax and Layout/SpaceAfterScriptMarker are formatter rules, each off until enabled. The first gives the ERB tags that sit on one line (<%=x%>, <%= x %>, <%-x-%>) one space inside the delimiters; comments, <%% literals and tags over several lines stay, and the “Add spaces inside this ERB tag” code action and source.erbSpacing work as before. The second turns <% # note %> into <%# note %> when the tag is only comment (every line of it, if it has several) and stands alone on its line; beside other content Rails comments out what follows a <% # tag, so those, <%= # x %> stays and <%- # x -%> is skipped to be conservative. The third adds a space after -, =, ~, !=, &=, !~ and &~ that start Ruby in Haml (-#, == and \= lines are text) and after -, = and == in Slim, at the start of a line and after a tag (%p=x, p=x); the first line of a multi-line Haml | script, code after a multi-line attribute hash and Slim’s p: =x are skipped. They run before the re-indentation and also in Format Selection, for the lines it covers. A file is only changed when the Ruby of every ERB tag stays the same, and when Haml or Slim compile it to the same template; otherwise the spacing is kept as it was and --verbose says so.
  • Layout/SpaceInHtmlTag is a formatter rule for ERB, off until enabled: in start and end tags it makes a run of blanks between the tag name and the attributes, or between attributes, one space, and removes blanks around = and before > (<div class = "a" > becomes <div class="a">). Runs that contain a newline, quoted and unquoted values, ERB tags (the space next to one is never added or removed), the bodies of <script>, <style>, <textarea>, <title>, <xmp>, <iframe>, <noscript>, <noembed>, <noframes> and <plaintext>, comments, CDATA and text such as a < b stay. The result is kept only when every tag has the same name, attributes and values, and it applies to Format Selection too.
  • Layout/IndentationStyle writes ERB indentation with tabs: [Layout.IndentationStyle] style = "tabs" (or "spaces"). Each indent level the formatter writes is one tab; <pre>, <textarea>, <script>, <style>, <xmp>, <listing>, Ruby in tags, attribute values and comments keep every byte, and the continuation lines of a multi-line tag keep their indentation relative to the first line as written. Without style, the editor’s insertSpaces decides, then .editorconfig’s indent_style, then spaces; enabled = false always writes spaces. Haml and Slim are unchanged. The rule is on for ERB, so a file that asks for tabs without the toml now gets them where it used to be re-indented with spaces: a tab-indented ERB file when VS Code’s editor.detectIndentation turns insertSpaces off for it (the extension sets editor.insertSpaces: true for [erb], so a global tabs preference doesn’t reach ERB), with the EditorConfig extension, or with an [erb] user setting editor.insertSpaces: false. On the command line and in CI, a project .editorconfig with indent_style = tab makes ruby-templates format --check fail until the views are reformatted. [Layout.IndentationStyle] style = "spaces" restores the old behaviour.
  • Layout/TrailingWhitespace is a rule now, on for ERB, Haml and Slim as before; [Layout.TrailingWhitespace] enabled = false keeps the whitespace at the line ends, whitespace-only lines included.
  • rust-version of the server is 1.85, for the TOML parser.
  • <a href=x/ >y</a> is no longer read as a self-closing tag, which made Html/UnexpectedCloseTag report </a> and left the file unformatted: only a / right before the >, outside an unquoted value, self-closes, as in the HTML tokenizer. The close-tag completion follows the same rule.
  • Html/ImgAlt and Html/DuplicateId read a quoted string after an ERB tag in an attribute value (class=<% if x %>"a id=b"<% end %>) as part of that value, instead of reading the words in it as attributes.
  • Find References, renaming a strict local and renaming or moving partials read each Slim file with the extra_engines of its own .ruby-templates.toml (or the editor setting), not those of the current file.

0.10.1

  • The strict-locals check also runs when a render matches several files (a variant next to the base file, or two handlers): a local is reported missing when every file requires it and unknown when none takes it, and a file without a locals: comment still skips the call. With actionview 8 or newer the message names the first file and the count of the others (for _badge.html.erb and 1 other). “Declare in the locals” adds the local to all of the files.

0.10.0

  • Documentation now lives at https://x-ror.github.io/vscode-ruby-templates/: guides, examples in ERB, Haml and Slim, a reference of every diagnostic code with an example and the exact message, and this changelog. The README is a short overview.
  • Extract Selection to Partial works the same in ERB, Haml and Slim: the partial and the render call are one edit (a single undo, and a failed create leaves the template alone), the partial keeps the template’s line endings (CRLF files no longer get an LF partial), an existing partial is overwritten only after confirming, the name must be a Ruby identifier optionally prefixed by folders (shared/card), and the render path is qualified (render "users/card") in Slim too. A name that points at the partial being edited is refused.
  • New liquid language for .liquid and .html.liquid files: highlighting of {{ }} outputs (filters, strings, numbers, ranges, operators), {% %} tags, {% comment %} and {% # %} comments, {% raw %} and multi-line {% liquid %} tags over HTML, JavaScript and CSS, plus comment toggling, auto-closing and indentation. The server accepts these files and answers nothing for them (no Rails lookups, checks or formatting). Plain .html files are not claimed; map Jekyll includes with files.associations.
  • Slim completion offers the same HTML tags and attributes as ERB and Haml; 32 tags such as <kbd> and <wbr> were missing.
  • The outline leaves out a render behind a Ruby comment (<% # render "old" %>, Slim - # render "old"), as Haml’s already did, and keeps a render that follows a comment line inside a multi-line ERB tag.
  • When a render passes a key twice that the partial’s strict locals don’t take, the quick fix offers one Remove or Declare action instead of one per copy.
  • A Ruby syntax error at the very end of an ERB code tag is marked at its place instead of over the whole line.
  • An app checked out under a folder named controllers no longer loses partial references and renames from its helpers and models.
  • HTML checks read the content of <title> as text, and custom elements such as <title-bar> or <style-guide> as markup: before, everything up to their end tag was skipped by the checks and by ERB completion.
  • A view created or deleted while the server lists app/views is no longer missed until the next change.
  • Routes are read for an app whose whole bin/ folder was moved into the workspace, without reloading the window.

0.9.0

  • Bare-name render checks (render "post") are also skipped when a module the controller includes (found by Zeitwerk naming in app/*, app/*/concerns or lib, and the modules it includes) mentions prepend_view_path, _prefixes or another lookup change, or when an installed gem has app/views/<controller folder> (or its gems aren’t installed), so they no longer warn where Rails finds the view elsewhere.
  • .editorconfig sections follow the EditorConfig core tests: a glob without / matches in any folder, /**/ also matches one / ([app/**/*.erb] applies to app/x.erb), unmatched or escaped brackets and braces are literal instead of disabling the section, and numeric ranges don’t match leading zeros. A range inside an untaken brace branch no longer crashes the formatter.
  • render "blog/post" finds partials and templates in the app’s other view roots, as Rails does: the Rails root’s app/views first (so the app overrides an engine), then the file’s own root, then the engines’, packs’ and components’ */app/views under the Rails root by path (not a test’s test/dummy or spec/dummy), then the views of installed gems that define a Rails::Engine (Devise, Administrate, …). Links, go to definition, hover and the missing-partial warning use it, and completion of dir/name also offers the in-repo engines’ partials (gem views are left out: they can be many). The roots are found off the server task, kept until a view, Gemfile.lock or app file is created or deleted, and found again then. Files in a gem’s install folder get no render warnings or rename edits (syntax diagnostics still show). Renaming or finding renders stays inside the root of the file. Bare names (render "post") and prepend_view_path folders are not followed yet.
  • New erb-percent language id for plain-Ruby ERB with trim_mode: "%": a line starting with % is highlighted as Ruby and %% as an escaped %. It is opt-in through files.associations and nothing gets it by default, because Rails views (Erubi) read % complete as text. The server compiles % lines as Ruby for that id only, so mixed % if / <% else %> / % end templates give no false errors and errors in a % line are reported on it; %% stays text. Preview and Show Compiled Ruby use stdlib ERB with trim_mode: "%-" for it. Only diagnostics and the preview know % lines: no completion or definition inside them, no formatting and no HTML checks; the command line checker stays Erubi-only.
  • Ctrl+/ (Cmd+/) on lines inside a multi-line ERB code or output tag, indented or not, toggles a Ruby # comment (common indent kept) instead of wrapping the line in <%# %>; the <% and %> lines and everything outside a tag toggle as before.
  • Semantic highlighting for the Ruby locals the grammars can’t tell apart, in ERB, Haml and Slim: uses of strict locals (user after <%# locals: (user:) %>; the declaration in the comment is left alone) get the parameter token type, and block variables (|item|) and their uses the variable type. Only those identifiers get a token, so the TextMate colours of everything else stay; turn it off with editor.semanticHighlighting.enabled. Strict locals are plain calls in Slim, which has no locals: comment, so only its block variables and assignments are coloured.
  • Comment toggling follows the host of an ERB file: // in .js.erb and .scss.erb, /* */ in .css.erb, # in .rb.erb and .yml.erb, <!-- --> in .xml.erb and .md.erb. The README lists the file types, language ids (for [erb-js] settings) and what works in each.
  • Hover on an app helper (format_price from app/helpers, current_user from a controller’s helper_method) shows its def line with the parameters (a wrapped list joined), a link to where it is defined, and the # comment block above it, in ERB, Haml and Slim. Rails helpers keep their API docs, and a block variable or local of the same name shows nothing.
  • Instance variables are found in views of a folder without a controller (shared/, layouts/, application/-less partials): they get those ApplicationController sets in before_actions that run for every action (no only:, except:, if: or unless:), and mailer layouts and *_mailer folders get ApplicationMailer’s. Hover, F12 and @ completion work on @current_user in shared/_nav.html.haml. Callbacks any controller skips with skip_before_action, those nested in with_options/if blocks, and pwa/ and rails/ views are left out. Folders with a controller are unchanged.
  • Hover and go to definition on a model column of a record the controller makes plain to see: report.title in @reports.each do |report| (also map, each_with_index, select, … and brace blocks) when @reports is Report.order(:title), Report.where(…), …, and @report.title when @report is Report.find(…), find_by, new, first, …. Hover shows the type, null: and default: (`reports.title` — string, null: false), and F12 opens its line in db/schema.rb, in ERB, Haml and Slim. The table is Rails’ name for the model (Admin::Report → admin_reports, else reports) or its self.table_name. Method calls, conditionals and assignments that disagree answer nothing.
  • ERB tag checks keep going past a conditional wrapper with branches (<% if link %><a><% else %><span><% end %> … <% if link %></a><% else %></span><% end %>) and past one written with a negated condition (<% if !admin %><div><% end %> … <% unless admin %></div><% end %>); before, they stopped silently there. if !x, if not x and unless x count as the same condition.
  • An end tag with no open element of its name no longer ends the tag checks of the file: it is reported and dropped, as browsers do, and the rest is still checked. A heading end tag (<h3>…</h2>) closes the open heading. At most 20 tag diagnostics are reported per file.
  • render "uesrs/card" is flagged when no view folder of that name exists in the app, its engines and packs under the Rails root, or the installed gems that could ship views (found in vendor/bundle, .bundle/config’s BUNDLE_PATH, BUNDLE_PATH, GEM_HOME and GEM_PATH). Nothing is flagged when a gem that could ship views isn’t found there, when the app’s code adds view paths (prepend_view_path, a views folder inside app/ other than app/views), or when the app has a local engine or path gem with views.
  • Moving a partial directly into app/views rewrites its render calls to /card, the name Rails finds it by (the slash drops the controller’s folder prefixes); before, they were left pointing at the old place. render "/card" also resolves to that partial, and so does render "/users/card" for app/views/users/_card.html.erb: before, the leading slash pointed outside app/views, so those renders were not linked, renamed or checked.
  • render partial: and render template: with literal formats:, variants: or handlers: go to the file Rails picks: formats: [:json] finds _card.json.jbuilder from an HTML view, variants: [:phone] puts _card.html+phone.erb first, handlers: [:haml] keeps only .haml. The options may precede the name (render formats: [:json], partial: "card"), sit on later lines or before a block, and the same choice drives links, go to definition, hover, the missing-partial warning and its create-partial quick fix (which makes _card.json.erb, _card.html+phone.erb or a .haml file to match). In render "card", formats: [:json] the hash is the partial’s locals, as in Rails, so nothing changes there.
  • An untitled ERB document counts as HTML ERB, as Slim already did: it gets the HTML checks, folding, formatting and attribute completion. Rails lookups on documents that aren’t files answer nothing instead of erroring, now covered by a test for ERB, Haml and Slim.
  • Link tooltips (Cmd/Ctrl+click on a partial, Stimulus controller, asset or layout) and the partial list in render hovers show the path from the Rails root (app/javascript/controllers/hello_controller.js) instead of the full path on disk.
  • Attribute completion knows the Stimulus controllers of the element being written: after data-controller="search" it offers data-search-target, and data-search-<name>-value, -class and -outlet for the names in the controller’s static values, classes and outlets, in ERB, Haml (data-… attributes and data: hash keys) and Slim. Slim also completes the Turbo attributes (data-turbo-method, data-turbo-confirm, …).
  • Go to definition on a block variable (|user|, |(a, b)|, an outer block’s variable) or a local the template assigns (<% total = … %>) jumps to where it is declared, in ERB, Haml and Slim.

0.8.1

  • Instance variables from a parent controller are found when a controller inside module Admin names it without the module (class ReportsController < BaseController), as Ruby resolves it.
  • Completing a helper whose call already has arguments (link_to user.name, path, link_to() inserts only its name, not the argument snippet in front of them.
  • A stray <% end %> in ERB is reported on the end itself, not on the last line of the file.
  • Slim formatting collapses the blank lines at the end of a file in one save: also after a | text block or a / comment, and when the last line holds only the indentation the editor put there.

0.8.0

  • Rename a partial’s strict local with F2: the locals: comment (and the defaults that read it), every read and assignment in the partial and the keys of the render calls that pass it change together, in ERB, Slim and Haml. A name the partial already uses, or a Ruby keyword, is refused, and a warning lists the render calls whose locals can’t be read. Find all references on the local lists the same places.

0.7.0

  • macOS builds are back: a .vsix for Apple silicon and Intel Macs, and the ruby-templates npm package for both.
  • Quick fixes on strict-locals warnings: pass the missing locals to the render call, or remove an unknown local from it or declare it in the partial’s locals: comment.

0.6.0

  • Hover on a local in a partial with strict locals shows whether it is required or its default, and how many render calls pass it.
  • The strict-locals check reads the same render calls as partial-local completion: :locals => { and :a => 1 keys, and render partial: with other options such as layout: next to locals:.

0.5.0

  • Hover on @user in a view lists where its controller assigns it (the action and its before_action callbacks), with the code of each assignment, in full when it continues on later lines, and a link to it.
  • Partial-local completion also in render :partial => "card", :locals => { and Jbuilder’s json.partial! "card", locals: {.
  • Strict-locals checks, partial-local completion and go to definition from a partial’s local to its callers also read render calls whose arguments continue on later lines, such as user: u on the line after render "card",.

0.4.0

  • Completion of block variables: inside @users.each do |user|, us offers user in ERB, Slim and Haml, and the locals assigned in the block too.
  • Completion of a partial’s locals in render "users/card", and locals: {: the names from its locals: comment that the call doesn’t pass yet, inserted as user: .

0.3.2

  • Faster, with the same results:
    • formatting skips the checks whose answer is already known, so format --check on an already formatted app is up to 8 times faster (freeats’ Slim views: 0.50 s to 0.06 s);
    • ERB folding and outline analyse each tag once;
    • Slim and Haml templates are parsed and compiled with far fewer allocations;
    • completion and code actions read the project’s Gemfile.lock and initializers only when they need them, at most once per request;
    • the app/views file list and the locale files are read in the background after a view opens or files change, so the first partial or translation completion doesn’t wait for them;
    • f.text_field : completion finds the table in db/schema.rb faster.

0.3.1

  • Faster in large apps, with the same results:
    • the language server keeps diagnostics, formatting, outline, folding and helper lookups off the thread that answers requests, so completion and hover no longer wait behind them, and it publishes only a document’s latest diagnostics;
    • ruby-templates check loads each app’s Gemfile.lock and initializers once and checks files in parallel: GitLab’s views take 0.12 s instead of 1.5 s;
    • ERB tags are found without a regular expression, so ERB checks run up to twice as fast and the tag lookups behind code actions about 20 times faster;
    • render checks resolve each partial once per file, and find references, i18n references, route and translation completion and translation hover skip what can’t match.
  • ruby-templates check no longer panics when its output is closed early (check app | head).
  • Go to definition and slots find components with acronyms (UI::ButtonComponent in ui/button_component.rb, HTMLCardComponent in html_card_component.rb).
  • Haml: - café = if x no longer hides the Got "else" with no preceding "if" error after it, and block balance in ERB, Haml and Slim no longer finds a keyword inside an identifier that starts with a non-ASCII letter (éif).
  • Templates with thousands of unclosed <%, void elements with content, <script>/<style> bodies or repeated ids, and Slim attributes left open before many empty lines, check in linear time.

0.3.0

  • Haml: Ruby syntax errors (ruby-syntax) in -, =, !=, &= and ~ lines, code after a tag, {} attribute hashes, #{} in text, comments and () attribute values, and :ruby filters, as Prism finds them in the Ruby Haml generates for Rails. That includes a - rescue, - ensure, - in or second - else in the wrong place and lines nested under code that opens no block, which Haml accepts and only the generated Ruby rejects. They are checked once Haml itself accepts the template.
  • Slim: a Ruby error in code that follows closed markup (div / p / - x = foo(1,,2)) is underlined where it is, not at column 1.
  • Large templates check much faster. With a Ruby error on every line, 20,000 lines of Haml went from 102 s to 0.6 s, 20,000 lines of Slim from over 100 s to 0.6 s and 10,000 lines of ERB from 12 s to 0.24 s. A valid 20,000-line Slim file went from 3.7 s to 0.12 s.

0.2.0

  • ERB formatting: rubyTemplates.erb.format.alignComments (off by default; --align-comments on the command line) moves the lines inside HTML comments and multi-line <%# … %> comments with the line the comment opens on, keeping their relative indentation.
  • Haml, Slim, Jbuilder and Builder: the strict locals comment (-# locals: (…), /# locals: (…), # locals: (…)) gets the same ruby-syntax checks as ERB with actionview 7.1 or newer: a signature Ruby can’t parse, and Rails’ “a set as non-keyword argument for app/views/users/_card.html.haml. Locals can only be set as keyword arguments.” for locals that aren’t keywords.
  • Strict locals, all languages:
    • a syntax error in the signature is underlined where it is, not on the whole line;
    • the non-keyword message is on the line of locals:, also when the # is on the line above;
    • with actionview 7.1 to 8.0 the comment ends at the last ) on its line, as Rails’ older regexp has it, for the checks, completion and hover alike.

0.1.0

First release. Requires VS Code 1.100 or newer.

Languages

  • ERB (.erb, .html.erb, .rhtml, .html+variant.erb), Haml (.haml) and Slim (.slim, .skim), with host grammars for .js.erb, .css.erb, .json.erb, .text.erb, .xml.erb, .md.erb, .rb.erb and .yml.erb.
  • Erubi / Erubis (.erubi, .erubis) open as ERB, with the same diagnostics and navigation.
  • Jbuilder and Builder views (.jbuilder, .builder, kept on the ruby language): partial navigation, hover, completion and warnings for json.partial!, json.array! @xs, partial: "x" and xml << render(...).
  • Generator templates (*.erb.tt, *.slim.tt, *.haml.tt under lib/templates or lib/generators) open as ERB / Slim / Haml, without diagnostics or formatting.
  • Embedded Ruby, JavaScript, CSS, SCSS, Sass, CoffeeScript and Markdown inside filters and embedded blocks; language configuration (brackets, auto-closing pairs, comment toggling, indentation, folding, onEnterRules).
  • Rails-oriented snippets for all three languages, including Hotwire and ViewComponent.

Language server

  • Diagnostics for ERB, Haml and Slim, as the official implementations report them: Erubi 1.13.1 with Rails 8.1.4’s options, Haml 7.5.1 and Slim 5.2.2, with their messages and positions. Every diagnostic has a code; rubyTemplates.erb.disabledDiagnostics, rubyTemplates.haml.disabledDiagnostics and rubyTemplates.slim.disabledDiagnostics turn checks off, and rubyTemplates.<erb|haml|slim>.diagnostics.enabled (on by default) all of a language’s; Skim follows slim, and the ruby-templates command line is unaffected. A diagnostic’s source is the file’s language (erb, haml, slim; ruby for Jbuilder and Builder views), HTML and Rails checks included.
  • The extension runs no third-party linters or formatters: every check and the formatter are built into the server.
  • Ruby in ERB and Slim is checked with Prism (Ruby’s own parser) as Rails compiles the template, under ruby-syntax, with Ruby’s messages. ERB builds the method source Erubi generates (trim: true, @output_buffer, the locals: magic comment as the method’s parameters) and reports every error at the template line it comes from (exactly, when it is in a tag’s code), whatever else is wrong with the file: <%= if x %> gets expected `then` or `;` or '\n'. The grammar is the project’s Ruby, from the nearest .ruby-version, else the RUBY VERSION of the nearest Gemfile.lock, else the latest.
  • ERB: unclosed-tag warns about a <% with no %> after it in HTML templates, which Rails compiles as text but the HTML parser rejects (invalid-first-character-of-tag-name, HTML 13.2.5.6); it is found with Erubi’s own scan, and .text.erb or .js.erb templates get none.
  • ERB’s HTML checks for views cite the HTML standard: unexpected, mismatched and unclosed elements (“Unexpected end tag </span>: no open element of that name (HTML 13.2.6.4.7)”, “End tag </div> doesn’t close the open <span> (line 2)”, “<div> is never closed”), end tags of void elements (HTML 13.1.2) and a </p> after a block start tag closed the p (not inside button, object, template, table cells, SVG/MathML or where ERB branches disagree); img-missing-alt and duplicate-id quote the standard’s own sentence (4.8.4.4.1, 3.2.6). <caption> may omit its end tag, and <img generator-unable-to-provide-required-alt=""> is accepted, as the standard says.
  • Haml: indentation errors (Indentation can't use both tabs and spaces., Inconsistent indentation: …, The line was indented 2 levels deeper than the previous line., Indenting at the beginning of the document is illegal.; only the first is reported), illegal nesting on the nested line (content both on the line and nested, nesting in plain text, a self-closing tag, a tag that already has content, or a doctype), Illegal element: classes and ids must have values., Unbalanced brackets. and Invalid attribute list: … (unclosed-attribute), Invalid tag: … and There's no Ruby code for = to evaluate. (syntax-error), You don't need to use "- end" in Haml. (explicit-end), Invalid filter name … and FilterCompiler for 'nope' was not found, and - else/elsif/when placement (Got "else" with no preceding "if", "else" is indented at wrong level: …), all with Haml’s messages. Content in a void element (%br with children or text) is the warning void-children.
  • Slim: the first error Slim’s parser raises (Malformed indentation, Unexpected indentation, Expected closing delimiter ), Unknown line indicator, Illegal shortcut, Expected tag, …) at Slim’s line and column, Temple’s Multiple id attributes specified and Invalid doctype, and Explicit end statements are forbidden. Smart text (> text, slim/smart) is an unknown line indicator, as in Slim’s default parser.
  • Partials: go to definition, links, hover, completion, find references and rename for render "x", render partial:, template:, layout:, hash-rocket forms, render @user and render FooComponent.new. Partials resolve in the view’s format and variant, across symlinked folders, and with the formats an app registers in config/initializers (Mime::Type.register, register_alias), so _card.mobile.erb is the partial card. Unresolved renders and strict-locals mismatches are warnings with Rails’ own messages (Missing partial users/_card, missing local: :b, unknown keywords: :c, :d, no locals accepted, with for app/views/… from Rails 7.2), and a quick fix creates the missing partial.
  • Rails checks follow the project’s Gemfile.lock: unresolved-render runs when it lists actionview, strict-locals with actionview 7.1 or newer; Sinatra projects and folders without a lockfile get neither. Diagnostics refresh when Gemfile.lock changes.
  • Strict locals (<%# locals: (…) %>): completion, hover and diagnostics. With actionview 7.1 or newer, locals must be keywords: <%# locals: (a, b:) -%> gets Rails’ “a set as non-keyword argument for app/views/users/_card.html.erb. Locals can only be set as keyword arguments.” under ruby-syntax.
  • Controllers and helpers: go to definition from def show to its templates, from layout "admin" and render :edit to their views, and from a helper call to its def in app/helpers or a helper_method.
  • Instance variables: completion after @ and go to definition, from the view’s controller action and its before_action callbacks.
  • Route helpers: completion, hover and go to definition from the app’s routes (rubyTemplates.routes.enabled).
  • i18n: completion, hover, go to definition and find references for t("…") keys, from config/locales.
  • Rails helpers: completion with snippet arguments, form builder methods after f., model attributes after f.text_field : from db/schema.rb, Turbo Stream actions after turbo_stream., asset paths.
  • Stimulus: controller names in data-controller and data-action, action methods after hello#, and static targets names.
  • ViewComponent: class completion after render , with_* slot completion and slot readers, go to the component’s class and template.
  • HTML in ERB: element, closing tag and attribute completion; typing > closes the start tag (rubyTemplates.erb.autoCloseTags); linked editing of tag names; code actions that add spaces inside <%=foo%> tags.
  • Haml: completion of filters, tags, attributes and helpers; hover on filters and line prefixes. Slim: completion, hover and formatting through a port of Slim::Parser.
  • Outline, breadcrumbs, folding, and Go to Symbol in Workspace by render name.
  • Find references, file renames and the definition of a partial’s local scan the app on their own thread, so hover, completion and diagnostics answer while they run.
  • ruby-templates-ls --check runs the diagnostics from the command line.

Commands and formatting

  • Toggle ERB tag, wrap selection in ERB; toggle Haml -/=/-# and Slim line markers; wrap in = / #{}.
  • Extract selection to a partial (ERB, Haml, Slim).
  • Switch to Related File: a view’s other formats, its controller or mailer action, and a ViewComponent’s class, template and preview.
  • Preview rendered HTML and show compiled Ruby, through Erubi like Rails (rubyTemplates.commands.ruby), or inside the Rails app (rubyTemplates.preview.useRails).
  • Converters: ERB → Haml / Slim, Haml → ERB / Slim, Slim → ERB, HTML → Haml.
  • Command line: ruby-templates check and ruby-templates format [--check|--write] for CI and editors, shipped next to the server and on npm as @x-ror/ruby-templates with prebuilt binaries for Linux and Windows; ruby-templates-ls --check takes folders too.
  • Formatting: a built-in whitespace-only formatter for HTML ERB, Haml and Slim, on by default for Format Document and format on save, with Format Selection for ERB and Haml. rubyTemplates.<erb|haml|slim>.format.enabled (on by default) switches it off, and the extension then registers no formatter for that language, so another extension’s can be used.
  • The formatter takes only the indentation width from the editor or .editorconfig (default 2): it always writes spaces and keeps each line’s ending. ERB keeps the end of the file as it is and trims trailing whitespace of text lines; a file with an ERB error or an HTML structure error is left unchanged.
  • Haml and Slim formatting never changes what the template compiles: the formatter compares what Haml reads (nodes, depths, filter bodies) or the Ruby Slim::Engine generates, before and after, and leaves the file as it is if they differ, or if it has any Haml or Slim error. Empty lines at the end collapse into the final line ending; in Slim, whitespace-only lines there stay, as Slim compiles them.

Packaging

  • One .vsix per platform (Linux x64 / arm64, Windows x64; macOS builds are paused until GitHub’s macOS runners pick up jobs again), each with only its own server binary.

Development

  • bun test/oracle.js slim|haml|erb compares the diagnostics and formatter (also on perturbed copies of each file) with Slim 5.2.2, Haml 7.5.1 and Rails 8.1.4’s ERB, on their test suites and on public apps’ views (GitLab’s too, for Haml); bun test/oracle.js context compares the render checks with actionview 7.0 to 8.0 (test/oracle/README.md).