mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-68197 XPath 2: wrong number of quotes at the very end of string literal is green
This commit is contained in:
+99
-89
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+39
-20
@@ -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="''" />
|
||||
<xsl:value-of select="'"abc"'" />
|
||||
<xsl:value-of select="<error descr="Malformed string literal">'a''b'</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="''" />
|
||||
<xsl:value-of select="'"abc"'" />
|
||||
<xsl:value-of select="'a''b'" />
|
||||
<xsl:value-of select="<error descr="Malformed string literal">'a''</error>" />
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
||||
Reference in New Issue
Block a user