-
-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new
check_source
check to check source code as a string (#189)
Co-authored-by: Tim Austin <tim@neenjaw.com> Co-authored-by: Angelika Tyborska <angelikatyborska@fastmail.com>
- Loading branch information
1 parent
d3e3980
commit f3e1868
Showing
11 changed files
with
503 additions
and
61 deletions.
There are no files selected for viewing
Submodule elixir
updated
8 files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
defmodule ElixirAnalyzer.ExerciseTest.CheckSource do | ||
@moduledoc """ | ||
Defines a `check_source` macro that allows checking the source code | ||
""" | ||
|
||
@doc false | ||
defmacro __using__(_opts) do | ||
quote do | ||
import unquote(__MODULE__) | ||
|
||
@check_source_tests [] | ||
end | ||
end | ||
|
||
@doc """ | ||
Defines a macro which runs a boolean function on the source code. | ||
This macro then collates the block into a map structure resembling: | ||
test_data = %{ | ||
description: description, | ||
type: :actionable, | ||
comment: "message", | ||
suppress_if: {"name of other test", :fail} | ||
} | ||
and an AST for the function | ||
""" | ||
defmacro check_source(description, do: block) do | ||
:ok = validate_check_block(block) | ||
|
||
test_data = | ||
block | ||
|> walk_check_source_block() | ||
|> Map.put(:description, description) | ||
|> Map.put_new(:type, :informative) | ||
|
||
check = test_data.check | ||
|
||
test_data = | ||
test_data | ||
|> Map.delete(:check) | ||
# made into a key-val list for better quoting | ||
|> Map.to_list() | ||
|
||
unless Keyword.has_key?(test_data, :comment) do | ||
raise "Comment must be defined for each check_source test" | ||
end | ||
|
||
quote do | ||
@check_source_tests [ | ||
{unquote(test_data), unquote(Macro.escape(check))} | @check_source_tests | ||
] | ||
end | ||
end | ||
|
||
@supported_expressions [:comment, :type, :suppress_if, :check] | ||
defp validate_check_block({:__block__, _, args}) do | ||
Enum.each(args, fn {name, _, _} -> | ||
if name not in @supported_expressions do | ||
raise """ | ||
Unsupported expression `#{name}`. | ||
The macro `check_source` supports expressions: #{Enum.join(@supported_expressions, ", ")}. | ||
""" | ||
end | ||
end) | ||
end | ||
|
||
defp walk_check_source_block(block, test_data \\ %{}) do | ||
{_, test_data} = Macro.prewalk(block, test_data, &do_walk_check_source_block/2) | ||
test_data | ||
end | ||
|
||
defp do_walk_check_source_block({:comment, _, [comment]} = node, test_data) do | ||
{node, Map.put(test_data, :comment, comment)} | ||
end | ||
|
||
@supported_types ~w(essential actionable informative celebratory)a | ||
defp do_walk_check_source_block({:type, _, [type]} = node, test_data) do | ||
if type not in @supported_types do | ||
raise """ | ||
Unsupported type `#{type}`. | ||
The macro `check_source` supports the following types: #{Enum.join(@supported_types, ", ")}. | ||
""" | ||
end | ||
|
||
{node, Map.put(test_data, :type, type)} | ||
end | ||
|
||
defp do_walk_check_source_block({:suppress_if, _, [name, condition]} = node, test_data) do | ||
{node, Map.put(test_data, :suppress_if, {name, condition})} | ||
end | ||
|
||
defp do_walk_check_source_block({:check, _, [source, [do: function]]} = node, test_data) do | ||
function = {:fn, [], [{:->, [], [[source], function]}]} | ||
|
||
{node, Map.put(test_data, :check, function)} | ||
end | ||
|
||
defp do_walk_check_source_block(node, test_data) do | ||
{node, test_data} | ||
end | ||
end |
30 changes: 30 additions & 0 deletions
30
lib/elixir_analyzer/exercise_test/check_source/compiler.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
defmodule ElixirAnalyzer.ExerciseTest.CheckSource.Compiler do | ||
@moduledoc false | ||
|
||
alias ElixirAnalyzer.Comment | ||
|
||
def compile({check_source_data, check_function}, code_string) do | ||
name = Keyword.fetch!(check_source_data, :description) | ||
comment = Keyword.fetch!(check_source_data, :comment) | ||
type = Keyword.get(check_source_data, :type, :informative) | ||
suppress_if = Keyword.get(check_source_data, :suppress_if, false) | ||
|
||
test_description = | ||
Macro.escape(%Comment{ | ||
name: name, | ||
comment: comment, | ||
type: type, | ||
suppress_if: suppress_if | ||
}) | ||
|
||
quote do | ||
(fn string -> | ||
if unquote(check_function).(string) do | ||
{:pass, unquote(test_description)} | ||
else | ||
{:fail, unquote(test_description)} | ||
end | ||
end).(unquote(code_string)) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
test/elixir_analyzer/exercise_test/check_source_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
defmodule ElixirAnalyzer.ExerciseTest.CheckSourceTest do | ||
use ElixirAnalyzer.ExerciseTestCase, | ||
exercise_test_module: ElixirAnalyzer.Support.AnalyzerVerification.CheckSource | ||
|
||
test_exercise_analysis "empty module", | ||
comments: ["always return false", "didn't use multiline"] do | ||
~S""" | ||
defmodule CheckSourceVerification do | ||
end | ||
""" | ||
end | ||
|
||
test_exercise_analysis "contains integer literals", | ||
comments: [ | ||
"always return false", | ||
"used integer literal from ?a to ?z", | ||
"didn't use multiline" | ||
] do | ||
~S""" | ||
defmodule CheckSourceVerification do | ||
def foo(x) do | ||
case x do | ||
97 -> "a" | ||
98 -> "b" | ||
_ -> "z" | ||
end | ||
end | ||
end | ||
""" | ||
end | ||
|
||
test_exercise_analysis "contains integer but false positive", | ||
comments: [ | ||
"always return false", | ||
"used integer literal from ?a to ?z", | ||
"didn't use multiline" | ||
] do | ||
~S""" | ||
defmodule CheckSourceVerification do | ||
def best_version(game) do | ||
case game do | ||
"fifa" -> "fifa 98" | ||
"tomb raider" -> "tomb raider II (1997)" | ||
_ -> "goat simulator" | ||
end | ||
end | ||
end | ||
""" | ||
end | ||
|
||
test_exercise_analysis "uses multiline strings", | ||
comments: ["always return false"] do | ||
[ | ||
~S''' | ||
defmodule CheckSourceVerification do | ||
@moduledoc """ | ||
this module doesn't do much | ||
""" | ||
end | ||
''', | ||
~S''' | ||
defmodule CheckSourceVerification do | ||
def foo do | ||
""" | ||
all | ||
you | ||
need | ||
is | ||
love | ||
""" | ||
end | ||
end | ||
''', | ||
~S""" | ||
defmodule CheckSourceVerification do | ||
def foo do | ||
''' | ||
love | ||
is | ||
all | ||
you | ||
need | ||
''' | ||
end | ||
end | ||
""" | ||
] | ||
end | ||
|
||
test_exercise_analysis "short module", | ||
comments: ["always return false", "module is too short", "didn't use multiline"] do | ||
~S""" | ||
defmodule C do | ||
end | ||
""" | ||
end | ||
|
||
test_exercise_analysis "badly formatted modules", | ||
comments: ["always return false", "didn't use multiline", "module is not formatted"] do | ||
[ | ||
~S""" | ||
defmodule CheckSourceVerification do | ||
def foo(), do: :ok | ||
end | ||
""", | ||
~S""" | ||
defmodule CheckSourceVerification do | ||
def foo, do: :ok | ||
end | ||
""", | ||
~S""" | ||
defmodule CheckSourceVerification do | ||
end | ||
""", | ||
~S""" | ||
defmodule CheckSourceVerification do end | ||
""" | ||
] | ||
end | ||
end |
Oops, something went wrong.