From 5ada84dfed00a6d7202e80e737722c16225a5877 Mon Sep 17 00:00:00 2001
From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com>
Date: Fri, 31 May 2024 08:49:17 +0200
Subject: [PATCH] Component attr values should be working.
---
.../main/antlr/WebViewComponentsLexerBase.g4 | 118 +++++++++++++++++-
.../antlr/AbstractWebViewComponentsLexer.java | 41 ++++++
.../src/test/lexer/componentValue.wvc | 1 +
.../src/test/lexer/componentValueNested.wvc | 1 +
.../src/test/lexer/componentValueNestedP.wvc | 1 +
.../test/lexer/componentValueSelfClosing.wvc | 1 +
.../src/test/lexer/componentValueWithNlws.wvc | 1 +
.../componentValueNestedP_tokens.txt | 27 ++++
.../componentValueNested_tokens.txt | 23 ++++
.../componentValueSelfClosing_tokens.txt | 14 +++
.../componentValueWithNlws_tokens.txt | 14 +++
.../tokens-files/componentValue_tokens.txt | 18 +++
.../src/test/parser/componentValue.wvc | 1 +
.../src/test/parser/componentValueNested.wvc | 1 +
.../src/test/parser/componentValueNestedP.wvc | 1 +
.../componentValueNestedP_parseTree.txt | 55 ++++++++
.../componentValueNested_parseTree.txt | 43 +++++++
.../componentValue_parseTree.txt | 40 ++++++
18 files changed, 397 insertions(+), 4 deletions(-)
create mode 100644 web-view-components-compiler/src/test/lexer/componentValue.wvc
create mode 100644 web-view-components-compiler/src/test/lexer/componentValueNested.wvc
create mode 100644 web-view-components-compiler/src/test/lexer/componentValueNestedP.wvc
create mode 100644 web-view-components-compiler/src/test/lexer/componentValueSelfClosing.wvc
create mode 100644 web-view-components-compiler/src/test/lexer/componentValueWithNlws.wvc
create mode 100644 web-view-components-compiler/src/test/lexer/tokens-files/componentValueNestedP_tokens.txt
create mode 100644 web-view-components-compiler/src/test/lexer/tokens-files/componentValueNested_tokens.txt
create mode 100644 web-view-components-compiler/src/test/lexer/tokens-files/componentValueSelfClosing_tokens.txt
create mode 100644 web-view-components-compiler/src/test/lexer/tokens-files/componentValueWithNlws_tokens.txt
create mode 100644 web-view-components-compiler/src/test/lexer/tokens-files/componentValue_tokens.txt
create mode 100644 web-view-components-compiler/src/test/parser/componentValue.wvc
create mode 100644 web-view-components-compiler/src/test/parser/componentValueNested.wvc
create mode 100644 web-view-components-compiler/src/test/parser/componentValueNestedP.wvc
create mode 100644 web-view-components-compiler/src/test/parser/parse-tree-files/componentValueNestedP_parseTree.txt
create mode 100644 web-view-components-compiler/src/test/parser/parse-tree-files/componentValueNested_parseTree.txt
create mode 100644 web-view-components-compiler/src/test/parser/parse-tree-files/componentValue_parseTree.txt
diff --git a/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4 b/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4
index 8dfacd5..5cf9db7 100644
--- a/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4
+++ b/web-view-components-compiler/src/main/antlr/WebViewComponentsLexerBase.g4
@@ -23,6 +23,7 @@ channels {
@header {
import java.util.Set;
+ import groowt.view.component.web.WebViewComponentBugError;
import static groowt.view.component.web.antlr.LexerSemanticPredicates.*;
}
@@ -109,6 +110,10 @@ channels {
this.pushMode(GROOVY_CODE);
}
+ private boolean inAttrComponent() {
+ return this.peekMode(1) == COMPONENT_ATTR_VALUE;
+ }
+
}
// ----------------------------------------
@@ -262,11 +267,30 @@ TagStartError
mode IN_TAG;
ComponentClose
- : GT -> popMode
+ : GT
+ {
+ if (this.inAttrComponent() && this.attrComponentFinished()) {
+ this.popMode();
+ this.popMode();
+ } else {
+ this.popMode();
+ }
+ }
;
ComponentSelfClose
- : FS GT -> popMode
+ : FS GT
+ {
+ if (this.inAttrComponent()) {
+ this.exitAttrComponent();
+ if (this.attrComponentFinished()) {
+ this.popMode();
+ this.popMode();
+ }
+ } else {
+ this.popMode();
+ }
+ }
;
ConstructorOpen
@@ -313,10 +337,14 @@ ClosureAttrValueStart
ComponentAttrValueStart
: LEFT_CURLY InTagNlws? { this.isNext('<') }?
+ {
+ this.enterAttrComponent();
+ this.pushMode(COMPONENT_ATTR_VALUE);
+ }
;
ComponentAttrValueEnd
- : GT RIGHT_CURLY
+ : InTagNlws? RIGHT_CURLY { this.popAttrComponent(); }
;
InTagNlws
@@ -327,6 +355,85 @@ TagError
: . -> channel(ERROR)
;
+// ----------------------------------------
+mode COMPONENT_ATTR_VALUE;
+
+AttrComponentOpen
+ : LT { !isAnyOf(this.getNextChar(), '/', '>') }? -> type(ComponentOpen), pushMode(TAG_START)
+ ;
+
+AttrClosingComponentOpen
+ : LT FS { !this.isNext('>') }?
+ {
+ this.exitAttrComponent();
+ } -> type(ClosingComponentOpen), pushMode(TAG_START)
+ ;
+
+AttrFragmentOpen
+ : LT GT -> type(FragmentOpen)
+ ;
+
+AttrFragmentClose
+ : LT FS GT {
+ this.exitAttrComponent();
+ } -> type(FragmentClose), popMode
+ ;
+
+AttrEqualsScriptletOpen
+ : LT PERCENT EQ -> type(EqualsScriptletOpen), pushMode(GROOVY_CODE)
+ ;
+
+AttrPlainScriptletOpen
+ : LT PERCENT -> type(PlainScriptletOpen), pushMode(GROOVY_CODE)
+ ;
+
+AttrDollarScriptletOpen
+ : DOLLAR LEFT_CURLY {
+ this.curlies.push(() -> {
+ this.setType(DollarScriptletClose);
+ this.popMode();
+ });
+ this.curlies.increment();
+ this.setType(DollarScriptletOpen);
+ this.pushMode(GROOVY_CODE);
+ }
+ ;
+
+AttrDollarReferenceStart
+ : DOLLAR { isGStringIdentifierStartChar(this.getNextChar()) }?
+ -> type(DollarReferenceStart), pushMode(IN_G_STRING_PATH)
+ ;
+
+AttrQuestionTagOpen
+ : LT QUESTION -> type(QuestionTagOpen)
+ ;
+
+AttrQuestionTagClose
+ : QUESTION GT -> type(QuestionTagClose)
+ ;
+
+AttrHtmlCommentOpen
+ : LT BANG TWO_DASH -> type(HtmlCommentOpen)
+ ;
+
+AttrHtmlCommentClose
+ : TWO_DASH GT -> type(HtmlCommentClose)
+ ;
+
+AttrRawText
+ : ( ~[-<$?]
+ | MINUS { !this.isNext("->") }?
+ | LT { canFollowLessThan(this.getNextCharAsString()) }?
+ | LT BANG { !this.isNext("--") }?
+ | DOLLAR { !(this.isNext('{') || isIdentifierStartChar(this.getNextChar())) }?
+ | QUESTION { !this.isNext('>') }?
+ )+ -> type(RawText)
+ ;
+
+AttrError
+ : . -> type(ErrorChar), channel(ERROR)
+ ;
+
// ----------------------------------------
mode GROOVY_CODE;
@@ -542,8 +649,11 @@ GStringPathEnd
yield new StringContinueEndSpec();
}
}
+ case COMPONENT_ATTR_VALUE -> new StringContinueEndSpec();
case MAIN -> new StringContinueEndSpec();
- default -> throw new IllegalStateException("not a valid gString context: " + this.getModeName(this.peekMode(1)));
+ default -> throw new IllegalStateException(
+ "not a valid gStringPath context: " + this.getModeName(this.peekMode(1))
+ );
};
switch (endSpec) {
case StringContinueEndSpec ignored -> {
diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/AbstractWebViewComponentsLexer.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/AbstractWebViewComponentsLexer.java
index 489b624..62b795d 100644
--- a/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/AbstractWebViewComponentsLexer.java
+++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/antlr/AbstractWebViewComponentsLexer.java
@@ -1,6 +1,7 @@
package groowt.view.component.web.antlr;
import groovyjarjarantlr4.runtime.Token;
+import groowt.view.component.web.WebViewComponentBugError;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.atn.ATN;
@@ -13,6 +14,7 @@ import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -57,6 +59,8 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
private boolean canPreamble = true;
private boolean inPreamble;
private boolean inConstructor;
+ private Deque attrComponentFinishedStack = new LinkedList<>();
+
public AbstractWebViewComponentsLexer(CharStream input) {
super(input);
@@ -115,6 +119,14 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.inConstructor = inConstructor;
}
+ public Deque getAttrComponentFinishedStack() {
+ return this.attrComponentFinishedStack;
+ }
+
+ public void setAttrComponentFinishedStack(Deque attrComponentFinishedStack) {
+ this.attrComponentFinishedStack = attrComponentFinishedStack;
+ }
+
@Override
public void reset() {
this.curlies.clear();
@@ -122,6 +134,7 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.canPreamble = true;
this.inPreamble = false;
this.inConstructor = false;
+ this.attrComponentFinishedStack = new LinkedList<>();
super.reset();
}
@@ -204,6 +217,30 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.inConstructor = false;
}
+ protected void enterAttrComponent() {
+ this.attrComponentFinishedStack.push(new AtomicBoolean());
+ }
+
+ protected void exitAttrComponent() {
+ final AtomicBoolean attrComponentFinished = this.attrComponentFinishedStack.peek();
+ if (attrComponentFinished == null) {
+ throw new WebViewComponentBugError(new IllegalStateException());
+ }
+ attrComponentFinished.set(true);
+ }
+
+ protected boolean attrComponentFinished() {
+ final AtomicBoolean attrComponentFinished = this.attrComponentFinishedStack.peek();
+ if (attrComponentFinished == null) {
+ throw new WebViewComponentBugError(new IllegalStateException());
+ }
+ return attrComponentFinished.get();
+ }
+
+ protected void popAttrComponent() {
+ this.attrComponentFinishedStack.pop();
+ }
+
protected String getNextCharsAsString(int numberOfChars) {
final var b = new StringBuilder();
for (int i = 1; i <= numberOfChars; i++) {
@@ -224,6 +261,10 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
return this._input.LA(1);
}
+ protected boolean isPrevious(char test) {
+ return this._input.LA(-2) == test;
+ }
+
protected boolean isNext(char test) {
return this._input.LA(1) == test;
}
diff --git a/web-view-components-compiler/src/test/lexer/componentValue.wvc b/web-view-components-compiler/src/test/lexer/componentValue.wvc
new file mode 100644
index 0000000..d2c4f48
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/componentValue.wvc
@@ -0,0 +1 @@
+$it
} />
diff --git a/web-view-components-compiler/src/test/lexer/componentValueNested.wvc b/web-view-components-compiler/src/test/lexer/componentValueNested.wvc
new file mode 100644
index 0000000..5dde995
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/componentValueNested.wvc
@@ -0,0 +1 @@
+} />} />
diff --git a/web-view-components-compiler/src/test/lexer/componentValueNestedP.wvc b/web-view-components-compiler/src/test/lexer/componentValueNestedP.wvc
new file mode 100644
index 0000000..d0f2cda
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/componentValueNestedP.wvc
@@ -0,0 +1 @@
+$it} />} />
diff --git a/web-view-components-compiler/src/test/lexer/componentValueSelfClosing.wvc b/web-view-components-compiler/src/test/lexer/componentValueSelfClosing.wvc
new file mode 100644
index 0000000..4c11a85
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/componentValueSelfClosing.wvc
@@ -0,0 +1 @@
+} />
diff --git a/web-view-components-compiler/src/test/lexer/componentValueWithNlws.wvc b/web-view-components-compiler/src/test/lexer/componentValueWithNlws.wvc
new file mode 100644
index 0000000..3ce0887
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/componentValueWithNlws.wvc
@@ -0,0 +1 @@
+ } />
diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/componentValueNestedP_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/componentValueNestedP_tokens.txt
new file mode 100644
index 0000000..201b635
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/tokens-files/componentValueNestedP_tokens.txt
@@ -0,0 +1,27 @@
+0: ComponentOpen[1,1](<)
+1: TypedIdentifier[1,2](Test)
+2: Nlws[1,6]( )
+3: AttributeIdentifier[1,7](transform)
+4: Equals[1,16](=)
+5: ComponentAttrValueStart[1,17]({)
+6: ComponentOpen[1,18](<)
+7: TypedIdentifier[1,19](Test)
+8: Nlws[1,23]( )
+9: AttributeIdentifier[1,24](transform)
+10: Equals[1,33](=)
+11: ComponentAttrValueStart[1,34]({)
+12: ComponentOpen[1,35](<)
+13: StringIdentifier[1,36](p)
+14: ComponentClose[1,37](>)
+15: DollarReferenceStart[1,38]($)
+16: GroovyCode[1,39](it)
+17: ClosingComponentOpen[1,41]()
+18: StringIdentifier[1,43](p)
+19: ComponentClose[1,44](>)
+20: ComponentAttrValueEnd[1,45](})
+21: Nlws[1,46]( )
+22: ComponentSelfClose[1,47](/>)
+23: ComponentAttrValueEnd[1,49](})
+24: Nlws[1,50]( )
+25: ComponentSelfClose[1,51](/>)
+26: RawText[1,53](\n)
\ No newline at end of file
diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/componentValueNested_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/componentValueNested_tokens.txt
new file mode 100644
index 0000000..475ea8b
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/tokens-files/componentValueNested_tokens.txt
@@ -0,0 +1,23 @@
+0: ComponentOpen[1,1](<)
+1: TypedIdentifier[1,2](Test)
+2: Nlws[1,6]( )
+3: AttributeIdentifier[1,7](transform)
+4: Equals[1,16](=)
+5: ComponentAttrValueStart[1,17]({)
+6: ComponentOpen[1,18](<)
+7: TypedIdentifier[1,19](Test)
+8: Nlws[1,23]( )
+9: AttributeIdentifier[1,24](transform)
+10: Equals[1,33](=)
+11: ComponentAttrValueStart[1,34]({)
+12: ComponentOpen[1,35](<)
+13: TypedIdentifier[1,36](Test)
+14: Nlws[1,40]( )
+15: ComponentSelfClose[1,41](/>)
+16: ComponentAttrValueEnd[1,43](})
+17: Nlws[1,44]( )
+18: ComponentSelfClose[1,45](/>)
+19: ComponentAttrValueEnd[1,47](})
+20: Nlws[1,48]( )
+21: ComponentSelfClose[1,49](/>)
+22: RawText[1,51](\n)
\ No newline at end of file
diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/componentValueSelfClosing_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/componentValueSelfClosing_tokens.txt
new file mode 100644
index 0000000..ea7e087
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/tokens-files/componentValueSelfClosing_tokens.txt
@@ -0,0 +1,14 @@
+0: ComponentOpen[1,1](<)
+1: TypedIdentifier[1,2](Test)
+2: Nlws[1,6]( )
+3: AttributeIdentifier[1,7](transform)
+4: Equals[1,16](=)
+5: ComponentAttrValueStart[1,17]({)
+6: ComponentOpen[1,18](<)
+7: TypedIdentifier[1,19](Test)
+8: Nlws[1,23]( )
+9: ComponentSelfClose[1,24](/>)
+10: ComponentAttrValueEnd[1,26](})
+11: Nlws[1,27]( )
+12: ComponentSelfClose[1,28](/>)
+13: RawText[1,30](\n)
\ No newline at end of file
diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/componentValueWithNlws_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/componentValueWithNlws_tokens.txt
new file mode 100644
index 0000000..98e7fba
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/tokens-files/componentValueWithNlws_tokens.txt
@@ -0,0 +1,14 @@
+0: ComponentOpen[1,1](<)
+1: TypedIdentifier[1,2](Test)
+2: Nlws[1,6]( )
+3: AttributeIdentifier[1,7](test)
+4: Equals[1,11](=)
+5: ComponentAttrValueStart[1,12]({ )
+6: ComponentOpen[1,14](<)
+7: TypedIdentifier[1,15](Test)
+8: Nlws[1,19]( )
+9: ComponentSelfClose[1,20](/>)
+10: ComponentAttrValueEnd[1,22]( })
+11: Nlws[1,24]( )
+12: ComponentSelfClose[1,25](/>)
+13: RawText[1,27](\n)
\ No newline at end of file
diff --git a/web-view-components-compiler/src/test/lexer/tokens-files/componentValue_tokens.txt b/web-view-components-compiler/src/test/lexer/tokens-files/componentValue_tokens.txt
new file mode 100644
index 0000000..f1c1f5e
--- /dev/null
+++ b/web-view-components-compiler/src/test/lexer/tokens-files/componentValue_tokens.txt
@@ -0,0 +1,18 @@
+0: ComponentOpen[1,1](<)
+1: TypedIdentifier[1,2](Test)
+2: Nlws[1,6]( )
+3: AttributeIdentifier[1,7](transform)
+4: Equals[1,16](=)
+5: ComponentAttrValueStart[1,17]({)
+6: ComponentOpen[1,18](<)
+7: StringIdentifier[1,19](p)
+8: ComponentClose[1,20](>)
+9: DollarReferenceStart[1,21]($)
+10: GroovyCode[1,22](it)
+11: ClosingComponentOpen[1,24]()
+12: StringIdentifier[1,26](p)
+13: ComponentClose[1,27](>)
+14: ComponentAttrValueEnd[1,28](})
+15: Nlws[1,29]( )
+16: ComponentSelfClose[1,30](/>)
+17: RawText[1,32](\n)
\ No newline at end of file
diff --git a/web-view-components-compiler/src/test/parser/componentValue.wvc b/web-view-components-compiler/src/test/parser/componentValue.wvc
new file mode 100644
index 0000000..8a686fc
--- /dev/null
+++ b/web-view-components-compiler/src/test/parser/componentValue.wvc
@@ -0,0 +1 @@
+$it} />
diff --git a/web-view-components-compiler/src/test/parser/componentValueNested.wvc b/web-view-components-compiler/src/test/parser/componentValueNested.wvc
new file mode 100644
index 0000000..4758b5f
--- /dev/null
+++ b/web-view-components-compiler/src/test/parser/componentValueNested.wvc
@@ -0,0 +1 @@
+} />} />
diff --git a/web-view-components-compiler/src/test/parser/componentValueNestedP.wvc b/web-view-components-compiler/src/test/parser/componentValueNestedP.wvc
new file mode 100644
index 0000000..d582a57
--- /dev/null
+++ b/web-view-components-compiler/src/test/parser/componentValueNestedP.wvc
@@ -0,0 +1 @@
+$it} />} />
diff --git a/web-view-components-compiler/src/test/parser/parse-tree-files/componentValueNestedP_parseTree.txt b/web-view-components-compiler/src/test/parser/parse-tree-files/componentValueNestedP_parseTree.txt
new file mode 100644
index 0000000..dc9dcff
--- /dev/null
+++ b/web-view-components-compiler/src/test/parser/parse-tree-files/componentValueNestedP_parseTree.txt
@@ -0,0 +1,55 @@
+compilationUnit[1,1..2,1]
+ body[1,1..1,43]
+ component[1,1..1,43]
+ selfClosingComponent[1,1..1,43]
+ ComponentOpen[1,1](<)
+ componentArgs[1,2..1,39]
+ componentType[1,2..1,6]
+ TypedIdentifier[1,2](Test)
+ attr[1,7..1,39]
+ keyValueAttr[1,7..1,39]
+ AttributeIdentifier[1,7](test)
+ Equals[1,11](=)
+ value[1,12..1,39]
+ componentAttrValue[1,12..1,39]
+ ComponentAttrValueStart[1,12]({)
+ component[1,13..1,39]
+ selfClosingComponent[1,13..1,39]
+ ComponentOpen[1,13](<)
+ componentArgs[1,14..1,35]
+ componentType[1,14..1,18]
+ TypedIdentifier[1,14](Test)
+ attr[1,19..1,35]
+ keyValueAttr[1,19..1,35]
+ AttributeIdentifier[1,19](test)
+ Equals[1,23](=)
+ value[1,24..1,35]
+ componentAttrValue[1,24..1,35]
+ ComponentAttrValueStart[1,24]({)
+ component[1,25..1,34]
+ componentWithChildren[1,25..1,34]
+ openComponent[1,25..1,27]
+ ComponentOpen[1,25](<)
+ componentArgs[1,26..1,26]
+ componentType[1,26..1,26]
+ StringIdentifier[1,26](p)
+ ComponentClose[1,27](>)
+ body[1,28..1,31]
+ bodyText[1,28..1,31]
+ bodyTextGroovyElement[1,28..1,31]
+ dollarReference[1,28..1,31]
+ DollarReferenceStart[1,28]($)
+ GroovyCode[1,29](it)
+ closingComponent[1,31..1,34]
+ ClosingComponentOpen[1,31]()
+ componentType[1,33..1,33]
+ StringIdentifier[1,33](p)
+ ComponentClose[1,34](>)
+ ComponentAttrValueEnd[1,35](})
+ ComponentSelfClose[1,37](/>)
+ ComponentAttrValueEnd[1,39](})
+ ComponentSelfClose[1,41](/>)
+ bodyText[1,43..1,43]
+ text[1,43..1,43]
+ RawText[1,43](\n)
+ EOF[2,1]()
diff --git a/web-view-components-compiler/src/test/parser/parse-tree-files/componentValueNested_parseTree.txt b/web-view-components-compiler/src/test/parser/parse-tree-files/componentValueNested_parseTree.txt
new file mode 100644
index 0000000..512c8f0
--- /dev/null
+++ b/web-view-components-compiler/src/test/parser/parse-tree-files/componentValueNested_parseTree.txt
@@ -0,0 +1,43 @@
+compilationUnit[1,1..2,1]
+ body[1,1..1,41]
+ component[1,1..1,41]
+ selfClosingComponent[1,1..1,41]
+ ComponentOpen[1,1](<)
+ componentArgs[1,2..1,37]
+ componentType[1,2..1,6]
+ TypedIdentifier[1,2](Test)
+ attr[1,7..1,37]
+ keyValueAttr[1,7..1,37]
+ AttributeIdentifier[1,7](test)
+ Equals[1,11](=)
+ value[1,12..1,37]
+ componentAttrValue[1,12..1,37]
+ ComponentAttrValueStart[1,12]({)
+ component[1,13..1,37]
+ selfClosingComponent[1,13..1,37]
+ ComponentOpen[1,13](<)
+ componentArgs[1,14..1,33]
+ componentType[1,14..1,18]
+ TypedIdentifier[1,14](Test)
+ attr[1,19..1,33]
+ keyValueAttr[1,19..1,33]
+ AttributeIdentifier[1,19](test)
+ Equals[1,23](=)
+ value[1,24..1,33]
+ componentAttrValue[1,24..1,33]
+ ComponentAttrValueStart[1,24]({)
+ component[1,25..1,33]
+ selfClosingComponent[1,25..1,33]
+ ComponentOpen[1,25](<)
+ componentArgs[1,26..1,30]
+ componentType[1,26..1,30]
+ TypedIdentifier[1,26](Test)
+ ComponentSelfClose[1,31](/>)
+ ComponentAttrValueEnd[1,33](})
+ ComponentSelfClose[1,35](/>)
+ ComponentAttrValueEnd[1,37](})
+ ComponentSelfClose[1,39](/>)
+ bodyText[1,41..1,41]
+ text[1,41..1,41]
+ RawText[1,41](\n)
+ EOF[2,1]()
diff --git a/web-view-components-compiler/src/test/parser/parse-tree-files/componentValue_parseTree.txt b/web-view-components-compiler/src/test/parser/parse-tree-files/componentValue_parseTree.txt
new file mode 100644
index 0000000..5632c8e
--- /dev/null
+++ b/web-view-components-compiler/src/test/parser/parse-tree-files/componentValue_parseTree.txt
@@ -0,0 +1,40 @@
+compilationUnit[1,1..2,1]
+ body[1,1..1,27]
+ component[1,1..1,27]
+ selfClosingComponent[1,1..1,27]
+ ComponentOpen[1,1](<)
+ componentArgs[1,2..1,23]
+ componentType[1,2..1,6]
+ TypedIdentifier[1,2](Test)
+ attr[1,7..1,23]
+ keyValueAttr[1,7..1,23]
+ AttributeIdentifier[1,7](test)
+ Equals[1,11](=)
+ value[1,12..1,23]
+ componentAttrValue[1,12..1,23]
+ ComponentAttrValueStart[1,12]({)
+ component[1,13..1,22]
+ componentWithChildren[1,13..1,22]
+ openComponent[1,13..1,15]
+ ComponentOpen[1,13](<)
+ componentArgs[1,14..1,14]
+ componentType[1,14..1,14]
+ StringIdentifier[1,14](p)
+ ComponentClose[1,15](>)
+ body[1,16..1,19]
+ bodyText[1,16..1,19]
+ bodyTextGroovyElement[1,16..1,19]
+ dollarReference[1,16..1,19]
+ DollarReferenceStart[1,16]($)
+ GroovyCode[1,17](it)
+ closingComponent[1,19..1,22]
+ ClosingComponentOpen[1,19]()
+ componentType[1,21..1,21]
+ StringIdentifier[1,21](p)
+ ComponentClose[1,22](>)
+ ComponentAttrValueEnd[1,23](})
+ ComponentSelfClose[1,25](/>)
+ bodyText[1,27..1,27]
+ text[1,27..1,27]
+ RawText[1,27](\n)
+ EOF[2,1]()