IDEA-68197 XPath 2: wrong number of quotes at the very end of string literal is green

This commit is contained in:
sweinreuter
2011-04-18 19:38:38 +02:00
parent 5c860fb2ab
commit 2a90d04933
13 changed files with 192 additions and 109 deletions
@@ -17,10 +17,12 @@ package org.intellij.lang.xpath.psi.impl;
import com.intellij.extapi.psi.ASTWrapperPsiElement;
import com.intellij.lang.ASTNode;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.impl.PsiTreeDebugBuilder;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.util.IncorrectOperationException;
import org.intellij.lang.xpath.XPath2ElementTypes;
import org.intellij.lang.xpath.XPathElementTypes;
@@ -34,100 +36,100 @@ import org.jetbrains.annotations.NotNull;
public class XPathElementImpl extends ASTWrapperPsiElement implements XPathElement {
private final NotNullLazyValue<ContextProvider> myContext = new NotNullLazyValue<ContextProvider>() {
@NotNull
@Override
protected synchronized ContextProvider compute() {
return ContextProvider.getContextProvider(XPathElementImpl.this);
}
};
public XPathElementImpl(ASTNode node) {
super(node);
}
public String toString() {
final String name = getClass().getName();
return name.substring(name.lastIndexOf('.') + 1) + ": " + getText();
}
public PsiElement addBefore(@NotNull PsiElement psiElement, final PsiElement anchor) throws IncorrectOperationException {
final ASTNode node = getNode();
final ASTNode child = psiElement.getNode();
assert child != null;
node.addChild(child, anchor.getNode());
return node.getPsi();
}
public PsiElement addAfter(@NotNull PsiElement psiElement, final PsiElement anchor) throws IncorrectOperationException {
final ASTNode astNode = anchor.getNode();
assert astNode != null;
final ASTNode next = astNode.getTreeNext();
final ASTNode node = getNode();
final ASTNode newNode = psiElement.getNode();
assert newNode != null;
if (next != null) {
node.addChild(newNode, next);
} else {
node.addChild(newNode);
}
return node.getPsi();
}
public PsiElement add(@NotNull PsiElement psiElement) throws IncorrectOperationException {
final ASTNode child = psiElement.getNode();
assert child != null;
getNode().addChild(child);
return getNode().getPsi();
}
public void delete() throws IncorrectOperationException {
final ASTNode node = getNode();
final ASTNode parent = node.getTreeParent();
final ASTNode next = node.getTreeNext();
parent.removeChild(node);
if (XPath2ElementTypes.EXPRESSIONS.contains(node.getElementType())) {
if (parent.getElementType() == XPathElementTypes.FUNCTION_CALL) {
if (next != null && next.getElementType() == XPathTokenTypes.COMMA) {
parent.removeChild(next);
}
}
}
}
public PsiElement replace(@NotNull PsiElement psiElement) throws IncorrectOperationException {
final ASTNode newNode = psiElement.getNode();
final ASTNode myNode = getNode();
assert newNode != null;
myNode.getTreeParent().replaceChild(myNode, newNode);
return newNode.getPsi();
}
private final NotNullLazyValue<ContextProvider> myContext = new NotNullLazyValue<ContextProvider>() {
@NotNull
@SuppressWarnings({ "ConstantConditions", "EmptyMethod" })
public final ASTNode getNode() {
return super.getNode();
}
@Override
public XPathFile getContainingFile() {
return (XPathFile)super.getContainingFile();
protected synchronized ContextProvider compute() {
return ContextProvider.getContextProvider(XPathElementImpl.this);
}
};
@Override
public ContextProvider getXPathContext() {
return myContext.getValue();
}
public XPathElementImpl(ASTNode node) {
super(node);
}
@Override
public XPathVersion getXPathVersion() {
return getContainingFile().getXPathVersion();
public String toString() {
final String name = getClass().getName();
return name.substring(name.lastIndexOf('.') + 1) + ": " + getText();
}
public PsiElement addBefore(@NotNull PsiElement psiElement, final PsiElement anchor) throws IncorrectOperationException {
final ASTNode node = getNode();
final ASTNode child = psiElement.getNode();
assert child != null;
node.addChild(child, anchor.getNode());
return node.getPsi();
}
public PsiElement addAfter(@NotNull PsiElement psiElement, final PsiElement anchor) throws IncorrectOperationException {
final ASTNode astNode = anchor.getNode();
assert astNode != null;
final ASTNode next = astNode.getTreeNext();
final ASTNode node = getNode();
final ASTNode newNode = psiElement.getNode();
assert newNode != null;
if (next != null) {
node.addChild(newNode, next);
} else {
node.addChild(newNode);
}
return node.getPsi();
}
public PsiElement add(@NotNull PsiElement psiElement) throws IncorrectOperationException {
final ASTNode child = psiElement.getNode();
assert child != null;
getNode().addChild(child);
return getNode().getPsi();
}
public void delete() throws IncorrectOperationException {
final ASTNode node = getNode();
final ASTNode parent = node.getTreeParent();
final ASTNode next = node.getTreeNext();
parent.removeChild(node);
if (XPath2ElementTypes.EXPRESSIONS.contains(node.getElementType())) {
if (parent.getElementType() == XPathElementTypes.FUNCTION_CALL) {
if (next != null && next.getElementType() == XPathTokenTypes.COMMA) {
parent.removeChild(next);
}
}
}
}
public PsiElement replace(@NotNull PsiElement psiElement) throws IncorrectOperationException {
final ASTNode newNode = psiElement.getNode();
final ASTNode myNode = getNode();
assert newNode != null;
myNode.getTreeParent().replaceChild(myNode, newNode);
return newNode.getPsi();
}
@NotNull
@SuppressWarnings({ "ConstantConditions", "EmptyMethod" })
public final ASTNode getNode() {
return super.getNode();
}
@Override
public XPathFile getContainingFile() {
return (XPathFile)super.getContainingFile();
}
@Override
public ContextProvider getXPathContext() {
return myContext.getValue();
}
@Override
public XPathVersion getXPathVersion() {
return getContainingFile().getXPathVersion();
}
protected String unexpectedPsiAssertion() {
final PsiTreeDebugBuilder builder = new PsiTreeDebugBuilder();
@@ -144,6 +146,14 @@ public class XPathElementImpl extends ASTWrapperPsiElement implements XPathEleme
}
public void accept(XPathElementVisitor visitor) {
visitor.visitXPathElement(this);
visitor.visitXPathElement(this);
}
public final String getUnescapedText() {
if (InjectedLanguageUtil.isInInjectedLanguagePrefixSuffix(this)) {
// do not attempt to decode text if PsiElement is part of prefix/suffix
return getText();
}
return InjectedLanguageManager.getInstance(getProject()).getUnescapedText(this);
}
}
@@ -16,8 +16,7 @@
package org.intellij.lang.xpath.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import org.intellij.lang.xpath.XPath2Language;
import org.intellij.lang.xpath.context.XPathVersion;
import org.intellij.lang.xpath.psi.XPathElementVisitor;
import org.intellij.lang.xpath.psi.XPathString;
import org.intellij.lang.xpath.psi.XPathType;
@@ -34,34 +33,54 @@ public class XPathStringImpl extends XPathElementImpl implements XPathString {
}
public boolean isWellFormed() {
final boolean xpath2 = getContainingFile().getLanguage() == Language.findInstance(XPath2Language.class);
final String text = getText();
if (text.startsWith("'")) {
if (!text.endsWith("'")) {
return false;
}
if (!xpath2 && getValue().indexOf('\'') != getValue().lastIndexOf('\'')) {
return false;
}
} else if (text.startsWith("\"")) {
if (!text.endsWith("\"")) {
return false;
}
if (!xpath2 && getValue().indexOf('\"') != getValue().lastIndexOf('"')) {
final String text = getUnescapedText();
final char quoteChar = getQuoteChar();
if (!text.endsWith(String.valueOf(quoteChar))) {
return false;
}
if (getXPathVersion() == XPathVersion.V2) {
final String value = getStringBetweenQuotes();
final String unescaped = unescape(quoteChar, value);
return escape(quoteChar, unescaped).equals(value);
} else {
if (getValue().indexOf(quoteChar) != getValue().lastIndexOf(quoteChar)) {
return false;
}
}
return xpath2 || text.indexOf('\n') == -1 && text.indexOf("\r") == -1;
return text.indexOf('\n') == -1 && text.indexOf('\r') == -1;
}
public String getValue() {
final String value = getText().substring(0, getTextLength() - 1);
if (getContainingFile().getLanguage() == Language.findInstance(XPath2Language.class)) {
return value.replaceAll("\"\"", "\"").replaceAll("''", "'");
final String value = getStringBetweenQuotes();
if (getXPathVersion() == XPathVersion.V2) {
return unescape(getQuoteChar(), value);
}
return value;
}
private String getStringBetweenQuotes() {
final String text = getUnescapedText();
return text.endsWith(String.valueOf(getQuoteChar())) ? text.substring(1, text.length() - 1) : text.substring(1, text.length());
}
private char getQuoteChar() {
return getUnescapedText().charAt(0);
}
private static String unescape(char quote, String value) {
final String singleQuote = String.valueOf(quote);
final String escapedQuote = singleQuote + quote;
return value.replaceAll(escapedQuote, singleQuote);
}
private static String escape(char quote, String value) {
final String singleQuote = String.valueOf(quote);
final String escapedQuote = singleQuote + quote;
return value.replaceAll(singleQuote, escapedQuote);
}
public void accept(XPathElementVisitor visitor) {
visitor.visitXPathString(this);
}
@@ -37,6 +37,18 @@ public class XPath2HighlightingTest extends XPath2HighlightingTestBase {
doXPathHighlighting();
}
public void testMalformedStringLiteral() throws Throwable {
doXPathHighlighting();
}
public void testQuotedStringLiteral() throws Throwable {
doXPathHighlighting();
}
public void testEmptyStringLiteral() throws Throwable {
doXPathHighlighting();
}
@Override
protected String getSubPath() {
return "xpath/highlighting";
@@ -64,6 +64,14 @@ public class XPathHighlightingTest extends TestBase {
doXPathHighlighting();
}
public void testMalformedStringLiteral() throws Throwable {
doXPathHighlighting();
}
public void testQuotedStringLiteral() throws Throwable {
doXPathHighlighting();
}
private void doXPathHighlighting(String... moreFiles) throws Throwable {
final String name = getTestFileName();
myFixture.testHighlighting(true, false, false, ArrayUtil.append(moreFiles, name + ".xpath"));
@@ -49,6 +49,10 @@ public class Xslt2HighlightingTest extends TestBase {
doXsltHighlighting();
}
public void testEscapedXPathString() throws Throwable {
doXsltHighlighting();
}
private void doXsltHighlighting(String... moreFiles) throws Throwable {
final String name = getTestFileName();
myFixture.testHighlighting(true, false, false, ArrayUtil.append(moreFiles, name + ".xsl"));
@@ -115,6 +115,10 @@ public class XsltHighlightingTest extends TestBase {
doXsltHighlighting();
}
public void testEscapedXPathString() throws Throwable {
doXsltHighlighting();
}
public void testTemplateWithPrefix() throws Throwable {
myFixture.enableInspections(XmlUnusedNamespaceInspection.class);
doXsltHighlighting();
@@ -0,0 +1 @@
concat("", '')
@@ -0,0 +1 @@
<error descr="Malformed string literal">'abc</error>
@@ -0,0 +1 @@
<error descr="Malformed string literal">'abc''</error>
@@ -0,0 +1,5 @@
concat(
<error descr="Malformed string literal">'a''b'</error>,
<error descr="Malformed string literal">"c""d"</error>,
<error descr="Malformed string literal">"""xz"""</error>,
<error descr="Malformed string literal">''''</error>)
@@ -0,0 +1 @@
concat('a''b', "c""d", """xz""", '''')
@@ -0,0 +1,8 @@
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="abc">
<xsl:value-of select="&apos;&apos;" />
<xsl:value-of select="&apos;&quot;abc&quot;&apos;" />
<xsl:value-of select="<error descr="Malformed string literal">&apos;a&apos;&apos;b&apos;</error>" />
</xsl:template>
</xsl:stylesheet>
@@ -0,0 +1,9 @@
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="abc">
<xsl:value-of select="&apos;&apos;" />
<xsl:value-of select="&apos;&quot;abc&quot;&apos;" />
<xsl:value-of select="&apos;a&apos;&apos;b&apos;" />
<xsl:value-of select="<error descr="Malformed string literal">&apos;a&apos;&apos;</error>" />
</xsl:template>
</xsl:stylesheet>