Skip to content

Commit a2e6f24

Browse files
authored
Parser: Expose Prism Nodes in Herb Syntax Tree (#1350)
This pull request updates the parser to be able expose the Prism Nodes as part of the Herb Syntax Tree. We now expose three new parser options that control the way the parser exposes the Prism Nodes it sees: `prism_nodes`, `prism_nodes_deep`, and `prism_program`. * `prism_nodes` exposes the Prism nodes for each individual `ERB*` node on the node itself * `prism_nodes_deep` does the same thing as `prism_nodes`, but it also includes all nodes of all children inside of itself. For `<%= tag.div do %><%= user.name %><% end %>` the `tag.div` ERB block node will also have the `user.name` nodes as part of the `prism_node` field on itself. * `prism_program` extracts the Ruby from the whole file and passes that to Prism. It then exposes the whole template on the `DocumentNode` on the `prism_node` field. For example for a template like: ```html+erb <h1><%= @post.title %></h1> ``` The parser will now produces the following using `prism_nodes: true`: ```js @ DocumentNode (location: (1:0)-(1:27)) └── children: (1 item) └── @ HTMLElementNode (location: (1:0)-(1:27)) ├── open_tag: │ └── @ HTMLOpenTagNode (location: (1:0)-(1:4)) │ ├── tag_opening: "<" (location: (1:0)-(1:1)) │ ├── tag_name: "h1" (location: (1:1)-(1:3)) │ ├── tag_closing: ">" (location: (1:3)-(1:4)) │ ├── children: [] │ └── is_void: false │ ├── tag_name: "h1" (location: (1:1)-(1:3)) ├── body: (1 item) │ └── @ ERBContentNode (location: (1:4)-(1:22)) │ ├── tag_opening: "<%=" (location: (1:4)-(1:7)) │ ├── content: " @post.title " (location: (1:7)-(1:20)) │ ├── tag_closing: "%>" (location: (1:20)-(1:22)) │ ├── parsed: true │ ├── valid: true │ └── prism_node: │ └── @ CallNode (location: (1:8)-(1:19)) │ ├── receiver: │ │ └── @ InstanceVariableReadNode (location: (1:8)-(1:13)) │ │ └── name: "@post" │ ├── callOperatorLoc: (location: (1:13)-(1:14)) │ ├── name: "title" │ ├── messageLoc: (location: (1:14)-(1:19)) │ ├── openingLoc: ∅ │ ├── arguments_: ∅ │ ├── closingLoc: ∅ │ ├── equalLoc: ∅ │ └── block: ∅ │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:22)-(1:27)) │ ├── tag_opening: "</" (location: (1:22)-(1:24)) │ ├── tag_name: "h1" (location: (1:24)-(1:26)) │ ├── children: [] │ └── tag_closing: ">" (location: (1:26)-(1:27)) │ ├── is_void: false └── element_source: "HTML" ``` Resolves #1036 Resolves #1121
1 parent 2ec7dc8 commit a2e6f24

File tree

184 files changed

+10350
-151
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

184 files changed

+10350
-151
lines changed

.rubocop.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ Metrics/CyclomaticComplexity:
5858
- lib/herb/ast/nodes.rb
5959
- lib/herb/cli.rb
6060
- lib/herb/engine.rb
61+
- lib/herb/prism_inspect.rb
6162
- lib/herb/engine/**/*.rb
63+
- templates/template.rb
6264

6365
Metrics/MethodLength:
6466
Max: 20
@@ -68,6 +70,7 @@ Metrics/MethodLength:
6870
- lib/herb/project.rb
6971
- lib/herb/engine.rb
7072
- lib/herb/engine/**/*.rb
73+
- lib/herb/prism_inspect.rb
7174
- templates/template.rb
7275
- test/fork_helper.rb
7376
- test/snapshot_utils.rb
@@ -83,6 +86,7 @@ Metrics/AbcSize:
8386
- lib/herb/engine.rb
8487
- lib/herb/engine/**/*.rb
8588
- lib/herb/token.rb
89+
- lib/herb/prism_inspect.rb
8690
- templates/template.rb
8791
- test/fork_helper.rb
8892
- test/snapshot_utils.rb
@@ -98,6 +102,7 @@ Metrics/ClassLength:
98102
- lib/herb/engine/**/*.rb
99103
- lib/herb/project.rb
100104
- lib/herb/visitor.rb
105+
- lib/herb/ast/nodes.rb
101106
- test/**/*_test.rb
102107

103108
Metrics/ModuleLength:
@@ -121,14 +126,17 @@ Metrics/ParameterLists:
121126
Exclude:
122127
- lib/herb/ast/node.rb
123128
- lib/herb/ast/nodes.rb
124-
- lib/herb/errors.rb
125129
- lib/herb/engine/validators/security_validator.rb
130+
- lib/herb/errors.rb
131+
- lib/herb/parser_options.rb
132+
- lib/herb/prism_inspect.rb
126133

127134
Metrics/PerceivedComplexity:
128135
Exclude:
129136
- lib/herb/ast/nodes.rb
130137
- lib/herb/cli.rb
131138
- lib/herb/project.rb
139+
- lib/herb/prism_inspect.rb
132140
- lib/herb/engine.rb
133141
- lib/herb/engine/**/*.rb
134142
- templates/template.rb

config.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,12 @@ nodes:
369369
type: array
370370
kind: Node
371371
372+
- name: prism_context
373+
type: prism_context
374+
375+
- name: prism_node
376+
type: prism_node
377+
372378
- name: LiteralNode
373379
fields:
374380
- name: content
@@ -643,6 +649,9 @@ nodes:
643649
- name: valid
644650
type: boolean
645651
652+
- name: prism_node
653+
type: prism_node
654+
646655
- name: ERBEndNode
647656
fields:
648657
- name: tag_opening
@@ -686,6 +695,9 @@ nodes:
686695
# - name: predicate
687696
# type: prism_node
688697
698+
- name: prism_node
699+
type: prism_node
700+
689701
- name: statements
690702
type: array
691703
kind: Node
@@ -717,6 +729,9 @@ nodes:
717729
# - name: opener
718730
# type: prism_node
719731
732+
- name: prism_node
733+
type: prism_node
734+
720735
- name: body
721736
type: array
722737
kind: Node
@@ -765,6 +780,9 @@ nodes:
765780
# - name: predicate
766781
# type: prism_node
767782
783+
- name: prism_node
784+
type: prism_node
785+
768786
- name: conditions
769787
type: array
770788
kind: ERBWhenNode
@@ -795,6 +813,9 @@ nodes:
795813
# - name: predicate
796814
# type: prism_node
797815
816+
- name: prism_node
817+
type: prism_node
818+
798819
- name: conditions
799820
type: array
800821
kind: ERBInNode
@@ -821,6 +842,9 @@ nodes:
821842
# - name: predicate
822843
# type: prism_node
823844
845+
- name: prism_node
846+
type: prism_node
847+
824848
- name: statements
825849
type: array
826850
kind: Node
@@ -843,6 +867,9 @@ nodes:
843867
# - name: predicate
844868
# type: prism_node
845869
870+
- name: prism_node
871+
type: prism_node
872+
846873
- name: statements
847874
type: array
848875
kind: Node
@@ -868,6 +895,9 @@ nodes:
868895
# - name: collection
869896
# type: prism_node
870897
898+
- name: prism_node
899+
type: prism_node
900+
871901
- name: statements
872902
type: array
873903
kind: Node
@@ -928,6 +958,9 @@ nodes:
928958
- name: tag_closing
929959
type: token
930960
961+
- name: prism_node
962+
type: prism_node
963+
931964
- name: statements
932965
type: array
933966
kind: Node
@@ -965,6 +998,9 @@ nodes:
965998
# - name: predicate
966999
# type: prism_node
9671000
1001+
- name: prism_node
1002+
type: prism_node
1003+
9681004
- name: statements
9691005
type: array
9701006
kind: Node

ext/herb/extconf.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
$CFLAGS << " -DPRISM_EXCLUDE_PRETTYPRINT"
2828
$CFLAGS << " -DPRISM_EXCLUDE_JSON"
2929
$CFLAGS << " -DPRISM_EXCLUDE_PACK"
30-
$CFLAGS << " -DPRISM_EXCLUDE_SERIALIZATION"
3130

3231
herb_src_files = Dir.glob("#{$srcdir}/../../src/**/*.c").map { |file| file.delete_prefix("../../../../ext/herb/") }.sort
3332

ext/herb/extension.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,18 @@ static VALUE Herb_parse(int argc, VALUE* argv, VALUE self) {
136136
}
137137
if (!NIL_P(action_view_helpers) && RTEST(action_view_helpers)) { parser_options.action_view_helpers = true; }
138138

139+
VALUE prism_nodes = rb_hash_lookup(options, rb_utf8_str_new_cstr("prism_nodes"));
140+
if (NIL_P(prism_nodes)) { prism_nodes = rb_hash_lookup(options, ID2SYM(rb_intern("prism_nodes"))); }
141+
if (!NIL_P(prism_nodes) && RTEST(prism_nodes)) { parser_options.prism_nodes = true; }
142+
143+
VALUE prism_nodes_deep = rb_hash_lookup(options, rb_utf8_str_new_cstr("prism_nodes_deep"));
144+
if (NIL_P(prism_nodes_deep)) { prism_nodes_deep = rb_hash_lookup(options, ID2SYM(rb_intern("prism_nodes_deep"))); }
145+
if (!NIL_P(prism_nodes_deep) && RTEST(prism_nodes_deep)) { parser_options.prism_nodes_deep = true; }
146+
147+
VALUE prism_program = rb_hash_lookup(options, rb_utf8_str_new_cstr("prism_program"));
148+
if (NIL_P(prism_program)) { prism_program = rb_hash_lookup(options, ID2SYM(rb_intern("prism_program"))); }
149+
if (!NIL_P(prism_program) && RTEST(prism_program)) { parser_options.prism_program = true; }
150+
139151
VALUE arena_stats = rb_hash_lookup(options, rb_utf8_str_new_cstr("arena_stats"));
140152
if (NIL_P(arena_stats)) { arena_stats = rb_hash_lookup(options, ID2SYM(rb_intern("arena_stats"))); }
141153
if (!NIL_P(arena_stats) && RTEST(arena_stats)) { print_arena_stats = true; }

ext/herb/extension_helpers.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ VALUE create_parse_result(AST_DOCUMENT_NODE_T* root, VALUE source, const parser_
8989
rb_hash_aset(kwargs, ID2SYM(rb_intern("track_whitespace")), options->track_whitespace ? Qtrue : Qfalse);
9090
rb_hash_aset(kwargs, ID2SYM(rb_intern("analyze")), options->analyze ? Qtrue : Qfalse);
9191
rb_hash_aset(kwargs, ID2SYM(rb_intern("action_view_helpers")), options->action_view_helpers ? Qtrue : Qfalse);
92+
rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_nodes")), options->prism_nodes ? Qtrue : Qfalse);
93+
rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_nodes_deep")), options->prism_nodes_deep ? Qtrue : Qfalse);
94+
rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_program")), options->prism_program ? Qtrue : Qfalse);
9295

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

java/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ PRISM_BUILD = $(PRISM_PATH)/build
3030

3131
JAVAC = $(JAVA_HOME)/bin/javac
3232
JAVA_CMD = $(JAVA_HOME)/bin/java
33-
CFLAGS = -std=c99 -Wall -Wextra -fPIC -O2 -DHERB_EXCLUDE_PRETTYPRINT -DPRISM_EXCLUDE_PRETTYPRINT -DPRISM_EXCLUDE_JSON -DPRISM_EXCLUDE_PACK -DPRISM_EXCLUDE_SERIALIZATION
33+
CFLAGS = -std=c99 -Wall -Wextra -fPIC -O2 -DHERB_EXCLUDE_PRETTYPRINT -DPRISM_EXCLUDE_PRETTYPRINT -DPRISM_EXCLUDE_JSON -DPRISM_EXCLUDE_PACK
3434
INCLUDES = -I. -I$(SRC_DIR)/include -I$(PRISM_INCLUDE) $(JNI_INCLUDES)
3535
LDFLAGS = -shared
3636
LIBS = $(PRISM_BUILD)/libprism.a

java/herb_jni.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ Java_org_herb_Herb_parse(JNIEnv* env, jclass clazz, jstring source, jobject opti
6868
jboolean actionViewHelpers = (*env)->CallBooleanMethod(env, options, getActionViewHelpers);
6969
parser_options.action_view_helpers = (actionViewHelpers == JNI_TRUE);
7070
}
71+
72+
jmethodID getPrismNodes =
73+
(*env)->GetMethodID(env, optionsClass, "isPrismNodes", "()Z");
74+
75+
if (getPrismNodes != NULL) {
76+
jboolean prismNodes = (*env)->CallBooleanMethod(env, options, getPrismNodes);
77+
parser_options.prism_nodes = (prismNodes == JNI_TRUE);
78+
}
79+
80+
jmethodID getPrismNodesDeep =
81+
(*env)->GetMethodID(env, optionsClass, "isPrismNodesDeep", "()Z");
82+
83+
if (getPrismNodesDeep != NULL) {
84+
jboolean prismNodesDeep = (*env)->CallBooleanMethod(env, options, getPrismNodesDeep);
85+
parser_options.prism_nodes_deep = (prismNodesDeep == JNI_TRUE);
86+
}
87+
88+
jmethodID getPrismProgram =
89+
(*env)->GetMethodID(env, optionsClass, "isPrismProgram", "()Z");
90+
91+
if (getPrismProgram != NULL) {
92+
jboolean prismProgram = (*env)->CallBooleanMethod(env, options, getPrismProgram);
93+
parser_options.prism_program = (prismProgram == JNI_TRUE);
94+
}
7195
}
7296

7397
hb_allocator_T allocator;

java/org/herb/HerbTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,51 @@ void testParserOptionsTrackWhitespace() {
102102
assertFalse(inspectWithout.contains("WhitespaceNode"));
103103
}
104104

105+
@Test
106+
void testParserOptionsPrismNodes() {
107+
String source = "<%= user.name %>";
108+
109+
ParseResult withPrism = Herb.parse(source, ParserOptions.create().prismNodes(true));
110+
String inspected = withPrism.inspect();
111+
112+
assertTrue(inspected.contains("prism_node:"));
113+
}
114+
115+
@Test
116+
void testParserOptionsPrismNodesString() {
117+
String source = "<%= \"String\" %>";
118+
119+
ParseResult withPrism = Herb.parse(source, ParserOptions.create().prismNodes(true));
120+
String inspected = withPrism.inspect();
121+
122+
assertTrue(inspected.contains("prism_node:"));
123+
assertTrue(inspected.contains("String"));
124+
}
125+
126+
@Test
127+
void testParserOptionsPrismProgram() {
128+
String source = "<%= \"hello\" %>";
129+
130+
ParseResult withPrism = Herb.parse(source, ParserOptions.create().prismProgram(true));
131+
String inspected = withPrism.inspect();
132+
133+
assertTrue(inspected.contains("prism_node:"));
134+
}
135+
136+
@Test
137+
void testParserOptionsPrismNodesDeep() {
138+
String source = "<% if true %><%= \"yes\" %><% end %>";
139+
140+
ParseResult withDeep = Herb.parse(source, ParserOptions.create().prismNodes(true).prismNodesDeep(true));
141+
ParseResult withoutDeep = Herb.parse(source, ParserOptions.create().prismNodes(true).prismNodesDeep(false));
142+
143+
String inspectDeep = withDeep.inspect();
144+
String inspectShallow = withoutDeep.inspect();
145+
146+
assertTrue(inspectDeep.contains("prism_node:"));
147+
assertTrue(inspectShallow.contains("prism_node:"));
148+
}
149+
105150
@Test
106151
void testParserOptionsAnalyze() {
107152
String source = "<% if true %><div></div><% end %>";

java/org/herb/ParseResult.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public String inspect() {
5050
StringBuilder builder = new StringBuilder();
5151

5252
if (value != null) {
53-
builder.append(value.inspect());
53+
builder.append(value.inspect(source));
5454
}
5555

5656
if (hasErrors()) {

java/org/herb/ParserOptions.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ public class ParserOptions {
55
private boolean analyze = true;
66
private boolean strict = true;
77
private boolean actionViewHelpers = false;
8+
private boolean prismNodes = false;
9+
private boolean prismNodesDeep = false;
10+
private boolean prismProgram = false;
811

912
public ParserOptions() {}
1013

@@ -44,6 +47,33 @@ public boolean isActionViewHelpers() {
4447
return actionViewHelpers;
4548
}
4649

50+
public ParserOptions prismNodes(boolean value) {
51+
this.prismNodes = value;
52+
return this;
53+
}
54+
55+
public boolean isPrismNodes() {
56+
return prismNodes;
57+
}
58+
59+
public ParserOptions prismNodesDeep(boolean value) {
60+
this.prismNodesDeep = value;
61+
return this;
62+
}
63+
64+
public boolean isPrismNodesDeep() {
65+
return prismNodesDeep;
66+
}
67+
68+
public ParserOptions prismProgram(boolean value) {
69+
this.prismProgram = value;
70+
return this;
71+
}
72+
73+
public boolean isPrismProgram() {
74+
return prismProgram;
75+
}
76+
4777
public static ParserOptions create() {
4878
return new ParserOptions();
4979
}

0 commit comments

Comments
 (0)