token.ex (8114B)
1 defmodule Credo.Code.Token do 2 @moduledoc """ 3 This module provides helper functions to analyse tokens returned by `Credo.Code.to_tokens/1`. 4 """ 5 6 @doc """ 7 Returns `true` if the given `token` contains a line break. 8 """ 9 def eol?(token) 10 11 def eol?(list) when is_list(list) do 12 Enum.any?(list, &eol?/1) 13 end 14 15 def eol?({_, {_, _, _}, _, list, _, _}) when is_list(list) do 16 Enum.any?(list, &eol?/1) 17 end 18 19 def eol?({_, {_, _, _}, list}) when is_list(list) do 20 Enum.any?(list, &eol?/1) 21 end 22 23 def eol?({{_, _, _}, list}) when is_list(list) do 24 Enum.any?(list, &eol?/1) 25 end 26 27 def eol?({:eol, {_, _, _}}), do: true 28 def eol?(_), do: false 29 30 @doc """ 31 Returns the position of a token in the form 32 33 {line_no_start, col_start, line_no_end, col_end} 34 """ 35 def position(token) 36 37 def position({_, {line_no, col_start, _}, atom_or_charlist, _, _, _}) do 38 position_tuple(atom_or_charlist, line_no, col_start) 39 end 40 41 def position({_, {line_no, col_start, _}, atom_or_charlist, _, _}) do 42 position_tuple(atom_or_charlist, line_no, col_start) 43 end 44 45 def position({_, {line_no, col_start, _}, atom_or_charlist, _}) do 46 position_tuple(atom_or_charlist, line_no, col_start) 47 end 48 49 def position({:bin_string, {line_no, col_start, _}, atom_or_charlist}) do 50 position_tuple_for_quoted_string(atom_or_charlist, line_no, col_start) 51 end 52 53 def position({:list_string, {line_no, col_start, _}, atom_or_charlist}) do 54 position_tuple_for_quoted_string(atom_or_charlist, line_no, col_start) 55 end 56 57 def position({:bin_heredoc, {line_no, col_start, _}, atom_or_charlist}) do 58 position_tuple_for_heredoc(atom_or_charlist, line_no, col_start) 59 end 60 61 def position({:list_heredoc, {line_no, col_start, _}, atom_or_charlist}) do 62 position_tuple_for_heredoc(atom_or_charlist, line_no, col_start) 63 end 64 65 def position({:atom_unsafe, {line_no, col_start, _}, atom_or_charlist}) do 66 position_tuple_for_quoted_string(atom_or_charlist, line_no, col_start) 67 end 68 69 # Elixir >= 1.10.0 tuple syntax 70 def position({:sigil, {line_no, col_start, nil}, _, atom_or_charlist, _list, _number, _binary}) do 71 position_tuple_for_quoted_string(atom_or_charlist, line_no, col_start) 72 end 73 74 # Elixir >= 1.9.0 tuple syntax 75 def position({{line_no, col_start, nil}, {_line_no2, _col_start2, nil}, atom_or_charlist}) do 76 position_tuple_for_quoted_string(atom_or_charlist, line_no, col_start) 77 end 78 79 def position({:kw_identifier_unsafe, {line_no, col_start, _}, atom_or_charlist}) do 80 position_tuple_for_quoted_string(atom_or_charlist, line_no, col_start) 81 end 82 83 # Elixir < 1.9.0 tuple syntax 84 def position({_, {line_no, col_start, _}, atom_or_charlist}) do 85 position_tuple(atom_or_charlist, line_no, col_start) 86 end 87 88 def position({atom_or_charlist, {line_no, col_start, _}}) do 89 position_tuple(atom_or_charlist, line_no, col_start) 90 end 91 92 # interpolation 93 def position({{line_no, col_start, _}, list}) when is_list(list) do 94 {line_no, col_start, line_no_end, col_end} = 95 position_tuple_for_quoted_string(list, line_no, col_start) 96 97 {line_no, col_start, line_no_end, col_end} 98 end 99 100 # 101 102 defp position_tuple(list, line_no, col_start) when is_list(list) do 103 binary = to_string(list) 104 col_end = col_start + String.length(binary) 105 106 {line_no, col_start, line_no, col_end} 107 end 108 109 defp position_tuple(atom, line_no, col_start) when is_atom(atom) do 110 binary = to_string(atom) 111 col_end = col_start + String.length(binary) 112 113 {line_no, col_start, line_no, col_end} 114 end 115 116 defp position_tuple(number, line_no, col_start) when is_number(number) do 117 binary = to_string([number]) 118 col_end = col_start + String.length(binary) 119 120 {line_no, col_start, line_no, col_end} 121 end 122 123 defp position_tuple(_, _line_no, _col_start), do: nil 124 125 defp position_tuple_for_heredoc(list, line_no, col_start) 126 when is_list(list) do 127 # add 3 for """ (closing double quote) 128 {line_no_end, col_end, _terminator} = convert_to_col_end(line_no, col_start, list) 129 130 col_end = col_end + 3 131 132 {line_no, col_start, line_no_end, col_end} 133 end 134 135 @doc false 136 def position_tuple_for_quoted_string(list, line_no, col_start) 137 when is_list(list) do 138 # add 1 for " (closing double quote) 139 {line_no_end, col_end, terminator} = convert_to_col_end(line_no, col_start, list) 140 141 {line_no_end, col_end} = 142 case terminator do 143 :eol -> 144 # move to next line 145 {line_no_end + 1, 1} 146 147 _ -> 148 # add 1 for " (closing double quote) 149 {line_no_end, col_end + 1} 150 end 151 152 {line_no, col_start, line_no_end, col_end} 153 end 154 155 # 156 157 defp convert_to_col_end(line_no, col_start, list) when is_list(list) do 158 Enum.reduce(list, {line_no, col_start, nil}, &reduce_to_col_end/2) 159 end 160 161 # Elixir < 1.9.0 162 # 163 # {{1, 25, 32}, [{:identifier, {1, 27, 31}, :name}]} 164 defp convert_to_col_end(_, _, {{line_no, col_start, _}, list}) do 165 {line_no_end, col_end, _terminator} = convert_to_col_end(line_no, col_start, list) 166 167 # add 1 for } (closing parens of interpolation) 168 col_end = col_end + 1 169 170 {line_no_end, col_end, :interpolation} 171 end 172 173 # Elixir >= 1.9.0 174 # 175 # {{1, 25, nil}, {1, 31, nil}, [{:identifier, {1, 27, nil}, :name}]} 176 defp convert_to_col_end( 177 _, 178 _, 179 {{line_no, col_start, nil}, {_line_no2, _col_start2, nil}, list} 180 ) do 181 {line_no_end, col_end, _terminator} = convert_to_col_end(line_no, col_start, list) 182 183 # add 1 for } (closing parens of interpolation) 184 col_end = col_end + 1 185 186 {line_no_end, col_end, :interpolation} 187 end 188 189 defp convert_to_col_end(_, _, {:eol, {line_no, col_start, _}}) do 190 {line_no, col_start, :eol} 191 end 192 193 defp convert_to_col_end(_, _, {value, {line_no, col_start, _}}) do 194 {line_no, to_col_end(col_start, value), nil} 195 end 196 197 defp convert_to_col_end(_, _, {:bin_string, {line_no, col_start, nil}, list}) 198 when is_list(list) do 199 # add 2 for opening and closing " 200 {line_no, col_end, terminator} = 201 Enum.reduce(list, {line_no, col_start, nil}, &reduce_to_col_end/2) 202 203 {line_no, col_end + 2, terminator} 204 end 205 206 defp convert_to_col_end(_, _, {:bin_string, {line_no, col_start, nil}, value}) do 207 # add 2 for opening and closing " 208 {line_no, to_col_end(col_start, value, 2), :bin_string} 209 end 210 211 defp convert_to_col_end(_, _, {:list_string, {line_no, col_start, nil}, value}) do 212 # add 2 for opening and closing ' 213 {line_no, to_col_end(col_start, value, 2), :bin_string} 214 end 215 216 defp convert_to_col_end(_, _, {_, {line_no, col_start, nil}, list}) 217 when is_list(list) do 218 Enum.reduce(list, {line_no, col_start, nil}, &reduce_to_col_end/2) 219 end 220 221 defp convert_to_col_end(_, _, {_, {line_no, col_start, nil}, value}) do 222 {line_no, to_col_end(col_start, value), nil} 223 end 224 225 defp convert_to_col_end(_, _, {:aliases, {line_no, col_start, _}, list}) do 226 value = Enum.map(list, &to_string/1) 227 228 {line_no, to_col_end(col_start, value), nil} 229 end 230 231 defp convert_to_col_end(_, _, {_, {line_no, col_start, _}, value}) do 232 {line_no, to_col_end(col_start, value), nil} 233 end 234 235 defp convert_to_col_end(_, _, {:sigil, {line_no, col_start, nil}, _, list, _, _}) 236 when is_list(list) do 237 Enum.reduce(list, {line_no, col_start, nil}, &reduce_to_col_end/2) 238 end 239 240 # Elixir >= 1.11 241 defp convert_to_col_end( 242 _, 243 _, 244 {:sigil, {line_no, col_start, nil}, _, list, _list, _number, _binary} 245 ) 246 when is_list(list) do 247 Enum.reduce(list, {line_no, col_start, nil}, &reduce_to_col_end/2) 248 end 249 250 defp convert_to_col_end(_, _, {:sigil, {line_no, col_start, nil}, _, value, _, _}) do 251 {line_no, to_col_end(col_start, value), nil} 252 end 253 254 defp convert_to_col_end(line_no, col_start, value) do 255 {line_no, to_col_end(col_start, value), nil} 256 end 257 258 # 259 260 defp reduce_to_col_end(value, {current_line_no, current_col_start, _}) do 261 convert_to_col_end(current_line_no, current_col_start, value) 262 end 263 264 # 265 266 def to_col_end(col_start, value, add \\ 0) do 267 col_start + String.length(to_string(value)) + add 268 end 269 end