Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #63 from dmarcotte/block-match-inspection

Enhance mismatched block mustache errors
  • Loading branch information...
commit 50ff858b18d63da335c99cfff6d65b27eb651ea3 2 parents dd40a10 + 31b2989
@dmarcotte authored
Showing with 347 additions and 105 deletions.
  1. +1 −0  META-INF/plugin.xml
  2. +7 −3 resources/messages/HbBundle.properties
  3. +102 −0 src/com/dmarcotte/handlebars/inspections/HbBlockMismatchFix.java
  4. +58 −0 src/com/dmarcotte/handlebars/inspections/HbBlockMismatchInspection.java
  5. +12 −67 src/com/dmarcotte/handlebars/parsing/HbParsing.java
  6. +29 −0 src/com/dmarcotte/handlebars/psi/HbBlockMustache.java
  7. +4 −1 src/com/dmarcotte/handlebars/psi/HbCloseBlockMustache.java
  8. +4 −1 src/com/dmarcotte/handlebars/psi/HbOpenBlockMustache.java
  9. +27 −0 src/com/dmarcotte/handlebars/psi/impl/HbBlockMustacheImpl.java
  10. +13 −1 src/com/dmarcotte/handlebars/psi/impl/HbCloseBlockMustacheImpl.java
  11. +10 −6 src/com/dmarcotte/handlebars/psi/impl/HbOpenBlockMustacheImpl.java
  12. +2 −3 test/data/folding/foldsWithUnclosedBlocks.hbs
  13. +4 −0 test/data/inspections/afterWrongCloseBlock1.hbs
  14. +4 −0 test/data/inspections/afterWrongCloseBlock2.hbs
  15. +4 −0 test/data/inspections/afterWrongOpenBlock1.hbs
  16. +4 −0 test/data/inspections/afterWrongOpenBlock2.hbs
  17. +4 −0 test/data/inspections/beforeWrongCloseBlock1.hbs
  18. +4 −0 test/data/inspections/beforeWrongCloseBlock2.hbs
  19. +4 −0 test/data/inspections/beforeWrongOpenBlock1.hbs
  20. +4 −0 test/data/inspections/beforeWrongOpenBlock2.hbs
  21. +8 −9 test/data/parser/CloseNotFollowingOpen.txt
  22. +14 −12 test/data/parser/PoorlyNestedMustaches.txt
  23. +3 −2 test/src/com/dmarcotte/handlebars/editor/actions/HbEnterHandlerTest.java
  24. +21 −0 test/src/com/dmarcotte/handlebars/inspections/HbBlockMismatchFixTest.java
View
1  META-INF/plugin.xml
@@ -224,5 +224,6 @@
<codeFoldingOptionsProvider
instance="com.dmarcotte.handlebars.config.HbFoldingOptionsProvider" />
<lang.psiStructureViewFactory language="Handlebars" implementationClass="com.dmarcotte.handlebars.structure.HbStructureViewFactory"/>
+ <annotator language="Handlebars" implementationClass="com.dmarcotte.handlebars.inspections.HbBlockMismatchInspection"/>
</extensions>
</idea-plugin>
View
10 resources/messages/HbBundle.properties
@@ -10,8 +10,6 @@ hb.page.colors.descriptor.data.key=Data
hb.page.colors.descriptor.escape.key=Escape Character
hb.parsing.no.open.mustache=No corresponding open mustache
hb.parsing.invalid=Invalid
-hb.parsing.block.not.closed="{0}" block not closed
-hb.parsing.end.tag.bad.match="{0}" does not match "{1}" from block start
hb.parsing.expected.path.or.data=Expected a path or @data
hb.parsing.expected.parameter=Expected a parameter
hb.parsing.expected.hash=Expected a hash
@@ -41,4 +39,10 @@ hb.pages.options.title=Handlebars/Mustache
hb.pages.folding.auto.collapse.blocks=Handlebars/Mustache blocks
hb.page.options.commenter.language=&Language for comments\:
hb.page.options.commenter.language.tooltip=Controls which language's comment syntax to use for "Comment with Block Comment" and "Comment with Line Comment" actions
-hb.parsing.comment.unclosed=Unclosed comment
+hb.parsing.comment.unclosed=Unclosed comment
+hb.block.mismatch.intention.rename.open=Change block start ''{0}'' to ''{1}''
+hb.block.mismatch.intention.rename.close=Change block end ''{0}'' to ''{1}''
+hb.block.mismatch.inspection.open.block=''{0}'' does not match ''{1}'' from block end
+hb.block.mismatch.inspection.close.block=''{1}'' does not match ''{0}'' from block start
+hb.block.mismatch.inspection.missing.end.block=''{0}'' block not closed
+hb.block.mismatch.inspection.missing.start.block=No block start for ''{0}''
View
102 src/com/dmarcotte/handlebars/inspections/HbBlockMismatchFix.java
@@ -0,0 +1,102 @@
+package com.dmarcotte.handlebars.inspections;
+
+import com.dmarcotte.handlebars.HbBundle;
+import com.dmarcotte.handlebars.psi.HbBlockMustache;
+import com.dmarcotte.handlebars.psi.HbOpenBlockMustache;
+import com.dmarcotte.handlebars.psi.HbPath;
+import com.intellij.codeInsight.CodeInsightUtilBase;
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+
+class HbBlockMismatchFix implements IntentionAction {
+ private final boolean myUpdateOpenMustache;
+ private final String myCorrectedName;
+ private final String myOriginalName;
+
+ /**
+ * @param correctedName The name this action will update a block element to
+ * @param originalName The original name of the element this action corrects
+ * @param updateOpenMustache Whether or not this updates the open mustache of this block
+ */
+ public HbBlockMismatchFix(String correctedName, String originalName, boolean updateOpenMustache) {
+ myUpdateOpenMustache = updateOpenMustache;
+ myCorrectedName = correctedName;
+ myOriginalName = originalName;
+ }
+
+ @NotNull
+ @Override
+ public String getText() {
+ return getName();
+ }
+
+ @NotNull
+ @Override
+ public String getFamilyName() {
+ return getName();
+ }
+
+ @Override
+ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
+ return true;
+ }
+
+ @Override
+ public void invoke(@NotNull Project project, Editor editor, PsiFile file)
+ throws IncorrectOperationException {
+ final int offset = editor.getCaretModel().getOffset();
+ PsiElement psiElement = file.findElementAt(offset);
+
+ if (psiElement == null || !psiElement.isValid()) return;
+ if (!CodeInsightUtilBase.prepareFileForWrite(psiElement.getContainingFile())) return;
+
+ if (psiElement instanceof PsiWhiteSpace) psiElement = PsiTreeUtil.prevLeaf(psiElement);
+
+ HbBlockMustache blockMustache = (HbBlockMustache) PsiTreeUtil.findFirstParent(psiElement, true, new Condition<PsiElement>() {
+ @Override
+ public boolean value(PsiElement psiElement) {
+ return psiElement instanceof HbBlockMustache;
+ }
+ });
+
+ if (blockMustache == null) {
+ return;
+ }
+
+ HbBlockMustache targetBlockMustache = blockMustache;
+
+ // ensure we update the open or close mustache for this block appropriately
+ if (myUpdateOpenMustache != (targetBlockMustache instanceof HbOpenBlockMustache)) {
+ targetBlockMustache = blockMustache.getPairedElement();
+ }
+
+ HbPath path = PsiTreeUtil.findChildOfType(targetBlockMustache, HbPath.class);
+ final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
+ if (path != null && document != null) {
+ final TextRange textRange = path.getTextRange();
+ document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(), myCorrectedName);
+ }
+ }
+
+ @Override
+ public boolean startInWriteAction() {
+ return true;
+ }
+
+ private String getName() {
+ return myUpdateOpenMustache
+ ? HbBundle.message("hb.block.mismatch.intention.rename.open", myOriginalName, myCorrectedName)
+ : HbBundle.message("hb.block.mismatch.intention.rename.close", myOriginalName, myCorrectedName);
+ }
+}
View
58 src/com/dmarcotte/handlebars/inspections/HbBlockMismatchInspection.java
@@ -0,0 +1,58 @@
+package com.dmarcotte.handlebars.inspections;
+
+import com.dmarcotte.handlebars.HbBundle;
+import com.dmarcotte.handlebars.psi.HbCloseBlockMustache;
+import com.dmarcotte.handlebars.psi.HbOpenBlockMustache;
+import com.dmarcotte.handlebars.psi.HbPath;
+import com.intellij.lang.annotation.Annotation;
+import com.intellij.lang.annotation.AnnotationHolder;
+import com.intellij.lang.annotation.Annotator;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+
+public class HbBlockMismatchInspection implements Annotator {
+ @Override
+ public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
+ if (element instanceof HbOpenBlockMustache) {
+ HbOpenBlockMustache openBlockMustache = (HbOpenBlockMustache) element;
+ HbPath openBlockMainPath = openBlockMustache.getBlockMainPath();
+
+ HbCloseBlockMustache closeBlockMustache = openBlockMustache.getPairedElement();
+ if (closeBlockMustache != null) {
+ HbPath closeBlockMainPath = closeBlockMustache.getBlockMainPath();
+
+ if (openBlockMainPath == null || closeBlockMainPath == null) {
+ return;
+ }
+
+ String openBlockName = openBlockMainPath.getName();
+ String closeBlockName = closeBlockMainPath.getName();
+ if (!openBlockName.equals(closeBlockName)) {
+ Annotation openBlockAnnotation
+ = holder.createErrorAnnotation(openBlockMainPath, HbBundle.message("hb.block.mismatch.inspection.open.block", openBlockName, closeBlockName));
+ openBlockAnnotation.registerFix(new HbBlockMismatchFix(closeBlockName, openBlockName, true));
+ openBlockAnnotation.registerFix(new HbBlockMismatchFix(openBlockName, closeBlockName, false));
+
+ Annotation closeBlockAnnotation
+ = holder.createErrorAnnotation(closeBlockMainPath, HbBundle.message("hb.block.mismatch.inspection.close.block", openBlockName, closeBlockName));
+ closeBlockAnnotation.registerFix(new HbBlockMismatchFix(openBlockName, closeBlockName, false));
+ closeBlockAnnotation.registerFix(new HbBlockMismatchFix(closeBlockName, openBlockName, true));
+ }
+ } else {
+ holder.createErrorAnnotation(openBlockMainPath, HbBundle.message("hb.block.mismatch.inspection.missing.end.block", openBlockMustache.getName()));
+ }
+ }
+
+ if (element instanceof HbCloseBlockMustache) {
+ HbCloseBlockMustache closeBlockMustache = (HbCloseBlockMustache) element;
+ PsiElement openBlockElement = closeBlockMustache.getPairedElement();
+ if (openBlockElement == null) {
+ HbPath closeBlockMainPath = closeBlockMustache.getBlockMainPath();
+ if (closeBlockMainPath == null) {
+ return;
+ }
+ holder.createErrorAnnotation(closeBlockMainPath, HbBundle.message("hb.block.mismatch.inspection.missing.start.block", closeBlockMustache.getName()));
+ }
+ }
+ }
+}
View
79 src/com/dmarcotte/handlebars/parsing/HbParsing.java
@@ -4,7 +4,6 @@
import com.dmarcotte.handlebars.exception.ShouldNotHappenException;
import com.intellij.lang.PsiBuilder;
import com.intellij.psi.tree.IElementType;
-import com.intellij.util.containers.Stack;
import java.util.HashSet;
import java.util.Set;
@@ -34,12 +33,12 @@
import static com.dmarcotte.handlebars.parsing.HbTokenTypes.PARAM;
import static com.dmarcotte.handlebars.parsing.HbTokenTypes.PARTIAL_NAME;
import static com.dmarcotte.handlebars.parsing.HbTokenTypes.PARTIAL_STACHE;
+import static com.dmarcotte.handlebars.parsing.HbTokenTypes.PATH;
import static com.dmarcotte.handlebars.parsing.HbTokenTypes.SEP;
import static com.dmarcotte.handlebars.parsing.HbTokenTypes.SIMPLE_INVERSE;
import static com.dmarcotte.handlebars.parsing.HbTokenTypes.STATEMENTS;
import static com.dmarcotte.handlebars.parsing.HbTokenTypes.STRING;
import static com.dmarcotte.handlebars.parsing.HbTokenTypes.UNCLOSED_COMMENT;
-import static com.dmarcotte.handlebars.parsing.HbTokenTypes.PATH;
/**
* The parser is based directly on Handlebars.yy
@@ -52,7 +51,6 @@
*/
class HbParsing {
private final PsiBuilder builder;
- private final Stack<String> openTagNamesStack = new Stack<String>();
// the set of tokens which, if we encounter them while in a bad state, we'll try to
// resume parsing from them
@@ -84,9 +82,7 @@ public void parse() {
int problemOffset = builder.getCurrentOffset();
if (tokenType == OPEN_ENDBLOCK) {
- PsiBuilder.Marker badEndBlockMarker = builder.mark();
parseCloseBlock(builder);
- badEndBlockMarker.error(HbBundle.message("hb.parsing.no.open.mustache"));
}
if (builder.getCurrentOffset() == problemOffset) {
@@ -174,9 +170,8 @@ private boolean parseStatement(PsiBuilder builder) {
}
PsiBuilder.Marker blockMarker = builder.mark();
- PsiBuilder.Marker openInverseMarker = builder.mark();
if (parseOpenInverse(builder)) {
- openBlockMarker(builder, openInverseMarker, blockMarker);
+ parseRestOfBlock(builder, blockMarker);
} else {
return false;
}
@@ -186,9 +181,8 @@ private boolean parseStatement(PsiBuilder builder) {
if (tokenType == OPEN_BLOCK) {
PsiBuilder.Marker blockMarker = builder.mark();
- PsiBuilder.Marker openBlockMarker = builder.mark();
if (parseOpenBlock(builder)) {
- openBlockMarker(builder, openBlockMarker, blockMarker);
+ parseRestOfBlock(builder, blockMarker);
} else {
return false;
}
@@ -233,25 +227,13 @@ private boolean parseStatement(PsiBuilder builder) {
}
/**
- * Helper method to take care of the business need after an "open-type mustache" (openBlock or openInverse),
- * including ensuring we've got the right close tag
+ * Helper method to take care of the business needed after an "open-type mustache" (openBlock or openInverse)
*
- * NOTE: will resolve the given openMustacheMarker
+ * NOTE: will resolve the given blockMarker
*/
- private void openBlockMarker(PsiBuilder builder, PsiBuilder.Marker openMustacheMarker, PsiBuilder.Marker blockMarker) {
- PsiBuilder.Marker parseProgramMarker = builder.mark();
+ private void parseRestOfBlock(PsiBuilder builder, PsiBuilder.Marker blockMarker) {
parseProgram(builder);
- if(parseCloseBlock(builder)) {
- openMustacheMarker.drop();
- } else {
- if (!openTagNamesStack.empty()) {
- openMustacheMarker.errorBefore(HbBundle.message("hb.parsing.block.not.closed", openTagNamesStack.pop()), parseProgramMarker);
- } else {
- openMustacheMarker.drop();
- }
- }
- parseProgramMarker.drop();
-
+ parseCloseBlock(builder);
blockMarker.done(HbTokenTypes.BLOCK_WRAPPER);
}
@@ -267,7 +249,7 @@ private boolean parseOpenBlock(PsiBuilder builder) {
return false;
}
- if (parseInMustache(builder, true)) {
+ if (parseInMustache(builder)) {
parseLeafTokenGreedy(builder, CLOSE);
}
@@ -297,7 +279,7 @@ private boolean parseOpenInverse(PsiBuilder builder) {
regularInverseMarker.drop();
}
- if(parseInMustache(builder, true)) {
+ if(parseInMustache(builder)) {
parseLeafTokenGreedy(builder, CLOSE);
}
@@ -318,26 +300,6 @@ private boolean parseCloseBlock(PsiBuilder builder) {
return false;
}
- // HB_CUSTOMIZATION: we store open/close IDs to detect mismatches. Note that if the current token is not
- // an id, we do nothing: the actual parser takes care of detecting the problem
- if (builder.getTokenType() == HbTokenTypes.ID && !openTagNamesStack.empty()) {
- String expectedCloseTag = openTagNamesStack.pop();
- String actualCloseTag = builder.getTokenText();
- if (!expectedCloseTag.equals(actualCloseTag)) {
- // advance all the way to a recovery token or the close stache for this open block 'stache
- while (builder.getTokenType() != CLOSE && !RECOVERY_SET.contains(builder.getTokenType()) && !builder.eof()) {
- builder.advanceLexer();
- }
-
- if (builder.getTokenType() == CLOSE) {
- builder.advanceLexer();
- }
- closeBlockMarker.error(
- HbBundle.message("hb.parsing.end.tag.bad.match", actualCloseTag, expectedCloseTag));
- return true;
- }
- }
-
if(parsePath(builder)) {
parseLeafToken(builder, CLOSE);
}
@@ -362,7 +324,7 @@ private void parseMustache(PsiBuilder builder) {
throw new ShouldNotHappenException();
}
- parseInMustache(builder, false);
+ parseInMustache(builder);
// whether our parseInMustache hit trouble or not, we absolutely must have
// a CLOSE token, so let's find it
parseLeafTokenGreedy(builder, CLOSE);
@@ -446,22 +408,11 @@ private boolean parseSimpleInverse(PsiBuilder builder) {
* | path { $$ = [[$1], null]; }
* | DATA { $$ = [[new yy.DataNode($1)], null]; }
* ;
- *
- * @param hasOpenTag is used to tell this method that the first ID in this 'stache is the open
- * tag of a block (this method stores it so that we can compare to the close tag later)
*/
- private boolean parseInMustache(PsiBuilder builder, boolean hasOpenTag) {
+ private boolean parseInMustache(PsiBuilder builder) {
PsiBuilder.Marker inMustacheMarker = builder.mark();
- // HB_CUSTOMIZATION: we store open/close IDs to detect mismatches. Note that if the current token is not
- // an id, we do nothing: the actual parser takes care of detecting the problem
- if (hasOpenTag && builder.getTokenType() == HbTokenTypes.ID) {
- openTagNamesStack.push(builder.getTokenText());
- }
-
- PsiBuilder.Marker pathMarker = builder.mark();
if (!parsePath(builder)) {
- pathMarker.rollbackTo();
// not a path, try to parse DATA
if (builder.getTokenType() == DATA_PREFIX
&& parseLeafToken(builder, DATA_PREFIX)
@@ -472,8 +423,6 @@ private boolean parseInMustache(PsiBuilder builder, boolean hasOpenTag) {
inMustacheMarker.error(HbBundle.message("hb.parsing.expected.path.or.data"));
return false;
}
- } else {
- pathMarker.drop();
}
// try to extend the 'path' we found to 'path hash'
@@ -550,13 +499,9 @@ private boolean parseParams(PsiBuilder builder) {
private boolean parseParam(PsiBuilder builder) {
PsiBuilder.Marker paramMarker = builder.mark();
- PsiBuilder.Marker pathMarker = builder.mark();
if (parsePath(builder)) {
- pathMarker.drop();
paramMarker.done(PARAM);
return true;
- } else {
- pathMarker.rollbackTo();
}
PsiBuilder.Marker stringMarker = builder.mark();
@@ -675,7 +620,7 @@ private boolean parsePath(PsiBuilder builder) {
pathMarker.done(PATH);
return true;
}
- pathMarker.drop();
+ pathMarker.rollbackTo();
return false;
}
View
29 src/com/dmarcotte/handlebars/psi/HbBlockMustache.java
@@ -0,0 +1,29 @@
+package com.dmarcotte.handlebars.psi;
+
+/**
+ * Base type for all mustaches which define blocks (openBlock, openInverseBlock, closeBlock... others in the future?)
+ */
+public interface HbBlockMustache extends HbPsiElement {
+
+ /**
+ * Returns the {@link HbPath} which is the "main" path in this block. i.e. the "foo.bar" in
+ * <pre>{{#foo.bar baz}}</pre>
+ * and
+ * <pre>{{/foo.bar}}</pre>
+ *
+ * <p>
+ * This path is important because it is used to pair open and close block mustaches
+ *
+ * @return the main {@link HbPath} for this block or null if none found (which should only happen if there are
+ * currently parse errors in the file)
+ */
+ public HbPath getBlockMainPath();
+
+ /**
+ * Get the block element paired with this one
+ *
+ * @return the matching {@link HbBlockMustache} element (i.e. for {{#foo}}, returns {{/foo}} and vice-versa or null
+ * if none found (which should only happen if there are currently parse errors in the file)
+ */
+ public HbBlockMustache getPairedElement();
+}
View
5 src/com/dmarcotte/handlebars/psi/HbCloseBlockMustache.java
@@ -3,5 +3,8 @@
/**
* Element for close block mustaches: "{{/foo}}"
*/
-public interface HbCloseBlockMustache extends HbPsiElement {
+public interface HbCloseBlockMustache extends HbBlockMustache {
+
+ @Override
+ HbOpenBlockMustache getPairedElement();
}
View
5 src/com/dmarcotte/handlebars/psi/HbOpenBlockMustache.java
@@ -3,5 +3,8 @@
/**
* Base element for mustaches which open blocks (i.e. "{{#foo}}" and "{{^foo}}")
*/
-public interface HbOpenBlockMustache extends HbPsiElement {
+public interface HbOpenBlockMustache extends HbBlockMustache {
+
+ @Override
+ HbCloseBlockMustache getPairedElement();
}
View
27 src/com/dmarcotte/handlebars/psi/impl/HbBlockMustacheImpl.java
@@ -0,0 +1,27 @@
+package com.dmarcotte.handlebars.psi.impl;
+
+import com.dmarcotte.handlebars.psi.HbBlockMustache;
+import com.dmarcotte.handlebars.psi.HbPath;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.util.PsiTreeUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+abstract class HbBlockMustacheImpl extends HbPsiElementImpl implements HbBlockMustache {
+ protected HbBlockMustacheImpl(@NotNull ASTNode astNode) {
+ super(astNode);
+ }
+
+ @Override
+ @Nullable
+ public HbPath getBlockMainPath() {
+ return PsiTreeUtil.findChildOfType(this, HbPath.class);
+ }
+
+ @Override
+ @Nullable
+ public String getName() {
+ HbPath mainPath = getBlockMainPath();
+ return mainPath == null ? null : mainPath.getName();
+ }
+}
View
14 src/com/dmarcotte/handlebars/psi/impl/HbCloseBlockMustacheImpl.java
@@ -1,11 +1,23 @@
package com.dmarcotte.handlebars.psi.impl;
import com.dmarcotte.handlebars.psi.HbCloseBlockMustache;
+import com.dmarcotte.handlebars.psi.HbOpenBlockMustache;
import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
-public class HbCloseBlockMustacheImpl extends HbPsiElementImpl implements HbCloseBlockMustache {
+public class HbCloseBlockMustacheImpl extends HbBlockMustacheImpl implements HbCloseBlockMustache {
public HbCloseBlockMustacheImpl(@NotNull ASTNode astNode) {
super(astNode);
}
+
+ @Override
+ public HbOpenBlockMustache getPairedElement() {
+ PsiElement openBlockElement = getParent().getFirstChild();
+ if (openBlockElement instanceof HbOpenBlockMustache) {
+ return (HbOpenBlockMustache) openBlockElement;
+ }
+
+ return null;
+ }
}
View
16 src/com/dmarcotte/handlebars/psi/impl/HbOpenBlockMustacheImpl.java
@@ -1,24 +1,28 @@
package com.dmarcotte.handlebars.psi.impl;
import com.dmarcotte.handlebars.HbIcons;
+import com.dmarcotte.handlebars.psi.HbCloseBlockMustache;
import com.dmarcotte.handlebars.psi.HbOpenBlockMustache;
-import com.dmarcotte.handlebars.psi.HbPath;
import com.intellij.lang.ASTNode;
-import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.Icon;
-public class HbOpenBlockMustacheImpl extends HbPsiElementImpl implements HbOpenBlockMustache {
+public class HbOpenBlockMustacheImpl extends HbBlockMustacheImpl implements HbOpenBlockMustache {
public HbOpenBlockMustacheImpl(@NotNull ASTNode astNode) {
super(astNode);
}
@Override
- public String getName() {
- HbPath namePath = PsiTreeUtil.findChildOfType(this, HbPath.class);
- return namePath == null ? null : namePath.getName();
+ public HbCloseBlockMustache getPairedElement() {
+ PsiElement closeBlockElement = getParent().getLastChild();
+ if (closeBlockElement instanceof HbCloseBlockMustache) {
+ return (HbCloseBlockMustache) closeBlockElement;
+ }
+
+ return null;
}
@Nullable
View
5 test/data/folding/foldsWithUnclosedBlocks.hbs
@@ -1,4 +1,3 @@
-{{! Note that there is no folding here. This is a regression test against an NPE found while developing }}
{{#baz}}
- {{#bam}}
- {{/baz}}
+ {{#bam<fold text='...'>}}
+ {{/baz</fold>}}
View
4 test/data/inspections/afterWrongCloseBlock1.hbs
@@ -0,0 +1,4 @@
+<!-- "Change block end 'bar' to 'foo'" "true" -->
+{{#foo}}
+
+{{/foo}}
View
4 test/data/inspections/afterWrongCloseBlock2.hbs
@@ -0,0 +1,4 @@
+<!-- "Change block end 'bar' to 'foo'" "true" -->
+{{#foo}}
+
+{{/foo}}
View
4 test/data/inspections/afterWrongOpenBlock1.hbs
@@ -0,0 +1,4 @@
+<!-- "Change block start 'bar' to 'foo'" "true" -->
+{{#foo}}
+
+{{/foo}}
View
4 test/data/inspections/afterWrongOpenBlock2.hbs
@@ -0,0 +1,4 @@
+<!-- "Change block start 'bar' to 'foo'" "true" -->
+{{#foo}}
+
+{{/foo}}
View
4 test/data/inspections/beforeWrongCloseBlock1.hbs
@@ -0,0 +1,4 @@
+<!-- "Change block end 'bar' to 'foo'" "true" -->
+{{#<caret>foo}}
+
+{{/bar}}
View
4 test/data/inspections/beforeWrongCloseBlock2.hbs
@@ -0,0 +1,4 @@
+<!-- "Change block end 'bar' to 'foo'" "true" -->
+{{#foo}}
+
+{{/<caret>bar}}
View
4 test/data/inspections/beforeWrongOpenBlock1.hbs
@@ -0,0 +1,4 @@
+<!-- "Change block start 'bar' to 'foo'" "true" -->
+{{#<caret>bar}}
+
+{{/foo}}
View
4 test/data/inspections/beforeWrongOpenBlock2.hbs
@@ -0,0 +1,4 @@
+<!-- "Change block start 'bar' to 'foo'" "true" -->
+{{#bar}}
+
+{{/<caret>foo}}
View
17 test/data/parser/CloseNotFollowingOpen.txt
@@ -1,12 +1,11 @@
FILE
HbStatementsImpl(STATEMENTS)
<empty list>
- PsiErrorElement:No corresponding open mustache
- HbCloseBlockMustacheImpl(CLOSE_BLOCK_STACHE)
- HbPsiElementImpl([Hb] OPEN_ENDBLOCK)
- PsiElement([Hb] OPEN_ENDBLOCK)('{{/')
- HbPathImpl(PATH)
- HbPsiElementImpl([Hb] ID)
- PsiElement([Hb] ID)('unopened')
- HbPsiElementImpl([Hb] CLOSE)
- PsiElement([Hb] CLOSE)('}}')
+ HbCloseBlockMustacheImpl(CLOSE_BLOCK_STACHE)
+ HbPsiElementImpl([Hb] OPEN_ENDBLOCK)
+ PsiElement([Hb] OPEN_ENDBLOCK)('{{/')
+ HbPathImpl(PATH)
+ HbPsiElementImpl([Hb] ID)
+ PsiElement([Hb] ID)('unopened')
+ HbPsiElementImpl([Hb] CLOSE)
+ PsiElement([Hb] CLOSE)('}}')
View
26 test/data/parser/PoorlyNestedMustaches.txt
@@ -1,15 +1,14 @@
FILE
HbStatementsImpl(STATEMENTS)
HbBlockWrapperImpl(BLOCK_WRAPPER)
- PsiErrorElement:"foo" block not closed
- HbOpenBlockMustacheImpl(OPEN_BLOCK_STACHE)
- HbPsiElementImpl([Hb] OPEN_BLOCK)
- PsiElement([Hb] OPEN_BLOCK)('{{#')
- HbPathImpl(PATH)
- HbPsiElementImpl([Hb] ID)
- PsiElement([Hb] ID)('foo')
- HbPsiElementImpl([Hb] CLOSE)
- PsiElement([Hb] CLOSE)('}}')
+ HbOpenBlockMustacheImpl(OPEN_BLOCK_STACHE)
+ HbPsiElementImpl([Hb] OPEN_BLOCK)
+ PsiElement([Hb] OPEN_BLOCK)('{{#')
+ HbPathImpl(PATH)
+ HbPsiElementImpl([Hb] ID)
+ PsiElement([Hb] ID)('foo')
+ HbPsiElementImpl([Hb] CLOSE)
+ PsiElement([Hb] CLOSE)('}}')
PsiWhiteSpace('\n ')
HbStatementsImpl(STATEMENTS)
HbBlockWrapperImpl(BLOCK_WRAPPER)
@@ -32,10 +31,13 @@ FILE
HbPsiElementImpl([Hb] CLOSE)
PsiElement([Hb] CLOSE)('}}')
PsiWhiteSpace('\n')
- PsiErrorElement:"foo" does not match "bar" from block start
+ HbCloseBlockMustacheImpl(CLOSE_BLOCK_STACHE)
HbPsiElementImpl([Hb] OPEN_ENDBLOCK)
PsiElement([Hb] OPEN_ENDBLOCK)('{{/')
- PsiElement([Hb] ID)('foo')
- PsiElement([Hb] CLOSE)('}}')
+ HbPathImpl(PATH)
+ HbPsiElementImpl([Hb] ID)
+ PsiElement([Hb] ID)('foo')
+ HbPsiElementImpl([Hb] CLOSE)
+ PsiElement([Hb] CLOSE)('}}')
PsiErrorElement:Expected Open End Block "{{/"
<empty list>
View
5 test/src/com/dmarcotte/handlebars/editor/actions/HbEnterHandlerTest.java
@@ -42,7 +42,7 @@ public void testEnterBetweenMatchingHbTags() {
/**
* On Enter between MIS-matched open/close tags,
- * expect a standard newline
+ * we still get the standard behavior
*/
public void testEnterBetweenMismatchedHbTags() {
doEnterTest(
@@ -51,7 +51,8 @@ public void testEnterBetweenMismatchedHbTags() {
"stuff",
"{{#foo}}\n" +
- "<caret>{{/bar}}" +
+ "<caret>\n" +
+ "{{/bar}}" +
"stuff"
);
}
View
21 test/src/com/dmarcotte/handlebars/inspections/HbBlockMismatchFixTest.java
@@ -0,0 +1,21 @@
+package com.dmarcotte.handlebars.inspections;
+
+import com.dmarcotte.handlebars.util.HbTestUtils;
+import com.intellij.codeInsight.daemon.quickFix.LightQuickFixTestCase;
+import org.jetbrains.annotations.NotNull;
+
+public class HbBlockMismatchFixTest extends LightQuickFixTestCase {
+
+ public void test() throws Exception { doAllTests(); }
+
+ @Override
+ protected String getBasePath() {
+ return "/inspections";
+ }
+
+ @NotNull
+ @Override
+ protected String getTestDataPath() {
+ return HbTestUtils.BASE_TEST_DATA_PATH;
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.