mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[pycharm] PY-84775 Update UI text inspection + add tests
[pycharm] PY-84775 Fix issues with CapitalizationInspectionTest [pycharm] PY-84775 Add one more assertion to check capitalization [pycharm] PY-84775 Update UI text inspection + add tests Merge-request: IJ-MR-179263 Merged-by: Ekaterina Itsenko <ekaterina.itsenko@jetbrains.com> GitOrigin-RevId: 289bbc55860e3f4f009b8a4c5c1fbf635e712bf5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8a9905544f
commit
23691966d8
@@ -5,25 +5,92 @@ import com.intellij.openapi.util.text.StringUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class NlsCapitalizationUtil {
|
||||
private static final Set<String> TITLE_CASE_LOWERCASE_WORDS = Set.of(
|
||||
"a", "an", "the",
|
||||
"and", "or", "but",
|
||||
"at", "by", "for", "from", "in", "into", "of", "off", "on", "onto", "out", "over", "to", "up", "with"
|
||||
);
|
||||
private static final Pattern PERIOD_PATTERN = Pattern.compile("\\.(?!\\s*$)");
|
||||
private static final Pattern DOUBLE_QUOTES_PATTERN = Pattern.compile("[“”\"]");
|
||||
private static final Pattern EXCLAMATION_PATTERN = Pattern.compile("!");
|
||||
private static final Pattern CONTRACTION_PATTERN = Pattern.compile("(?i)\\b(can't|won't|isn't|aren't|wasn't|weren't|hasn't|haven't|hadn't|doesn't|don't|didn't|shouldn't|wouldn't|couldn't|mightn't|mustn't)\\b(?<!Don't)");
|
||||
private static final Pattern WHITESPACE_SPLIT_PATTERN = Pattern.compile("(?<=\\s)|(?=\\s)");
|
||||
private static final Pattern PUNCTUATION_WITH_WORD_PATTERN = Pattern.compile("(^\\P{Alnum}*)([\\p{Alnum}]+)(\\P{Alnum}*$)");
|
||||
private static final Pattern LEADING_PUNCTUATION_WITH_FIRST_LETTER_PATTERN = Pattern.compile("(^\\P{Alnum}*)(\\p{Alpha})(.*)");
|
||||
private static final Pattern SPECIAL_PREFIX_PATTERN = Pattern.compile("^[.*~].*");
|
||||
|
||||
public static boolean isCapitalizationSatisfied(String value, Nls.Capitalization capitalization) {
|
||||
if (StringUtil.isEmpty(value) || capitalization == Nls.Capitalization.NotSpecified) {
|
||||
return true;
|
||||
}
|
||||
return capitalization == Nls.Capitalization.Title
|
||||
? StringUtil.wordsToBeginFromUpperCase(value).equals(value)
|
||||
? checkTitleCapitalization(value)
|
||||
: checkSentenceCapitalization(value);
|
||||
}
|
||||
|
||||
private static List<String> splitByWhitespace(String s) {
|
||||
return Arrays.stream(s.trim().split("\\s+"))
|
||||
.filter(str -> !str.isEmpty())
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static boolean checkTitleCapitalization(@NotNull String value) {
|
||||
List<String> words = splitByWhitespace(value);
|
||||
final int wordCount = words.size();
|
||||
if (wordCount == 0) return true;
|
||||
for (int i = 0; i < wordCount; i++) {
|
||||
String word = words.get(i);
|
||||
if (word.isEmpty()) continue;
|
||||
String cleanWord = stripPunctuation(word);
|
||||
if (cleanWord.isEmpty()) continue;
|
||||
// Check if it's a special case (like iOS, macOS)
|
||||
if (hasInternalCapitalization(cleanWord)) {
|
||||
continue;
|
||||
}
|
||||
if (i == 0 || i == wordCount - 1) {
|
||||
if (!isCapitalizedWord(cleanWord)) return false;
|
||||
}
|
||||
else {
|
||||
String lowerWord = cleanWord.toLowerCase(Locale.ENGLISH);
|
||||
if (TITLE_CASE_LOWERCASE_WORDS.contains(lowerWord)) {
|
||||
if (isCapitalizedWord(cleanWord)) return false;
|
||||
}
|
||||
else {
|
||||
if (!isCapitalizedWord(cleanWord)) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasInternalCapitalization(@NotNull String word) {
|
||||
if (word.length() <= 1) return false;
|
||||
boolean hasLowerCase = false;
|
||||
boolean hasUpperCaseAfterFirst = false;
|
||||
for (int i = 0; i < word.length(); i++) {
|
||||
char c = word.charAt(i);
|
||||
if (Character.isLetter(c)) {
|
||||
if (Character.isLowerCase(c)) {
|
||||
hasLowerCase = true;
|
||||
}
|
||||
else if (i > 0 && Character.isUpperCase(c)) {
|
||||
hasUpperCaseAfterFirst = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasLowerCase && hasUpperCaseAfterFirst;
|
||||
}
|
||||
|
||||
private static boolean checkSentenceCapitalization(@NotNull String value) {
|
||||
List<String> words = StringUtil.split(value, " ");
|
||||
final int wordCount = words.size();
|
||||
if (wordCount == 0) return true;
|
||||
|
||||
if (Character.isLetter(words.get(0).charAt(0)) && !isCapitalizedWord(words.get(0))) return false;
|
||||
if (Character.isLetter(words.getFirst().charAt(0)) && !isCapitalizedWord(words.getFirst())) return false;
|
||||
if (wordCount == 1) return true;
|
||||
|
||||
int capitalized = 1;
|
||||
@@ -49,9 +116,98 @@ public final class NlsCapitalizationUtil {
|
||||
return !word.isEmpty() && Character.isLetter(word.charAt(0)) && Character.isUpperCase(word.charAt(0));
|
||||
}
|
||||
|
||||
private static @NotNull String stripPunctuation(@NotNull String word) {
|
||||
int start = 0;
|
||||
int end = word.length();
|
||||
while (start < end && !Character.isLetterOrDigit(word.charAt(start))) {
|
||||
start++;
|
||||
}
|
||||
while (end > start && !Character.isLetterOrDigit(word.charAt(end - 1))) {
|
||||
end--;
|
||||
}
|
||||
return start < end ? word.substring(start, end) : "";
|
||||
}
|
||||
|
||||
public static boolean checkPunctuation(@NotNull String value) {
|
||||
if (PERIOD_PATTERN.matcher(value).find()) {
|
||||
return value.endsWith(".");
|
||||
}
|
||||
if (value.endsWith(".")) {
|
||||
return false;
|
||||
}
|
||||
if (DOUBLE_QUOTES_PATTERN.matcher(value).find()) {
|
||||
return false;
|
||||
}
|
||||
if (EXCLAMATION_PATTERN.matcher(value).find()) {
|
||||
return false;
|
||||
}
|
||||
if (CONTRACTION_PATTERN.matcher(value).find()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static @NotNull String fixValue(String string, Nls.Capitalization capitalization) {
|
||||
return capitalization == Nls.Capitalization.Title
|
||||
? StringUtil.wordsToBeginFromUpperCase(string)
|
||||
: StringUtil.capitalize(StringUtil.wordsToBeginFromLowerCase(string));
|
||||
if (capitalization == Nls.Capitalization.Title) {
|
||||
return fixTitleCapitalization(string);
|
||||
}
|
||||
else {
|
||||
return StringUtil.capitalize(StringUtil.wordsToBeginFromLowerCase(string));
|
||||
}
|
||||
}
|
||||
|
||||
private static String fixTitleCapitalization(String text) {
|
||||
if (text == null || text.isBlank()) return text;
|
||||
String[] tokens = WHITESPACE_SPLIT_PATTERN.split(text);
|
||||
|
||||
int firstWordIndex = -1, lastWordIndex = -1;
|
||||
for (int i = 0; i < tokens.length; i++) {
|
||||
String cleanedToken = stripPunctuation(tokens[i]);
|
||||
if (!cleanedToken.isEmpty()) {
|
||||
if (firstWordIndex == -1) firstWordIndex = i;
|
||||
lastWordIndex = i;
|
||||
}
|
||||
}
|
||||
if (firstWordIndex == -1) return text;
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < tokens.length; i++) {
|
||||
String token = tokens[i];
|
||||
if (token.isBlank()) {
|
||||
result.append(token);
|
||||
continue;
|
||||
}
|
||||
String cleanedToken = stripPunctuation(token);
|
||||
if (cleanedToken.isEmpty()) {
|
||||
result.append(token);
|
||||
continue;
|
||||
}
|
||||
String lowercaseToken = cleanedToken.toLowerCase(Locale.ENGLISH);
|
||||
boolean isFirstWord = i == firstWordIndex;
|
||||
boolean isLastWord = i == lastWordIndex;
|
||||
|
||||
if (!isFirstWord && !isLastWord && TITLE_CASE_LOWERCASE_WORDS.contains(lowercaseToken)) {
|
||||
result.append(applyLowercaseToTokenPreservingPunctuation(token, lowercaseToken));
|
||||
} else if (hasInternalCapitalization(cleanedToken)) {
|
||||
result.append(token);
|
||||
} else {
|
||||
result.append(capitalizeFirstLetter(token));
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static String applyLowercaseToTokenPreservingPunctuation(String token, String lowercaseWord) {
|
||||
var matcher = PUNCTUATION_WITH_WORD_PATTERN.matcher(token);
|
||||
return matcher.matches() ? matcher.group(1) + lowercaseWord + matcher.group(3) : token;
|
||||
}
|
||||
|
||||
private static String capitalizeFirstLetter(String token) {
|
||||
if (token.isEmpty() || SPECIAL_PREFIX_PATTERN.matcher(token).matches()) return token;
|
||||
var matcher = LEADING_PUNCTUATION_WITH_FIRST_LETTER_PATTERN.matcher(token);
|
||||
if (matcher.matches()) {
|
||||
return matcher.group(1) + Character.toUpperCase(matcher.group(2).charAt(0)) + matcher.group(3);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
+73
@@ -18,6 +18,7 @@ package com.intellij.codeInspection;
|
||||
import junit.framework.TestCase;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
|
||||
import static com.intellij.codeInspection.NlsCapitalizationUtil.checkPunctuation;
|
||||
import static com.intellij.codeInspection.NlsCapitalizationUtil.isCapitalizationSatisfied;
|
||||
|
||||
public class NlsCapitalizationUtilTest extends TestCase {
|
||||
@@ -99,4 +100,76 @@ public class NlsCapitalizationUtilTest extends TestCase {
|
||||
private static void assertNotCapitalization(String value, Nls.Capitalization capitalization) {
|
||||
assertFalse("'" + value + "' should not satisfy " + capitalization, isCapitalizationSatisfied(value, capitalization));
|
||||
}
|
||||
|
||||
public void testTitleCapitalizationArticles() {
|
||||
assertTitle("Compare with the Latest Repository Version");
|
||||
assertTitle("A Good Example");
|
||||
assertTitle("An Important Message");
|
||||
}
|
||||
|
||||
public void testTitleCapitalizationConjunctions() {
|
||||
assertTitle("Search and Replace");
|
||||
assertTitle("Save or Discard Changes");
|
||||
assertTitle("This but Not That");
|
||||
}
|
||||
|
||||
public void testTitleCapitalizationPrepositions() {
|
||||
assertTitle("Compare with Latest Version");
|
||||
assertTitle("Search in Files");
|
||||
assertTitle("Go to Declaration");
|
||||
assertTitle("Copy from Here");
|
||||
}
|
||||
|
||||
public void testTitleCapitalizationFirstAndLastWords() {
|
||||
assertTitle("Compare With");
|
||||
assertTitle("The First Word");
|
||||
assertTitle("Go to the End");
|
||||
}
|
||||
|
||||
public void testTitleCapitalizationNotSatisfied() {
|
||||
assertNotCapitalization("Compare With The Latest Repository Version", Nls.Capitalization.Title);
|
||||
assertNotCapitalization("Search And Replace", Nls.Capitalization.Title);
|
||||
assertNotCapitalization("compare with Latest Version", Nls.Capitalization.Title);
|
||||
assertNotCapitalization("Compare with", Nls.Capitalization.Title);
|
||||
}
|
||||
|
||||
public void testPunctuationSingleSentenceNoPeriod() {
|
||||
assertTrue("Single sentence should not have period", checkPunctuation("This is a single sentence"));
|
||||
}
|
||||
|
||||
public void testPunctuationSingleSentenceWithPeriod() {
|
||||
assertFalse("Single sentence should not end with period", checkPunctuation("This is a single sentence."));
|
||||
}
|
||||
|
||||
public void testPunctuationMultipleSentencesWithPeriods() {
|
||||
assertTrue("Multiple sentences should end with periods", checkPunctuation("First sentence. Second sentence."));
|
||||
}
|
||||
|
||||
public void testPunctuationMultipleSentencesWithoutFinalPeriod() {
|
||||
assertFalse("Multiple sentences should end with periods", checkPunctuation("First sentence. Second sentence"));
|
||||
}
|
||||
|
||||
public void testPunctuationNoDoubleQuotes() {
|
||||
assertFalse("Should not use double quotes", checkPunctuation("This is \"quoted\" text"));
|
||||
}
|
||||
|
||||
public void testPunctuationSingleQuotesAllowed() {
|
||||
assertTrue("Single quotes are allowed", checkPunctuation("This is 'quoted' text"));
|
||||
}
|
||||
|
||||
public void testPunctuationNoExclamation() {
|
||||
assertFalse("Should not use exclamation points", checkPunctuation("This is important!"));
|
||||
}
|
||||
|
||||
public void testPunctuationNoContractions() {
|
||||
assertFalse("Should not use contractions", checkPunctuation("Path can't be found"));
|
||||
}
|
||||
|
||||
public void testPunctuationDontAgainAllowed() {
|
||||
assertTrue("'Don't [verb] again' is allowed", checkPunctuation("Don't ask again"));
|
||||
}
|
||||
|
||||
public void testFixValueTitleWithArticles() {
|
||||
assertEquals("Compare with the Latest Version", NlsCapitalizationUtil.fixValue("Compare With The Latest Version", Nls.Capitalization.Title));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user