Module: VivlioStarter::CLI::Lint::Tokenizer

Defined in:
lib/vivlio_starter/cli/lint/tokenizer.rb

Overview

Markdownファイルから英単語トークンを抽出する

Constant Summary collapse

FRONTMATTER_SEP =
/^---\s*$/
VS_LINT_DISABLE =

vs-lint コメント記法の定義

/^\s*<!--\s*vs-lint-disable\s*-->\s*$/
VS_LINT_ENABLE =
/^\s*<!--\s*vs-lint-enable\s*-->\s*$/
VS_LINT_DISABLE_NEXT_LINE =
/^\s*<!--\s*vs-lint-disable-next-line\s*-->\s*$/

Class Method Summary collapse

Class Method Details

.build_excluded_lines(content) ⇒ Array(Set<Integer>, Integer?)

vs-lint コメントに基づいて除外すべき行番号のセットを構築する

Parameters:

  • content (String)

    Markdownファイル全体の内容

Returns:

  • (Array(Set<Integer>, Integer?))

    除外行番号セットと、 未クローズ disable ブロックの開始行番号(クローズ済みなら nil)



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/vivlio_starter/cli/lint/tokenizer.rb', line 82

def build_excluded_lines(content)
  excluded_lines = Set.new
  disable_opened_at = nil
  line_no = 0

  content.each_line do |line|
    line_no += 1

    # vs-lint-disable コメント行自体を除外
    if line.match?(VS_LINT_DISABLE)
      disable_opened_at ||= line_no
      excluded_lines.add(line_no)
      next
    end

    # vs-lint-enable コメント行自体を除外
    if line.match?(VS_LINT_ENABLE)
      disable_opened_at = nil
      excluded_lines.add(line_no)
      next
    end

    # vs-lint-disable-next-line コメント行自体を除外し、次の行も除外
    if line.match?(VS_LINT_DISABLE_NEXT_LINE)
      excluded_lines.add(line_no)
      excluded_lines.add(line_no + 1)
      next
    end

    # disable ブロック内の行を除外
    excluded_lines.add(line_no) if disable_opened_at
  end

  [excluded_lines, disable_opened_at]
end

.code_fence_lines(content) ⇒ Object

コード(フェンス区切り行・内容行)とみなす行番号の集合を Masking で判定する。



71
72
73
74
75
76
# File 'lib/vivlio_starter/cli/lint/tokenizer.rb', line 71

def code_fence_lines(content)
  prose = Set.new
  Masking.each_prose_line(content) { |_line, lineno| prose << lineno }
  total = content.each_line.count
  (1..total).reject { prose.include?(it) }.to_set
end

.extract_words(line) ⇒ Array<String>

Returns 抽出された英単語の配列.

Parameters:

  • line (String)

    1行のMarkdownテキスト

Returns:

  • (Array<String>)

    抽出された英単語の配列



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/vivlio_starter/cli/lint/tokenizer.rb', line 130

def extract_words(line)
  cleaned = line.dup
  cleaned.gsub!(/`[^`]*`/, ' ')                          # インラインコードを除去
  cleaned.gsub!(/<[^>]+>/, ' ')                          # HTMLタグを除去
  cleaned.gsub!(/\{[^}]*\}/, ' ')                        # Vivliostyle拡張記法 {.aki} 等
  cleaned.gsub!(/(?<![A-Za-z0-9_])@[A-Za-z][A-Za-z0-9-]*/, ' ') # 相互参照ラベル @id(メールは除外)
  cleaned.gsub!(/!?\[([^\]]*)\]\([^)]*\)/, '\1') # Markdownリンク・画像
  cleaned.gsub!(/!?\[([^\]]*)\]\[[^\]]*\]/, '\1')        # 参照リンク
  cleaned.gsub!(%r{https?://\S+}, ' ')                   # URLを除去
  cleaned.gsub!(/^#+\s*/, '')                            # 見出し記号を除去

  cleaned.scan(/[a-zA-Z]+(?:-[a-zA-Z]+)*/).select { it.length >= 2 }
end

.tokenize(content, check_code_blocks: false, path: nil) ⇒ Array<[String, Integer]>

Returns [word, line_no] のペア配列.

Parameters:

  • content (String)

    Markdownファイル全体の内容

  • check_code_blocks (Boolean) (defaults to: false)

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

  • path (String, nil) (defaults to: nil)

    警告メッセージに含めるファイルパス(省略可)

Returns:

  • (Array<[String, Integer]>)

    [word, line_no] のペア配列



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/vivlio_starter/cli/lint/tokenizer.rb', line 24

def tokenize(content, check_code_blocks: false, path: nil)
  # VFM 記法(showcase の座標行・クラス属性など)を先に中和する。textlint 側と
  # 同じ NotationGuard を通すことで、記法の判定が lint 全体で一本化される。
  # 行数は保存されるため、以降の行番号は原稿のものと一致したままになる。
  content = NotationGuard.strip_notation(content)

  tokens           = []
  in_frontmatter   = false
  line_no          = 0

  # vs-lint コメントによる除外行番号セットを構築
  excluded_lines, unclosed_disable_at = build_excluded_lines(content)
  warn_unclosed_disable(path, unclosed_disable_at) if unclosed_disable_at

  # コードフェンス行の集合を Masking(唯一の実装)で判定する。
  # 従来の /^```/ 単純トグルと異なり、可変長フェンス(```/````/~~~)と
  # 入れ子・```include: 除外に追従するため、入れ子フェンスで本文を誤って
  # コード扱いしなくなる。check_code_blocks 時はコードも検査対象に含める。
  code_lines = check_code_blocks ? Set.new : code_fence_lines(content)

  content.each_line do |line|
    line_no += 1

    # YAMLフロントマター(先頭 --- 〜 --- )をスキップ
    if line_no == 1 && line.match?(FRONTMATTER_SEP)
      in_frontmatter = true
      next
    end

    if in_frontmatter
      in_frontmatter = false if line.match?(FRONTMATTER_SEP)
      next
    end

    # コードブロック内のスキップ
    next if code_lines.include?(line_no)

    # vs-lint コメントによる除外
    next if excluded_lines.include?(line_no)

    extract_words(line).each { |word| tokens << [word, line_no] }
  end

  tokens
end

.warn_unclosed_disable(path, opened_at) ⇒ Object

vs-lint-disable が閉じられないままファイル末尾に達した場合に警告を出す。 著者が誤って enable を書き忘れたケースを検知するためのガード。

Parameters:

  • path (String, nil)

    ファイルパス(警告メッセージ用)

  • opened_at (Integer)

    disable が開始された行番号



122
123
124
125
126
# File 'lib/vivlio_starter/cli/lint/tokenizer.rb', line 122

def warn_unclosed_disable(path, opened_at)
  location = path ? "#{path}:#{opened_at}" : "line #{opened_at}"
  warn "[vs-lint] 警告: #{location} の <!-- vs-lint-disable --> が " \
       '<!-- vs-lint-enable --> で閉じられていません。ファイル末尾まで lint が無効化されます。'
end