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
10 changes: 10 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,16 @@ errors:

fields: []

- name: DotNotationCasingError
message:
template: "Dot-notation component tags require the first segment to start with an uppercase letter. `%s` does not start with an uppercase letter."
arguments:
- segment->value

fields:
- name: segment
type: token

warnings:
fields: []
types: []
Expand Down
1 change: 1 addition & 0 deletions ext/herb/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
$VPATH << "$(srcdir)/../../src"
$VPATH << "$(srcdir)/../../src/analyze"
$VPATH << "$(srcdir)/../../src/analyze/action_view"
$VPATH << "$(srcdir)/../../src/parser"
$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 @@ -136,6 +136,12 @@ static VALUE Herb_parse(int argc, VALUE* argv, VALUE self) {
}
if (!NIL_P(action_view_helpers) && RTEST(action_view_helpers)) { parser_options.action_view_helpers = true; }

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

VALUE render_nodes = rb_hash_lookup(options, rb_utf8_str_new_cstr("render_nodes"));
if (NIL_P(render_nodes)) { render_nodes = rb_hash_lookup(options, ID2SYM(rb_intern("render_nodes"))); }
if (!NIL_P(render_nodes) && RTEST(render_nodes)) { parser_options.render_nodes = true; }
Expand Down
8 changes: 8 additions & 0 deletions java/herb_jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ Java_org_herb_Herb_parse(JNIEnv* env, jclass clazz, jstring source, jobject opti
parser_options.prism_program = (prismProgram == JNI_TRUE);
}

jmethodID getDotNotationTags =
(*env)->GetMethodID(env, optionsClass, "isDotNotationTags", "()Z");

if (getDotNotationTags != NULL) {
jboolean dotNotationTags = (*env)->CallBooleanMethod(env, options, getDotNotationTags);
parser_options.dot_notation_tags = (dotNotationTags == JNI_TRUE);
}

jmethodID getHtml =
(*env)->GetMethodID(env, optionsClass, "isHtml", "()Z");

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 @@ -10,6 +10,7 @@ public class ParserOptions {
private boolean prismNodes = false;
private boolean prismNodesDeep = false;
private boolean prismProgram = false;
private boolean dotNotationTags = false;
private boolean html = true;

public ParserOptions() {}
Expand Down Expand Up @@ -95,6 +96,15 @@ public boolean isPrismProgram() {
return prismProgram;
}

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

public boolean isDotNotationTags() {
return dotNotationTags;
}

public ParserOptions html(boolean value) {
this.html = value;
return this;
Expand Down
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 @@ -8,6 +8,7 @@ export interface ParseOptions {
prism_nodes?: boolean
prism_nodes_deep?: boolean
prism_program?: boolean
dot_notation_tags?: boolean
html?: boolean
}

Expand All @@ -23,6 +24,7 @@ export const DEFAULT_PARSER_OPTIONS: SerializedParserOptions = {
prism_nodes: false,
prism_nodes_deep: false,
prism_program: false,
dot_notation_tags: false,
html: true,
}

Expand Down Expand Up @@ -57,6 +59,9 @@ export class ParserOptions {
/** Whether the full Prism ProgramNode was serialized on the DocumentNode. */
readonly prism_program: boolean

/** Whether dot-notation component tags (e.g. Dialog.Button) are parsed as HTML elements. */
readonly dot_notation_tags: boolean

/** Whether HTML tag parsing is enabled during parsing. When false, HTML-like content is treated as literal text. */
readonly html: boolean

Expand All @@ -74,6 +79,7 @@ export class ParserOptions {
this.prism_nodes = options.prism_nodes ?? DEFAULT_PARSER_OPTIONS.prism_nodes
this.prism_nodes_deep = options.prism_nodes_deep ?? DEFAULT_PARSER_OPTIONS.prism_nodes_deep
this.prism_program = options.prism_program ?? DEFAULT_PARSER_OPTIONS.prism_program
this.dot_notation_tags = options.dot_notation_tags ?? DEFAULT_PARSER_OPTIONS.dot_notation_tags
this.html = options.html ?? DEFAULT_PARSER_OPTIONS.html
}
}
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 @@ -75,6 +75,7 @@ describe("ParseCache", () => {
strict: true,
strict_locals: false,
action_view_helpers: false,
dot_notation_tags: false,
html: true,
})
})
Expand All @@ -93,6 +94,7 @@ describe("ParseCache", () => {
strict: false,
strict_locals: false,
action_view_helpers: false,
dot_notation_tags: false,
html: true,
})
})
Expand All @@ -111,6 +113,7 @@ describe("ParseCache", () => {
strict: true,
strict_locals: false,
action_view_helpers: false,
dot_notation_tags: false,
html: true,
})
})
Expand All @@ -129,6 +132,7 @@ describe("ParseCache", () => {
strict: false,
strict_locals: false,
action_view_helpers: false,
dot_notation_tags: false,
html: true,
})
})
Expand Down
1 change: 1 addition & 0 deletions javascript/packages/node/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"./extension/libherb/lexer_peek_helpers.c",
"./extension/libherb/lexer.c",
"./extension/libherb/location.c",
"./extension/libherb/parser/dot_notation.c",
"./extension/libherb/parser_helpers.c",
"./extension/libherb/parser_match_tags.c",
"./extension/libherb/parser.c",
Expand Down
10 changes: 10 additions & 0 deletions playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,16 @@
/>
<span class="select-none">HTML</span>
</label>

<label class="flex items-center gap-1.5 text-gray-300 text-sm" title="Enable dot-notation component tags like &lt;Dialog.Button&gt; — requires each segment to start with an uppercase letter">
<input
type="checkbox"
data-option="dot_notation_tags"
data-action="change->playground#onOptionChange"
class="rounded border-gray-600 text-green-600 focus:ring-green-500 bg-gray-700"
/>
<span class="select-none">Dot-notation tags</span>
</label>
</div>

<pre
Expand Down
1 change: 1 addition & 0 deletions playground/src/controllers/playground_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,7 @@ export default class extends Controller {
prism_program: false,
prism_nodes: false,
prism_nodes_deep: false,
dot_notation_tags: false,
html: true,
}

Expand Down
3 changes: 3 additions & 0 deletions rust/src/herb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct ParserOptions {
pub prism_nodes: bool,
pub prism_nodes_deep: bool,
pub prism_program: bool,
pub dot_notation_tags: bool,
pub html: bool,
}

Expand All @@ -29,6 +30,7 @@ impl Default for ParserOptions {
prism_nodes: false,
prism_nodes_deep: false,
prism_program: false,
dot_notation_tags: false,
html: true,
}
}
Expand Down Expand Up @@ -111,6 +113,7 @@ pub fn parse_with_options(source: &str, options: &ParserOptions) -> Result<Parse
prism_program: options.prism_program,
prism_nodes: options.prism_nodes,
prism_nodes_deep: options.prism_nodes_deep,
dot_notation_tags: options.dot_notation_tags,
html: options.html,
start_line: 0,
start_column: 0,
Expand Down
18 changes: 18 additions & 0 deletions sig/herb/errors.rbs

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

1 change: 1 addition & 0 deletions src/include/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ typedef struct PARSER_OPTIONS_STRUCT {
bool prism_program;
bool prism_nodes;
bool prism_nodes_deep;
bool dot_notation_tags;
bool html;
uint32_t start_line;
uint32_t start_column;
Expand Down
12 changes: 12 additions & 0 deletions src/include/parser/dot_notation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef HERB_PARSER_DOT_NOTATION_H
#define HERB_PARSER_DOT_NOTATION_H

#include <stdbool.h>

#include "../parser.h"
#include "../util/hb_array.h"

bool parser_lookahead_is_valid_dot_notation_open_tag(parser_T* parser);
void parser_consume_dot_notation_segments(parser_T* parser, token_T* tag_name, hb_array_T* errors);

#endif
28 changes: 27 additions & 1 deletion src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "include/html_util.h"
#include "include/lexer.h"
#include "include/lexer_peek_helpers.h"
#include "include/parser/dot_notation.h"
#include "include/parser_helpers.h"
#include "include/token.h"
#include "include/token_matchers.h"
Expand Down Expand Up @@ -42,6 +43,7 @@ const parser_options_T HERB_DEFAULT_PARSER_OPTIONS = { .track_whitespace = false
.prism_nodes_deep = false,
.prism_nodes = false,
.prism_program = false,
.dot_notation_tags = false,
.html = true,
.start_line = 0,
.start_column = 0 };
Expand Down Expand Up @@ -941,6 +943,22 @@ static bool starts_with_keyword(hb_string_T string, const char* keyword) {
return is_whitespace(string.data[prefix.length]);
}

static AST_HTML_TEXT_NODE_T* parser_advance_as_text(parser_T* parser) {
token_T* token = parser_advance(parser);

AST_HTML_TEXT_NODE_T* text = ast_html_text_node_init(
token->value,
token->location.start,
token->location.end,
hb_array_init(0, parser->allocator),
parser->allocator
);

token_free(token, parser->allocator);

return text;
}

// TODO: ideally we could avoid basing this off of strings, and use the step in analyze.c
static bool parser_lookahead_erb_is_control_flow(parser_T* parser) {
lexer_T lexer_copy = *parser->lexer;
Expand Down Expand Up @@ -1009,6 +1027,8 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) {
token_T* tag_start = parser_consume_expected(parser, TOKEN_HTML_TAG_START, errors);
token_T* tag_name = parser_consume_expected(parser, TOKEN_IDENTIFIER, errors);

parser_consume_dot_notation_segments(parser, tag_name, errors);

while (token_is_none_of(parser, TOKEN_HTML_TAG_END, TOKEN_HTML_TAG_SELF_CLOSE, TOKEN_EOF)) {
if (token_is_any_of(parser, TOKEN_HTML_TAG_START, TOKEN_HTML_TAG_START_CLOSE)) {
append_unclosed_open_tag_error(
Expand Down Expand Up @@ -1186,6 +1206,8 @@ static AST_HTML_CLOSE_TAG_NODE_T* parser_parse_html_close_tag(parser_T* parser)

token_T* tag_name = parser_consume_expected(parser, TOKEN_IDENTIFIER, errors);

parser_consume_dot_notation_segments(parser, tag_name, errors);

parser_consume_whitespace(parser, children);

token_T* tag_closing = parser_consume_if_present(parser, TOKEN_HTML_TAG_END);
Expand Down Expand Up @@ -1499,7 +1521,11 @@ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, h
}

if (token_is(parser, TOKEN_HTML_TAG_START)) {
hb_array_append(children, parser_parse_html_element(parser));
if (parser_lookahead_is_valid_dot_notation_open_tag(parser)) {
hb_array_append(children, parser_parse_html_element(parser));
} else {
hb_array_append(children, parser_advance_as_text(parser));
}
parser->consecutive_error_count = 0;
continue;
}
Expand Down
Loading
Loading