 c4439c630f
			
		
	
	
		c4439c630f
		
			
		
	
	
	
	
		
			
			grep -rl '# Copyright © .* Pleroma' * | xargs sed -i 's;Copyright © .* Pleroma .*;Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>;'
		
			
				
	
	
		
			166 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
| # Pleroma: A lightweight social networking server
 | |
| # Originally taken from
 | |
| # https://github.com/VeryBigThings/elixir_common/blob/master/lib/vbt/credo/check/consistency/file_location.ex
 | |
| # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 | |
| # SPDX-License-Identifier: AGPL-3.0-only
 | |
| 
 | |
| defmodule Credo.Check.Consistency.FileLocation do
 | |
|   @moduledoc false
 | |
| 
 | |
|   # credo:disable-for-this-file Credo.Check.Readability.Specs
 | |
| 
 | |
|   @checkdoc """
 | |
|   File location should follow the namespace hierarchy of the module it defines.
 | |
| 
 | |
|   Examples:
 | |
| 
 | |
|       - `lib/my_system.ex` should define the `MySystem` module
 | |
|       - `lib/my_system/accounts.ex` should define the `MySystem.Accounts` module
 | |
|   """
 | |
|   @explanation [warning: @checkdoc]
 | |
| 
 | |
|   @special_namespaces [
 | |
|     "controllers",
 | |
|     "views",
 | |
|     "operations",
 | |
|     "channels"
 | |
|   ]
 | |
| 
 | |
|   # `use Credo.Check` required that module attributes are already defined, so we need
 | |
|   # to place these attributes
 | |
|   # before use/alias expressions.
 | |
|   # credo:disable-for-next-line VBT.Credo.Check.Consistency.ModuleLayout
 | |
|   use Credo.Check, category: :warning, base_priority: :high
 | |
| 
 | |
|   alias Credo.Code
 | |
| 
 | |
|   def run(source_file, params \\ []) do
 | |
|     case verify(source_file, params) do
 | |
|       :ok ->
 | |
|         []
 | |
| 
 | |
|       {:error, module, expected_file} ->
 | |
|         error(IssueMeta.for(source_file, params), module, expected_file)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   defp verify(source_file, params) do
 | |
|     source_file.filename
 | |
|     |> Path.relative_to_cwd()
 | |
|     |> verify(Code.ast(source_file), params)
 | |
|   end
 | |
| 
 | |
|   @doc false
 | |
|   def verify(relative_path, ast, params) do
 | |
|     if verify_path?(relative_path, params),
 | |
|       do: ast |> main_module() |> verify_module(relative_path, params),
 | |
|       else: :ok
 | |
|   end
 | |
| 
 | |
|   defp verify_path?(relative_path, params) do
 | |
|     case Path.split(relative_path) do
 | |
|       ["lib" | _] -> not exclude?(relative_path, params)
 | |
|       ["test", "support" | _] -> false
 | |
|       ["test", "test_helper.exs"] -> false
 | |
|       ["test" | _] -> not exclude?(relative_path, params)
 | |
|       _ -> false
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   defp exclude?(relative_path, params) do
 | |
|     params
 | |
|     |> Keyword.get(:exclude, [])
 | |
|     |> Enum.any?(&String.starts_with?(relative_path, &1))
 | |
|   end
 | |
| 
 | |
|   defp main_module(ast) do
 | |
|     {_ast, modules} = Macro.prewalk(ast, [], &traverse/2)
 | |
|     Enum.at(modules, -1)
 | |
|   end
 | |
| 
 | |
|   defp traverse({:defmodule, _meta, args}, modules) do
 | |
|     [{:__aliases__, _, name_parts}, _module_body] = args
 | |
|     {args, [Module.concat(name_parts) | modules]}
 | |
|   end
 | |
| 
 | |
|   defp traverse(ast, state), do: {ast, state}
 | |
| 
 | |
|   # empty file - shouldn't really happen, but we'll let it through
 | |
|   defp verify_module(nil, _relative_path, _params), do: :ok
 | |
| 
 | |
|   defp verify_module(main_module, relative_path, params) do
 | |
|     parsed_path = parsed_path(relative_path, params)
 | |
| 
 | |
|     expected_file =
 | |
|       expected_file_base(parsed_path.root, main_module) <>
 | |
|         Path.extname(parsed_path.allowed)
 | |
| 
 | |
|     cond do
 | |
|       expected_file == parsed_path.allowed ->
 | |
|         :ok
 | |
| 
 | |
|       special_namespaces?(parsed_path.allowed) ->
 | |
|         original_path = parsed_path.allowed
 | |
| 
 | |
|         namespace =
 | |
|           Enum.find(@special_namespaces, original_path, fn namespace ->
 | |
|             String.contains?(original_path, namespace)
 | |
|           end)
 | |
| 
 | |
|         allowed = String.replace(original_path, "/" <> namespace, "")
 | |
| 
 | |
|         if expected_file == allowed,
 | |
|           do: :ok,
 | |
|           else: {:error, main_module, expected_file}
 | |
| 
 | |
|       true ->
 | |
|         {:error, main_module, expected_file}
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   defp special_namespaces?(path), do: String.contains?(path, @special_namespaces)
 | |
| 
 | |
|   defp parsed_path(relative_path, params) do
 | |
|     parts = Path.split(relative_path)
 | |
| 
 | |
|     allowed =
 | |
|       Keyword.get(params, :ignore_folder_namespace, %{})
 | |
|       |> Stream.flat_map(fn {root, folders} -> Enum.map(folders, &Path.join([root, &1])) end)
 | |
|       |> Stream.map(&Path.split/1)
 | |
|       |> Enum.find(&List.starts_with?(parts, &1))
 | |
|       |> case do
 | |
|         nil ->
 | |
|           relative_path
 | |
| 
 | |
|         ignore_parts ->
 | |
|           Stream.drop(ignore_parts, -1)
 | |
|           |> Enum.concat(Stream.drop(parts, length(ignore_parts)))
 | |
|           |> Path.join()
 | |
|       end
 | |
| 
 | |
|     %{root: hd(parts), allowed: allowed}
 | |
|   end
 | |
| 
 | |
|   defp expected_file_base(root_folder, module) do
 | |
|     {parent_namespace, module_name} = module |> Module.split() |> Enum.split(-1)
 | |
| 
 | |
|     relative_path =
 | |
|       if parent_namespace == [],
 | |
|         do: "",
 | |
|         else: parent_namespace |> Module.concat() |> Macro.underscore()
 | |
| 
 | |
|     file_name = module_name |> Module.concat() |> Macro.underscore()
 | |
| 
 | |
|     Path.join([root_folder, relative_path, file_name])
 | |
|   end
 | |
| 
 | |
|   defp error(issue_meta, module, expected_file) do
 | |
|     format_issue(issue_meta,
 | |
|       message:
 | |
|         "Mismatch between file name and main module #{inspect(module)}. " <>
 | |
|           "Expected file path to be #{expected_file}. " <>
 | |
|           "Either move the file or rename the module.",
 | |
|       line_no: 1
 | |
|     )
 | |
|   end
 | |
| end
 |