Permalink
Browse files

Initiation ⛪️

  • Loading branch information...
rrrene committed Sep 27, 2015
0 parents commit f40e3d33f9028105a2819ea1aa07bc70eef41461
@@ -0,0 +1,10 @@
{
"files": {
"include": ["lib/**/*.{ex,exs}"],
"exclude": []
},
"rules": {
"Style.MaxLineLength": {"max_length": 100},
"Style.TrailingBlankLine": true
}
}
@@ -0,0 +1,4 @@
/_build
/deps
erl_crash.dump
*.ez
@@ -0,0 +1,12 @@
sudo: false # faster builds
language: elixir
elixir:
- 1.0.5
- 1.1.0
otp_release:
- 17.4
- 18.0
- 18.1
20 LICENSE
@@ -0,0 +1,20 @@
Copyright (c) 2015 René Föhring
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,4 @@
# Credo [![Build Status](https://travis-ci.org/rrrene/credo.svg)](https://travis-ci.org/rrrene/credo)
Credo is a wrapper around [Dogma](https://github.com/lpil/dogma) (get it?). It is intended as a playground to implement my own, idiosyncratic rules.
@@ -0,0 +1,24 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for third-
# party users, it should be done in your mix.exs file.
# Sample configuration:
#
# config :logger, :console,
# level: :info,
# format: "$date $time [$level] $metadata$message\n",
# metadata: [:user_id]
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
@@ -0,0 +1,28 @@
defmodule Credo do
@version Mix.Project.config[:version]
def run(dir, formatter) do
config = dir |> to_config
source_files = config |> Credo.Sources.find
Dogma.Formatter.start(source_files, formatter)
source_files = Credo.Rule.Runner.run(source_files, config, fn(source_file) ->
Dogma.Formatter.script(source_file, formatter)
end)
Dogma.Formatter.finish(source_files, formatter)
source_files_w_issues =
Enum.reject(source_files, &Enum.empty?(&1.errors))
|> List.flatten
if Enum.any?(source_files_w_issues) do
{:error, source_files_w_issues}
else
:ok
end
end
def version, do: @version
defp to_config(dir), do: Credo.Config.read_or_default(dir)
end
@@ -0,0 +1,26 @@
defmodule Credo.CLI do
def main(argv) do
case run(argv) do
:ok -> System.halt(0)
{:error, _} -> System.halt(1)
end
end
defp run(argv) do
{dir, formatter} = parse_options(argv)
Credo.run(dir, formatter)
end
defp parse_options(argv) do
switches = [format: :string]
{switches, files, []} = OptionParser.parse(argv, switches: switches)
dir = files |> List.first |> to_string
format = Keyword.get(switches, :format)
formatter = Map.get(Dogma.Formatter.formatters,
format,
Dogma.Formatter.default_formatter)
{dir, formatter}
end
end
@@ -0,0 +1,24 @@
defmodule Credo.Code do
def ast(source) do
case Code.string_to_quoted(source, line: 1) do
{:ok, ast} -> {:ok, ast}
{:error, error} -> {:error, [issue_for(error)]}
end
end
def to_lines(source) do
source
|> String.split("\n")
|> Enum.with_index
|> Enum.map(fn {line, i} -> {i + 1, line} end)
end
defp issue_for({line, error_message, _}) do
%Credo.Issue{
rule: Error,
category: :error,
message: error_message,
line: line
}
end
end
@@ -0,0 +1,65 @@
defmodule Credo.Config do
defstruct files: nil,
rules: nil
@config_filename ".credo.json"
@default_files_included ["lib/**/*.{ex,exs}"]
@default_files_excluded []
@default_rules [
{Dogma.Rule.BarePipeChainStart},
{Dogma.Rule.ComparisonToBoolean},
{Dogma.Rule.DebuggerStatement},
{Dogma.Rule.FinalCondition},
{Dogma.Rule.FinalNewline},
{Dogma.Rule.FunctionArity, max: 4},
{Dogma.Rule.FunctionName},
{Dogma.Rule.HardTabs},
{Dogma.Rule.LineLength, max_length: 80},
{Dogma.Rule.LiteralInCondition},
{Dogma.Rule.LiteralInInterpolation},
{Dogma.Rule.MatchInCondition},
{Dogma.Rule.ModuleAttributeName},
{Dogma.Rule.ModuleDoc},
{Dogma.Rule.ModuleName},
{Dogma.Rule.NegatedIfUnless},
{Dogma.Rule.PredicateName},
{Dogma.Rule.TrailingBlankLines},
{Dogma.Rule.UnlessElse},
{Dogma.Rule.VariableName},
{Dogma.Rule.WindowsLineEndings},
]
def default_rules, do: @default_rules
def read_or_default(dir) do
case File.read(Path.join(dir, @config_filename)) do
{:ok, body} -> from_json(body)
{:error, _} -> from_json
end
end
def from_json(json_string \\ "{}") do
data = Poison.decode!(json_string)
%Credo.Config{
files: files_from_json(data),
rules: rules_from_json(data)
}
end
defp files_from_json(data) do
files = data["files"] || %{}
%{
included: files["included"] || @default_files_included,
excluded: files["excluded"] || @default_files_excluded,
}
end
defp rules_from_json(data) do
case data["rules"] do
rules when is_list(rules) -> []
_ -> default_rules
end
end
end
@@ -0,0 +1,8 @@
defmodule Credo.Issue do
defstruct rule: nil,
category: nil,
message: nil,
trigger: nil,
line: nil,
column: nil
end
@@ -0,0 +1,33 @@
defmodule Credo.Rule.Runner do
def run(%Credo.SourceFile{} = source_file, config, format_callback) do
issues = run_rules(source_file, config.rules)
source_file = %Credo.SourceFile{ source_file | errors: issues }
format_callback.(source_file)
source_file
end
def run(source_files, config, format_callback) when is_list(source_files) do
source_files
|> Enum.map(
&Task.async(fn ->
run(&1, config, fn(source_file) ->
format_callback.(source_file)
end)
end)
)
|> Enum.map(&Task.await/1)
end
defp run_rules(source_file, rules) do
rules
|> Enum.map( &run_rule(&1, source_file) )
|> List.flatten
end
defp run_rule({rule}, source_file) do
run_rule({rule, []}, source_file)
end
defp run_rule({rule, custom_config}, source_file) do
rule.test(source_file, custom_config)
end
end
@@ -0,0 +1,37 @@
defmodule Credo.SourceFile do
defstruct path: nil,
source: nil,
lines: nil,
ast: nil,
valid?: nil,
errors: []
def parse(source, path) do
%Credo.SourceFile{
path: path,
source: source,
lines: source |> Credo.Code.to_lines,
} |> with_ast
end
def line_at(source_file, line_no) do
Enum.find_value(source_file.lines, fn {_line_no, line} ->
if _line_no == line_no, do: line
end)
end
def column(source_file, line_no, trigger) do
line = line_at(source_file, line_no)
{col, _} = Regex.run(~r/#{trigger}/, line, return: :index) |> List.first
col
end
defp with_ast(%Credo.SourceFile{source: source} = source_file) do
case Credo.Code.ast(source) do
{:ok, ast} ->
%Credo.SourceFile{source_file | valid?: true, ast: ast}
{:error, errors} ->
%Credo.SourceFile{source_file | valid?: false, ast: [], errors: errors}
end
end
end
@@ -0,0 +1,45 @@
defmodule Credo.Sources do
@always_excluded_dirs ~w(_build/ deps/ tmp/)
def find(%Credo.Config{files: files}) do
files.included
|> Enum.map(&find/1)
|> List.flatten
|> exclude(files.excluded)
|> to_source_files
end
def find(path) do
path
|> to_glob
|> Path.wildcard
|> Enum.reject( &String.starts_with?(&1, @always_excluded_dirs) )
end
def exclude(files, patterns \\ []) do
Enum.reject(files, &matches?(&1, patterns))
end
defp to_glob(path) do
if File.dir?(path) do
[path, "**", "*.{ex,exs}"]
|> Path.join
else
path
end
end
def to_source_files(files) do
Enum.map(files, &to_source_file(&1))
end
defp to_source_file(filename) do
filename
|> File.read!
|> Credo.SourceFile.parse(filename)
end
defp matches?(string, regexes) do
Enum.any?(regexes, &Regex.match?(&1, string))
end
end
@@ -0,0 +1,10 @@
defmodule Mix.Tasks.Credo do
use Mix.Task
@shortdoc "Statically analyse Elixir source files"
@moduledoc @shortdoc
def run(argv) do
Credo.CLI.main(argv)
end
end
26 mix.exs
@@ -0,0 +1,26 @@
defmodule Credo.Mixfile do
use Mix.Project
def project do
[
app: :credo,
version: "0.0.1-dev",
elixir: "~> 1.0",
escript: [main_module: Credo.CLI],
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps
]
end
def application do
[applications: [:logger]]
end
defp deps do
[
{:poison, "~> 1.2"},
{:dogma, "~> 0.0.7"}
]
end
end
@@ -0,0 +1,2 @@
%{"dogma": {:hex, :dogma, "0.0.7"},
"poison": {:hex, :poison, "1.5.0"}}
Oops, something went wrong.

0 comments on commit f40e3d3

Please sign in to comment.