diff --git a/json/backend/tests/test/com/intellij/json/JsonSpellcheckerTest.java b/json/backend/tests/test/com/intellij/json/JsonSpellcheckerTest.java index ce37e291dc9d..433859420856 100644 --- a/json/backend/tests/test/com/intellij/json/JsonSpellcheckerTest.java +++ b/json/backend/tests/test/com/intellij/json/JsonSpellcheckerTest.java @@ -1,6 +1,7 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.json; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.json.json5.Json5FileType; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.IdeActions; @@ -8,7 +9,6 @@ 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.testFramework.ServiceContainerUtil; import com.intellij.testFramework.fixtures.InjectionTestFixture; import com.jetbrains.jsonSchema.JsonSchemaTestProvider; @@ -24,7 +24,7 @@ import java.util.function.Predicate; public class JsonSpellcheckerTest extends JsonTestCase { private void doTest() { - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); myFixture.configureByFile(getTestName(false) + ".json"); myFixture.checkHighlighting(true, false, true); } @@ -52,14 +52,14 @@ public class JsonSpellcheckerTest extends JsonTestCase { JsonSchemaTestServiceImpl.setProvider(null); } }); - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); myFixture.checkHighlighting(true, false, true); } // WEB-31894 EA-117068 public void testAfterModificationOfStringLiteralWithEscaping() { myFixture.configureByFile(getTestName(false) + ".json"); - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); myFixture.checkHighlighting(); myFixture.performEditorAction(IdeActions.ACTION_EDITOR_BACKSPACE); myFixture.performEditorAction(IdeActions.ACTION_EDITOR_BACKSPACE); @@ -67,7 +67,7 @@ public class JsonSpellcheckerTest extends JsonTestCase { } public void testHashesQuotedSpelling() { - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); myFixture.configureByText("hashes.json", """ { "typo": "hereistheerror", @@ -83,7 +83,7 @@ public class JsonSpellcheckerTest extends JsonTestCase { } public void testInjectedFragments() { - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); myFixture.configureByText(Json5FileType.INSTANCE, """ { diff --git a/platform/analysis-impl/resources/com/intellij/codeInspection/inspection-black-list.txt b/platform/analysis-impl/resources/com/intellij/codeInspection/inspection-black-list.txt index b6af62adeb15..1a8eca07f393 100644 --- a/platform/analysis-impl/resources/com/intellij/codeInspection/inspection-black-list.txt +++ b/platform/analysis-impl/resources/com/intellij/codeInspection/inspection-black-list.txt @@ -17,13 +17,13 @@ com.intellij.codeInspection.uncheckedWarnings.UncheckedWarningLocalInspection com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection com.intellij.codeInspection.varScopeCanBeNarrowed.FieldCanBeLocalInspection com.intellij.codeInspection.visibility.VisibilityInspection +com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection com.intellij.lang.javascript.inspections.JSMismatchedCollectionQueryUpdateInspection com.intellij.lang.javascript.inspections.JSPotentiallyInvalidConstructorUsageInspection com.intellij.lang.javascript.inspections.JSSuspiciousNameCombinationInspection com.intellij.lang.javascript.inspections.UnterminatedStatementJSInspection com.intellij.psi.css.inspections.CssUnknownPropertyInspection com.intellij.restructuredtext.python.inspections.RestRoleInspection -com.intellij.spellchecker.inspections.SpellCheckingInspection com.jetbrains.php.lang.inspections.PhpUndefinedClassInspection com.jetbrains.php.lang.inspections.PhpUndefinedVariableInspection com.jetbrains.php.lang.inspections.PhpUnusedParameterInspection diff --git a/platform/platform-tests/BUILD.bazel b/platform/platform-tests/BUILD.bazel index 9c2aff1a550d..e997f9429bd2 100644 --- a/platform/platform-tests/BUILD.bazel +++ b/platform/platform-tests/BUILD.bazel @@ -63,6 +63,7 @@ jvm_library( "//platform/vcs-impl:vcs-impl_test_lib", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "@lib//:http-client", "//jps/model-impl", "@lib//:gson", diff --git a/platform/platform-tests/intellij.platform.tests.iml b/platform/platform-tests/intellij.platform.tests.iml index 2a9f59eda913..4978466bf0f6 100644 --- a/platform/platform-tests/intellij.platform.tests.iml +++ b/platform/platform-tests/intellij.platform.tests.iml @@ -51,6 +51,7 @@ + diff --git a/platform/platform-tests/testSrc/com/intellij/codeHighlighting/CustomFileTypeInspectionTest.kt b/platform/platform-tests/testSrc/com/intellij/codeHighlighting/CustomFileTypeInspectionTest.kt index 2c2596fdc06b..703b9007aa4f 100644 --- a/platform/platform-tests/testSrc/com/intellij/codeHighlighting/CustomFileTypeInspectionTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/codeHighlighting/CustomFileTypeInspectionTest.kt @@ -1,7 +1,7 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.codeHighlighting -import com.intellij.spellchecker.inspections.SpellCheckingInspection +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection import com.intellij.testFramework.fixtures.BasePlatformTestCase class CustomFileTypeInspectionTest : BasePlatformTestCase() { @@ -15,7 +15,7 @@ infixl -- keyword -- infixl in line comment {- infixl in block comment -} """) - myFixture.enableInspections(SpellCheckingInspection()) + myFixture.enableInspections(GrazieSpellCheckingInspection()) myFixture.checkHighlighting() } diff --git a/plugins/devkit/devkit-tests/BUILD.bazel b/plugins/devkit/devkit-tests/BUILD.bazel index 4d10fa9db36c..4d49ae2fa474 100644 --- a/plugins/devkit/devkit-tests/BUILD.bazel +++ b/plugins/devkit/devkit-tests/BUILD.bazel @@ -35,6 +35,7 @@ jvm_library( "//xml/xml-psi-api:psi", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", ] ) ### auto-generated section `build intellij.devkit.testFramework` end diff --git a/plugins/devkit/devkit-tests/intellij.devkit.testFramework.iml b/plugins/devkit/devkit-tests/intellij.devkit.testFramework.iml index db6490ecc788..391e92b2f4ac 100644 --- a/plugins/devkit/devkit-tests/intellij.devkit.testFramework.iml +++ b/plugins/devkit/devkit-tests/intellij.devkit.testFramework.iml @@ -26,6 +26,7 @@ + \ No newline at end of file diff --git a/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/DevKitMnemonicsSpellingTest.kt b/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/DevKitMnemonicsSpellingTest.kt index b0b5c03145a5..f496d2298c2c 100644 --- a/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/DevKitMnemonicsSpellingTest.kt +++ b/plugins/devkit/devkit-tests/testSrc/org/jetbrains/idea/devkit/DevKitMnemonicsSpellingTest.kt @@ -1,10 +1,10 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.devkit +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection import com.intellij.openapi.module.Module import com.intellij.openapi.roots.ContentEntry import com.intellij.openapi.roots.ModifiableRootModel -import com.intellij.spellchecker.inspections.SpellCheckingInspection import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase @@ -27,7 +27,7 @@ internal class DevKitMnemonicsSpellingTest : LightJavaCodeInsightFixtureTestCase @Throws(Exception::class) override fun setUp() { super.setUp() - myFixture.enableInspections(SpellCheckingInspection::class.java) + myFixture.enableInspections(GrazieSpellCheckingInspection::class.java) } fun testMnemonics() { diff --git a/plugins/devkit/intellij.devkit.i18n/BUILD.bazel b/plugins/devkit/intellij.devkit.i18n/BUILD.bazel index cd814b8f6d20..a2f2c83084c2 100644 --- a/plugins/devkit/intellij.devkit.i18n/BUILD.bazel +++ b/plugins/devkit/intellij.devkit.i18n/BUILD.bazel @@ -69,6 +69,7 @@ jvm_library( "//java/testFramework", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//platform/core-ui", ], runtime_deps = [ diff --git a/plugins/devkit/intellij.devkit.i18n/intellij.devkit.i18n.iml b/plugins/devkit/intellij.devkit.i18n/intellij.devkit.i18n.iml index 6bb19983eba0..69b8007df942 100644 --- a/plugins/devkit/intellij.devkit.i18n/intellij.devkit.i18n.iml +++ b/plugins/devkit/intellij.devkit.i18n/intellij.devkit.i18n.iml @@ -30,6 +30,7 @@ + \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.i18n/test/PluginXmlCapitalizationInspectionTest.java b/plugins/devkit/intellij.devkit.i18n/test/PluginXmlCapitalizationInspectionTest.java index 2e1bc81631eb..2d562f14b0a2 100644 --- a/plugins/devkit/intellij.devkit.i18n/test/PluginXmlCapitalizationInspectionTest.java +++ b/plugins/devkit/intellij.devkit.i18n/test/PluginXmlCapitalizationInspectionTest.java @@ -2,8 +2,8 @@ package org.jetbrains.idea.devkit.i18n; import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.openapi.project.IntelliJProjectUtil; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.intellij.testFramework.TestDataPath; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; @@ -79,7 +79,7 @@ public class PluginXmlCapitalizationInspectionTest extends LightJavaCodeInsightF " enum Capitalization {NotSpecified,Title,Sentence}" + " Capitalization capitalization() default Capitalization.NotSpecified;" + "}"); - myFixture.enableInspections(new SpellCheckingInspection()); + myFixture.enableInspections(new GrazieSpellCheckingInspection()); myFixture.testHighlighting("pluginXmlCapitalization_extensionPoint.xml", "MyExtensionPoint.java"); } } diff --git a/plugins/grazie/resources/META-INF/plugin.xml b/plugins/grazie/resources/META-INF/plugin.xml index 8f4ddfab071f..09be3aaa1e0c 100644 --- a/plugins/grazie/resources/META-INF/plugin.xml +++ b/plugins/grazie/resources/META-INF/plugin.xml @@ -67,6 +67,11 @@ implementationClass="com.intellij.grazie.ide.inspection.detection.LanguageDetectionInspection" level="WEAK WARNING" key="grazie.detection.inspection.text" language=""/> + + diff --git a/plugins/grazie/src/BUILD.bazel b/plugins/grazie/src/BUILD.bazel index 6b4bdfc453d6..c05dce05a8a2 100644 --- a/plugins/grazie/src/BUILD.bazel +++ b/plugins/grazie/src/BUILD.bazel @@ -27,6 +27,7 @@ jvm_library( "//platform/editor-ui-api:editor-ui", "//platform/indexing-api:indexing", "//platform/projectModel-api:projectModel", + "//platform/refactoring", "//platform/vcs-api:vcs", "//platform/editor-ui-ex:editor-ex", "//platform/lang-api:lang", diff --git a/plugins/grazie/src/intellij.grazie.core.iml b/plugins/grazie/src/intellij.grazie.core.iml index 9c32cfd39966..cac1663bbdd3 100644 --- a/plugins/grazie/src/intellij.grazie.core.iml +++ b/plugins/grazie/src/intellij.grazie.core.iml @@ -22,6 +22,7 @@ + diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/spellcheck/GrazieSpellCheckingInspection.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/spellcheck/GrazieSpellCheckingInspection.java new file mode 100644 index 000000000000..10759fee4127 --- /dev/null +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/spellcheck/GrazieSpellCheckingInspection.java @@ -0,0 +1,346 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.grazie.spellcheck; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInspection.*; +import com.intellij.codeInspection.options.OptPane; +import com.intellij.lang.LanguageNamesValidation; +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.lang.refactoring.NamesValidator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.profile.codeInspection.InspectionProfileManager; +import com.intellij.psi.*; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.refactoring.rename.RenameUtil; +import com.intellij.spellchecker.SpellCheckerManager; +import com.intellij.spellchecker.SpellCheckerSeveritiesProvider; +import com.intellij.spellchecker.grazie.diacritic.Diacritics; +import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.spellchecker.inspections.SpellcheckingExtension; +import com.intellij.spellchecker.inspections.SpellcheckingExtension.SpellCheckingResult; +import com.intellij.spellchecker.inspections.SpellcheckingExtension.SpellingTypo; +import com.intellij.spellchecker.inspections.Splitter; +import com.intellij.spellchecker.tokenizer.SpellcheckingStrategy; +import com.intellij.spellchecker.tokenizer.SuppressibleSpellcheckingStrategy; +import com.intellij.spellchecker.tokenizer.TokenConsumer; +import com.intellij.spellchecker.util.SpellCheckerBundle; +import com.intellij.util.Consumer; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.text.StringSearcher; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static com.intellij.codeInspection.options.OptPane.checkbox; +import static com.intellij.codeInspection.options.OptPane.pane; +import static com.intellij.spellchecker.tokenizer.SpellcheckingStrategy.getSpellcheckingStrategy; + +public final class GrazieSpellCheckingInspection extends SpellCheckingInspection { + + @Override + public SuppressQuickFix @NotNull [] getBatchSuppressActions(@Nullable PsiElement element) { + if (element != null) { + SpellcheckingStrategy strategy = getSpellcheckingStrategy(element); + if (strategy instanceof SuppressibleSpellcheckingStrategy) { + return ((SuppressibleSpellcheckingStrategy)strategy).getSuppressActions(element, getShortName()); + } + } + return super.getBatchSuppressActions(element); + } + + @Override + public boolean isSuppressedFor(@NotNull PsiElement element) { + SpellcheckingStrategy strategy = getSpellcheckingStrategy(element); + if (strategy instanceof SuppressibleSpellcheckingStrategy) { + return ((SuppressibleSpellcheckingStrategy)strategy).isSuppressedFor(element, getShortName()); + } + return super.isSuppressedFor(element); + } + + @Override + public @NonNls @NotNull String getShortName() { + return SPELL_CHECKING_INSPECTION_TOOL_NAME; + } + + @Override + public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return super.buildVisitor(holder, isOnTheFly); + } + + @Override + public @NotNull HighlightDisplayLevel getDefaultLevel() { + return Objects.requireNonNull(HighlightDisplayLevel.find(SpellCheckerSeveritiesProvider.TYPO)); + } + + @Override + public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, + boolean isOnTheFly, + @NotNull LocalInspectionToolSession session) { + if (!Registry.is("spellchecker.inspection.enabled", true) || + InspectionProfileManager.hasTooLowSeverity(session, this) || + InjectedLanguageManager.getInstance(holder.getProject()).isFrankensteinInjection(holder.getFile())) { + return PsiElementVisitor.EMPTY_VISITOR; + } + + var scopes = buildAllowedScopes(); + SpellCheckerManager manager = SpellCheckerManager.getInstance(holder.getProject()); + return new PsiElementVisitor() { + @Override + public void visitWhiteSpace(@NotNull PsiWhiteSpace space) { } + + @Override + public void visitElement(final @NotNull PsiElement element) { + if (holder.getResultCount() > 1000 || element.getNode() == null) return; + + var strategy = getSpellcheckingStrategy(element); + if (strategy == null || !strategy.elementFitsScope(element, scopes) || isCopyrightComment(strategy, element)) return; + + SpellCheckingResult result = SpellcheckingExtension.Companion.spellcheck( + element, strategy, session, + typo -> { + if (hasSameNamedReferenceInFile(typo.getWord(), element, strategy)) return; + registerProblem(typo, holder); + } + ); + if (result == SpellCheckingResult.Checked) return; + + tokenize( + strategy, element, + new MyTokenConsumer(manager, strategy, holder, LanguageNamesValidation.INSTANCE.forLanguage(element.getLanguage())), + scopes + ); + } + }; + } + + private Set buildAllowedScopes() { + var result = new HashSet(); + if (processLiterals) { + result.add(SpellCheckingScope.Literals); + } + if (processComments) { + result.add(SpellCheckingScope.Comments); + } + if (processCode) { + result.add(SpellCheckingScope.Code); + } + return result; + } + + private static void addBatchDescriptor(@NotNull PsiElement element, + @NotNull TextRange textRange, + @NotNull String word, + @NotNull ProblemsHolder holder) { + var fixes = SpellcheckingStrategy.getDefaultBatchFixes(element, textRange, word); + ProblemDescriptor problemDescriptor = createProblemDescriptor(element, textRange, fixes, false); + holder.registerProblem(problemDescriptor); + } + + private static void addRegularDescriptor(@NotNull PsiElement element, @NotNull TextRange textRange, @NotNull ProblemsHolder holder, + boolean useRename, String wordWithTypo) { + SpellcheckingStrategy strategy = getSpellcheckingStrategy(element); + + LocalQuickFix[] fixes = strategy != null + ? strategy.getRegularFixes(element, textRange, useRename, wordWithTypo) + : SpellcheckingStrategy.getDefaultRegularFixes(useRename, wordWithTypo, element, textRange); + + final ProblemDescriptor problemDescriptor = createProblemDescriptor(element, textRange, fixes, true); + holder.registerProblem(problemDescriptor); + } + + private static ProblemDescriptor createProblemDescriptor(PsiElement element, TextRange textRange, + LocalQuickFix[] fixes, + boolean onTheFly) { + final String description = SpellCheckerBundle.message("typo.in.word.ref"); + return new ProblemDescriptorBase(element, element, description, fixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + false, textRange, onTheFly, onTheFly); + } + + @SuppressWarnings("PublicField") + public boolean processCode = true; + public boolean processLiterals = true; + public boolean processComments = true; + + @Override + public @NotNull OptPane getOptionsPane() { + return pane( + checkbox("processCode", SpellCheckerBundle.message("process.code")), + checkbox("processLiterals", SpellCheckerBundle.message("process.literals")), + checkbox("processComments", SpellCheckerBundle.message("process.comments")) + ); + } + + private static final class MyTokenConsumer extends TokenConsumer implements Consumer { + private static final Pattern NON_ENGLISH_LETTERS = Pattern.compile(".*[^a-zA-Z].*"); + + private final Set myAlreadyChecked = CollectionFactory.createSmallMemoryFootprintSet(); + private final SpellCheckerManager myManager; + private final ProblemsHolder myHolder; + private final NamesValidator myNamesValidator; + private final SpellcheckingStrategy myStrategy; + private boolean myCodeLike; + private PsiElement myElement; + private String myText; + private boolean myUseRename; + private int myOffset; + + MyTokenConsumer(SpellCheckerManager manager, SpellcheckingStrategy strategy, ProblemsHolder holder, NamesValidator namesValidator) { + myManager = manager; + myStrategy = strategy; + myHolder = holder; + myNamesValidator = namesValidator; + } + + @Override + public void consumeToken(final PsiElement element, + final String text, + final boolean useRename, + final int offset, + TextRange rangeToCheck, + Splitter splitter) { + myElement = element; + myText = text; + myUseRename = useRename; + myOffset = offset; + myCodeLike = myStrategy.elementFitsScope(myElement, Set.of(SpellCheckingScope.Code)); + splitter.split(text, rangeToCheck, this); + } + + @Override + public void consume(TextRange range) { + // Tokenization of large texts can produce a lot of tokens, but we are inside RA + ProgressManager.checkCanceled(); + String word = range.substring(myText); + if (!myHolder.isOnTheFly() && myAlreadyChecked.contains(word)) { + return; + } + + boolean keyword = myNamesValidator.isKeyword(word, myElement.getProject()); + if (keyword || !hasProblem(word, range) || hasSameNamedReferenceInFile(word, myElement, myStrategy)) { + return; + } + + range = myStrategy.getTokenizer(myElement) + .getHighlightingRange(myElement, myOffset, range); + assert range.getStartOffset() >= 0; + + if (!myHolder.isOnTheFly()) { + myAlreadyChecked.add(word); + } + registerProblem(myHolder, myElement, range, myUseRename, word); + } + + private boolean hasCamelCaseMatch(String word, TextRange range) { + Set camelCaseWords = myManager.getUserCamelCaseWords() + .stream() + .filter(camelCaseWord -> camelCaseWord.contains(word)) + .collect(Collectors.toSet()); + if (camelCaseWords.isEmpty()) { + return false; + } + + String text = myElement.getText(); + for (String camelCaseWord : camelCaseWords) { + ProgressManager.checkCanceled(); + int[] indexes = new StringSearcher(camelCaseWord, false, true).findAllOccurrences(text); + for (int index : indexes) { + TextRange hitRange = new TextRange(index, index + camelCaseWord.length()); + if (range.intersectsStrict(hitRange)) { + return true; + } + } + } + return false; + } + + private boolean hasProblem(String word, TextRange range) { + if (!myManager.hasProblem(word)) { + return false; + } + + // Check if a user has added CamelCase word to the project / app dictionary + if (hasCamelCaseMatch(word, range)) { + return false; + } + + // If the word isn't "code" or contains letters outside the English alphabet, + // then diacritic check should be skipped + if (!myCodeLike || NON_ENGLISH_LETTERS.matcher(word).matches()) { + return true; + } + + Project project = myElement.getProject(); + return SpellCheckerManager.getInstance(project).getSuggestions(word) + .stream() + .filter(suggestion -> RenameUtil.isValidName(project, myElement, suggestion)) + .noneMatch(suggestion -> Diacritics.equalsIgnoringDiacritics(word, suggestion)); + } + } + + private static boolean hasSameNamedReferenceInFile(String word, PsiElement element, SpellcheckingStrategy strategy) { + if (!strategy.elementFitsScope(element, Set.of(SpellCheckingScope.Comments))) { + return false; + } + + PsiFile file = element.getContainingFile(); + Map references = CachedValuesManager.getProjectPsiDependentCache(file, (psi) -> new ConcurrentHashMap<>()); + return references.computeIfAbsent(word, key -> hasSameNamedReferencesInFile(key, file)); + } + + private static boolean hasSameNamedReferencesInFile(String word, PsiFile file) { + int[] occurrences = new StringSearcher(word, true, true).findAllOccurrences(file.getText()); + if (occurrences.length <= 1) { + return false; + } + + for (int occurrence : occurrences) { + PsiReference reference = file.findReferenceAt(occurrence); + PsiElement resolvedReference = reference != null ? reference.resolve() : null; + if (reference != null && resolvedReference != null && reference.getElement() != resolvedReference) { + return true; + } + } + return false; + } + + private static boolean isCopyrightComment(SpellcheckingStrategy strategy, PsiElement psi) { + return strategy.elementFitsScope(psi, Set.of(SpellCheckingScope.Comments)) + && StringUtil.containsIgnoreCase(psi.getText(), "Copyright") + && isAtFileStart(psi); + } + + private static boolean isAtFileStart(PsiElement psi) { + PsiFile file = psi.getContainingFile(); + int textStart = psi.getTextRange().getStartOffset(); + return file.getViewProvider().getContents().subSequence(0, textStart).chars().noneMatch(Character::isLetterOrDigit); + } + + private static void registerProblem(@NotNull SpellingTypo typo, @NotNull ProblemsHolder holder) { + registerProblem(holder, typo.getElement(), typo.getRange(), false, typo.getWord()); + } + + private static void registerProblem(@NotNull ProblemsHolder holder, + @NotNull PsiElement element, + @NotNull TextRange range, + boolean useRename, + String word) { + if (holder.isOnTheFly()) { + addRegularDescriptor(element, range, holder, useRename, word); + } + else { + addBatchDescriptor(element, range, word, holder); + } + } +} diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt index 5d4e46eb6d44..363d7c916e54 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt @@ -8,6 +8,7 @@ import com.intellij.grazie.ide.inspection.grammar.GrazieInspection import com.intellij.grazie.jlanguage.Lang import com.intellij.grazie.remote.HunspellDescriptor import com.intellij.grazie.spellcheck.GrazieCheckers +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection import com.intellij.grazie.style.StyleInspection import com.intellij.grazie.text.TextChecker import com.intellij.grazie.text.TextContent @@ -25,7 +26,6 @@ import com.intellij.openapi.util.registry.Registry import com.intellij.psi.PsiElement import com.intellij.psi.PsiPlainText import com.intellij.spellchecker.SpellCheckerManager.Companion.getInstance -import com.intellij.spellchecker.inspections.SpellCheckingInspection import com.intellij.testFramework.ExtensionTestUtil import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.fixtures.BasePlatformTestCase @@ -37,7 +37,7 @@ import kotlin.io.path.Path abstract class GrazieTestBase : BasePlatformTestCase() { companion object { val inspectionTools by lazy { - arrayOf(GrazieInspection(), SpellCheckingInspection(), StyleInspection()) + arrayOf(GrazieInspection(), GrazieSpellCheckingInspection(), StyleInspection()) } /** diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/SpellcheckerInspectionTestCase.java b/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/SpellcheckerInspectionTestCase.java index 021de415e758..41eeb1aa1231 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/SpellcheckerInspectionTestCase.java +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/SpellcheckerInspectionTestCase.java @@ -2,7 +2,7 @@ package com.intellij.grazie.spellchecker.inspection; import com.intellij.codeInspection.LocalInspectionTool; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.testFramework.fixtures.BasePlatformTestCase; public abstract class SpellcheckerInspectionTestCase extends BasePlatformTestCase { @@ -22,6 +22,6 @@ public abstract class SpellcheckerInspectionTestCase extends BasePlatformTestCas } public static LocalInspectionTool[] getInspectionTools() { - return new LocalInspectionTool[]{new SpellCheckingInspection()}; + return new LocalInspectionTool[]{new GrazieSpellCheckingInspection()}; } } diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/quickfixes/AbstractSpellCheckerFixesTest.java b/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/quickfixes/AbstractSpellCheckerFixesTest.java index 0c47461fe1e2..a19e2b24aa2d 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/quickfixes/AbstractSpellCheckerFixesTest.java +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/inspection/quickfixes/AbstractSpellCheckerFixesTest.java @@ -16,8 +16,8 @@ package com.intellij.grazie.spellchecker.inspection.quickfixes; import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.grazie.spellchecker.inspection.SpellcheckerInspectionTestCase; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.intellij.spellchecker.quickfixes.ChangeTo; import com.intellij.util.containers.ContainerUtil; @@ -33,7 +33,7 @@ public abstract class AbstractSpellCheckerFixesTest extends SpellcheckerInspecti private void doChangeToTest(int toSelect) { myFixture.configureByFile(getBeforeFile()); - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); final IntentionAction intention = ContainerUtil.filter( myFixture.getAvailableIntentions(), (it) -> it.getFamilyName().equals(ChangeTo.getFixName()) ).get(toSelect + 1); @@ -44,7 +44,7 @@ public abstract class AbstractSpellCheckerFixesTest extends SpellcheckerInspecti protected void doNoQuickFixTest(String quickfix) { myFixture.configureByFile(getBeforeFile()); - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); assertNull(myFixture.getAvailableIntention(quickfix)); } diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/ui/SpellCheckingEditorCustomizationTest.java b/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/ui/SpellCheckingEditorCustomizationTest.java index e7fb96595028..041187cbb9fb 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/ui/SpellCheckingEditorCustomizationTest.java +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/spellchecker/ui/SpellCheckingEditorCustomizationTest.java @@ -16,13 +16,13 @@ package com.intellij.grazie.spellchecker.ui; import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.SpellCheckingEditorCustomizationProvider; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.PlainTextFileType; import com.intellij.openapi.fileTypes.PlainTextLanguage; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.intellij.testFramework.fixtures.BasePlatformTestCase; import com.intellij.ui.EditorCustomization; import com.intellij.ui.EditorTextFieldProvider; @@ -48,7 +48,7 @@ public class SpellCheckingEditorCustomizationTest extends BasePlatformTestCase { InspectionProfileImpl.INIT_INSPECTIONS = true; try { myFixture.configureByText(PlainTextFileType.INSTANCE, document); - myFixture.enableInspections(new SpellCheckingInspection()); + myFixture.enableInspections(new GrazieSpellCheckingInspection()); EditorCustomization customization = SpellCheckingEditorCustomizationProvider.getInstance().getCustomization(enabled); assertNotNull(customization); @@ -62,7 +62,7 @@ public class SpellCheckingEditorCustomizationTest extends BasePlatformTestCase { } public void testHighlightingWorksInEditorTextFieldWithCustomization() { - myFixture.enableInspections(new SpellCheckingInspection()); + myFixture.enableInspections(new GrazieSpellCheckingInspection()); var customization = SpellCheckingEditorCustomizationProvider.getInstance().getEnabledCustomization(); assertNotNull(customization); diff --git a/plugins/groovy/BUILD.bazel b/plugins/groovy/BUILD.bazel index b0715a79228d..cde20dd99cf7 100644 --- a/plugins/groovy/BUILD.bazel +++ b/plugins/groovy/BUILD.bazel @@ -146,6 +146,7 @@ jvm_library( "//jps/jps-builders:build", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//plugins/junit", "//plugins/junit_rt:junit-rt", "//java/java-indexing-api:indexing", diff --git a/plugins/groovy/intellij.groovy.iml b/plugins/groovy/intellij.groovy.iml index 9b1ca2044e67..9bf571696208 100644 --- a/plugins/groovy/intellij.groovy.iml +++ b/plugins/groovy/intellij.groovy.iml @@ -53,6 +53,7 @@ + diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovySpellcheckerTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovySpellcheckerTest.java index 00ef3f55328c..9deceae87086 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovySpellcheckerTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovySpellcheckerTest.java @@ -1,7 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.plugins.groovy.lang; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; public class GroovySpellcheckerTest extends LightJavaCodeInsightFixtureTestCase { @@ -14,7 +14,7 @@ public class GroovySpellcheckerTest extends LightJavaCodeInsightFixtureTestCase } private void checkTypos() { - myFixture.enableInspections(new SpellCheckingInspection()); + myFixture.enableInspections(new GrazieSpellCheckingInspection()); myFixture.checkHighlighting(false, false, true); } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrSuppressionTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrSuppressionTest.java index a644b2f94c57..23ef0383f26a 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrSuppressionTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrSuppressionTest.java @@ -2,8 +2,7 @@ package org.jetbrains.plugins.groovy.lang.highlighting; import com.intellij.analysis.AnalysisBundle; -import com.intellij.codeInsight.intention.IntentionAction; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture; import org.jetbrains.plugins.groovy.util.GroovyLatestTest; import org.jetbrains.plugins.groovy.util.HighlightingTest; @@ -12,7 +11,7 @@ import org.junit.Test; public class GrSuppressionTest extends GroovyLatestTest implements HighlightingTest { private void doTest(String before, String after) { JavaCodeInsightTestFixture fixture = getFixture(); - fixture.enableInspections(SpellCheckingInspection.class); + fixture.enableInspections(GrazieSpellCheckingInspection.class); configureByText(before); fixture.checkHighlighting(); fixture.launchAction(fixture.getAvailableIntention(AnalysisBundle.message("suppress.inspection.file"))); diff --git a/plugins/htmltools/BUILD.bazel b/plugins/htmltools/BUILD.bazel index a6f0cbb87e3b..fa177d9286a9 100644 --- a/plugins/htmltools/BUILD.bazel +++ b/plugins/htmltools/BUILD.bazel @@ -56,6 +56,7 @@ jvm_library( "//platform/testFramework:testFramework_test_lib", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//xml/xml-psi-api:psi", "//xml/tests:tests_test_lib", "//platform/core-ui", diff --git a/plugins/htmltools/intellij.html.tools.tests.iml b/plugins/htmltools/intellij.html.tools.tests.iml index cf6ce47a4000..3612ffb77f90 100644 --- a/plugins/htmltools/intellij.html.tools.tests.iml +++ b/plugins/htmltools/intellij.html.tools.tests.iml @@ -16,6 +16,7 @@ + diff --git a/plugins/htmltools/testSrc/com/intellij/htmltools/codeInsight/daemon/HtmlHighlightingTest.java b/plugins/htmltools/testSrc/com/intellij/htmltools/codeInsight/daemon/HtmlHighlightingTest.java index 1844b1c2de68..9a2db575442d 100644 --- a/plugins/htmltools/testSrc/com/intellij/htmltools/codeInsight/daemon/HtmlHighlightingTest.java +++ b/plugins/htmltools/testSrc/com/intellij/htmltools/codeInsight/daemon/HtmlHighlightingTest.java @@ -12,6 +12,7 @@ import com.intellij.codeInsight.intention.impl.CachedIntentions; import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.ex.InspectionProfileImpl; import com.intellij.codeInspection.htmlInspections.*; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.htmltools.HtmlToolsTestsUtil; import com.intellij.htmltools.codeInspection.htmlInspections.HtmlDeprecatedAttributeInspection; import com.intellij.htmltools.codeInspection.htmlInspections.HtmlDeprecatedTagInspection; @@ -31,7 +32,6 @@ import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlTokenType; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.intellij.testFramework.HighlightTestInfo; import com.intellij.testFramework.fixtures.BasePlatformTestCase; import com.intellij.xml.Html5SchemaProvider; @@ -841,7 +841,7 @@ public class HtmlHighlightingTest extends BasePlatformTestCase { } public void testSpelling() { - myFixture.enableInspections(new SpellCheckingInspection()); + myFixture.enableInspections(new GrazieSpellCheckingInspection()); doTest(); } diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/checkers/AbstractKotlinHighlightVisitorTest.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/checkers/AbstractKotlinHighlightVisitorTest.java index 5e2d7ad4b7f2..d3aa82779a39 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/checkers/AbstractKotlinHighlightVisitorTest.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/checkers/AbstractKotlinHighlightVisitorTest.java @@ -3,12 +3,12 @@ package org.jetbrains.kotlin.checkers; import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.platform.testFramework.core.FileComparisonFailedError; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.intellij.testFramework.ExpectedHighlightingData; import com.intellij.testFramework.fixtures.impl.JavaCodeInsightTestFixtureImpl; import kotlin.Unit; @@ -60,7 +60,7 @@ public abstract class AbstractKotlinHighlightVisitorTest extends KotlinLightCode void doTestWithInfos(@NotNull String __) { myFixture.configureByFile(fileName()); - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); try { // TODO fix duplicate highlighting of some symbols diff --git a/plugins/kotlin/idea/tests/testData/inspections/spelling/inspectionData/inspections.test b/plugins/kotlin/idea/tests/testData/inspections/spelling/inspectionData/inspections.test index 7b9a63d95f8a..5c8ac1c822f2 100644 --- a/plugins/kotlin/idea/tests/testData/inspections/spelling/inspectionData/inspections.test +++ b/plugins/kotlin/idea/tests/testData/inspections/spelling/inspectionData/inspections.test @@ -1 +1 @@ -// INSPECTION_CLASS: com.intellij.spellchecker.inspections.SpellCheckingInspection +// INSPECTION_CLASS: com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection diff --git a/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressActive.kt b/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressActive.kt index b7d45875a7c1..bf21292110d9 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressActive.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressActive.kt @@ -1,5 +1,5 @@ // "Typo: Change to..." "false" -// TOOL: com.intellij.spellchecker.inspections.SpellCheckingInspection +// TOOL: com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection // ACTION: Convert property initializer to getter // ACTION: Convert to 'buildString' call // ACTION: Convert to lazy property diff --git a/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressAvailable.kt b/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressAvailable.kt index 0ad86079b1bf..448fb635d91f 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressAvailable.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressAvailable.kt @@ -1,5 +1,5 @@ // "Suppress 'SpellCheckingInspection' for val str" "true" -// TOOL: com.intellij.spellchecker.inspections.SpellCheckingInspection +// TOOL: com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection val str = "kjsghkjshtiurhuig" // FUS_K2_QUICKFIX_NAME: com.intellij.codeInspection.SuppressIntentionActionFromFix diff --git a/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressAvailable.kt.after b/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressAvailable.kt.after index af5f536c5f02..7b60259e935b 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressAvailable.kt.after +++ b/plugins/kotlin/idea/tests/testData/quickfix/suppress/external/suppressAvailable.kt.after @@ -1,5 +1,5 @@ // "Suppress 'SpellCheckingInspection' for val str" "true" -// TOOL: com.intellij.spellchecker.inspections.SpellCheckingInspection +// TOOL: com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection @Suppress("SpellCheckingInspection") val str = "kjsghkjshtiurhuig" diff --git a/plugins/markdown/test/BUILD.bazel b/plugins/markdown/test/BUILD.bazel index 45b791454d8d..3c61e87faa0c 100644 --- a/plugins/markdown/test/BUILD.bazel +++ b/plugins/markdown/test/BUILD.bazel @@ -38,6 +38,7 @@ jvm_library( "//platform/usageView", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//xml/xml-psi-api:psi", "//plugins/yaml", "//plugins/yaml:yaml_test_lib", diff --git a/plugins/markdown/test/intellij.markdown.tests.iml b/plugins/markdown/test/intellij.markdown.tests.iml index 298ad632f78d..2bda490d4374 100644 --- a/plugins/markdown/test/intellij.markdown.tests.iml +++ b/plugins/markdown/test/intellij.markdown.tests.iml @@ -26,6 +26,7 @@ + diff --git a/plugins/markdown/test/src/org/intellij/plugins/markdown/spellchecking/MarkdownSpellcheckerTest.java b/plugins/markdown/test/src/org/intellij/plugins/markdown/spellchecking/MarkdownSpellcheckerTest.java index 45e4ffeb2f05..d9a709316c7b 100644 --- a/plugins/markdown/test/src/org/intellij/plugins/markdown/spellchecking/MarkdownSpellcheckerTest.java +++ b/plugins/markdown/test/src/org/intellij/plugins/markdown/spellchecking/MarkdownSpellcheckerTest.java @@ -2,7 +2,7 @@ package org.intellij.plugins.markdown.spellchecking; import com.intellij.codeInsight.intention.IntentionManager; import com.intellij.codeInsight.intention.impl.config.IntentionManagerImpl; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.testFramework.fixtures.BasePlatformTestCase; import org.intellij.plugins.markdown.MarkdownTestingUtil; @@ -11,7 +11,7 @@ public class MarkdownSpellcheckerTest extends BasePlatformTestCase { @Override protected void setUp() throws Exception { super.setUp(); - myFixture.enableInspections(new SpellCheckingInspection()); + myFixture.enableInspections(new GrazieSpellCheckingInspection()); } public void testAll() { diff --git a/plugins/maven/src/test/BUILD.bazel b/plugins/maven/src/test/BUILD.bazel index 8803021914b6..b56d7e2a241f 100644 --- a/plugins/maven/src/test/BUILD.bazel +++ b/plugins/maven/src/test/BUILD.bazel @@ -45,6 +45,7 @@ jvm_library( "//plugins/properties/properties-psi-api:psi", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//platform/vcs-api:vcs", "//plugins/groovy", "//plugins/groovy:groovy_test_lib", diff --git a/plugins/maven/src/test/intellij.maven.tests.iml b/plugins/maven/src/test/intellij.maven.tests.iml index 05cb19d01e64..c15720177b84 100644 --- a/plugins/maven/src/test/intellij.maven.tests.iml +++ b/plugins/maven/src/test/intellij.maven.tests.iml @@ -31,6 +31,7 @@ + diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenSpellcheckerTest.kt b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenSpellcheckerTest.kt index ad2ddab2396d..ffd90caaf84e 100644 --- a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenSpellcheckerTest.kt +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenSpellcheckerTest.kt @@ -15,15 +15,15 @@ */ package org.jetbrains.idea.maven.dom +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection import com.intellij.maven.testFramework.MavenDomTestCase -import com.intellij.spellchecker.inspections.SpellCheckingInspection import kotlinx.coroutines.runBlocking import org.junit.Test class MavenSpellcheckerTest : MavenDomTestCase() { @Test fun testSpell() = runBlocking { - fixture.enableInspections(SpellCheckingInspection::class.java) + fixture.enableInspections(GrazieSpellCheckingInspection::class.java) createProjectPom(""" test diff --git a/plugins/properties/tests/BUILD.bazel b/plugins/properties/tests/BUILD.bazel index 37d796a07d33..bca2163b8135 100644 --- a/plugins/properties/tests/BUILD.bazel +++ b/plugins/properties/tests/BUILD.bazel @@ -34,6 +34,7 @@ jvm_library( "//platform/util/diff", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//java/openapi:java", "//xml/impl", "//plugins/properties:properties-backend", diff --git a/plugins/properties/tests/intellij.properties.tests.iml b/plugins/properties/tests/intellij.properties.tests.iml index 76de6b2bf756..41f91f203bc4 100644 --- a/plugins/properties/tests/intellij.properties.tests.iml +++ b/plugins/properties/tests/intellij.properties.tests.iml @@ -25,6 +25,7 @@ + diff --git a/plugins/properties/tests/testSrc/com/intellij/lang/properties/PropertiesSpellcheckingTest.java b/plugins/properties/tests/testSrc/com/intellij/lang/properties/PropertiesSpellcheckingTest.java index 8e73637bc95c..835d8915c378 100644 --- a/plugins/properties/tests/testSrc/com/intellij/lang/properties/PropertiesSpellcheckingTest.java +++ b/plugins/properties/tests/testSrc/com/intellij/lang/properties/PropertiesSpellcheckingTest.java @@ -15,13 +15,13 @@ */ package com.intellij.lang.properties; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; public class PropertiesSpellcheckingTest extends LightJavaCodeInsightFixtureTestCase { public void testPropertiesSpellcheckingStrategy() { - myFixture.enableInspections(new SpellCheckingInspection()); + myFixture.enableInspections(new GrazieSpellCheckingInspection()); myFixture.configureByText("test.properties", """ diff --git a/plugins/properties/tests/testSrc/com/intellij/lang/properties/rename/PropertyRenameToTest.kt b/plugins/properties/tests/testSrc/com/intellij/lang/properties/rename/PropertyRenameToTest.kt index 262c6fa94d39..a75201ecebfc 100644 --- a/plugins/properties/tests/testSrc/com/intellij/lang/properties/rename/PropertyRenameToTest.kt +++ b/plugins/properties/tests/testSrc/com/intellij/lang/properties/rename/PropertyRenameToTest.kt @@ -1,7 +1,7 @@ package com.intellij.lang.properties.rename +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection import com.intellij.lang.properties.psi.impl.PropertyKeyImpl -import com.intellij.spellchecker.inspections.SpellCheckingInspection import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.util.ProcessingContext @@ -21,7 +21,7 @@ class PropertyRenameToTest : BasePlatformTestCase() { fun `test rename action is enabled and visible`() { myFixture.configureByText("a.properties", "helloworld=value") - myFixture.enableInspections(SpellCheckingInspection()) + myFixture.enableInspections(GrazieSpellCheckingInspection()) myFixture.checkHighlighting() myFixture.getAvailableIntention("Typo: Rename to…") ?: error("RenameTo intention is not available") } diff --git a/plugins/sh/core/BUILD.bazel b/plugins/sh/core/BUILD.bazel index 08c6d91ace31..0f5b1c72f300 100644 --- a/plugins/sh/core/BUILD.bazel +++ b/plugins/sh/core/BUILD.bazel @@ -55,6 +55,7 @@ jvm_library( "//plugins/sh/plugin:plugin-main", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//platform/testFramework", "//platform/testFramework:testFramework_test_lib", "//platform/analysis-impl", diff --git a/plugins/sh/core/intellij.sh.tests.iml b/plugins/sh/core/intellij.sh.tests.iml index 68bf13386721..73496f49bc6f 100644 --- a/plugins/sh/core/intellij.sh.tests.iml +++ b/plugins/sh/core/intellij.sh.tests.iml @@ -16,6 +16,7 @@ + diff --git a/plugins/sh/core/tests/com/intellij/sh/spellchecker/ShSpellcheckerTest.java b/plugins/sh/core/tests/com/intellij/sh/spellchecker/ShSpellcheckerTest.java index 72642bc631a1..192f3070f762 100644 --- a/plugins/sh/core/tests/com/intellij/sh/spellchecker/ShSpellcheckerTest.java +++ b/plugins/sh/core/tests/com/intellij/sh/spellchecker/ShSpellcheckerTest.java @@ -1,8 +1,8 @@ // 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.sh.spellchecker; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.openapi.application.PluginPathManager; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.intellij.testFramework.fixtures.BasePlatformTestCase; public class ShSpellcheckerTest extends BasePlatformTestCase { @@ -14,7 +14,7 @@ public class ShSpellcheckerTest extends BasePlatformTestCase { @Override public void setUp() throws Exception { super.setUp(); - myFixture.enableInspections(new SpellCheckingInspection()); + myFixture.enableInspections(new GrazieSpellCheckingInspection()); } public void testTypoInString() { doTest(); } diff --git a/plugins/textmate/tests/BUILD.bazel b/plugins/textmate/tests/BUILD.bazel index 8c7a575cf1fb..15d80b92ce6f 100644 --- a/plugins/textmate/tests/BUILD.bazel +++ b/plugins/textmate/tests/BUILD.bazel @@ -41,6 +41,7 @@ jvm_library( "//platform/editor-ui-api:editor-ui", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//platform/projectModel-api:projectModel", "//plugins/textmate/plugin", "//plugins/textmate/backend", diff --git a/plugins/textmate/tests/intellij.textmate.tests.iml b/plugins/textmate/tests/intellij.textmate.tests.iml index 03e29db6ac21..2675aa0107d5 100644 --- a/plugins/textmate/tests/intellij.textmate.tests.iml +++ b/plugins/textmate/tests/intellij.textmate.tests.iml @@ -27,6 +27,7 @@ + diff --git a/plugins/textmate/tests/tests/org/jetbrains/plugins/textmate/spellchecker/TextMateSpellcheckingTest.java b/plugins/textmate/tests/tests/org/jetbrains/plugins/textmate/spellchecker/TextMateSpellcheckingTest.java index 9a47f15e81f6..64f8e1a5249e 100644 --- a/plugins/textmate/tests/tests/org/jetbrains/plugins/textmate/spellchecker/TextMateSpellcheckingTest.java +++ b/plugins/textmate/tests/tests/org/jetbrains/plugins/textmate/spellchecker/TextMateSpellcheckingTest.java @@ -1,11 +1,11 @@ package org.jetbrains.plugins.textmate.spellchecker; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import org.jetbrains.plugins.textmate.TextMateAcceptanceTestCase; public class TextMateSpellcheckingTest extends TextMateAcceptanceTestCase { public void testSimple() { - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); myFixture.configureByText("test.md_hack", "some typooo"); myFixture.checkHighlighting(); } diff --git a/plugins/yaml/backend/BUILD.bazel b/plugins/yaml/backend/BUILD.bazel index 06cdd3a89f63..5385a4de5642 100644 --- a/plugins/yaml/backend/BUILD.bazel +++ b/plugins/yaml/backend/BUILD.bazel @@ -73,6 +73,7 @@ jvm_library( "//platform/testFramework:testFramework_test_lib", "//spellchecker", "//spellchecker:spellchecker_test_lib", + "//plugins/grazie/src:core", "//platform/platform-impl:ide-impl", "//json/backend", "//json/backend/tests:tests_test_lib", diff --git a/plugins/yaml/backend/intellij.yaml.backend.iml b/plugins/yaml/backend/intellij.yaml.backend.iml index aa121b363404..78f84ef98731 100644 --- a/plugins/yaml/backend/intellij.yaml.backend.iml +++ b/plugins/yaml/backend/intellij.yaml.backend.iml @@ -24,6 +24,7 @@ + diff --git a/plugins/yaml/backend/testSrc/highlighting/YAMLSpellCheckerTest.kt b/plugins/yaml/backend/testSrc/highlighting/YAMLSpellCheckerTest.kt index 9bda78a809ca..005dca29fbe8 100644 --- a/plugins/yaml/backend/testSrc/highlighting/YAMLSpellCheckerTest.kt +++ b/plugins/yaml/backend/testSrc/highlighting/YAMLSpellCheckerTest.kt @@ -1,7 +1,7 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.yaml.highlighting -import com.intellij.spellchecker.inspections.SpellCheckingInspection +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.testFramework.fixtures.InjectionTestFixture import org.intellij.lang.regexp.RegExpLanguage @@ -10,7 +10,7 @@ class YAMLSpellCheckerTest : BasePlatformTestCase() { override fun setUp() { super.setUp() - myFixture.enableInspections(SpellCheckingInspection::class.java) + myFixture.enableInspections(GrazieSpellCheckingInspection::class.java) } fun testSpellChecking() { diff --git a/plugins/yaml/backend/testSrc/schema/YamlWithSchemaSpellcheckingTest.kt b/plugins/yaml/backend/testSrc/schema/YamlWithSchemaSpellcheckingTest.kt index df3fbe2f2a4b..d87f2800c050 100644 --- a/plugins/yaml/backend/testSrc/schema/YamlWithSchemaSpellcheckingTest.kt +++ b/plugins/yaml/backend/testSrc/schema/YamlWithSchemaSpellcheckingTest.kt @@ -2,10 +2,10 @@ package org.jetbrains.yaml.schema import com.intellij.codeInspection.InspectionProfileEntry +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection import com.intellij.openapi.application.ex.PathManagerEx import com.intellij.openapi.fileTypes.LanguageFileType import com.intellij.openapi.vfs.VirtualFile -import com.intellij.spellchecker.inspections.SpellCheckingInspection import com.jetbrains.jsonSchema.JsonSchemaHighlightingTestBase import org.jetbrains.yaml.YAMLLanguage import java.util.function.Predicate @@ -31,7 +31,7 @@ class YamlWithSchemaSpellcheckingTest : JsonSchemaHighlightingTestBase() { } fun testYamlSpellcheckingWithSchema() { - myFixture.enableInspections(SpellCheckingInspection::class.java) + myFixture.enableInspections(GrazieSpellCheckingInspection::class.java) val schema = """ { "properties": { diff --git a/python/testSrc/com/jetbrains/python/PySpellCheckerTest.java b/python/testSrc/com/jetbrains/python/PySpellCheckerTest.java index c4c844d69b67..ad59d38695b0 100644 --- a/python/testSrc/com/jetbrains/python/PySpellCheckerTest.java +++ b/python/testSrc/com/jetbrains/python/PySpellCheckerTest.java @@ -1,7 +1,7 @@ // Copyright 2000-2021 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.jetbrains.python; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.LanguageLevel; @@ -70,7 +70,7 @@ public class PySpellCheckerTest extends PyTestCase { } private void doTest() { - myFixture.enableInspections(SpellCheckingInspection.class); + myFixture.enableInspections(GrazieSpellCheckingInspection.class); myFixture.configureByFile("inspections/spelling/" + getTestName(true) + ".py"); myFixture.checkHighlighting(true, false, true); } diff --git a/python/testSrc/com/jetbrains/python/inspections/flake8/Flake8InspectionSuppressorTest.java b/python/testSrc/com/jetbrains/python/inspections/flake8/Flake8InspectionSuppressorTest.java index cfed0d835a72..d67012de2b1c 100644 --- a/python/testSrc/com/jetbrains/python/inspections/flake8/Flake8InspectionSuppressorTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/flake8/Flake8InspectionSuppressorTest.java @@ -2,8 +2,8 @@ package com.jetbrains.python.inspections.flake8; import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection; import com.intellij.psi.PsiFile; -import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.jetbrains.python.PythonFileType; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.inspections.PyComparisonWithNoneInspection; @@ -39,7 +39,7 @@ public class Flake8InspectionSuppressorTest extends PyTestCase { public void testSuppressingNonPythonSpecificInspections() { doTestByText("s = 'ipaddress' # noqa", - SpellCheckingInspection.class); + GrazieSpellCheckingInspection.class); } public void testTopLevelCommentsIgnored() { diff --git a/python/testSrc/com/jetbrains/python/inspections/typeignore/TypeIgnoreInspectionSuppressorTest.kt b/python/testSrc/com/jetbrains/python/inspections/typeignore/TypeIgnoreInspectionSuppressorTest.kt index f0963353a317..07de41789061 100644 --- a/python/testSrc/com/jetbrains/python/inspections/typeignore/TypeIgnoreInspectionSuppressorTest.kt +++ b/python/testSrc/com/jetbrains/python/inspections/typeignore/TypeIgnoreInspectionSuppressorTest.kt @@ -1,8 +1,8 @@ // Copyright 2000-2021 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.jetbrains.python.inspections.typeignore +import com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection import com.intellij.psi.PsiFile -import com.intellij.spellchecker.inspections.SpellCheckingInspection import com.jetbrains.python.PythonFileType import com.jetbrains.python.fixtures.PyTestCase import com.jetbrains.python.inspections.* @@ -53,7 +53,7 @@ class TypeIgnoreInspectionSuppressorTest : PyTestCase() { } fun testWithNoqa() { - myFixture.enableInspections(SpellCheckingInspection::class.java) + myFixture.enableInspections(GrazieSpellCheckingInspection::class.java) doTestByText(""" def foo(x: str): print(x.bar + 'ajsd') # type: ignore # noqa diff --git a/spellchecker/resources/intellij.spellchecker.xml b/spellchecker/resources/intellij.spellchecker.xml index 998e2ddcd4fb..ec57114422ec 100644 --- a/spellchecker/resources/intellij.spellchecker.xml +++ b/spellchecker/resources/intellij.spellchecker.xml @@ -28,11 +28,6 @@ - - diff --git a/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java b/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java index ffb44994ccbd..4244ec9ddea9 100644 --- a/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java +++ b/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java @@ -2,145 +2,25 @@ package com.intellij.spellchecker.inspections; import com.intellij.codeInspection.*; -import com.intellij.codeInspection.options.OptPane; -import com.intellij.lang.LanguageNamesValidation; -import com.intellij.lang.injection.InjectedLanguageManager; -import com.intellij.lang.refactoring.NamesValidator; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.profile.codeInspection.InspectionProfileManager; -import com.intellij.psi.*; -import com.intellij.psi.util.CachedValuesManager; -import com.intellij.refactoring.rename.RenameUtil; -import com.intellij.spellchecker.SpellCheckerManager; -import com.intellij.spellchecker.grazie.diacritic.Diacritics; -import com.intellij.spellchecker.inspections.SpellcheckingExtension.SpellCheckingResult; -import com.intellij.spellchecker.inspections.SpellcheckingExtension.SpellingTypo; -import com.intellij.spellchecker.tokenizer.LanguageSpellchecking; +import com.intellij.psi.PsiElement; import com.intellij.spellchecker.tokenizer.SpellcheckingStrategy; -import com.intellij.spellchecker.tokenizer.SuppressibleSpellcheckingStrategy; import com.intellij.spellchecker.tokenizer.TokenConsumer; -import com.intellij.spellchecker.util.SpellCheckerBundle; -import com.intellij.util.Consumer; -import com.intellij.util.containers.CollectionFactory; -import com.intellij.util.text.StringSearcher; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.HashSet; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import static com.intellij.codeInspection.options.OptPane.checkbox; -import static com.intellij.codeInspection.options.OptPane.pane; +import static com.intellij.spellchecker.tokenizer.SpellcheckingStrategy.getSpellcheckingStrategy; -public final class SpellCheckingInspection extends LocalInspectionTool implements DumbAware { +/** + * Please use {@link com.intellij.grazie.spellcheck.GrazieSpellCheckingInspection} instead. + * The class is not marked as deprecated even though it is not used as an inspection anymore + * because {@link SpellCheckingScope} and both {@link SpellCheckingInspection#tokenize} methods are not deprecated and will not be in foreseeable future. + * They should be present here to preserve backward compatibility. + */ +public abstract class SpellCheckingInspection extends LocalInspectionTool implements DumbAware { public static final String SPELL_CHECKING_INSPECTION_TOOL_NAME = "SpellCheckingInspection"; - @Override - public SuppressQuickFix @NotNull [] getBatchSuppressActions(@Nullable PsiElement element) { - if (element != null) { - SpellcheckingStrategy strategy = getSpellcheckingStrategy(element); - if (strategy instanceof SuppressibleSpellcheckingStrategy) { - return ((SuppressibleSpellcheckingStrategy)strategy).getSuppressActions(element, getShortName()); - } - } - return super.getBatchSuppressActions(element); - } - - public static SpellcheckingStrategy getSpellcheckingStrategy(@NotNull PsiElement element) { - DumbService dumbService = DumbService.getInstance(element.getProject()); - for (SpellcheckingStrategy strategy : LanguageSpellchecking.INSTANCE.allForLanguage(element.getLanguage())) { - if (dumbService.isUsableInCurrentContext(strategy) && strategy.isMyContext(element)) { - return strategy; - } - } - return null; - } - - @Override - public boolean isSuppressedFor(@NotNull PsiElement element) { - SpellcheckingStrategy strategy = getSpellcheckingStrategy(element); - if (strategy instanceof SuppressibleSpellcheckingStrategy) { - return ((SuppressibleSpellcheckingStrategy)strategy).isSuppressedFor(element, getShortName()); - } - return super.isSuppressedFor(element); - } - - @Override - public @NonNls @NotNull String getShortName() { - return SPELL_CHECKING_INSPECTION_TOOL_NAME; - } - - @Override - public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - return super.buildVisitor(holder, isOnTheFly); - } - - @Override - public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, - boolean isOnTheFly, - @NotNull LocalInspectionToolSession session) { - if (!Registry.is("spellchecker.inspection.enabled", true) || - InspectionProfileManager.hasTooLowSeverity(session, this) || - InjectedLanguageManager.getInstance(holder.getProject()).isFrankensteinInjection(holder.getFile())) { - return PsiElementVisitor.EMPTY_VISITOR; - } - - var scopes = buildAllowedScopes(); - SpellCheckerManager manager = SpellCheckerManager.getInstance(holder.getProject()); - return new PsiElementVisitor() { - @Override - public void visitWhiteSpace(@NotNull PsiWhiteSpace space) { } - - @Override - public void visitElement(final @NotNull PsiElement element) { - if (holder.getResultCount() > 1000 || element.getNode() == null) return; - - var strategy = getSpellcheckingStrategy(element); - if (strategy == null || !strategy.elementFitsScope(element, scopes) || isCopyrightComment(strategy, element)) return; - - SpellCheckingResult result = SpellcheckingExtension.Companion.spellcheck( - element, strategy, session, - typo -> { - if (hasSameNamedReferenceInFile(typo.getWord(), element, strategy)) return; - registerProblem(typo, holder); - } - ); - if (result == SpellCheckingResult.Checked) return; - - tokenize( - strategy, element, - new MyTokenConsumer(manager, strategy, holder, LanguageNamesValidation.INSTANCE.forLanguage(element.getLanguage())), - scopes - ); - } - }; - } - - private Set buildAllowedScopes() { - var result = new HashSet(); - if (processLiterals) { - result.add(SpellCheckingScope.Literals); - } - if (processComments) { - result.add(SpellCheckingScope.Comments); - } - if (processCode) { - result.add(SpellCheckingScope.Code); - } - return result; - } - /** * Splits element text in tokens according to spell checker strategy of given language * @@ -156,220 +36,15 @@ public final class SpellCheckingInspection extends LocalInspectionTool implement tokenize(factoryByLanguage, element, consumer, allowedScopes); } - private static void tokenize(SpellcheckingStrategy strategy, - PsiElement element, - TokenConsumer consumer, - Set allowedScopes) { + public static void tokenize(SpellcheckingStrategy strategy, + PsiElement element, + TokenConsumer consumer, + Set allowedScopes) { var tokenizer = strategy.getTokenizer(element, allowedScopes); //noinspection unchecked tokenizer.tokenize(element, consumer); } - private static void addBatchDescriptor(@NotNull PsiElement element, - @NotNull TextRange textRange, - @NotNull String word, - @NotNull ProblemsHolder holder) { - var fixes = SpellcheckingStrategy.getDefaultBatchFixes(element, textRange, word); - ProblemDescriptor problemDescriptor = createProblemDescriptor(element, textRange, fixes, false); - holder.registerProblem(problemDescriptor); - } - - private static void addRegularDescriptor(@NotNull PsiElement element, @NotNull TextRange textRange, @NotNull ProblemsHolder holder, - boolean useRename, String wordWithTypo) { - SpellcheckingStrategy strategy = getSpellcheckingStrategy(element); - - LocalQuickFix[] fixes = strategy != null - ? strategy.getRegularFixes(element, textRange, useRename, wordWithTypo) - : SpellcheckingStrategy.getDefaultRegularFixes(useRename, wordWithTypo, element, textRange); - - final ProblemDescriptor problemDescriptor = createProblemDescriptor(element, textRange, fixes, true); - holder.registerProblem(problemDescriptor); - } - - private static ProblemDescriptor createProblemDescriptor(PsiElement element, TextRange textRange, - LocalQuickFix[] fixes, - boolean onTheFly) { - final String description = SpellCheckerBundle.message("typo.in.word.ref"); - return new ProblemDescriptorBase(element, element, description, fixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - false, textRange, onTheFly, onTheFly); - } - - @SuppressWarnings("PublicField") - public boolean processCode = true; - public boolean processLiterals = true; - public boolean processComments = true; - - @Override - public @NotNull OptPane getOptionsPane() { - return pane( - checkbox("processCode", SpellCheckerBundle.message("process.code")), - checkbox("processLiterals", SpellCheckerBundle.message("process.literals")), - checkbox("processComments", SpellCheckerBundle.message("process.comments")) - ); - } - - private static final class MyTokenConsumer extends TokenConsumer implements Consumer { - private static final Pattern NON_ENGLISH_LETTERS = Pattern.compile(".*[^a-zA-Z].*"); - - private final Set myAlreadyChecked = CollectionFactory.createSmallMemoryFootprintSet(); - private final SpellCheckerManager myManager; - private final ProblemsHolder myHolder; - private final NamesValidator myNamesValidator; - private final SpellcheckingStrategy myStrategy; - private boolean myCodeLike; - private PsiElement myElement; - private String myText; - private boolean myUseRename; - private int myOffset; - - MyTokenConsumer(SpellCheckerManager manager, SpellcheckingStrategy strategy, ProblemsHolder holder, NamesValidator namesValidator) { - myManager = manager; - myStrategy = strategy; - myHolder = holder; - myNamesValidator = namesValidator; - } - - @Override - public void consumeToken(final PsiElement element, - final String text, - final boolean useRename, - final int offset, - TextRange rangeToCheck, - Splitter splitter) { - myElement = element; - myText = text; - myUseRename = useRename; - myOffset = offset; - myCodeLike = myStrategy.elementFitsScope(myElement, Set.of(SpellCheckingScope.Code)); - splitter.split(text, rangeToCheck, this); - } - - @Override - public void consume(TextRange range) { - // Tokenization of large texts can produce a lot of tokens, but we are inside RA - ProgressManager.checkCanceled(); - String word = range.substring(myText); - if (!myHolder.isOnTheFly() && myAlreadyChecked.contains(word)) { - return; - } - - boolean keyword = myNamesValidator.isKeyword(word, myElement.getProject()); - if (keyword || !hasProblem(word, range) || hasSameNamedReferenceInFile(word, myElement, myStrategy)) { - return; - } - - range = myStrategy.getTokenizer(myElement) - .getHighlightingRange(myElement, myOffset, range); - assert range.getStartOffset() >= 0; - - if (!myHolder.isOnTheFly()) { - myAlreadyChecked.add(word); - } - registerProblem(myHolder, myElement, range, myUseRename, word); - } - - private boolean hasCamelCaseMatch(String word, TextRange range) { - Set camelCaseWords = myManager.getUserCamelCaseWords() - .stream() - .filter(camelCaseWord -> camelCaseWord.contains(word)) - .collect(Collectors.toSet()); - if (camelCaseWords.isEmpty()) { - return false; - } - - String text = myElement.getText(); - for (String camelCaseWord : camelCaseWords) { - ProgressManager.checkCanceled(); - int[] indexes = new StringSearcher(camelCaseWord, false, true).findAllOccurrences(text); - for (int index : indexes) { - TextRange hitRange = new TextRange(index, index + camelCaseWord.length()); - if (range.intersectsStrict(hitRange)) { - return true; - } - } - } - return false; - } - - private boolean hasProblem(String word, TextRange range) { - if (!myManager.hasProblem(word)) { - return false; - } - - // Check if a user has added CamelCase word to the project / app dictionary - if (hasCamelCaseMatch(word, range)) { - return false; - } - - // If the word isn't "code" or contains letters outside the English alphabet, - // then diacritic check should be skipped - if (!myCodeLike || NON_ENGLISH_LETTERS.matcher(word).matches()) { - return true; - } - - Project project = myElement.getProject(); - return SpellCheckerManager.getInstance(project).getSuggestions(word) - .stream() - .filter(suggestion -> RenameUtil.isValidName(project, myElement, suggestion)) - .noneMatch(suggestion -> Diacritics.equalsIgnoringDiacritics(word, suggestion)); - } - } - - private static boolean hasSameNamedReferenceInFile(String word, PsiElement element, SpellcheckingStrategy strategy) { - if (!strategy.elementFitsScope(element, Set.of(SpellCheckingScope.Comments))) { - return false; - } - - PsiFile file = element.getContainingFile(); - Map references = CachedValuesManager.getProjectPsiDependentCache(file, (psi) -> new ConcurrentHashMap<>()); - return references.computeIfAbsent(word, key -> hasSameNamedReferencesInFile(key, file)); - } - - private static boolean hasSameNamedReferencesInFile(String word, PsiFile file) { - int[] occurrences = new StringSearcher(word, true, true).findAllOccurrences(file.getText()); - if (occurrences.length <= 1) { - return false; - } - - for (int occurrence : occurrences) { - PsiReference reference = file.findReferenceAt(occurrence); - PsiElement resolvedReference = reference != null ? reference.resolve() : null; - if (reference != null && resolvedReference != null && reference.getElement() != resolvedReference) { - return true; - } - } - return false; - } - - private static boolean isCopyrightComment(SpellcheckingStrategy strategy, PsiElement psi) { - return strategy.elementFitsScope(psi, Set.of(SpellCheckingScope.Comments)) - && StringUtil.containsIgnoreCase(psi.getText(), "Copyright") - && isAtFileStart(psi); - } - - private static boolean isAtFileStart(PsiElement psi) { - PsiFile file = psi.getContainingFile(); - int textStart = psi.getTextRange().getStartOffset(); - return file.getViewProvider().getContents().subSequence(0, textStart).chars().noneMatch(Character::isLetterOrDigit); - } - - private static void registerProblem(@NotNull SpellingTypo typo, @NotNull ProblemsHolder holder) { - registerProblem(holder, typo.getElement(), typo.getRange(), false, typo.getWord()); - } - - private static void registerProblem(@NotNull ProblemsHolder holder, - @NotNull PsiElement element, - @NotNull TextRange range, - boolean useRename, - String word) { - if (holder.isOnTheFly()) { - addRegularDescriptor(element, range, holder, useRename, word); - } - else { - addBatchDescriptor(element, range, word, holder); - } - } - public enum SpellCheckingScope { Comments, Literals, diff --git a/spellchecker/src/com/intellij/spellchecker/statistics/SpellcheckerRateTracker.kt b/spellchecker/src/com/intellij/spellchecker/statistics/SpellcheckerRateTracker.kt index ea5455d6ed30..9771c88d4ba2 100644 --- a/spellchecker/src/com/intellij/spellchecker/statistics/SpellcheckerRateTracker.kt +++ b/spellchecker/src/com/intellij/spellchecker/statistics/SpellcheckerRateTracker.kt @@ -5,8 +5,8 @@ import com.intellij.lang.Language import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.ui.CommitMessage import com.intellij.psi.PsiElement -import com.intellij.spellchecker.inspections.SpellCheckingInspection import com.intellij.spellchecker.inspections.SpellCheckingInspection.SpellCheckingScope +import com.intellij.spellchecker.tokenizer.SpellcheckingStrategy.getSpellcheckingStrategy import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi @@ -30,7 +30,7 @@ class SpellcheckerRateTracker private constructor( companion object { private fun determineDomain(element: PsiElement): String { - val strategy = SpellCheckingInspection.getSpellcheckingStrategy(element) + val strategy = getSpellcheckingStrategy(element) if (CommitMessage.isCommitMessage(element)) { return "commit" } diff --git a/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java b/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java index 45f6d8700eed..d3c6b30ec1de 100644 --- a/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java +++ b/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java @@ -9,6 +9,7 @@ import com.intellij.lang.ParserDefinition; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.impl.CustomSyntaxTableFileType; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.PossiblyDumbAware; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; @@ -150,6 +151,16 @@ public class SpellcheckingStrategy implements PossiblyDumbAware { return getDefaultRegularFixes(useRename, typo, element, textRange); } + public static SpellcheckingStrategy getSpellcheckingStrategy(@NotNull PsiElement element) { + DumbService dumbService = DumbService.getInstance(element.getProject()); + for (SpellcheckingStrategy strategy : LanguageSpellchecking.INSTANCE.allForLanguage(element.getLanguage())) { + if (dumbService.isUsableInCurrentContext(strategy) && strategy.isMyContext(element)) { + return strategy; + } + } + return null; + } + public static LocalQuickFix[] getDefaultRegularFixes(boolean useRename, String typo, @NotNull PsiElement element, diff --git a/spellchecker/src/com/intellij/spellchecker/ui/SpellCheckingEditorCustomization.java b/spellchecker/src/com/intellij/spellchecker/ui/SpellCheckingEditorCustomization.java index 031535561f41..e9158751f0e6 100644 --- a/spellchecker/src/com/intellij/spellchecker/ui/SpellCheckingEditorCustomization.java +++ b/spellchecker/src/com/intellij/spellchecker/ui/SpellCheckingEditorCustomization.java @@ -5,10 +5,8 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.intention.IntentionManager; import com.intellij.codeInspection.InspectionProfile; -import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.ex.InspectionProfileImpl; import com.intellij.codeInspection.ex.InspectionProfileWrapper; -import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.project.Project; @@ -21,9 +19,6 @@ import com.intellij.util.ConcurrencyUtil; import com.intellij.util.containers.CollectionFactory; import org.jetbrains.annotations.NotNull; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; @@ -34,51 +29,14 @@ import java.util.function.Function; * Thread-safe. */ public class SpellCheckingEditorCustomization extends SimpleEditorCustomization { - private static volatile Map SPELL_CHECK_TOOLS; - private static volatile boolean READY; - SpellCheckingEditorCustomization(boolean enabled) { super(enabled); } - private static @NotNull Map getSpellCheckTools() { - Map tools = SPELL_CHECK_TOOLS; - if (tools == null) { - tools = new HashMap<>(); - // It's assumed that default spell checking inspection settings are just fine for processing all types of data. - // Please perform corresponding settings tuning if that assumption is broken in the future. - Class[] inspectionClasses = (Class[])new Class[]{SpellCheckingInspection.class}; - for (Class inspectionClass : inspectionClasses) { - try { - LocalInspectionTool tool = inspectionClass.newInstance(); - tools.put(tool.getShortName(), new LocalInspectionToolWrapper(tool)); - } - catch (Throwable e) { - READY = false; - return Map.of(); - } - } - SPELL_CHECK_TOOLS = tools; - READY = true; - } - return tools; - } - - private static boolean isReady() { - if (SPELL_CHECK_TOOLS == null) { - getSpellCheckTools(); - } - return READY; - } - @Override public void customize(@NotNull EditorEx editor) { boolean apply = isEnabled(); - if (!isReady()) { - return; - } - Project project = editor.getProject(); if (project == null) { return; @@ -119,7 +77,7 @@ public class SpellCheckingEditorCustomization extends SimpleEditorCustomization } static Set getSpellCheckingToolNames() { - return Collections.unmodifiableSet(getSpellCheckTools().keySet()); + return Set.of(SpellCheckingInspection.SPELL_CHECKING_INSPECTION_TOOL_NAME); } private static class MyInspectionProfileStrategy implements Function { @@ -129,9 +87,6 @@ public class SpellCheckingEditorCustomization extends SimpleEditorCustomization @Override public @NotNull InspectionProfileWrapper apply(@NotNull InspectionProfile profile) { - if (!isReady()) { - return new InspectionProfileWrapper((InspectionProfileImpl)profile); - } MyInspectionProfileWrapper wrapper = myWrappers.get(profile); return wrapper == null ? ConcurrencyUtil.cacheOrGet(myWrappers, profile, new MyInspectionProfileWrapper(profile, myUseSpellCheck)) @@ -153,7 +108,7 @@ public class SpellCheckingEditorCustomization extends SimpleEditorCustomization @Override public boolean isToolEnabled(HighlightDisplayKey key, PsiElement element) { - return (key != null && getSpellCheckTools().containsKey(key.getShortName()) ? myUseSpellCheck : super.isToolEnabled(key, element)); + return (key != null && getSpellCheckingToolNames().contains(key.getShortName()) ? myUseSpellCheck : super.isToolEnabled(key, element)); } } }