From 4815f7807f255dcc977e43aa3f167bf38864d159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Vonr=C3=BCti?= Date: Wed, 2 Jan 2019 15:03:50 +0100 Subject: [PATCH] java.time.* isBefore/isAfter folding and LocalDate literals --- .../AdvancedExpressionFoldingBuilder.java | 118 +++++++++++++++++- ...ancedExpressionFoldingOptionsProvider.java | 5 + .../AdvancedExpressionFoldingSettings.java | 33 +++++ .../LocalDateLiteral.java | 103 +++++++++++++++ .../FoldingTest.java | 19 +++ testData/LocalDateLiteralPostfixTestData.java | 14 +++ testData/LocalDateLiteralTestData.java | 14 +++ testData/LocalDateTestData.java | 12 ++ 8 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 src/com/intellij/advancedExpressionFolding/LocalDateLiteral.java create mode 100644 testData/LocalDateLiteralPostfixTestData.java create mode 100644 testData/LocalDateLiteralTestData.java create mode 100644 testData/LocalDateTestData.java diff --git a/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingBuilder.java b/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingBuilder.java index 66abd2ad..8bc23d78 100644 --- a/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingBuilder.java +++ b/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingBuilder.java @@ -18,6 +18,8 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.*; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -101,6 +103,10 @@ public class AdvancedExpressionFoldingBuilder extends FoldingBuilderEx { add("unmodifiableSet"); add("unmodifiableList"); add("toString"); + add("isBefore"); + add("isAfter"); + // LocalDate literal + add("of"); } }; @@ -130,6 +136,7 @@ public class AdvancedExpressionFoldingBuilder extends FoldingBuilderEx { add("java.util.Collections"); add("java.util.Objects"); add("java.util.stream.Stream"); + add("java.time.LocalDate"); } }; @@ -857,10 +864,33 @@ private static Expression getLiteralExpression(@NotNull PsiLiteralExpression ele private static Expression getPrefixExpression(@NotNull PsiPrefixExpression element, @NotNull Document document) { AdvancedExpressionFoldingSettings settings = AdvancedExpressionFoldingSettings.getInstance(); if (element.getOperand() != null) { - if (element.getOperationSign().getText().equals("!") && settings.getState().isComparingExpressionsCollapse()) { - @NotNull Expression operand = getAnyExpression(element.getOperand(), document); - if (operand instanceof Equal) { - return new NotEqual(element, element.getTextRange(), ((Equal) operand).getOperands()); + if (element.getOperationSign().getText().equals("!")) { + if (settings.getState().isComparingLocalDatesCollapse()) { + if (element.getOperand() instanceof PsiMethodCallExpression) { + PsiMethodCallExpression operand = (PsiMethodCallExpression) element.getOperand(); + Optional methodCallInformationOptional = MethodCallInformation.tryGet(operand, document, Arrays.asList("java.time.LocalDate", "java.time.LocalTime", "java.time.LocalDateTime", "java.time.Year", "java.time.YearMonth", "java.time.ChronoLocalDateTime", "java.time.Instant"), "isBefore", "isAfter"); + if (methodCallInformationOptional.isPresent()) { + MethodCallInformation callInformation = methodCallInformationOptional.get(); + + if (callInformation.methodName.equals("isBefore")) { + return new GreaterEqual(element, element.getTextRange(), Arrays.asList(callInformation.qualifierExpression, callInformation.getFoldedArgument(0))); + } + + if (callInformation.methodName.equals("isAfter")) { + return new LessEqual(element, element.getTextRange(), Arrays.asList(callInformation.qualifierExpression, callInformation.getFoldedArgument(0))); + } + } + + } + } + if (settings.getState().isComparingExpressionsCollapse()) { + @NotNull Expression operand = getAnyExpression(element.getOperand(), document); + if (operand instanceof Equal) { + return new NotEqual(element, element.getTextRange(), ((Equal) operand).getOperands()); + } + } else if (element.getOperationSign().getText().equals("-")) { + @NotNull Expression operand = getAnyExpression(element.getOperand(), document); + return new Negate(element, element.getTextRange(), Collections.singletonList(operand)); } } else if (element.getOperationSign().getText().equals("-")) { @NotNull Expression operand = getAnyExpression(element.getOperand(), document); @@ -1532,6 +1562,20 @@ && startsWith(((PsiMethodCallExpression) argument).getMethodExpression().getRefe break; } } + // LocalDate handling + case "isBefore": + if (settings.getState().isComparingLocalDatesCollapse()) { + return new Less(element, element.getTextRange(), Arrays.asList(qualifierExpression, argumentExpression)); + } else { + break; + } + + case "isAfter": + if (settings.getState().isComparingLocalDatesCollapse()) { + return new Greater(element, element.getTextRange(), Arrays.asList(qualifierExpression, argumentExpression)); + } else { + break; + } } } else if (element.getArgumentList().getExpressions().length == 0) { switch (methodName) { @@ -1647,6 +1691,19 @@ && startsWith(((PsiMethodCallExpression) argument).getMethodExpression().getRefe } } } + else if (element.getArgumentList().getExpressions().length == 3) { + PsiExpression a1 = element.getArgumentList().getExpressions()[0]; + PsiExpression a2 = element.getArgumentList().getExpressions()[1]; + PsiExpression a3 = element.getArgumentList().getExpressions()[2]; + if (methodName.equals("of") && className.equals("java.time.LocalDate") && settings.getState().isLocalDateLiteralCollapse()) { + if (a1 instanceof PsiLiteralExpression && a2 instanceof PsiLiteralExpression && a3 instanceof PsiLiteralExpression) { + PsiLiteralExpression year = (PsiLiteralExpression) a1; + PsiLiteralExpression month = (PsiLiteralExpression) a2; + PsiLiteralExpression day = (PsiLiteralExpression) a3; + return new LocalDateLiteral(element, element.getTextRange(), year, month, day); + } + } + } if (element.getArgumentList().getExpressions().length == 1) { PsiExpression argument = element.getArgumentList().getExpressions()[0]; if (method.getName().equals("valueOf") && argument instanceof PsiLiteralExpression) { @@ -1971,4 +2028,57 @@ public boolean isCollapsedByDefault(@NotNull ASTNode astNode) { } return false; } + + private static class MethodCallInformation { + private final PsiMethodCallExpression element; + private final Expression qualifierExpression; + private final PsiExpression[] arguments; + private final Document document; + private final String methodName; + private final String className; + private final PsiClass psiClass; + + public MethodCallInformation(PsiMethodCallExpression element, Expression qualifierExpression, String methodName, String className, PsiClass psiClass, Document document) { + this.element = element; + this.qualifierExpression = qualifierExpression; + this.methodName = methodName; + this.className = className; + this.psiClass = psiClass; + this.arguments = element.getArgumentList().getExpressions(); + this.document = document; + } + + Expression getFoldedArgument(int index) { + return getAnyExpression(element.getArgumentList().getExpressions()[index], document); + } + + static Optional tryGet(PsiMethodCallExpression element, @NotNull Document document, List classNames, String... methodNames) { + return tryGet(element, document, Arrays.asList(methodNames)::contains, (actualClassName, methodName) -> classNames.contains(actualClassName)); + } + + static Optional tryGet(PsiMethodCallExpression element, @NotNull Document document, Predicate isMethodNameSupported, BiPredicate isMethodSupported) { + PsiReferenceExpression referenceExpression = element.getMethodExpression(); + Optional identifier = Stream.of(referenceExpression.getChildren()) + .filter(c -> c instanceof PsiIdentifier).findAny(); + @Nullable PsiExpression qualifier = element.getMethodExpression().getQualifierExpression(); + + if (identifier.isPresent() && isMethodNameSupported.test(identifier.get().getText())) { + PsiMethod method = (PsiMethod) referenceExpression.resolve(); + if (method != null) { + PsiClass psiClass = method.getContainingClass(); + if (psiClass != null && psiClass.getQualifiedName() != null) { + String className = eraseGenerics(psiClass.getQualifiedName()); + String methodName = identifier.get().getText(); + if (isMethodSupported.test(className, methodName) + && qualifier != null) { + + @NotNull Expression qualifierExpression = getAnyExpression(qualifier, document); + return Optional.of(new MethodCallInformation(element, qualifierExpression, methodName, className, psiClass, document)); + } + } + } + } + return Optional.empty(); + } + } } diff --git a/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingOptionsProvider.java b/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingOptionsProvider.java index d27d4541..1f96d825 100644 --- a/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingOptionsProvider.java +++ b/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingOptionsProvider.java @@ -4,10 +4,15 @@ public class AdvancedExpressionFoldingOptionsProvider extends com.intellij.opena protected AdvancedExpressionFoldingOptionsProvider() { super(AdvancedExpressionFoldingSettings.getInstance().getState()); AdvancedExpressionFoldingSettings settings = AdvancedExpressionFoldingSettings.getInstance(); + AdvancedExpressionFoldingSettings.State state = settings.getState(); + checkBox("arithmeticExpressionsCollapse", "Math, BigDecimal and BigInteger expressions (deprecated)"); checkBox("concatenationExpressionsCollapse", "StringBuilder.append and Collection.add/remove expressions, interpolated Strings and Stream expressions"); checkBox("slicingExpressionsCollapse", "List.subList and String.substring expressions"); checkBox("comparingExpressionsCollapse", "Object.equals and Comparable.compareTo expressions"); + checkBox("java.time isBefore/isAfter expressions", state::isComparingLocalDatesCollapse, state::setComparingLocalDatesCollapse); + checkBox("LocalDate.of literals (e.g. 2018-02-12)" , state::isLocalDateLiteralCollapse, state::setLocalDateLiteralCollapse); + checkBox("Postfix LocalDate literals (e.g. 2018Y-02M-12D) " , state::isLocalDateLiteralPostfix, state::setLocalDateLiteralPostfix); checkBox("getExpressionsCollapse", "List.get, List.set, Map.get and Map.put expressions, array and list literals"); checkBox("rangeExpressionsCollapse", "For loops, range expressions"); checkBox("checkExpressionsCollapse", "Null safe calls"); diff --git a/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingSettings.java b/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingSettings.java index 5841ae13..8f83aafe 100644 --- a/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingSettings.java +++ b/src/com/intellij/advancedExpressionFolding/AdvancedExpressionFoldingSettings.java @@ -44,6 +44,11 @@ public static final class State { private boolean CONCATENATION_EXPRESSIONS = true; private boolean SLICING_EXPRESSIONS = true; private boolean COMPARING_EXPRESSIONS = true; + + private boolean COMPARING_LOCAL_DATES = true; + private boolean LOCAL_DATE_LITERAL = true; + private boolean LOCAL_DATE_LITERAL_POSTFIX = true; + private boolean GET_EXPRESSIONS = true; private boolean RANGE_EXPRESSIONS = true; private boolean CHECK_EXPRESSIONS = true; @@ -76,6 +81,18 @@ public boolean isGetExpressionsCollapse() { return GET_EXPRESSIONS; } + public boolean isComparingLocalDatesCollapse() { + return COMPARING_LOCAL_DATES; + } + + public boolean isLocalDateLiteralCollapse() { + return LOCAL_DATE_LITERAL; + } + + public boolean isLocalDateLiteralPostfix() { + return LOCAL_DATE_LITERAL_POSTFIX; + } + public boolean isRangeExpressionsCollapse() { return RANGE_EXPRESSIONS; } @@ -132,6 +149,19 @@ public void setComparingExpressionsCollapse(boolean value) { COMPARING_EXPRESSIONS = value; } + public void setComparingLocalDatesCollapse(boolean value) { + this.COMPARING_LOCAL_DATES = value; + } + + public void setLocalDateLiteralCollapse(boolean value) { + this.LOCAL_DATE_LITERAL = value; + } + + public void setLocalDateLiteralPostfix(boolean value) { + this.LOCAL_DATE_LITERAL_POSTFIX = value; + } + + public void setGetExpressionsCollapse(boolean value) { GET_EXPRESSIONS = value; } @@ -192,6 +222,9 @@ public void disableAll() { this.setRangeExpressionsCollapse(false); this.setSemicolonsCollapse(false); this.setSlicingExpressionsCollapse(false); + this.setComparingLocalDatesCollapse(false); + this.setLocalDateLiteralCollapse(false); + this.setLocalDateLiteralPostfix(false); } } } diff --git a/src/com/intellij/advancedExpressionFolding/LocalDateLiteral.java b/src/com/intellij/advancedExpressionFolding/LocalDateLiteral.java new file mode 100644 index 00000000..fd4f8efc --- /dev/null +++ b/src/com/intellij/advancedExpressionFolding/LocalDateLiteral.java @@ -0,0 +1,103 @@ +package com.intellij.advancedExpressionFolding; + +import com.intellij.lang.folding.FoldingDescriptor; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.FoldingGroup; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLiteralExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class LocalDateLiteral extends Expression { + public static final String DATE_SEPARATOR = "-"; + + public static final String YEAR_POSTFIX = "Y"; + public static final String MONTH_POSTFIX = "M"; + public static final String DAY_POSTFIX = "D"; + + @NotNull + private final PsiLiteralExpression year; + @NotNull + private final PsiLiteralExpression month; + @NotNull + private final PsiLiteralExpression day; + + public LocalDateLiteral(@NotNull PsiElement element, @NotNull TextRange textRange, @NotNull PsiLiteralExpression year, @NotNull PsiLiteralExpression month, @NotNull PsiLiteralExpression day) { + super(element, textRange); + this.year = year; + this.month = month; + this.day = day; + } + + @Override + public boolean supportsFoldRegions(@NotNull Document document, + @Nullable Expression parent) { + return true; + } + + @Override + public FoldingDescriptor[] buildFoldRegions(@NotNull PsiElement element, @NotNull Document document, @Nullable Expression parent) { + FoldingGroup group = FoldingGroup.newGroup(ListLiteral.class.getName()); + ArrayList descriptors = new ArrayList<>(); + descriptors.add(new FoldingDescriptor(element.getNode(), TextRange.create(textRange.getStartOffset(), + year.getTextRange().getStartOffset()), group) { + @NotNull + @Override + public String getPlaceholderText() { + return ""; + } + }); + + boolean usePostfix = AdvancedExpressionFoldingSettings.getInstance().getState().isLocalDateLiteralPostfix(); + + final String dateSep = DATE_SEPARATOR; + final String yearPostfix = usePostfix ? YEAR_POSTFIX : ""; + final String monthPostfix = usePostfix ? MONTH_POSTFIX : ""; + final String dayPostfix = usePostfix ? DAY_POSTFIX : ""; + + descriptors.add(new FoldingDescriptor(element.getNode(), TextRange.create(year.getTextRange().getEndOffset(), + month.getTextRange().getStartOffset()), group) { + @NotNull + @Override + public String getPlaceholderText() { + // Add leading zero to month if month is only a single digit + if (month.getTextLength() == 1) { + return yearPostfix + dateSep + "0"; + } else { + return yearPostfix + dateSep; + } + } + }); + + descriptors.add(new FoldingDescriptor(element.getNode(), TextRange.create(month.getTextRange().getEndOffset(), + day.getTextRange().getStartOffset()), group) { + @NotNull + @Override + public String getPlaceholderText() { + // Add leading zero to day if day is only a single digit + if (day.getTextLength() == 1) { + return monthPostfix + dateSep + "0"; + } else { + return monthPostfix + dateSep; + } + } + }); + + descriptors.add(new FoldingDescriptor(element.getNode(), TextRange.create(day.getTextRange().getEndOffset(), + textRange.getEndOffset()), group) { + @NotNull + @Override + public String getPlaceholderText() { + return dayPostfix + ""; + } + }); +// descriptors.add(year.buildFoldRegions(year.getElement(), document, this); +// descriptors.add(month.buildFoldRegions(month.getElement(), document, this); +// descriptors.add(day.buildFoldRegions(day.getElement(), document, this); + return descriptors.toArray(new FoldingDescriptor[0]); + }} diff --git a/test/com/intellij/advancedExpressionFolding/FoldingTest.java b/test/com/intellij/advancedExpressionFolding/FoldingTest.java index f44bcc90..1dd37962 100644 --- a/test/com/intellij/advancedExpressionFolding/FoldingTest.java +++ b/test/com/intellij/advancedExpressionFolding/FoldingTest.java @@ -206,6 +206,25 @@ public void testControlFlowMultiStatementTestData() { doReadOnlyFoldingTest(); } + public void testLocalDateTestData() { + AdvancedExpressionFoldingSettings.State state = AdvancedExpressionFoldingSettings.getInstance().getState(); + state.setComparingLocalDatesCollapse(true); + doReadOnlyFoldingTest(); + } + + public void testLocalDateLiteralTestData() { + AdvancedExpressionFoldingSettings.State state = AdvancedExpressionFoldingSettings.getInstance().getState(); + state.setLocalDateLiteralCollapse(true); + doReadOnlyFoldingTest(); + } + + public void testLocalDateLiteralPostfixTestData() { + AdvancedExpressionFoldingSettings.State state = AdvancedExpressionFoldingSettings.getInstance().getState(); + state.setLocalDateLiteralCollapse(true); + state.setLocalDateLiteralPostfix(true); + doReadOnlyFoldingTest(); + } + public void testCompactControlFlowTestData() { AdvancedExpressionFoldingSettings.getInstance().getState().setCompactControlFlowSyntaxCollapse(true); doFoldingTest(); diff --git a/testData/LocalDateLiteralPostfixTestData.java b/testData/LocalDateLiteralPostfixTestData.java new file mode 100644 index 00000000..2286ebb4 --- /dev/null +++ b/testData/LocalDateLiteralPostfixTestData.java @@ -0,0 +1,14 @@ +import java.time.LocalDate; + +class LocalDateLiteralTestData { + public static void main(String[] args) { + LocalDate d1 = LocalDate.of(2018, 01, 10); + LocalDate d1 = LocalDate.of(2018, 01, 10); + LocalDate d2 = LocalDate.of(2018, 12, 10); + LocalDate d3 = LocalDate.of(2018, 4 , 4 ); + boolean isBefore = d1.isBefore(d2); + boolean isAfter = d1.isAfter(d2); + boolean d2SmallerOrEqualD1 = !d1.isBefore(d2); + boolean d1SmallerOrEqualD2 = !d1.isAfter(LocalDate.of(2013, 1, 10)); + } +} \ No newline at end of file diff --git a/testData/LocalDateLiteralTestData.java b/testData/LocalDateLiteralTestData.java new file mode 100644 index 00000000..72e2a1ff --- /dev/null +++ b/testData/LocalDateLiteralTestData.java @@ -0,0 +1,14 @@ +import java.time.LocalDate; + +class LocalDateLiteralPostfixTestData { + public static void main(String[] args) { + LocalDate d1 = LocalDate.of(2018, 01, 10); + LocalDate d1 = LocalDate.of(2018, 01, 10); + LocalDate d2 = LocalDate.of(2018, 12, 10); + LocalDate d3 = LocalDate.of(2018, 4 , 4 ); + boolean isBefore = d1.isBefore(d2); + boolean isAfter = d1.isAfter(d2); + boolean d2SmallerOrEqualD1 = !d1.isBefore(d2); + boolean d1SmallerOrEqualD2 = !d1.isAfter(LocalDate.of(2013, 1, 10)); + } +} \ No newline at end of file diff --git a/testData/LocalDateTestData.java b/testData/LocalDateTestData.java new file mode 100644 index 00000000..b8f3afa3 --- /dev/null +++ b/testData/LocalDateTestData.java @@ -0,0 +1,12 @@ +import java.time.LocalDate; + +class LocalDateTestData { + public static void main(String[] args) { + LocalDate d1 = LocalDate.of(2018, 12, 10); + LocalDate d2 = LocalDate.of(2018, 12, 10); + boolean isBefore = d1.isBefore(d2); + boolean isAfter = d1.isAfter(d2); + boolean d2SmallerOrEqualD1 = !d1.isBefore(d2);; + boolean d1SmallerOrEqualD2 = !d1.isAfter(d2); + } +} \ No newline at end of file