diff --git a/json/src/com/intellij/json/JsonSpellcheckerStrategy.java b/json/src/com/intellij/json/JsonSpellcheckerStrategy.java index 44868943a193..1463baae75b8 100644 --- a/json/src/com/intellij/json/JsonSpellcheckerStrategy.java +++ b/json/src/com/intellij/json/JsonSpellcheckerStrategy.java @@ -3,6 +3,7 @@ package com.intellij.json; import com.intellij.json.psi.JsonStringLiteral; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiUtilCore; @@ -10,11 +11,16 @@ import com.intellij.spellchecker.inspections.PlainTextSplitter; import com.intellij.spellchecker.tokenizer.SpellcheckingStrategy; import com.intellij.spellchecker.tokenizer.TokenConsumer; import com.intellij.spellchecker.tokenizer.Tokenizer; +import com.intellij.util.ThreeState; +import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker; import com.jetbrains.jsonSchema.ide.JsonSchemaService; -import com.jetbrains.jsonSchema.impl.JsonSchemaCompletionContributor; +import com.jetbrains.jsonSchema.impl.JsonOriginalPsiWalker; import com.jetbrains.jsonSchema.impl.JsonSchemaObject; +import com.jetbrains.jsonSchema.impl.JsonSchemaResolver; +import com.jetbrains.jsonSchema.impl.JsonSchemaVariantsTreeBuilder; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.List; /** @@ -42,22 +48,39 @@ public class JsonSpellcheckerStrategy extends SpellcheckingStrategy { } }; + private static boolean matchesNameFromSchema(@NotNull JsonStringLiteral element) { + final VirtualFile file = PsiUtilCore.getVirtualFile(element); + if (file == null) return false; + + final JsonSchemaService service = JsonSchemaService.Impl.get(element.getProject()); + if (!service.isApplicableToFile(file)) return false; + final JsonSchemaObject rootSchema = service.getSchemaObject(file); + if (rootSchema == null) return false; + + String value = element.getValue(); + if (StringUtil.isEmpty(value)) return false; + + JsonOriginalPsiWalker walker = JsonLikePsiWalker.JSON_ORIGINAL_PSI_WALKER; + final PsiElement checkable = walker.goUpToCheckable(element); + if (checkable == null) return false; + final ThreeState isName = walker.isName(checkable); + final List position = walker.findPosition(checkable, isName == ThreeState.NO); + if (position == null || position.isEmpty() && isName == ThreeState.NO) return false; + + final Collection schemas = new JsonSchemaResolver(rootSchema, false, position).resolve(); + if (schemas.isEmpty()) return false; + + return schemas.stream().anyMatch(s -> s.getProperties().keySet().contains(value) + || s.getMatchingPatternPropertySchema(value) != null); + } @NotNull @Override public Tokenizer getTokenizer(PsiElement element) { if (element instanceof JsonStringLiteral) { - final VirtualFile file = PsiUtilCore.getVirtualFile(element); - if (file == null) return ourStringLiteralTokenizer; - - final JsonSchemaService service = JsonSchemaService.Impl.get(element.getProject()); - if (!service.isApplicableToFile(file)) return ourStringLiteralTokenizer; - final JsonSchemaObject rootSchema = service.getSchemaObject(file); - if (rootSchema == null) return ourStringLiteralTokenizer; - - if (JsonSchemaCompletionContributor.getCompletionVariants(rootSchema, element, element) != null) { - return EMPTY_TOKENIZER; - } + return matchesNameFromSchema((JsonStringLiteral)element) + ? EMPTY_TOKENIZER + : ourStringLiteralTokenizer; } return super.getTokenizer(element); } diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java index c67082afd42a..0f78ee2447f4 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaCompletionContributor.java @@ -40,6 +40,7 @@ import com.jetbrains.jsonSchema.extension.adapters.JsonPropertyAdapter; import com.jetbrains.jsonSchema.ide.JsonSchemaService; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.util.*; @@ -94,6 +95,7 @@ public class JsonSchemaCompletionContributor extends CompletionContributor { } } + @TestOnly public static List getCompletionVariants(@NotNull final JsonSchemaObject schema, @NotNull final PsiElement position, @NotNull final PsiElement originalPosition) { final List result = new ArrayList<>(); diff --git a/json/tests/test/com/intellij/json/JsonSpellcheckerTest.java b/json/tests/test/com/intellij/json/JsonSpellcheckerTest.java index 849deb87402a..eed6d8eb2b40 100644 --- a/json/tests/test/com/intellij/json/JsonSpellcheckerTest.java +++ b/json/tests/test/com/intellij/json/JsonSpellcheckerTest.java @@ -1,7 +1,18 @@ package com.intellij.json; +import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.extensions.AreaPicoContainer; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.util.containers.Predicate; +import com.jetbrains.jsonSchema.JsonSchemaTestProvider; +import com.jetbrains.jsonSchema.JsonSchemaTestServiceImpl; +import com.jetbrains.jsonSchema.ide.JsonSchemaService; /** * @author Mikhail Golubev @@ -22,6 +33,29 @@ public class JsonSpellcheckerTest extends JsonTestCase { doTest(); } + protected Predicate getAvailabilityPredicate() { + return file -> file.getFileType() instanceof LanguageFileType && ((LanguageFileType)file.getFileType()).getLanguage().isKindOf( + JsonLanguage.INSTANCE); + } + + public void testWithSchema() { + PsiFile[] files = myFixture.configureByFiles(getTestName(false) + ".json", "Schema.json"); + JsonSchemaTestServiceImpl.setProvider(new JsonSchemaTestProvider(files[1].getVirtualFile(), + getAvailabilityPredicate())); + AreaPicoContainer container = Extensions.getArea(getProject()).getPicoContainer(); + String key = JsonSchemaService.class.getName(); + container.unregisterComponent(key); + container.registerComponentImplementation(key, JsonSchemaTestServiceImpl.class); + Disposer.register(getTestRootDisposable(), new Disposable() { + @Override + public void dispose() { + JsonSchemaTestServiceImpl.setProvider(null); + } + }); + myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.checkHighlighting(true, false, true); + } + // WEB-31894 EA-117068 public void testAfterModificationOfStringLiteralWithEscaping() { myFixture.configureByFile(getTestName(false) + ".json"); diff --git a/json/tests/testData/spellchecker/Schema.json b/json/tests/testData/spellchecker/Schema.json new file mode 100644 index 000000000000..6cba6896a998 --- /dev/null +++ b/json/tests/testData/spellchecker/Schema.json @@ -0,0 +1,8 @@ +{ + "properties": { + "qwertyuiop": {} + }, + "patternProperties": { + "^boo.*moo$": {} + } +} diff --git a/json/tests/testData/spellchecker/WithSchema.json b/json/tests/testData/spellchecker/WithSchema.json new file mode 100644 index 000000000000..98e0d7e7996f --- /dev/null +++ b/json/tests/testData/spellchecker/WithSchema.json @@ -0,0 +1,6 @@ +{ + "$schema": "./Schema.json", + "qwertyuiop": "a", + "booxxxxmoo": "a", + "qwertyzop": "b" +}