From 75284da775af9bf567aed28720a1ad656ba62507 Mon Sep 17 00:00:00 2001 From: Den Mukhametianov Date: Wed, 10 Jan 2024 10:49:24 +0100 Subject: [PATCH] [spellchecker] allow SpellCheckerStrategy to decide whether psi element fits or doesn't spell checker scope, pass this scope to getTokenizer Some backend-driven languages in Rider don't have detailed frontend lexer/parser, so the only node visible to spellchecker will be root PsiFile. We pass range-scope information from backend, and then rider's spell checking strategy will handle it itself. GitOrigin-RevId: b97c9eaaed30d6a0e09d69b13cf4cd5ad1b5d364 --- .../SpellCheckerDictionaryGenerator.java | 2 +- .../inspections/SpellCheckingInspection.java | 57 +++++++++++-------- .../tokenizer/SpellcheckingStrategy.java | 34 +++++++++++ 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/spellchecker/src/com/intellij/spellchecker/generator/SpellCheckerDictionaryGenerator.java b/spellchecker/src/com/intellij/spellchecker/generator/SpellCheckerDictionaryGenerator.java index 89191819077e..b985c4959652 100644 --- a/spellchecker/src/com/intellij/spellchecker/generator/SpellCheckerDictionaryGenerator.java +++ b/spellchecker/src/com/intellij/spellchecker/generator/SpellCheckerDictionaryGenerator.java @@ -185,7 +185,7 @@ public abstract class SpellCheckerDictionaryGenerator { addSeenWord(seenNames, word, language); }); } - }); + }, null); } protected void addSeenWord(HashSet seenNames, String word, Language language) { diff --git a/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java b/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java index 63c9a37cd2c3..6bfb3412b37d 100644 --- a/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java +++ b/spellchecker/src/com/intellij/spellchecker/inspections/SpellCheckingInspection.java @@ -12,7 +12,6 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiFile; -import com.intellij.psi.tree.IElementType; import com.intellij.spellchecker.SpellCheckerManager; import com.intellij.spellchecker.quickfixes.SpellCheckerQuickFix; import com.intellij.spellchecker.tokenizer.*; @@ -23,6 +22,7 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.HashSet; import java.util.Set; import static com.intellij.codeInspection.options.OptPane.checkbox; @@ -73,6 +73,7 @@ public final class SpellCheckingInspection extends LocalInspectionTool { return PsiElementVisitor.EMPTY_VISITOR; } final SpellCheckerManager manager = SpellCheckerManager.getInstance(holder.getProject()); + var scope = buildAllowedScopes(); return new PsiElementVisitor() { @Override @@ -84,51 +85,55 @@ public final class SpellCheckingInspection extends LocalInspectionTool { return; } - // Extract parser definition from element final Language language = element.getLanguage(); - final IElementType elementType = node.getElementType(); - final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language); + var strategy = getSpellcheckingStrategy(element, language); + if(strategy == null) + return; - // Handle selected options - if (parserDefinition != null) { - if (parserDefinition.getStringLiteralElements().contains(elementType)) { - if (!processLiterals) { - return; - } - } - else if (parserDefinition.getCommentTokens().contains(elementType)) { - if (!processComments) { - return; - } - } - else if (!processCode) { - return; - } - } + if(!strategy.elementFitsScope(element, scope)) + return; PsiFile containingFile = holder.getFile(); if (Boolean.TRUE.equals(containingFile.getUserData(InjectedLanguageManager.FRANKENSTEIN_INJECTION))) { return; } - tokenize(element, language, new MyTokenConsumer(manager, holder, LanguageNamesValidation.INSTANCE.forLanguage(language))); + tokenize(element, language, new MyTokenConsumer(manager, holder, LanguageNamesValidation.INSTANCE.forLanguage(language)), scope); } }; } + 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 * * @param element Psi element + * @param allowedScopes * @param language Usually element.getLanguage() * @param consumer the consumer of tokens */ - public static void tokenize(final @NotNull PsiElement element, final @NotNull Language language, TokenConsumer consumer) { + public static void tokenize(@NotNull final PsiElement element, + @NotNull final Language language, + TokenConsumer consumer, Set allowedScopes) { SpellcheckingStrategy factoryByLanguage = getSpellcheckingStrategy(element, language); if (factoryByLanguage == null) { return; } - Tokenizer tokenizer = factoryByLanguage.getTokenizer(element); + tokenize(factoryByLanguage, element, consumer, allowedScopes); + } + + private static void tokenize(SpellcheckingStrategy strategy, PsiElement element, TokenConsumer consumer, Set allowedScopes) { + var tokenizer = strategy.getTokenizer(element, allowedScopes); //noinspection unchecked tokenizer.tokenize(element, consumer); } @@ -239,4 +244,10 @@ public final class SpellCheckingInspection extends LocalInspectionTool { } } } + + public enum SpellCheckingScope { + Comments, + Literals, + Code, + } } diff --git a/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java b/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java index 663d1a9acd94..d682a78782ec 100644 --- a/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java +++ b/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java @@ -3,15 +3,20 @@ package com.intellij.spellchecker.tokenizer; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.SuppressionUtil; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageParserDefinitions; +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.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.spellchecker.DictionaryLevel; import com.intellij.spellchecker.inspections.PlainTextSplitter; +import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.intellij.spellchecker.quickfixes.ChangeTo; import com.intellij.spellchecker.quickfixes.RenameTo; import com.intellij.spellchecker.quickfixes.SaveTo; @@ -22,6 +27,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Set; /** * Defines spellchecking support for a custom language. @@ -50,6 +56,10 @@ public class SpellcheckingStrategy { private static final SpellCheckerQuickFix[] BATCH_FIXES = new SpellCheckerQuickFix[]{SaveTo.getSaveToLevelFix(DictionaryLevel.APP), SaveTo.getSaveToLevelFix(DictionaryLevel.PROJECT)}; + public Tokenizer getTokenizer(PsiElement element, Set scope) { + return getTokenizer(element); + } + /** * @return {@link #EMPTY_TOKENIZER} to skip spellchecking, {@link #TEXT_TOKENIZER} for full element text or custom Tokenizer implementation. */ @@ -82,6 +92,30 @@ public class SpellcheckingStrategy { return EMPTY_TOKENIZER; } + public boolean elementFitsScope(@NotNull PsiElement element, Set scope) { + + final Language language = element.getLanguage(); + final IElementType elementType = element.getNode().getElementType(); + final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language); + + if (parserDefinition != null) { + if (parserDefinition.getStringLiteralElements().contains(elementType)) { + if (!scope.contains(SpellCheckingInspection.SpellCheckingScope.Literals)) { + return false; + } + } + else if (parserDefinition.getCommentTokens().contains(elementType)) { + if (!scope.contains(SpellCheckingInspection.SpellCheckingScope.Comments)) { + return false; + } + } + else if (!scope.contains(SpellCheckingInspection.SpellCheckingScope.Code)) { + return false; + } + } + return true; + } + protected static boolean isInjectedLanguageFragment(@Nullable PsiElement element) { return element instanceof PsiLanguageInjectionHost && InjectedLanguageUtil.hasInjections((PsiLanguageInjectionHost)element);