tag_helper.ex (1722B)
1 defmodule Credo.Check.Design.TagHelper do 2 @moduledoc false 3 4 @doc_attribute_names [:doc, :moduledoc, :shortdoc] 5 6 alias Credo.SourceFile 7 8 def tags(source_file, tag_name, include_doc?) do 9 tags_from_module_attributes(source_file, tag_name, include_doc?) ++ 10 tags_from_comments(source_file, tag_name) 11 end 12 13 defp tags_from_module_attributes(source_file, tag_name, true) do 14 regex = Regex.compile!("\\A\\s*#{tag_name}:?\\s*.+", "i") 15 16 Credo.Code.prewalk(source_file, &traverse(&1, &2, regex)) 17 end 18 19 defp tags_from_module_attributes(_source_file, _tag_name, false) do 20 [] 21 end 22 23 defp tags_from_comments(source_file, tag_name) do 24 regex = Regex.compile!("(\\A|[^\\?])#\\s*#{tag_name}:?\\s*.+", "i") 25 source = SourceFile.source(source_file) 26 27 if source =~ regex do 28 source 29 |> Credo.Code.clean_charlists_strings_and_sigils() 30 |> String.split("\n") 31 |> Enum.with_index() 32 |> Enum.map(&find_tag_in_line(&1, regex)) 33 |> Enum.filter(&tags?/1) 34 else 35 [] 36 end 37 end 38 39 defp traverse({:@, _, [{name, meta, [string]} | _]} = ast, issues, regex) 40 when name in @doc_attribute_names and is_binary(string) do 41 if string =~ regex do 42 trimmed = String.trim_trailing(string) 43 {nil, issues ++ [{meta[:line], trimmed, trimmed}]} 44 else 45 {ast, issues} 46 end 47 end 48 49 defp traverse(ast, issues, _regex) do 50 {ast, issues} 51 end 52 53 defp find_tag_in_line({line, index}, regex) do 54 tag_list = 55 regex 56 |> Regex.run(line) 57 |> List.wrap() 58 |> Enum.map(&String.trim/1) 59 60 {index + 1, line, List.first(tag_list)} 61 end 62 63 defp tags?({_line_no, _line, nil}), do: false 64 defp tags?({_line_no, _line, _tag}), do: true 65 end