js regexp: support key=value unicode properties (part of WEB-27639)

This commit is contained in:
Maxim Kropotov
2019-03-07 11:40:39 +03:00
parent 6ce000424d
commit 694e98415c
12 changed files with 862 additions and 661 deletions
File diff suppressed because it is too large Load Diff
@@ -84,6 +84,11 @@ public enum RegExpCapability {
*/
CARET_NEGATED_PROPERTIES,
/**
* supports properties with name and value like \p{name=value}
*/
PROPERTY_VALUES,
/**
* \\u, \l, \\U, \L, and \E
*/
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.lookup.TailTypeDecorator;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressManager;
@@ -13,9 +14,11 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.psi.PsiElement;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import com.intellij.util.ui.EmptyIcon;
import org.intellij.lang.regexp.psi.RegExpProperty;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -62,6 +65,9 @@ public final class RegExpCompletionContributor extends CompletionContributor {
final ElementPattern<PsiElement> propertyPattern = psiElement().withText("\\\\p");
extend(CompletionType.BASIC, psiElement().afterLeaf(propertyPattern), new PropertyCompletionProvider());
extend(CompletionType.BASIC, psiElement().inside(RegExpProperty.class).afterLeaf(psiElement(RegExpTT.EQ)),
new PropertyValueCompletionProvider());
}
{
@@ -172,4 +178,20 @@ public final class RegExpCompletionContributor extends CompletionContributor {
});
}
}
private static class PropertyValueCompletionProvider extends CompletionProvider<CompletionParameters> {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull CompletionResultSet result) {
RegExpProperty property = ObjectUtils.tryCast(parameters.getPosition().getParent(), RegExpProperty.class);
ASTNode propertyNameNode = property != null ? property.getCategoryNode() : null;
if (propertyNameNode == null) {
return;
}
for (String[] value : RegExpLanguageHosts.getInstance().getAllPropertyValues(property, propertyNameNode.getText())) {
addLookupElement(result, value[0], value.length > 1 ? value[1] : null, null);
}
}
}
}
@@ -28,6 +28,8 @@ import java.util.EnumSet;
public interface RegExpLanguageHost {
EnumSet<RegExpGroup.Type> EMPTY_NAMED_GROUP_TYPES = EnumSet.noneOf(RegExpGroup.Type.class);
@SuppressWarnings("SSBasedInspection")
String[][] EMPTY_COMPLETION_ITEMS_ARRAY = new String[0][];
boolean characterNeedsEscaping(char c);
boolean supportsPerl5EmbeddedComments();
@@ -89,6 +91,11 @@ public interface RegExpLanguageHost {
}
boolean isValidCategory(@NotNull String category);
default boolean isValidPropertyValue(@NotNull String propertyName, @NotNull String value){
return true;
}
@NotNull
String[][] getAllKnownProperties();
@Nullable
@@ -109,6 +116,11 @@ public interface RegExpLanguageHost {
return Lookbehind.FULL; // to not break existing implementations, although rarely actually supported.
}
@NotNull
default String[][] getAllPropertyValues(@NotNull String propertyName){
return EMPTY_COMPLETION_ITEMS_ARRAY;
}
enum Lookbehind {
/** Lookbehind not supported. */
NOT_SUPPORTED,
@@ -151,6 +151,11 @@ public final class RegExpLanguageHosts extends ClassExtension<RegExpLanguageHost
final RegExpLanguageHost host = findRegExpHost(element);
return host != null ? host.isValidCategory(category) : myDefaultProvider.isValidCategory(category);
}
public boolean isValidPropertyValue(@NotNull PsiElement element, @NotNull String propertyName, @NotNull String propertyValue) {
final RegExpLanguageHost host = findRegExpHost(element);
return host == null || host.isValidPropertyValue(propertyName, propertyValue);
}
public boolean supportsNamedCharacters(@NotNull final RegExpNamedCharacter namedCharacter) {
final RegExpLanguageHost host = findRegExpHost(namedCharacter);
@@ -185,6 +190,12 @@ public final class RegExpLanguageHosts extends ClassExtension<RegExpLanguageHost
return host != null ? host.getAllKnownProperties() : myDefaultProvider.getAllKnownProperties();
}
@NotNull
public String[][] getAllPropertyValues(@NotNull PsiElement element, @NotNull String propertyName) {
final RegExpLanguageHost host = findRegExpHost(element);
return host != null ? host.getAllPropertyValues(propertyName) : RegExpLanguageHost.EMPTY_COMPLETION_ITEMS_ARRAY;
}
@Nullable
String getPropertyDescription(@NotNull final PsiElement element, @Nullable final String name) {
final RegExpLanguageHost host = findRegExpHost(element);
@@ -492,7 +492,11 @@ public class RegExpParser implements PsiParser, LightPsiParser {
builder.advanceLexer();
}
if (builder.getTokenType() == RegExpTT.NAME) {
builder.advanceLexer();
builder.advanceLexer(); //name
if (myCapabilities.contains(RegExpCapability.PROPERTY_VALUES) && builder.getTokenType() == RegExpTT.EQ) {
builder.advanceLexer(); //eq
checkMatches(builder, RegExpTT.NAME, "Property value expected");
}
checkMatches(builder, RegExpTT.RBRACE, "Unclosed property");
}
else
@@ -92,6 +92,10 @@ public interface RegExpTT {
IElementType DOT = new RegExpElementType("DOT");
/** "|" */
IElementType UNION = new RegExpElementType("UNION");
/**
* "=" in some unicode properties, e.g.(\p{name=value})
*/
IElementType EQ = new RegExpElementType("EQ");
/** ">" in Python/Ruby named group */
IElementType GT = new RegExpElementType("GT");
@@ -21,6 +21,7 @@ public class EcmaScriptUnicodeRegexpParserDefinition extends RegExpParserDefinit
private final EnumSet<RegExpCapability> CAPABILITIES = EnumSet.of(RegExpCapability.OCTAL_NO_LEADING_ZERO,
RegExpCapability.ALLOW_EMPTY_CHARACTER_CLASS,
RegExpCapability.NO_DANGLING_METACHARACTERS,
RegExpCapability.PROPERTY_VALUES,
RegExpCapability.EXTENDED_UNICODE_CHARACTER);
@Override
@@ -32,4 +32,10 @@ public interface RegExpProperty extends RegExpAtom, RegExpClassElement {
*/
@Nullable
ASTNode getCategoryNode();
/**
* The node that represents the category value, e.g 'Latin' in \p{Script=Latin}
*/
@Nullable
ASTNode getValueNode();
}
@@ -64,59 +64,78 @@ public class RegExpPropertyImpl extends RegExpElementImpl implements RegExpPrope
return getNode().findChildByType(RegExpTT.NAME);
}
@Override
public void accept(RegExpElementVisitor visitor) {
visitor.visitRegExpProperty(this);
}
@Nullable
@Override
public ASTNode getValueNode() {
ASTNode node = getNode();
ASTNode eq = node.findChildByType(RegExpTT.EQ);
return eq != null ? node.findChildByType(RegExpTT.NAME, eq) : null;
}
@Override
public void accept(RegExpElementVisitor visitor) {
visitor.visitRegExpProperty(this);
}
private class MyPsiReference implements PsiReference {
@Override
@NotNull
public PsiElement getElement() {
return RegExpPropertyImpl.this;
}
@Override
@NotNull
public PsiElement getElement() {
return RegExpPropertyImpl.this;
}
@Override
@NotNull
public TextRange getRangeInElement() {
ASTNode firstNode = getNode().findChildByType(RegExpTT.CARET);
if (firstNode == null) {
firstNode = getNode().findChildByType(RegExpTT.LBRACE);
}
assert firstNode != null;
final ASTNode rbrace = getNode().findChildByType(RegExpTT.RBRACE);
int to = rbrace == null ? getTextRange().getEndOffset() : rbrace.getTextRange().getEndOffset() - 1;
@Override
@NotNull
public TextRange getRangeInElement() {
ASTNode node = getNode();
ASTNode firstNode = node.findChildByType(RegExpTT.CARET);
if (firstNode == null) {
firstNode = node.findChildByType(RegExpTT.LBRACE);
}
assert firstNode != null;
ASTNode eq = node.findChildByType(RegExpTT.EQ);
final ASTNode rbrace = node.findChildByType(RegExpTT.RBRACE);
int to;
if (eq != null) {
to = eq.getTextRange().getEndOffset() - 1;
}
else if (rbrace != null) {
to = rbrace.getTextRange().getEndOffset() - 1;
}
else {
to = getTextRange().getEndOffset();
}
final TextRange t = new TextRange(firstNode.getStartOffset() + 1, to);
return t.shiftRight(-getTextRange().getStartOffset());
}
final TextRange t = new TextRange(firstNode.getStartOffset() + 1, to);
return t.shiftRight(-getTextRange().getStartOffset());
}
@Override
@Nullable
public PsiElement resolve() {
return RegExpPropertyImpl.this;
}
@Override
@Nullable
public PsiElement resolve() {
return RegExpPropertyImpl.this;
}
@Override
@NotNull
public String getCanonicalText() {
return getRangeInElement().substring(getElement().getText());
}
@Override
@NotNull
public String getCanonicalText() {
return getRangeInElement().substring(getElement().getText());
}
@Override
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
throw new IncorrectOperationException();
}
@Override
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
throw new IncorrectOperationException();
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
throw new IncorrectOperationException();
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
throw new IncorrectOperationException();
}
@Override
public boolean isReferenceTo(@NotNull PsiElement element) {
return false;
}
@Override
public boolean isReferenceTo(@NotNull PsiElement element) {
return false;
}
@Override
@NotNull
@@ -301,6 +301,7 @@ HEX_CHAR=[0-9a-fA-F]
<EMBRACED> {
"^" { return RegExpTT.CARET; }
{NAME} { return RegExpTT.NAME; }
"=" { return RegExpTT.EQ; }
{RBRACE} { yypopstate(); return RegExpTT.RBRACE; }
{ANY} { yypopstate(); yypushback(1); }
}
@@ -219,9 +219,16 @@ public final class RegExpAnnotator extends RegExpElementVisitor implements Annot
if (category == null) {
return;
}
if(!myLanguageHosts.isValidCategory(category.getPsi(), category.getText())) {
String propertyName = category.getText();
if(!myLanguageHosts.isValidCategory(category.getPsi(), propertyName)) {
final Annotation a = myHolder.createErrorAnnotation(category, "Unknown character category");
a.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
return;
}
ASTNode valueNode = property.getValueNode();
if (valueNode != null && !myLanguageHosts.isValidPropertyValue(category.getPsi(), propertyName, valueNode.getText())) {
final Annotation a = myHolder.createErrorAnnotation(valueNode, "Unknown property value");
a.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}