diff --git a/plugins/grazie/java/src/main/kotlin/com/intellij/grazie/ide/language/java/JavadocProblemFilter.java b/plugins/grazie/java/src/main/kotlin/com/intellij/grazie/ide/language/java/JavadocProblemFilter.java index fce53727b319..42caf266eb18 100644 --- a/plugins/grazie/java/src/main/kotlin/com/intellij/grazie/ide/language/java/JavadocProblemFilter.java +++ b/plugins/grazie/java/src/main/kotlin/com/intellij/grazie/ide/language/java/JavadocProblemFilter.java @@ -1,17 +1,19 @@ package com.intellij.grazie.ide.language.java; import com.intellij.grazie.text.ProblemFilter; -import com.intellij.grazie.text.RuleGroup; import com.intellij.grazie.text.TextContent; import com.intellij.grazie.text.TextProblem; -import com.intellij.grazie.utils.Text; +import com.intellij.grazie.utils.ProblemFilterUtil; +import com.intellij.psi.javadoc.PsiDocTag; import org.jetbrains.annotations.NotNull; class JavadocProblemFilter extends ProblemFilter { @Override public boolean shouldIgnore(@NotNull TextProblem problem) { - if (problem.getText().getDomain() == TextContent.TextDomain.DOCUMENTATION && Text.isSingleSentence(problem.getText())) { - return problem.fitsGroup(RuleGroup.UNDECORATED_SINGLE_SENTENCE); + if (problem.getText().getDomain() == TextContent.TextDomain.DOCUMENTATION && + problem.getText().getCommonParent() instanceof PsiDocTag && + (ProblemFilterUtil.isUndecoratedSingleSentenceIssue(problem) || ProblemFilterUtil.isInitialCasingIssue(problem))) { + return true; } return false; } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/ProblemFilter.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/ProblemFilter.java index a95972887ea5..ca0e46afefd7 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/ProblemFilter.java +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/ProblemFilter.java @@ -8,9 +8,10 @@ import java.util.stream.Stream; /** * An extension allowing to prevent some text problems from being reported, * registered in {@code plugin.xml} under {@code "com.intellij.grazie.problemFilter"} qualified name. + * @see com.intellij.grazie.utils.ProblemFilterUtil */ public abstract class ProblemFilter { - private static final LanguageExtension EP = new LanguageExtension("com.intellij.grazie.problemFilter"); + private static final LanguageExtension EP = new LanguageExtension<>("com.intellij.grazie.problemFilter"); public static Stream allIgnoringFilters(TextProblem problem) { return EP.allForLanguageOrAny(problem.getText().getCommonParent().getLanguage()).stream().filter(f -> f.shouldIgnore(problem)); diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/RuleGroup.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/RuleGroup.kt index 0cf20017c880..b8bd783fbd19 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/RuleGroup.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/RuleGroup.kt @@ -33,12 +33,15 @@ open class RuleGroup(rules: Set) { val EMPTY = RuleGroup() /** Rules for checking casing errors */ + @JvmField val CASING = RuleGroup(SENTENCE_START_CASE) /** Rules for checking punctuation errors */ + @JvmField val PUNCTUATION = RuleGroup(SENTENCE_END_PUNCTUATION, UNLIKELY_OPENING_PUNCTUATION) /** Rules that are usually disabled for literal strings */ + @JvmField val LITERALS = CASING + PUNCTUATION /** diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/ProblemFilterUtil.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/ProblemFilterUtil.java new file mode 100644 index 000000000000..41a5c8a531c6 --- /dev/null +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/ProblemFilterUtil.java @@ -0,0 +1,24 @@ +package com.intellij.grazie.utils; + +import com.intellij.grazie.text.RuleGroup; +import com.intellij.grazie.text.TextProblem; +import com.intellij.util.containers.ContainerUtil; + +public final class ProblemFilterUtil { + + /** + * Check if this problem reports a letter case issue at the very beginning of the text. + * Such issues are often ignored in documentation tags. + */ + public static boolean isInitialCasingIssue(TextProblem problem) { + return ContainerUtil.exists(problem.getHighlightRanges(), r -> r.getStartOffset() == 0) && problem.fitsGroup(RuleGroup.CASING); + } + + /** + * Check if this problem reports a sentence capitalization or missing trailing punctuation issue in a single-sentence text fragment. + * Such issues are often ignored in documentation tags or comments. + */ + public static boolean isUndecoratedSingleSentenceIssue(TextProblem problem) { + return Text.isSingleSentence(problem.getText()) && problem.fitsGroup(RuleGroup.UNDECORATED_SINGLE_SENTENCE); + } +} 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 fba6932e2905..0c39a5b0e532 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt @@ -24,7 +24,7 @@ abstract class GrazieTestBase : BasePlatformTestCase() { companion object { val inspectionTools by lazy { arrayOf(GrazieInspection(), SpellCheckingInspection()) } val enabledLanguages = setOf(Lang.AMERICAN_ENGLISH, Lang.GERMANY_GERMAN, Lang.RUSSIAN, Lang.ITALIAN) - val enabledRules = setOf("LanguageTool.EN.COMMA_WHICH") + val enabledRules = setOf("LanguageTool.EN.COMMA_WHICH", "LanguageTool.EN.UPPERCASE_SENTENCE_START") } protected open val additionalEnabledRules: Set = emptySet() diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/text/TextExtractionTest.java b/plugins/grazie/src/test/kotlin/com/intellij/grazie/text/TextExtractionTest.java index 979868bc583e..10f08f3c7dfb 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/text/TextExtractionTest.java +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/text/TextExtractionTest.java @@ -255,7 +255,7 @@ public class TextExtractionTest extends BasePlatformTestCase { assertEquals("|characters with markup\nand without it|", unknownOffsets(extractText("a.xml", "and without it", 22))); - assertEquals("abcd", unknownOffsets(extractText("a.xml", "", 14))); + assertEquals("abcd efg", unknownOffsets(extractText("a.xml", "", 14))); assertEquals("comment", extractText("a.xml", "", 10).toString()); assertEquals("top-level text", unknownOffsets(extractText("a.html", "top-level text", 2))); diff --git a/plugins/grazie/src/test/testData/ide/language/java/Docs.java b/plugins/grazie/src/test/testData/ide/language/java/Docs.java index 1b3430ed2cda..703efaa15bb1 100644 --- a/plugins/grazie/src/test/testData/ide/language/java/Docs.java +++ b/plugins/grazie/src/test/testData/ide/language/java/Docs.java @@ -10,7 +10,7 @@ * Item item = env.generateData(Generator.sampledFrom(sys.currentItems), "working on %s item"); * * - * @param T the type of member in this group. + * @param T the type of member in this group. And another sentence. */ class ExampleClassWithNoTypos { @@ -19,7 +19,7 @@ class ExampleClassWithNoTypos { /** * Creates an empty group. * - * @param name The name of the group. + * @param name The name of the group. And another sentence. */ public ExampleClassWithNoTypos(String name) { this.name = name; @@ -30,7 +30,7 @@ class ExampleClassWithNoTypos { * * @param cancellable Whether the progress can be cancelled. * @param member member to add - * @return the new size of the group. + * @return the new size of the group. And another sentence. */ Integer goodFunction(boolean cancellable, T member) { return 1; // no error comment @@ -70,8 +70,8 @@ class ExampleClassWithTypos { * It add a [member] to this grooup. * second sentence. * - * @param member member to add - * @return the new size of a the group. + * @param member member to add. And another sentence. + * @return the new size of a the group. and another sentence. */ Integer badFunction(T member) { return 1; // It are eror in the comment diff --git a/plugins/grazie/src/test/testData/ide/language/js/Docs.js b/plugins/grazie/src/test/testData/ide/language/js/Docs.js index 9f546285dfc0..63611194a73d 100644 --- a/plugins/grazie/src/test/testData/ide/language/js/Docs.js +++ b/plugins/grazie/src/test/testData/ide/language/js/Docs.js @@ -11,7 +11,7 @@ class ExampleClassWithNoTypos { /** * Creates an empty group - * @param {String} name the name of the group + * @param {String} name the name of the group. And another sentence. */ constructor(name) { /** @private */ diff --git a/plugins/grazie/src/test/testData/ide/language/xml/Example.html b/plugins/grazie/src/test/testData/ide/language/xml/Example.html index c4ecced40a30..5d283ea40bd3 100644 --- a/plugins/grazie/src/test/testData/ide/language/xml/Example.html +++ b/plugins/grazie/src/test/testData/ide/language/xml/Example.html @@ -6,7 +6,7 @@

Example hedder with a error, so what?

\ -are error in tag atributee"> +it are error in tag atributee"> Some widely used comparison methods (e.g. String.compareTo) actually return values. diff --git a/plugins/grazie/src/test/testData/ide/language/xml/Example.xml b/plugins/grazie/src/test/testData/ide/language/xml/Example.xml index 9d6b3a8b5ca6..50ccd0b90d16 100644 --- a/plugins/grazie/src/test/testData/ide/language/xml/Example.xml +++ b/plugins/grazie/src/test/testData/ide/language/xml/Example.xml @@ -3,7 +3,7 @@ xsi:noNamespaceSchemaLocation="Example.xsd"> johnn Smith - First sentence. it an friend. Last sentence. + First sentence. it an friend. Last sentence.
Some street 23
Не удалось авторизоваться.\nПопробуйте ещё раз. И пять карандаша. diff --git a/plugins/grazie/xml/main/kotlin/com/intellij/grazie/ide/language/xml/XmlTextExtractor.java b/plugins/grazie/xml/main/kotlin/com/intellij/grazie/ide/language/xml/XmlTextExtractor.java index dc0ea814cc46..6a7af667f1ad 100644 --- a/plugins/grazie/xml/main/kotlin/com/intellij/grazie/ide/language/xml/XmlTextExtractor.java +++ b/plugins/grazie/xml/main/kotlin/com/intellij/grazie/ide/language/xml/XmlTextExtractor.java @@ -68,7 +68,10 @@ public class XmlTextExtractor extends TextExtractor { } if (type == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN && allowedDomains.contains(LITERALS) && hasSuitableDialect(element)) { - return builder.build(element, LITERALS); + TextContent content = builder.build(element, LITERALS); + if (content != null && seemsNatural(content)) { + return content; + } } return null; @@ -157,6 +160,10 @@ public class XmlTextExtractor extends TextExtractor { return visitor.result; } + private static boolean seemsNatural(TextContent content) { + return content.toString().contains(" "); + } + private static TextContent extractRange(TextContent full, TextRange range) { return full.excludeRange(new TextRange(range.getEndOffset(), full.length())).excludeRange(new TextRange(0, range.getStartOffset())); } diff --git a/plugins/kotlin/grazie/src/org/jetbrains/kotlin/idea/grazie/KotlinProblemFilter.kt b/plugins/kotlin/grazie/src/org/jetbrains/kotlin/idea/grazie/KotlinProblemFilter.kt index fce98a2a5881..675abfc6d2b3 100644 --- a/plugins/kotlin/grazie/src/org/jetbrains/kotlin/idea/grazie/KotlinProblemFilter.kt +++ b/plugins/kotlin/grazie/src/org/jetbrains/kotlin/idea/grazie/KotlinProblemFilter.kt @@ -6,6 +6,7 @@ import com.intellij.grazie.text.RuleGroup import com.intellij.grazie.text.TextContent.TextDomain.DOCUMENTATION import com.intellij.grazie.text.TextContent.TextDomain.LITERALS import com.intellij.grazie.text.TextProblem +import com.intellij.grazie.utils.ProblemFilterUtil import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag class KotlinProblemFilter : ProblemFilter() { @@ -14,8 +15,9 @@ class KotlinProblemFilter : ProblemFilter() { if (domain == LITERALS) { return problem.fitsGroup(RuleGroup.LITERALS) } - if (domain == DOCUMENTATION && problem.text.commonParent::class == KDocTag::class) { - return problem.fitsGroup(RuleGroup.UNDECORATED_SINGLE_SENTENCE) + if (domain == DOCUMENTATION && problem.text.commonParent::class == KDocTag::class && + (ProblemFilterUtil.isUndecoratedSingleSentenceIssue(problem) || ProblemFilterUtil.isInitialCasingIssue(problem))) { + return true } return false } diff --git a/plugins/kotlin/idea/tests/testData/grazie/Docs.kt b/plugins/kotlin/idea/tests/testData/grazie/Docs.kt index f3c1ded8fc7c..96cbabf8b0d4 100644 --- a/plugins/kotlin/idea/tests/testData/grazie/Docs.kt +++ b/plugins/kotlin/idea/tests/testData/grazie/Docs.kt @@ -8,7 +8,7 @@ package ide.language.kotlin * This class has no useful logic; it's just a documentation example. * * @param T the type of member in this group. - * @property name the name of this group. + * @property name the name of this group. And another sentence. * @constructor Creates an empty group. */ class ExampleClassWithNoTypos(val name: String) { @@ -25,7 +25,7 @@ class ExampleClassWithNoTypos(val name: String) { * It is an friend of human * * @param T the type of a membr in this group. - * @property name the name which group + * @property name the name which group. and another sentence. * @constructor Creates an empty group. */ class ExampleClassWithTypos(val name: String) { diff --git a/python/python-grazie/resources/META-INF/python-grazie-plugin.xml b/python/python-grazie/resources/META-INF/python-grazie-plugin.xml index c2a27a4810b7..48c4c65b401f 100644 --- a/python/python-grazie/resources/META-INF/python-grazie-plugin.xml +++ b/python/python-grazie/resources/META-INF/python-grazie-plugin.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/python/python-grazie/src/com/intellij/grazie/ide/language/python/PythonProblemFilter.kt b/python/python-grazie/src/com/intellij/grazie/ide/language/python/PythonProblemFilter.kt new file mode 100644 index 000000000000..00c580716bf3 --- /dev/null +++ b/python/python-grazie/src/com/intellij/grazie/ide/language/python/PythonProblemFilter.kt @@ -0,0 +1,25 @@ +package com.intellij.grazie.ide.language.python + +import com.intellij.grazie.text.ProblemFilter +import com.intellij.grazie.text.RuleGroup +import com.intellij.grazie.text.TextContent +import com.intellij.grazie.text.TextProblem +import com.intellij.grazie.utils.ProblemFilterUtil + +class PythonProblemFilter : ProblemFilter() { + override fun shouldIgnore(problem: TextProblem): Boolean { + val domain = problem.text.domain + if (domain == TextContent.TextDomain.LITERALS) { + return problem.fitsGroup(RuleGroup.LITERALS) + } + if (domain == TextContent.TextDomain.DOCUMENTATION && seemsDocString(problem.text) && + (ProblemFilterUtil.isUndecoratedSingleSentenceIssue(problem) || ProblemFilterUtil.isInitialCasingIssue(problem))) { + return true + } + return false + } + + private fun seemsDocString(text: TextContent) = + text.containingFile.viewProvider.contents.subSequence(0, text.textOffsetToFile(0)).trim().endsWith(":") + +} \ No newline at end of file diff --git a/python/testData/grazie/Docs.py b/python/testData/grazie/Docs.py index dc0833b5e84c..dece70ae1d71 100644 --- a/python/testData/grazie/Docs.py +++ b/python/testData/grazie/Docs.py @@ -8,10 +8,10 @@ class ExampleClassWithNoTypos: This class has no useful logic; it's just a documentation example. Args: - name (str): the name of this group. + name (str): the name of this group. And another sentence. Attributes: - name (str): the name of this group. + name (str): the name of this group. and another sentence. """ diff --git a/python/testData/grazie/DocstringTagsAreExcluded.py b/python/testData/grazie/DocstringTagsAreExcluded.py index 307377329635..db9ea973bc20 100644 --- a/python/testData/grazie/DocstringTagsAreExcluded.py +++ b/python/testData/grazie/DocstringTagsAreExcluded.py @@ -2,6 +2,6 @@ def f(user): """ :param user: user Loose punctuation mark. - :param user: user + :param user: user """ pass \ No newline at end of file