• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ljacqu / wordeval / 14695353888

27 Apr 2025 07:21PM UTC coverage: 61.437% (+0.6%) from 60.848%
14695353888

push

github

ljacqu
Support comments in affix class declarations

385 of 694 branches covered (55.48%)

1 of 1 new or added line in 1 file covered. (100.0%)

6 existing lines in 2 files now uncovered.

975 of 1587 relevant lines covered (61.44%)

3.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.0
/src/main/java/ch/jalu/wordeval/dictionary/hunspell/HunspellUnmuncherService.java
1
package ch.jalu.wordeval.dictionary.hunspell;
2

3
import com.google.common.base.Preconditions;
4
import org.springframework.stereotype.Service;
5

6
import java.util.List;
7
import java.util.function.Function;
8
import java.util.stream.Stream;
9

10
/**
11
 * Service to "unmunch" words (i.e. expand) based on Hunspell affix rules.
12
 */
13
@Service
14
public class HunspellUnmuncherService {
3✔
15

16
  /**
17
   * Processes the given dictionary lines and expands them based on the specified affix rules.
18
   *
19
   * @param lines the lines of the dictionary file (including affix information)
20
   * @param affixDefinition the dictionary's affix definitions
21
   * @return all words isolated and expanded
22
   */
23
  public Stream<String> unmunch(Stream<String> lines, HunspellAffixes affixDefinition) {
24
    return lines.flatMap(line -> unmunch(line, affixDefinition));
11✔
25
  }
26

27
  private Stream<String> unmunch(String line, HunspellAffixes affixDefinition) {
28
    int indexOfSlash = line.indexOf('/');
4✔
29
    if (indexOfSlash <= 0) {
2✔
30
      // We don't support empty strings as base
31
      return Stream.of(line);
3✔
32
    }
33
    // Slashes can be escaped with a backslash apparently (nl.dic), but this currently goes beyond the desired scope,
34
    // since a word with a slash won't be interesting to wordeval anyway. So it's the job of a sanitizer to skip these
35
    // words before they're passed to this class.
36
    Preconditions.checkArgument(line.indexOf('\\') < 0, line);
9✔
37

38
    String baseWord = line.substring(0, indexOfSlash);
5✔
39
    List<String> affixFlags = extractAffixFlags(line.substring(indexOfSlash + 1), affixDefinition.getFlagType());
10✔
40

41
    if (affixDefinition.getForbiddenWordClass() != null
5✔
42
        && affixFlags.contains(affixDefinition.getForbiddenWordClass())) {
3✔
43
      return Stream.empty();
2✔
44
    }
45

46
    boolean includeBaseWord = affixDefinition.getNeedAffixFlag() == null
5✔
47
        || !affixFlags.contains(affixDefinition.getNeedAffixFlag());
7!
48
    if (includeBaseWord) {
2✔
49
      return Stream.concat(Stream.of(baseWord), streamThroughAllAffixes(baseWord, affixFlags, affixDefinition));
9✔
50
    }
51
    return streamThroughAllAffixes(baseWord, affixFlags, affixDefinition);
6✔
52
  }
53

54
  /**
55
   * Returns the affix flags indicated in the "meta part" of the line, i.e. the section after the slash separating
56
   * the word.
57
   *
58
   * @param metaPart the part with the affixes
59
   * @param flagType flag type to parse the list with
60
   * @return all affix flags in the given meta part
61
   */
62
  private List<String> extractAffixFlags(String metaPart, AffixFlagType flagType) {
63
    int indexFirstWhitespace = -1;
2✔
64
    for (int i = 0; i < metaPart.length(); ++i) {
8✔
65
      if (Character.isWhitespace(metaPart.charAt(i))) {
5✔
66
        indexFirstWhitespace = i;
2✔
67
        break;
1✔
68
      }
69
    }
70

71
    String affixList = indexFirstWhitespace >= 0 ? metaPart.substring(0, indexFirstWhitespace) : metaPart;
9✔
72
    return flagType.split(affixList);
4✔
73
  }
74

75
  private Stream<String> streamThroughAllAffixes(String baseWord, List<String> affixFlags,
76
                                                 HunspellAffixes affixDefinition) {
77
    return affixFlags.stream()
6✔
78
        .flatMap(affixFlag -> affixDefinition.streamThroughMatchingRules(baseWord, affixFlag))
11✔
79
        .flatMap(affixRule -> {
1✔
80
          String wordWithAffix = affixRule.applyRule(baseWord);
4✔
81
          if (wordWithAffix == null) {
2✔
82
            return Stream.empty();
2✔
83
          }
84

85
          boolean hasContinuationClasses = !affixRule.getContinuationClasses().isEmpty();
8✔
86
          boolean isCrossProductPrefix = affixRule.getType() == AffixType.PFX && affixRule.isCrossProduct();
11!
87
          if (hasContinuationClasses && isCrossProductPrefix) {
4!
UNCOV
88
            return Stream.of(
×
UNCOV
89
                    Stream.of(wordWithAffix),
×
UNCOV
90
                    streamThroughAllAffixes(wordWithAffix, affixRule.getContinuationClasses(), affixDefinition),
×
UNCOV
91
                    addSuffixes(wordWithAffix, affixFlags, affixDefinition))
×
UNCOV
92
                .flatMap(Function.identity());
×
93
          } else if (hasContinuationClasses) {
2✔
94
            return Stream.concat(
3✔
95
                    Stream.of(wordWithAffix),
4✔
96
                    streamThroughAllAffixes(wordWithAffix, affixRule.getContinuationClasses(), affixDefinition));
3✔
97
          } else if (isCrossProductPrefix) {
2✔
98
            return Stream.concat(
3✔
99
                    Stream.of(wordWithAffix),
4✔
100
                    addSuffixes(wordWithAffix, affixFlags, affixDefinition));
1✔
101
          } else {
102
            return Stream.of(wordWithAffix);
3✔
103
          }
104
        });
105
  }
106

107
  private static Stream<String> addSuffixes(String word,
108
                                            List<String> affixFlags,
109
                                            HunspellAffixes affixDefinition) {
110
    return affixFlags.stream()
5✔
111
        .flatMap(affixFlag -> affixDefinition.getAffixRulesByFlag().get(affixFlag).stream())
9✔
112
        .filter(rule -> rule.getType() == AffixType.SFX && rule.isCrossProduct() && rule.matches(word))
18✔
113
        .map(rule -> rule.applyRule(word));
5✔
114
  }
115
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc