Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .clang-format-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ src/errors.c
src/include/ast_nodes.h
src/include/ast_pretty_print.h
src/include/errors.h
src/include/util/hb_foreach.h
src/parser_match_tags.c
src/visitor.c
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
exec = herb
test_exec = run_herb_tests

sources = $(wildcard src/*.c) $(wildcard src/**/*.c)
headers = $(wildcard src/*.h) $(wildcard src/**/*.h)
sources = $(shell find src -name '*.c')
headers = $(shell find src -name '*.h')
objects = $(sources:.c=.o)

extension_sources = $(wildcard ext/**/*.c)
Expand Down
38 changes: 38 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -426,13 +426,19 @@ nodes:
- name: tag_name
type: token

- name: HTMLVirtualCloseTagNode
fields:
- name: tag_name
type: token

- name: HTMLElementNode
fields:
- name: open_tag
type: node
kind:
- HTMLOpenTagNode
- HTMLConditionalOpenTagNode
- ERBOpenTagNode

- name: tag_name
type: token
Expand All @@ -446,6 +452,7 @@ nodes:
kind:
- HTMLCloseTagNode
- HTMLOmittedCloseTagNode
- HTMLVirtualCloseTagNode

- name: is_void
type: boolean
Expand Down Expand Up @@ -526,6 +533,37 @@ nodes:
type: node
kind: HTMLAttributeValueNode

- name: RubyLiteralNode
fields:
- name: content
type: string

- name: RubyHTMLAttributesSplatNode
fields:
- name: content
type: string

- name: prefix
type: string

- name: ERBOpenTagNode
fields:
- name: tag_opening
type: token

- name: content
type: token

- name: tag_closing
type: token

- name: tag_name
type: token

- name: children
type: array
kind: Node

- name: HTMLTextNode
fields:
- name: content
Expand Down
1 change: 1 addition & 0 deletions ext/herb/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

$VPATH << "$(srcdir)/../../src"
$VPATH << "$(srcdir)/../../src/analyze"
$VPATH << "$(srcdir)/../../src/analyze/action_view"
$VPATH << "$(srcdir)/../../src/util"
$VPATH << prism_src_path
$VPATH << "#{prism_src_path}/util"
Expand Down
6 changes: 6 additions & 0 deletions ext/herb/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ static VALUE Herb_parse(int argc, VALUE* argv, VALUE self) {
if (NIL_P(strict)) { strict = rb_hash_lookup(options, ID2SYM(rb_intern("strict"))); }
if (!NIL_P(strict)) { parser_options.strict = RTEST(strict); }

VALUE action_view_helpers = rb_hash_lookup(options, rb_utf8_str_new_cstr("action_view_helpers"));
if (NIL_P(action_view_helpers)) {
action_view_helpers = rb_hash_lookup(options, ID2SYM(rb_intern("action_view_helpers")));
}
if (!NIL_P(action_view_helpers) && RTEST(action_view_helpers)) { parser_options.action_view_helpers = true; }

VALUE arena_stats = rb_hash_lookup(options, rb_utf8_str_new_cstr("arena_stats"));
if (NIL_P(arena_stats)) { arena_stats = rb_hash_lookup(options, ID2SYM(rb_intern("arena_stats"))); }
if (!NIL_P(arena_stats) && RTEST(arena_stats)) { print_arena_stats = true; }
Expand Down
1 change: 1 addition & 0 deletions ext/herb/extension_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ VALUE create_parse_result(AST_DOCUMENT_NODE_T* root, VALUE source, const parser_
rb_hash_aset(kwargs, ID2SYM(rb_intern("strict")), options->strict ? Qtrue : Qfalse);
rb_hash_aset(kwargs, ID2SYM(rb_intern("track_whitespace")), options->track_whitespace ? Qtrue : Qfalse);
rb_hash_aset(kwargs, ID2SYM(rb_intern("analyze")), options->analyze ? Qtrue : Qfalse);
rb_hash_aset(kwargs, ID2SYM(rb_intern("action_view_helpers")), options->action_view_helpers ? Qtrue : Qfalse);

VALUE parser_options_args[1] = { kwargs };
VALUE parser_options = rb_class_new_instance_kw(1, parser_options_args, cParserOptions, RB_PASS_KEYWORDS);
Expand Down
2 changes: 1 addition & 1 deletion java/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ INCLUDES = -I. -I$(SRC_DIR)/include -I$(PRISM_INCLUDE) $(JNI_INCLUDES)
LDFLAGS = -shared
LIBS = $(PRISM_BUILD)/libprism.a

HERB_SOURCES = $(wildcard $(SRC_DIR)/*.c) $(wildcard $(SRC_DIR)/**/*.c)
HERB_SOURCES = $(shell find $(SRC_DIR) -name '*.c')
HERB_OBJECTS = $(filter-out $(SRC_DIR)/main.o, $(HERB_SOURCES:.c=.o))

JNI_SOURCES = herb_jni.c extension_helpers.c
Expand Down
8 changes: 8 additions & 0 deletions java/herb_jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ Java_org_herb_Herb_parse(JNIEnv* env, jclass clazz, jstring source, jobject opti
jboolean strict = (*env)->CallBooleanMethod(env, options, getStrict);
parser_options.strict = (strict == JNI_TRUE);
}

jmethodID getActionViewHelpers =
(*env)->GetMethodID(env, optionsClass, "isActionViewHelpers", "()Z");

if (getActionViewHelpers != NULL) {
jboolean actionViewHelpers = (*env)->CallBooleanMethod(env, options, getActionViewHelpers);
parser_options.action_view_helpers = (actionViewHelpers == JNI_TRUE);
}
}

hb_allocator_T allocator;
Expand Down
10 changes: 10 additions & 0 deletions java/org/herb/ParserOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public class ParserOptions {
private boolean trackWhitespace = false;
private boolean analyze = true;
private boolean strict = true;
private boolean actionViewHelpers = false;

public ParserOptions() {}

Expand Down Expand Up @@ -34,6 +35,15 @@ public boolean isStrict() {
return strict;
}

public ParserOptions actionViewHelpers(boolean value) {
this.actionViewHelpers = value;
return this;
}

public boolean isActionViewHelpers() {
return actionViewHelpers;
}

public static ParserOptions create() {
return new ParserOptions();
}
Expand Down
6 changes: 3 additions & 3 deletions java/snapshots/snapshot_tests/testParse.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions javascript/packages/core/src/parser-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface ParseOptions {
track_whitespace?: boolean
analyze?: boolean
strict?: boolean
action_view_helpers?: boolean
}

export type SerializedParserOptions = Required<ParseOptions>
Expand All @@ -10,6 +11,7 @@ export const DEFAULT_PARSER_OPTIONS: SerializedParserOptions = {
track_whitespace: false,
analyze: true,
strict: true,
action_view_helpers: false,
}

/**
Expand All @@ -25,6 +27,9 @@ export class ParserOptions {
/** Whether analysis was performed during parsing. */
readonly analyze: boolean

/** Whether ActionView tag helper transformation was enabled during parsing. */
readonly action_view_helpers: boolean

static from(options: SerializedParserOptions): ParserOptions {
return new ParserOptions(options)
}
Expand All @@ -33,5 +38,6 @@ export class ParserOptions {
this.strict = options.strict ?? DEFAULT_PARSER_OPTIONS.strict
this.track_whitespace = options.track_whitespace ?? DEFAULT_PARSER_OPTIONS.track_whitespace
this.analyze = options.analyze ?? DEFAULT_PARSER_OPTIONS.analyze
this.action_view_helpers = options.action_view_helpers ?? DEFAULT_PARSER_OPTIONS.action_view_helpers
}
}
10 changes: 10 additions & 0 deletions javascript/packages/formatter/src/format-printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ import {
ERBUnlessNode,
ERBYieldNode,
ERBInNode,
ERBOpenTagNode,
HTMLVirtualCloseTagNode,
XMLDeclarationNode,
CDATANode,
Token
Expand Down Expand Up @@ -1005,6 +1007,14 @@ export class FormatPrinter extends Printer implements TextFlowDelegate, Attribut
}
}

visitERBOpenTagNode(node: ERBOpenTagNode) {
this.printERBNode(node)
}

visitHTMLVirtualCloseTagNode(_node: HTMLVirtualCloseTagNode) {
// Virtual closing tags don't print anything (they are synthetic)
}

visitERBEndNode(node: ERBEndNode) {
this.printERBNode(node)
}
Expand Down
12 changes: 9 additions & 3 deletions javascript/packages/formatter/src/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { hasFormatterIgnoreDirective } from "./format-ignore.js"

import type { Config } from "@herb-tools/config"
import type { RewriteContext } from "@herb-tools/rewriter"
import type { HerbBackend, ParseResult } from "@herb-tools/core"
import type { HerbBackend, ParseResult, ParseOptions } from "@herb-tools/core"
import type { FormatOptions } from "./options.js"

/**
Expand All @@ -16,6 +16,7 @@ import type { FormatOptions } from "./options.js"
export class Formatter {
private herb: HerbBackend
private options: Required<FormatOptions>
private parseOptions: ParseOptions

/**
* Creates a Formatter instance from a Config object (recommended).
Expand Down Expand Up @@ -48,9 +49,10 @@ export class Formatter {
* @param herb - The Herb backend instance for parsing
* @param options - Format options (including rewriters)
*/
constructor(herb: HerbBackend, options: FormatOptions = {}) {
constructor(herb: HerbBackend, options: FormatOptions = {}, parseOptions: ParseOptions = {}) {
this.herb = herb
this.options = resolveFormatOptions(options)
this.parseOptions = parseOptions
}

/**
Expand All @@ -59,6 +61,10 @@ export class Formatter {
format(source: string, options: FormatOptions = {}, filePath?: string): string {
const result = this.parse(source)

if (result.options.action_view_helpers) {
console.warn("[Herb Formatter] Warning: Formatting a document parsed with `action_view_helpers: true`. The result may not be 100% accurate.")
}

if (result.failed) return source
if (isScaffoldTemplate(result)) return source
if (hasFormatterIgnoreDirective(result.value)) return source
Expand Down Expand Up @@ -104,6 +110,6 @@ export class Formatter {

private parse(source: string): ParseResult {
this.herb.ensureBackend()
return this.herb.parse(source)
return this.herb.parse(source, this.parseOptions)
}
}
74 changes: 74 additions & 0 deletions javascript/packages/formatter/test/erb/action-view-helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, test, expect, beforeAll } from "vitest"
import { Herb } from "@herb-tools/node-wasm"
import { Formatter } from "../../src"

import dedent from "dedent"

let formatter: Formatter

describe("@herb-tools/formatter - ActionView Helpers", () => {
beforeAll(async () => {
await Herb.load()

formatter = new Formatter(Herb, {
indentWidth: 2,
maxLineLength: 80,
}, {
action_view_helpers: true,
})
})

test("tag.div with multiline data hash preserves original source", () => {
const source = dedent`
<%= tag.div(
data: {
controller: "hello",
action: "click->hello#greet"
}
) do %>
<div>Hello</div>
<% end %>
`
const result = formatter.format(source)
expect(result).toEqual(source)
})

test("tag.div with simple attributes preserves original source", () => {
const source = dedent`
<%= tag.div class: "content" do %>
Content
<% end %>
`
const result = formatter.format(source)
expect(result).toEqual(source)
})

test("tag.div without block preserves original source", () => {
const source = `<%= tag.div class: "content" %>`
const result = formatter.format(source)
expect(result).toEqual(source)
})

test("content_tag with attributes preserves original source", () => {
const source = dedent`
<%= content_tag :div, class: "content" do %>
Content
<% end %>
`
const result = formatter.format(source)
expect(result).toEqual(source)
})

test("tag.div with data and aria splats preserves original source", () => {
const source = dedent`
<%= tag.div(
data: { controller: "hello", **data_attrs },
aria: { label: "greeting", **aria_attrs }
) do %>
<div>Hello</div>
<% end %>
`
const result = formatter.format(source)
expect(result).toEqual(source)
})
})
4 changes: 4 additions & 0 deletions javascript/packages/linter/test/parse-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ describe("ParseCache", () => {
track_whitespace: true,
analyze: true,
strict: true,
action_view_helpers: false,
})
})

Expand All @@ -80,6 +81,7 @@ describe("ParseCache", () => {
track_whitespace: true,
analyze: true,
strict: false,
action_view_helpers: false,
})
})

Expand All @@ -91,6 +93,7 @@ describe("ParseCache", () => {
track_whitespace: false,
analyze: true,
strict: true,
action_view_helpers: false,
})
})

Expand All @@ -102,6 +105,7 @@ describe("ParseCache", () => {
track_whitespace: true,
analyze: false,
strict: false,
action_view_helpers: false,
})
})
})
Expand Down
Loading
Loading