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
11 changes: 11 additions & 0 deletions ext/herb/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,17 @@ static VALUE Herb_parse(int argc, VALUE* argv, VALUE self) {
if (NIL_P(timeout)) { timeout = rb_hash_lookup(options, ID2SYM(rb_intern("timeout"))); }
if (!NIL_P(timeout)) { parser_options.timeout_ms = (uint32_t) (NUM2DBL(timeout) * 1000); }

VALUE max_errors_sentinel = ID2SYM(rb_intern("__not_set__"));
VALUE max_errors = rb_hash_lookup2(options, rb_utf8_str_new_cstr("max_errors"), max_errors_sentinel);

if (max_errors == max_errors_sentinel) {
max_errors = rb_hash_lookup2(options, ID2SYM(rb_intern("max_errors")), max_errors_sentinel);
}

if (max_errors != max_errors_sentinel) {
parser_options.max_errors = NIL_P(max_errors) ? 0 : (uint32_t) NUM2UINT(max_errors);
}

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
6 changes: 6 additions & 0 deletions ext/herb/extension_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ VALUE create_parse_result(AST_DOCUMENT_NODE_T* root, VALUE source, const parser_
rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_program")), options->prism_program ? Qtrue : Qfalse);
rb_hash_aset(kwargs, ID2SYM(rb_intern("timeout")), DBL2NUM((double) options->timeout_ms / 1000.0));

rb_hash_aset(
kwargs,
ID2SYM(rb_intern("max_errors")),
options->max_errors == 0 ? Qnil : UINT2NUM(options->max_errors)
);

VALUE parser_options_args[1] = { kwargs };
VALUE parser_options = rb_class_new_instance_kw(1, parser_options_args, cParserOptions, RB_PASS_KEYWORDS);

Expand Down
15 changes: 15 additions & 0 deletions java/herb_jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ Java_org_herb_Herb_parse(JNIEnv* env, jclass clazz, jstring source, jobject opti
jint timeout = (*env)->CallIntMethod(env, options, getTimeout);
parser_options.timeout_ms = (uint32_t) timeout;
}

jmethodID getMaxErrors =
(*env)->GetMethodID(env, optionsClass, "getMaxErrors", "()Ljava/lang/Integer;");

if (getMaxErrors != NULL) {
jobject maxErrorsObj = (*env)->CallObjectMethod(env, options, getMaxErrors);

if (maxErrorsObj == NULL) {
parser_options.max_errors = 0;
} else {
jclass integerClass = (*env)->FindClass(env, "java/lang/Integer");
jmethodID intValue = (*env)->GetMethodID(env, integerClass, "intValue", "()I");
parser_options.max_errors = (uint32_t) (*env)->CallIntMethod(env, maxErrorsObj, intValue);
}
}
}

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 @@ -14,6 +14,7 @@ public class ParserOptions {
private boolean dotNotationTags = false;
private boolean html = true;
private int timeout = 1000;
private Integer maxErrors = 25;

public ParserOptions() {}

Expand Down Expand Up @@ -134,6 +135,15 @@ public int getTimeout() {
return timeout;
}

public ParserOptions maxErrors(Integer value) {
this.maxErrors = value;
return this;
}

public Integer getMaxErrors() {
return maxErrors;
}

public static ParserOptions create() {
return new ParserOptions();
}
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 @@ -12,6 +12,7 @@ export interface ParseOptions {
dot_notation_tags?: boolean
html?: boolean
timeout?: number
max_errors?: number | null
}

export type SerializedParserOptions = Required<ParseOptions>
Expand All @@ -30,6 +31,7 @@ export const DEFAULT_PARSER_OPTIONS: SerializedParserOptions = {
dot_notation_tags: false,
html: true,
timeout: 1000,
max_errors: 25,
}

/**
Expand Down Expand Up @@ -75,6 +77,9 @@ export class ParserOptions {
/** Parse timeout in milliseconds. 0 disables the timeout. */
readonly timeout: number

/** Maximum number of errors to report. null means unlimited. */
readonly max_errors: number | null

static from(options: SerializedParserOptions): ParserOptions {
return new ParserOptions(options)
}
Expand All @@ -93,5 +98,6 @@ export class ParserOptions {
this.dot_notation_tags = options.dot_notation_tags ?? DEFAULT_PARSER_OPTIONS.dot_notation_tags
this.html = options.html ?? DEFAULT_PARSER_OPTIONS.html
this.timeout = options.timeout ?? DEFAULT_PARSER_OPTIONS.timeout
this.max_errors = options.max_errors ?? DEFAULT_PARSER_OPTIONS.max_errors
}
}
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 @@ -79,6 +79,7 @@ describe("ParseCache", () => {
transform_conditionals: false,
html: true,
timeout: 1000,
max_errors: 25,
})
})

Expand All @@ -100,6 +101,7 @@ describe("ParseCache", () => {
transform_conditionals: false,
html: true,
timeout: 1000,
max_errors: 25,
})
})

Expand All @@ -121,6 +123,7 @@ describe("ParseCache", () => {
transform_conditionals: false,
html: true,
timeout: 1000,
max_errors: 25,
})
})

Expand All @@ -142,6 +145,7 @@ describe("ParseCache", () => {
transform_conditionals: false,
html: true,
timeout: 1000,
max_errors: 25,
})
})
})
Expand Down
11 changes: 8 additions & 3 deletions lib/herb/parser_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class ParserOptions
attr_reader :prism_nodes #: bool
attr_reader :prism_nodes_deep #: bool
attr_reader :timeout #: Numeric
attr_reader :max_errors #: Integer?

DEFAULT_STRICT = true #: bool
DEFAULT_TRACK_WHITESPACE = false #: bool
Expand All @@ -25,9 +26,10 @@ class ParserOptions
DEFAULT_PRISM_NODES = false #: bool
DEFAULT_PRISM_NODES_DEEP = false #: bool
DEFAULT_TIMEOUT = 1 #: Numeric
DEFAULT_MAX_ERRORS = 25 #: Integer

#: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?timeout: Numeric) -> void
def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPACE, analyze: DEFAULT_ANALYZE, action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS, render_nodes: DEFAULT_RENDER_NODES, strict_locals: DEFAULT_STRICT_LOCALS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM, timeout: DEFAULT_TIMEOUT)
def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPACE, analyze: DEFAULT_ANALYZE, action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS, render_nodes: DEFAULT_RENDER_NODES, strict_locals: DEFAULT_STRICT_LOCALS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM, timeout: DEFAULT_TIMEOUT, max_errors: DEFAULT_MAX_ERRORS)
@strict = strict
@track_whitespace = track_whitespace
@analyze = analyze
Expand All @@ -39,9 +41,10 @@ def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPAC
@prism_nodes_deep = prism_nodes_deep
@prism_program = prism_program
@timeout = timeout
@max_errors = max_errors
end

#: () -> Hash[Symbol, (bool | Numeric)]
#: () -> Hash[Symbol, (bool | Numeric | nil)]
def to_h
{
strict: @strict,
Expand All @@ -55,6 +58,7 @@ def to_h
prism_nodes_deep: @prism_nodes_deep,
prism_program: @prism_program,
timeout: @timeout,
max_errors: @max_errors,
}
end

Expand All @@ -71,7 +75,8 @@ def inspect
"prism_nodes=#{@prism_nodes}\n " \
"prism_nodes_deep=#{@prism_nodes_deep}\n " \
"prism_program=#{@prism_program}\n " \
"timeout=#{@timeout}>"
"timeout=#{@timeout}\n " \
"max_errors=#{@max_errors}>"
end
end
end
6 changes: 6 additions & 0 deletions rust/src/herb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct ParserOptions {
pub dot_notation_tags: bool,
pub html: bool,
pub timeout: u32,
pub max_errors: Option<u32>,
}

impl Default for ParserOptions {
Expand All @@ -36,6 +37,7 @@ impl Default for ParserOptions {
dot_notation_tags: false,
html: true,
timeout: 1000,
max_errors: Some(25),
}
}
}
Expand Down Expand Up @@ -123,6 +125,8 @@ pub fn parse_with_options(source: &str, options: &ParserOptions) -> Result<Parse
start_line: 0,
start_column: 0,
timeout_ms: options.timeout,
max_errors: options.max_errors.unwrap_or(0),
error_count: std::ptr::null_mut(),
deadline_ms: 0,
};

Expand Down Expand Up @@ -345,6 +349,8 @@ pub fn diff(old_source: &str, new_source: &str) -> Result<DiffResult, String> {
start_line: 0,
start_column: 0,
timeout_ms: 1000,
max_errors: 25,
error_count: std::ptr::null_mut(),
deadline_ms: 0,
};

Expand Down
8 changes: 6 additions & 2 deletions sig/herb/parser_options.rbs

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

3 changes: 3 additions & 0 deletions src/herb.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ HERB_EXPORTED_FUNCTION AST_DOCUMENT_NODE_T* herb_parse(
parser_options_T parser_options = HERB_DEFAULT_PARSER_OPTIONS;
if (options != NULL) { parser_options = *options; }

uint32_t error_count = 0;
parser_options.error_count = &error_count;

parser_options_set_deadline(&parser_options);

if (parser_options.start_line > 0) {
Expand Down
13 changes: 13 additions & 0 deletions src/include/parser/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ typedef struct PARSER_OPTIONS_STRUCT {
uint32_t start_line;
uint32_t start_column;
uint32_t timeout_ms;
uint32_t max_errors;
uint32_t* error_count;
uint64_t deadline_ms;
} parser_options_T;

Expand All @@ -52,6 +54,17 @@ static inline bool parser_options_past_deadline(const parser_options_T* options)
return hb_monotonic_ms() >= options->deadline_ms;
}

static inline bool parser_options_errors_exceeded(const parser_options_T* options) {
if (options == NULL || options->error_count == NULL) { return false; }
if (options->max_errors == 0) { return false; }

return *options->error_count >= options->max_errors;
}

static inline void parser_options_increment_error_count(const parser_options_T* options) {
if (options != NULL && options->error_count != NULL) { (*options->error_count)++; }
}

static inline void parser_options_set_deadline(parser_options_T* options) {
if (options->timeout_ms == 0) { return; }

Expand Down
15 changes: 9 additions & 6 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
#include <strings.h>

#define MAX_CONSECUTIVE_ERRORS 10
#define DEADLINE_CHECK_INTERVAL 1024

static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, hb_array_T* errors);
static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, hb_array_T* errors);
static AST_ERB_CONTENT_NODE_T* parser_parse_erb_tag(parser_T* parser);
Expand All @@ -51,6 +49,7 @@ const parser_options_T HERB_DEFAULT_PARSER_OPTIONS = { .track_whitespace = false
.start_line = 0,
.start_column = 0,
.timeout_ms = 1000,
.max_errors = 25,
.deadline_ms = 0 };

size_t parser_sizeof(void) {
Expand Down Expand Up @@ -1614,7 +1613,7 @@ static size_t find_matching_close_tag(
int depth = 0;

for (size_t index = start_index + 1; index < hb_array_size(nodes); index++) {
if ((index & (DEADLINE_CHECK_INTERVAL - 1)) == 0 && parser_options_past_deadline(options)) { return (size_t) -1; }
if (parser_options_past_deadline(options)) { return (size_t) -1; }

AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index);
if (node == NULL) { continue; }
Expand Down Expand Up @@ -1706,7 +1705,7 @@ static hb_array_T* parser_build_elements_from_tags(
hb_array_T* close_tag_names = collect_close_tag_names(nodes, allocator);

for (size_t index = 0; index < hb_array_size(nodes); index++) {
if ((index & (DEADLINE_CHECK_INTERVAL - 1)) == 0 && parser_options_past_deadline(options)) { break; }
if (parser_options_past_deadline(options)) { break; }

AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index);
if (node == NULL) { continue; }
Expand Down Expand Up @@ -1779,14 +1778,16 @@ static hb_array_T* parser_build_elements_from_tags(

index = implicit_close_index - 1;
} else {
if (hb_array_size(open_tag->base.errors) == 0) {
if (hb_array_size(open_tag->base.errors) == 0 && !parser_options_errors_exceeded(options)) {
append_missing_closing_tag_error(
open_tag->tag_name,
open_tag->base.location.start,
open_tag->base.location.end,
allocator,
open_tag->base.errors
);

parser_options_increment_error_count(options);
}

hb_array_append(result, node);
Expand Down Expand Up @@ -1826,14 +1827,16 @@ static hb_array_T* parser_build_elements_from_tags(
AST_HTML_CLOSE_TAG_NODE_T* close_tag = (AST_HTML_CLOSE_TAG_NODE_T*) node;

if (!is_void_element(close_tag->tag_name->value)) {
if (hb_array_size(close_tag->base.errors) == 0) {
if (hb_array_size(close_tag->base.errors) == 0 && !parser_options_errors_exceeded(options)) {
append_missing_opening_tag_error(
close_tag->tag_name,
close_tag->base.location.start,
close_tag->base.location.end,
allocator,
close_tag->base.errors
);

parser_options_increment_error_count(options);
}
}

Expand Down
Loading
Loading