Module: Vivlio::Starter::CLI::Lint::SpellChecker

Defined in:
lib/vivlio/starter/cli/lint/spell_checker.rb

Overview

英語スペルチェックを実行し、結果を出力する

Class Method Summary collapse

Class Method Details

.check(path, word_map, ignore_words: [], check_code_blocks: false) ⇒ Array<Hash>

ファイルのスペルチェックを実行する

Parameters:

  • path (String)

    チェック対象のMarkdownファイルパス

  • word_map (Hash)

    { downcase_word => display_word }

  • ignore_words (Array<String>) (defaults to: [])

    無視する単語のリスト(downcase)

  • check_code_blocks (Boolean) (defaults to: false)

    コードブロック内もチェックするか

Returns:

  • (Array<Hash>)

    { line:, word:, suggestion: } の配列



20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/vivlio/starter/cli/lint/spell_checker.rb', line 20

def check(path, word_map, ignore_words: [], check_code_blocks: false)
  content = File.read(path, encoding: 'UTF-8')
  tokens  = Tokenizer.tokenize(content, check_code_blocks: check_code_blocks, path: path)

  tokens.filter_map do |word, line_no|
    next if word_map.key?(word.downcase)
    next if ignore_words.include?(word.downcase)

    { line: line_no, word: word, suggestion: find_suggestion(word, word_map) }
  end
rescue Errno::ENOENT => e
  Common.log_warn("[spellcheck] ファイルを読み込めませんでした: #{path} (#{e.message})")
  []
end

.find_suggestion(word, word_map) ⇒ String?

Levenshtein距離で最良の候補語を返す

Parameters:

  • word (String)

    チェック対象の単語

  • word_map (Hash)

    辞書

Returns:

  • (String, nil)

    候補語、または閾値超過時に nil



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/vivlio/starter/cli/lint/spell_checker.rb', line 61

def find_suggestion(word, word_map)
  threshold = threshold_for(word)
  min_len   = [word.length - threshold, 1].max
  max_len   = word.length + threshold
  w_down    = word.downcase

  best_word = nil
  best_dist = threshold + 1

  word_map.each_value do |dict_word|
    next unless dict_word.length.between?(min_len, max_len)

    dist = DidYouMean::Levenshtein.distance(w_down, dict_word.downcase)
    if dist < best_dist
      best_dist = dist
      best_word = dict_word
    end
  end

  best_dist <= threshold ? best_word : nil
end

複数ファイルのエラーを標準出力に表示する

Parameters:

  • errors_by_file (Hash)

    { path => [{ line:, word:, suggestion: }] }

Returns:

  • (Boolean)

    エラーがあれば true



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/vivlio/starter/cli/lint/spell_checker.rb', line 38

def print_errors(errors_by_file)
  return false if errors_by_file.empty?

  errors_by_file.each do |path, errors|
    Common.log_always "📄 #{path}"
    errors.each do |err|
      if err[:suggestion]
        Common.log_always format('  %4d  %s => %s', err[:line], err[:word], err[:suggestion])
      else
        Common.log_always format('  %4d  %s', err[:line], err[:word])
      end
      Common.log_always '        綴りが誤っている可能性があります (spellcheck)'
    end
    Common.log_always ''
  end

  true
end

.threshold_for(word) ⇒ Integer

単語長に応じた許容Levenshtein距離を返す

Parameters:

  • word (String)

Returns:

  • (Integer)


86
87
88
89
90
91
92
# File 'lib/vivlio/starter/cli/lint/spell_checker.rb', line 86

def threshold_for(word)
  case word.length
  when 1..4 then 1
  when 5..8 then 2
  else           3
  end
end