From 7bbb655cd3e3bf8a24369d97120e4cee513ae9cf Mon Sep 17 00:00:00 2001 From: Anton Lobov Date: Tue, 1 Oct 2019 09:55:55 +0200 Subject: [PATCH] WEB-31865: add an intention action to sort properties GitOrigin-RevId: e58db1b1395a3c6de03bcac5638987e0c8cb3ed2 --- .../after.json.template | 6 + .../before.json.template | 6 + .../description.html | 5 + .../com/intellij/json/JsonBundle.properties | 4 +- .../JsonSortPropertiesIntention.java | 113 ++++++++++++++++++ .../json/JsonIntentionActionTest.java | 15 +++ .../testData/intention/SortProperties.json | 3 + .../intention/SortProperties_after.json | 11 ++ .../src/META-INF/JsonPlugin.xml | 4 + 9 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 json/resources/intentionDescriptions/JsonSortPropertiesIntention/after.json.template create mode 100644 json/resources/intentionDescriptions/JsonSortPropertiesIntention/before.json.template create mode 100644 json/resources/intentionDescriptions/JsonSortPropertiesIntention/description.html create mode 100644 json/src/com/intellij/json/intentions/JsonSortPropertiesIntention.java create mode 100644 json/tests/test/com/intellij/json/JsonIntentionActionTest.java create mode 100644 json/tests/testData/intention/SortProperties.json create mode 100644 json/tests/testData/intention/SortProperties_after.json diff --git a/json/resources/intentionDescriptions/JsonSortPropertiesIntention/after.json.template b/json/resources/intentionDescriptions/JsonSortPropertiesIntention/after.json.template new file mode 100644 index 000000000000..b5ded907ac4b --- /dev/null +++ b/json/resources/intentionDescriptions/JsonSortPropertiesIntention/after.json.template @@ -0,0 +1,6 @@ +{ + "a": 9, + "f": false, + "x": 5, + "z": true +} \ No newline at end of file diff --git a/json/resources/intentionDescriptions/JsonSortPropertiesIntention/before.json.template b/json/resources/intentionDescriptions/JsonSortPropertiesIntention/before.json.template new file mode 100644 index 000000000000..55478c3c9c01 --- /dev/null +++ b/json/resources/intentionDescriptions/JsonSortPropertiesIntention/before.json.template @@ -0,0 +1,6 @@ +{ + "x": 5, + "a": 9, + "z": true, + "f": false +} \ No newline at end of file diff --git a/json/resources/intentionDescriptions/JsonSortPropertiesIntention/description.html b/json/resources/intentionDescriptions/JsonSortPropertiesIntention/description.html new file mode 100644 index 000000000000..4c7d91b962ca --- /dev/null +++ b/json/resources/intentionDescriptions/JsonSortPropertiesIntention/description.html @@ -0,0 +1,5 @@ + + +This intention sorts JSON object properties alphabetically. + + diff --git a/json/src/com/intellij/json/JsonBundle.properties b/json/src/com/intellij/json/JsonBundle.properties index 77fa525a3382..0c038c65fc91 100644 --- a/json/src/com/intellij/json/JsonBundle.properties +++ b/json/src/com/intellij/json/JsonBundle.properties @@ -64,4 +64,6 @@ json.schema.ref.no.property=Property ''{0}'' not found settings.json.schema.add.mapping=Add mapping settings.json.schema.edit.mapping=Edit mapping -settings.json.schema.remove.mapping=Remove mapping \ No newline at end of file +settings.json.schema.remove.mapping=Remove mapping + +json.intention.sort.properties=Sort properties alphabetically \ No newline at end of file diff --git a/json/src/com/intellij/json/intentions/JsonSortPropertiesIntention.java b/json/src/com/intellij/json/intentions/JsonSortPropertiesIntention.java new file mode 100644 index 000000000000..d918adbe3135 --- /dev/null +++ b/json/src/com/intellij/json/intentions/JsonSortPropertiesIntention.java @@ -0,0 +1,113 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.json.intentions; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.intention.LowPriorityAction; +import com.intellij.json.JsonBundle; +import com.intellij.json.psi.JsonObject; +import com.intellij.json.psi.JsonProperty; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class JsonSortPropertiesIntention implements IntentionAction, LowPriorityAction { + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getText() { + return JsonBundle.message("json.intention.sort.properties"); + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getFamilyName() { + return JsonBundle.message("json.intention.sort.properties"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + PsiElement parent = findParentObject(editor, file); + return parent instanceof JsonObject && !isSorted((JsonObject)parent); + } + + @Nullable + private static PsiElement findParentObject(@NotNull Editor editor, @NotNull PsiFile file) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + JsonProperty property = PsiTreeUtil.getParentOfType(element, JsonProperty.class); + return property == null ? null : property.getParent(); + } + + private static boolean isSorted(@NotNull JsonObject parent) { + List list = parent.getPropertyList(); + if (list.size() <= 1) return true; + List names = ContainerUtil.map(list, p -> p.getName()); + return ContainerUtil.equalsIdentity(ContainerUtil.sorted(names), names); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PsiElement parentObject = findParentObject(editor, file); + assert parentObject instanceof JsonObject; + // cycle-sort performs the minimal amount of modifications, and we want to patch the tree as little as possible + cycleSortProperties(((JsonObject)parentObject).getPropertyList()); + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + codeStyleManager.reformat(parentObject); + } + + @Override + public boolean startInWriteAction() { + return true; + } + + private static void cycleSortProperties(@NotNull List properties) { + int size = properties.size(); + for (int cycleStart = 0; cycleStart < size; cycleStart++) { + JsonProperty item = properties.get(cycleStart); + int pos = advance(properties, size, cycleStart, item); + if (pos == -1) continue; + if (pos != cycleStart) { + exchange(properties, pos, cycleStart); + } + while (pos != cycleStart) { + pos = advance(properties, size, cycleStart, properties.get(cycleStart)); + if (pos == -1) break; + if (pos != cycleStart) { + exchange(properties, pos, cycleStart); + } + } + } + } + + private static int advance(@NotNull List properties, int size, int cycleStart, JsonProperty item) { + int pos = cycleStart; + String itemName = item.getName(); + for (int i = cycleStart + 1; i < size; i++) { + if (properties.get(i).getName().compareTo(itemName) < 0) pos++; + } + if (pos == cycleStart) return -1; + while (item == properties.get(pos)) pos++; + return pos; + } + + private static void exchange(@NotNull List properties, int pos, int item) { + JsonProperty propertyAtPos = properties.get(pos); + JsonProperty itemProperty = properties.get(item); + properties.set(pos, (JsonProperty)propertyAtPos.getParent().addBefore(itemProperty, propertyAtPos)); + properties.set(item, (JsonProperty)itemProperty.getParent().addBefore(propertyAtPos, itemProperty)); + propertyAtPos.delete(); + itemProperty.delete(); + } +} diff --git a/json/tests/test/com/intellij/json/JsonIntentionActionTest.java b/json/tests/test/com/intellij/json/JsonIntentionActionTest.java new file mode 100644 index 000000000000..d0ae6cd6385a --- /dev/null +++ b/json/tests/test/com/intellij/json/JsonIntentionActionTest.java @@ -0,0 +1,15 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.json; + +public class JsonIntentionActionTest extends JsonTestCase { + + private void doTest(@SuppressWarnings("SameParameterValue") String intentionKey) { + myFixture.testHighlighting("/intention/" + getTestName(false) + ".json"); + myFixture.launchAction(myFixture.getAvailableIntention(JsonBundle.message(intentionKey))); + myFixture.checkResultByFile("/intention/" + getTestName(false) + "_after.json"); + } + + public void testSortProperties() { + doTest("json.intention.sort.properties"); + } +} diff --git a/json/tests/testData/intention/SortProperties.json b/json/tests/testData/intention/SortProperties.json new file mode 100644 index 000000000000..f692aeed5203 --- /dev/null +++ b/json/tests/testData/intention/SortProperties.json @@ -0,0 +1,3 @@ +{ + "MAPBLKLOT": "0001001", "BLKLOT": "0001001", "BLOCK_NUM": "0001", "LOT_NUM": "001", "FROM_ST": "0", "TO_ST": "0", "STREET": "UNKNOWN", "ST_TYPE": null, "ODD_EVEN": "E" +} \ No newline at end of file diff --git a/json/tests/testData/intention/SortProperties_after.json b/json/tests/testData/intention/SortProperties_after.json new file mode 100644 index 000000000000..6e1e1fb74921 --- /dev/null +++ b/json/tests/testData/intention/SortProperties_after.json @@ -0,0 +1,11 @@ +{ + "BLKLOT": "0001001", + "BLOCK_NUM": "0001", + "FROM_ST": "0", + "LOT_NUM": "001", + "MAPBLKLOT": "0001001", + "ODD_EVEN": "E", + "STREET": "UNKNOWN", + "ST_TYPE": null, + "TO_ST": "0" +} \ No newline at end of file diff --git a/platform/platform-resources/src/META-INF/JsonPlugin.xml b/platform/platform-resources/src/META-INF/JsonPlugin.xml index 3fd4ccdacdfc..4869b397a0f6 100644 --- a/platform/platform-resources/src/META-INF/JsonPlugin.xml +++ b/platform/platform-resources/src/META-INF/JsonPlugin.xml @@ -111,6 +111,10 @@ + + com.intellij.json.intentions.JsonSortPropertiesIntention + JSON +