application_config_in_module_attribute.ex (2981B)
1 defmodule Credo.Check.Warning.ApplicationConfigInModuleAttribute do 2 use Credo.Check, 3 base_priority: :high, 4 tags: [:controversial], 5 category: :warning, 6 explanations: [ 7 check: """ 8 Module attributes are evaluated at compile time and not at run time. As 9 a result, certain configuration read calls made in your module attributes 10 may work as expected during local development, but may break once in a 11 deployed context. 12 13 This check analyzes all of the module attributes present within a module, 14 and validates that there are no unsafe calls. 15 16 These unsafe calls include: 17 18 - `Application.fetch_env/2` 19 - `Application.fetch_env!/2` 20 - `Application.get_all_env/1` 21 - `Application.get_env/3` 22 - `Application.get_env/2` 23 24 As of Elixir 1.10 you can leverage `Application.compile_env/3` and 25 `Application.compile_env!/2` if you wish to set configuration at 26 compile time using module attributes. 27 """ 28 ] 29 30 @doc false 31 @impl true 32 def run(%SourceFile{} = source_file, params \\ []) do 33 issue_meta = IssueMeta.for(source_file, params) 34 35 Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta)) 36 end 37 38 defp traverse({:@, meta, [attribute_definition]} = ast, issues, issue_meta) do 39 case traverse_attribute(attribute_definition) do 40 nil -> 41 {ast, issues} 42 43 {attribute, call} -> 44 {ast, issues_for_call(attribute, call, meta, issue_meta, issues)} 45 end 46 end 47 48 defp traverse(ast, issues, _issue_meta) do 49 {ast, issues} 50 end 51 52 defp traverse_attribute({attribute, _, _} = ast) do 53 case Macro.prewalk(ast, nil, &get_forbidden_call/2) do 54 {_ast, nil} -> 55 nil 56 57 {_ast, call} -> 58 {attribute, call} 59 end 60 end 61 62 defp traverse_attribute(_ast) do 63 nil 64 end 65 66 defp get_forbidden_call( 67 {{:., _, [{:__aliases__, _, [:Application]}, :fetch_env]}, _meta, _args} = ast, 68 _acc 69 ) do 70 {ast, "Application.fetch_env/2"} 71 end 72 73 defp get_forbidden_call( 74 {{:., _, [{:__aliases__, _, [:Application]}, :fetch_env!]}, _meta, _args} = ast, 75 _acc 76 ) do 77 {ast, "Application.fetch_env!/2"} 78 end 79 80 defp get_forbidden_call( 81 {{:., _, [{:__aliases__, _, [:Application]}, :get_all_env]}, _meta, _args} = ast, 82 _acc 83 ) do 84 {ast, "Application.get_all_env/1"} 85 end 86 87 defp get_forbidden_call( 88 {{:., _, [{:__aliases__, _, [:Application]}, :get_env]}, _meta, args} = ast, 89 _acc 90 ) do 91 {ast, "Application.get_env/#{length(args)}"} 92 end 93 94 defp get_forbidden_call(ast, acc) do 95 {ast, acc} 96 end 97 98 defp issues_for_call(attribute, call, meta, issue_meta, issues) do 99 options = [ 100 message: 101 "Module attribute @#{Atom.to_string(attribute)} makes use of unsafe Application configuration call #{call}", 102 trigger: call, 103 line_no: meta[:line] 104 ] 105 106 [format_issue(issue_meta, options) | issues] 107 end 108 end