~ubuntu-branches/ubuntu/wily/phabricator/wily

« back to all changes in this revision

Viewing changes to src/lint/linter/ArcanistSpellingLinter.php

  • Committer: Package Import Robot
  • Author(s): Richard Sellam
  • Date: 2014-11-01 23:20:06 UTC
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: package-import@ubuntu.com-20141101232006-mvlnp0cil67tsboe
Tags: upstream-0~git20141101/arcanist
Import upstream version 0~git20141101, component arcanist

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
/**
 
4
 * Enforces basic spelling. Spelling inside code is actually pretty hard to
 
5
 * get right without false positives. I take a conservative approach and just
 
6
 * use a blacklisted set of words that are commonly spelled incorrectly.
 
7
 */
 
8
final class ArcanistSpellingLinter extends ArcanistLinter {
 
9
 
 
10
  const LINT_SPELLING_EXACT   = 1;
 
11
  const LINT_SPELLING_PARTIAL = 2;
 
12
 
 
13
  private $dictionaries     = array();
 
14
  private $exactWordRules   = array();
 
15
  private $partialWordRules = array();
 
16
 
 
17
  public function getInfoName() {
 
18
    return pht('Spellchecker');
 
19
  }
 
20
 
 
21
  public function getInfoDescription() {
 
22
    return pht('Detects common misspellings of English words.');
 
23
  }
 
24
 
 
25
  public function getLinterName() {
 
26
    return 'SPELL';
 
27
  }
 
28
 
 
29
  public function getLinterConfigurationName() {
 
30
    return 'spelling';
 
31
  }
 
32
 
 
33
  public function getLinterConfigurationOptions() {
 
34
    $options = array(
 
35
      'spelling.dictionaries' => array(
 
36
        'type' => 'optional list<string>',
 
37
        'help' => pht('Pass in custom dictionaries.'),
 
38
      ),
 
39
    );
 
40
 
 
41
    return $options + parent::getLinterConfigurationOptions();
 
42
  }
 
43
 
 
44
  public function setLinterConfigurationValue($key, $value) {
 
45
    switch ($key) {
 
46
      case 'spelling.dictionaries':
 
47
        foreach ($value as $dictionary) {
 
48
          $this->loadDictionary($dictionary);
 
49
        }
 
50
        return;
 
51
    }
 
52
 
 
53
    return parent::setLinterConfigurationValue($key, $value);
 
54
  }
 
55
 
 
56
  public function loadDictionary($path) {
 
57
    $root = $this->getEngine()->getWorkingCopy()->getProjectRoot();
 
58
    $path = Filesystem::resolvePath($path, $root);
 
59
 
 
60
    $dict = phutil_json_decode(Filesystem::readFile($path));
 
61
    PhutilTypeSpec::checkMap(
 
62
      $dict,
 
63
      array(
 
64
        'rules' => 'map<string, map<string, string>>',
 
65
      ));
 
66
    $rules = $dict['rules'];
 
67
 
 
68
    $this->dictionaries[] = $path;
 
69
    $this->exactWordRules = array_merge(
 
70
      $this->exactWordRules,
 
71
      idx($rules, 'exact', array()));
 
72
    $this->partialWordRules = array_merge(
 
73
      $this->partialWordRules,
 
74
      idx($rules, 'partial', array()));
 
75
  }
 
76
 
 
77
  public function addExactWordRule($misspelling, $correction) {
 
78
    $this->exactWordRules = array_merge(
 
79
      $this->exactWordRules,
 
80
      array($misspelling => $correction));
 
81
  }
 
82
 
 
83
  public function addPartialWordRule($misspelling, $correction) {
 
84
    $this->partialWordRules = array_merge(
 
85
      $this->partialWordRules,
 
86
      array($misspelling => $correction));
 
87
  }
 
88
 
 
89
  public function getLintSeverityMap() {
 
90
    return array(
 
91
      self::LINT_SPELLING_EXACT   => ArcanistLintSeverity::SEVERITY_WARNING,
 
92
      self::LINT_SPELLING_PARTIAL => ArcanistLintSeverity::SEVERITY_WARNING,
 
93
    );
 
94
  }
 
95
 
 
96
  public function getLintNameMap() {
 
97
    return array(
 
98
      self::LINT_SPELLING_EXACT   => pht('Possible Spelling Mistake'),
 
99
      self::LINT_SPELLING_PARTIAL => pht('Possible Spelling Mistake'),
 
100
    );
 
101
  }
 
102
 
 
103
  public function lintPath($path) {
 
104
    // TODO: This is a bit hacky. If no dictionaries were specified, then add
 
105
    // the default dictionary.
 
106
    if (!$this->dictionaries) {
 
107
      $root = dirname(phutil_get_library_root('arcanist'));
 
108
      $this->loadDictionary($root.'/resources/spelling/english.json');
 
109
    }
 
110
 
 
111
    foreach ($this->exactWordRules as $misspelling => $correction) {
 
112
      $this->checkExactWord($path, $misspelling, $correction);
 
113
    }
 
114
 
 
115
    foreach ($this->partialWordRules as $misspelling => $correction) {
 
116
      $this->checkPartialWord($path, $misspelling, $correction);
 
117
    }
 
118
  }
 
119
 
 
120
  private function checkExactWord($path, $word, $correction) {
 
121
    $text = $this->getData($path);
 
122
    $matches = array();
 
123
    $num_matches = preg_match_all(
 
124
      '#\b'.preg_quote($word, '#').'\b#i',
 
125
      $text,
 
126
      $matches,
 
127
      PREG_OFFSET_CAPTURE);
 
128
    if (!$num_matches) {
 
129
      return;
 
130
    }
 
131
    foreach ($matches[0] as $match) {
 
132
      $original = $match[0];
 
133
      $replacement = self::fixLetterCase($correction, $original);
 
134
      $this->raiseLintAtOffset(
 
135
        $match[1],
 
136
        self::LINT_SPELLING_EXACT,
 
137
        pht(
 
138
          "Possible spelling error. You wrote '%s', but did you mean '%s'?",
 
139
          $word,
 
140
          $correction),
 
141
        $original,
 
142
        $replacement);
 
143
    }
 
144
  }
 
145
 
 
146
  private function checkPartialWord($path, $word, $correction) {
 
147
    $text = $this->getData($path);
 
148
    $pos = 0;
 
149
    while ($pos < strlen($text)) {
 
150
      $next = stripos($text, $word, $pos);
 
151
      if ($next === false) {
 
152
        return;
 
153
      }
 
154
      $original = substr($text, $next, strlen($word));
 
155
      $replacement = self::fixLetterCase($correction, $original);
 
156
      $this->raiseLintAtOffset(
 
157
        $next,
 
158
        self::LINT_SPELLING_PARTIAL,
 
159
        pht(
 
160
          "Possible spelling error. You wrote '%s', but did you mean '%s'?",
 
161
          $word,
 
162
          $correction),
 
163
        $original,
 
164
        $replacement);
 
165
      $pos = $next + 1;
 
166
    }
 
167
  }
 
168
 
 
169
  public static function fixLetterCase($string, $case) {
 
170
    switch ($case) {
 
171
      case strtolower($case):
 
172
        return strtolower($string);
 
173
      case strtoupper($case):
 
174
        return strtoupper($string);
 
175
      case ucwords(strtolower($case)):
 
176
        return ucwords(strtolower($string));
 
177
      default:
 
178
        return null;
 
179
    }
 
180
  }
 
181
 
 
182
}