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
3 changes: 3 additions & 0 deletions .clang-format-ignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
ext/herb/error_helpers.c
ext/herb/nodes.c
src/analyze_missing_end.c
src/analyze_transform.c
src/ast_nodes.c
src/ast_pretty_print.c
src/errors.c
src/include/ast_nodes.h
src/include/ast_pretty_print.h
src/include/errors.h
src/parser_match_tags.c
src/visitor.c
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,15 @@ rust/src/errors.rs
rust/src/nodes.rs
sig/serialized_ast_errors.rbs
sig/serialized_ast_nodes.rbs
src/analyze_missing_end.c
src/analyze_transform.c
src/ast_nodes.c
src/ast_pretty_print.c
src/errors.c
src/include/ast_nodes.h
src/include/ast_pretty_print.h
src/include/errors.h
src/parser_match_tags.c
src/visitor.c
wasm/error_helpers.cpp
wasm/error_helpers.h
Expand Down
24 changes: 22 additions & 2 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ errors:

- name: MissingOpeningTagError
message:
template: "Found closing tag `</%s>` at (%u:%u) without a matching opening tag."
template: "Found closing tag `</%s>` at (%u:%u) without a matching opening tag in the same scope."
arguments:
- closing_tag->value
- closing_tag->location.start.line
Expand All @@ -58,7 +58,7 @@ errors:

- name: MissingClosingTagError
message:
template: "Opening tag `<%s>` at (%u:%u) doesn't have a matching closing tag `</%s>`."
template: "Opening tag `<%s>` at (%u:%u) doesn't have a matching closing tag `</%s>` in the same scope."
arguments:
- opening_tag->value
- opening_tag->location.start.line
Expand Down Expand Up @@ -151,6 +151,26 @@ errors:
- name: level
type: string

- name: ERBControlFlowScopeError
message:
template: "%s appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)."
arguments:
- keyword

fields:
- name: keyword
type: string

- name: MissingERBEndTagError
message:
template: "%s started here but never closed with an end tag. The end tag may be in a different scope."
arguments:
- keyword

fields:
- name: keyword
type: string

warnings:
fields: []
types: []
Expand Down
2 changes: 2 additions & 0 deletions ext/herb/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ static VALUE Herb_parse_file(VALUE self, VALUE path) {

AST_DOCUMENT_NODE_T* root = herb_parse(string, NULL);

herb_analyze_parse_tree(root, string);

VALUE result = create_parse_result(root, source_value);

ast_node_free((AST_NODE_T*) root);
Expand Down
4 changes: 2 additions & 2 deletions javascript/packages/formatter/src/format-printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1056,8 +1056,8 @@ export class FormatPrinter extends Printer {
inner = node.children.map(child => {
if (isNode(child, HTMLTextNode) || isNode(child, LiteralNode)) {
return child.content
} else if (isERBNode(child) || isNode(child, ERBContentNode)) {
return this.reconstructERBNode(child, false)
} else if (isERBNode(child)) {
return IdentityPrinter.print(child)
} else {
return ""
}
Expand Down
37 changes: 14 additions & 23 deletions javascript/packages/linter/test/rules/parser-no-errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ describe("ParserNoErrorsRule", () => {
})

test("should report errors for mismatched tag names", () => {
expectError("Tag `<h3>` opened at (3:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Tag `<h2>` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Opening tag `<h2>` at (1:1) doesn't have a matching closing tag `</h2>`. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Opening tag `<h3>` at (3:1) doesn't have a matching closing tag `</h3>`. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Opening tag `<h2>` at (1:1) doesn't have a matching closing tag `</h2>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Opening tag `<h3>` at (3:1) doesn't have a matching closing tag `</h3>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
assertOffenses(dedent`
<h2>
Some content
Expand All @@ -29,10 +27,7 @@ describe("ParserNoErrorsRule", () => {
})

test("should report errors for unclosed elements", () => {
expectError("Tag `<p>` opened at (2:3) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Tag `<div>` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Opening tag `<div>` at (1:1) doesn't have a matching closing tag `</div>`. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Opening tag `<p>` at (2:3) closed with `</div>` at (3:2). (`TAG_NAMES_MISMATCH_ERROR`)")
expectError("Opening tag `<p>` at (2:3) doesn't have a matching closing tag `</p>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
assertOffenses(dedent`
<div>
<p>Some content
Expand All @@ -46,7 +41,7 @@ describe("ParserNoErrorsRule", () => {
})

test("should report errors for missing opening tags", () => {
expectError("Found closing tag `</div>` at (2:2) without a matching opening tag. (`MISSING_OPENING_TAG_ERROR`)")
expectError("Found closing tag `</div>` at (2:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)")
assertOffenses(dedent`
Some content
</div>
Expand All @@ -66,10 +61,10 @@ describe("ParserNoErrorsRule", () => {
})

test("should report multiple parser errors", () => {
expectError("Tag `<p>` opened at (2:3) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Tag `<h2>` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Opening tag `<p>` at (2:3) closed with `</div>` at (4:2). (`TAG_NAMES_MISMATCH_ERROR`)")
expectError("Opening tag `<p>` at (2:3) closed with `</h3>` at (3:2). (`TAG_NAMES_MISMATCH_ERROR`)")
expectError("Opening tag `<h2>` at (1:1) doesn't have a matching closing tag `</h2>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Opening tag `<p>` at (2:3) doesn't have a matching closing tag `</p>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Found closing tag `</h3>` at (3:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)")
expectError("Found closing tag `</div>` at (4:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)")
assertOffenses(dedent`
<h2>
<p>Unclosed paragraph
Expand All @@ -79,8 +74,8 @@ describe("ParserNoErrorsRule", () => {
})

test("should work alongside other linting rules", () => {
expectError("Tag `<h2>` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Opening tag `<h2>` at (1:1) closed with `</h3>` at (3:2). (`TAG_NAMES_MISMATCH_ERROR`)")
expectError("Opening tag `<h2>` at (1:1) doesn't have a matching closing tag `</h2>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Found closing tag `</h3>` at (3:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)")
assertOffenses(dedent`
<h2>
<% %>
Expand All @@ -89,10 +84,8 @@ describe("ParserNoErrorsRule", () => {
})

test("should include error location information", () => {
expectError("Tag `<h2>` opened at (2:3) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Tag `<div>` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Opening tag `<h2>` at (2:3) closed with `</div>` at (3:2). (`TAG_NAMES_MISMATCH_ERROR`)")
expectError("Opening tag `<h2>` at (2:3) closed with `</h3>` at (2:15). (`TAG_NAMES_MISMATCH_ERROR`)")
expectError("Opening tag `<h2>` at (2:3) doesn't have a matching closing tag `</h2>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Found closing tag `</h3>` at (2:15) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)")
assertOffenses(dedent`
<div>
<h2>Content</h3>
Expand All @@ -101,10 +94,8 @@ describe("ParserNoErrorsRule", () => {
})

test("should handle the specific case from issue #359", () => {
expectError("Tag `<h2>` opened at (1:25) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Tag `<h2>` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)")
expectError("Opening tag `<h2>` at (1:1) doesn't have a matching closing tag `</h2>`. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Opening tag `<h2>` at (1:25) doesn't have a matching closing tag `</h2>`. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Opening tag `<h2>` at (1:1) doesn't have a matching closing tag `</h2>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
expectError("Opening tag `<h2>` at (1:25) doesn't have a matching closing tag `</h2>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)")
assertOffenses(dedent`
<h2>Some heading content<h2>
`)
Expand Down
3 changes: 3 additions & 0 deletions javascript/packages/node/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

# Herb main source files
"./extension/libherb/analyze_helpers.c",
"./extension/libherb/analyze_missing_end.c",
"./extension/libherb/analyze_transform.c",
"./extension/libherb/analyze.c",
"./extension/libherb/analyzed_ruby.c",
"./extension/libherb/ast_node.c",
Expand All @@ -26,6 +28,7 @@
"./extension/libherb/lexer.c",
"./extension/libherb/location.c",
"./extension/libherb/parser_helpers.c",
"./extension/libherb/parser_match_tags.c",
"./extension/libherb/parser.c",
"./extension/libherb/pretty_print.c",
"./extension/libherb/prism_helpers.c",
Expand Down
22 changes: 15 additions & 7 deletions rust/tests/error_handling_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ fn test_unclosed_element_error() {
let result = parse(source).unwrap();

let tree_inspect = result.inspect();
assert!(tree_inspect.contains("UnclosedElementError"));
assert!(tree_inspect.contains("Tag `<div>` opened at (1:1) was never closed"));

assert!(tree_inspect.contains("MissingClosingTagError"));
assert!(tree_inspect.contains("Opening tag `<div>` at (1:1) doesn't have a matching closing tag"));
assert!(tree_inspect.contains("Opening tag `<div>` at (1:1) doesn't have a matching closing"));
}

#[test]
Expand All @@ -22,10 +21,19 @@ fn test_tag_names_mismatch_error() {

let source = "<div></span>";
let result = parse(source).unwrap();

let tree_inspect = result.inspect();
assert!(tree_inspect.contains("TagNamesMismatchError"));
assert!(tree_inspect.contains("Opening tag `<div>` at (1:1) closed with `</span>`"));

print!("{}", tree_inspect);

assert!(tree_inspect.contains("MissingClosingTagError (location: (1:0)-(1:5))"));
assert!(tree_inspect.contains(
"Opening tag `<div>` at (1:1) doesn't have a matching closing tag `</div>` in the same scope."
));

assert!(tree_inspect.contains("MissingOpeningTagError (location: (1:5)-(1:12))"));
assert!(tree_inspect.contains(
"Found closing tag `</span>` at (1:7) without a matching opening tag in the same scope."
));
}

#[test]
Expand All @@ -34,8 +42,8 @@ fn test_no_errors_with_valid_html() {

let source = "<div>Hello</div>";
let result = parse(source).unwrap();

let tree_inspect = result.inspect();

assert!(!tree_inspect.contains("error"));
assert!(!tree_inspect.contains("ERROR"));
}
10 changes: 5 additions & 5 deletions rust/tests/parse_result_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@ fn test_parse_result_failed_with_unclosed_element() {

let errors = result.recursive_errors();

assert!(errors
.iter()
.any(|e| e.error_type() == "UNCLOSED_ELEMENT_ERROR"));

assert!(errors
.iter()
.any(|e| e.error_type() == "MISSING_CLOSING_TAG_ERROR"));
Expand Down Expand Up @@ -81,7 +77,11 @@ fn test_parse_result_failed_with_tag_mismatch() {

assert!(errors
.iter()
.any(|e| e.error_type() == "TAG_NAMES_MISMATCH_ERROR"));
.any(|e| e.error_type() == "MISSING_OPENING_TAG_ERROR"));

assert!(errors
.iter()
.any(|e| e.error_type() == "MISSING_CLOSING_TAG_ERROR"));
}

#[test]
Expand Down
36 changes: 36 additions & 0 deletions sig/herb/errors.rbs

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

Loading
Loading