Skip to content

Commit

Permalink
Rewrite file-sniffer analysis (#412)
Browse files Browse the repository at this point in the history
* Update submodule

* Add ideal file-sniffer analysis implementation

* Add tests for assert_call

* Show in test that special forms are local functions

* Add one more test case
  • Loading branch information
angelikatyborska authored Jan 30, 2024
1 parent 85c5356 commit e3e7768
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 177 deletions.
2 changes: 1 addition & 1 deletion elixir
Submodule elixir updated 86 files
+2 −1 .formatter.exs
+7 −7 .github/workflows/ci.yml
+67 −0 .github/workflows/no-important-files-changed.yml
+18 −0 bin/check_formatting.sh
+14 −0 bin/format_approach_snippets.sh
+1 −1 concepts/anonymous-functions/about.md
+8 −4 concepts/basics/introduction.md
+2 −1 concepts/binaries/about.md
+2 −1 concepts/binaries/introduction.md
+1 −1 concepts/bit-manipulation/introduction.md
+5 −2 concepts/booleans/about.md
+2 −0 concepts/docs/about.md
+1 −0 concepts/enum/about.md
+1 −0 concepts/exceptions/about.md
+1 −0 concepts/exceptions/introduction.md
+1 −1 concepts/file/about.md
+1 −0 concepts/io/about.md
+4 −0 concepts/keyword-lists/about.md
+2 −0 concepts/links/about.md
+6 −2 concepts/list-comprehensions/about.md
+2 −0 concepts/list-comprehensions/introduction.md
+8 −4 concepts/lists/about.md
+2 −2 concepts/maps/about.md
+0 −1 concepts/module-attributes-as-constants/introduction.md
+1 −0 concepts/multiple-clause-functions/introduction.md
+2 −0 concepts/nil/about.md
+1 −1 concepts/pipe-operator/about.md
+1 −0 concepts/pipe-operator/introduction.md
+1 −1 concepts/ranges/about.md
+1 −0 concepts/strings/about.md
+1 −0 concepts/tail-call-recursion/about.md
+1 −0 concepts/tasks/about.md
+1 −0 concepts/try-rescue-else-after/about.md
+1 −0 concepts/try-rescue-else-after/introduction.md
+2 −0 concepts/try-rescue/about.md
+2 −0 concepts/try-rescue/introduction.md
+9 −4 concepts/with/about.md
+2 −0 docs/REPRESENTER_NORMALIZATIONS.md
+6 −3 docs/TESTS.md
+1 −0 exercises/concept/basketball-website/.docs/instructions.md
+2 −2 exercises/concept/bird-count/test/bird_count_test.exs
+11 −7 exercises/concept/boutique-inventory/.docs/instructions.md
+6 −0 exercises/concept/boutique-suggestions/.docs/instructions.md
+2 −0 exercises/concept/boutique-suggestions/.docs/introduction.md
+4 −4 exercises/concept/dancing-dots/.docs/instructions.md
+3 −0 exercises/concept/date-parser/.docs/instructions.md
+2 −1 exercises/concept/file-sniffer/.docs/introduction.md
+1 −0 exercises/concept/guessing-game/.docs/introduction.md
+1 −0 exercises/concept/high-school-sweetheart/.docs/introduction.md
+0 −1 exercises/concept/high-score/.docs/introduction.md
+8 −4 exercises/concept/lasagna/.docs/introduction.md
+1 −0 exercises/concept/newsletter/.docs/instructions.md
+1 −1 exercises/concept/paint-by-number/.docs/hints.md
+2 −1 exercises/concept/remote-control-car/.docs/instructions.md
+3 −1 exercises/concept/rpn-calculator-inspection/.docs/instructions.md
+1 −0 exercises/concept/rpn-calculator-output/.docs/introduction.md
+2 −0 exercises/concept/rpn-calculator/.docs/introduction.md
+1 −1 exercises/concept/secrets/.docs/introduction.md
+1 −0 exercises/concept/stack-underflow/.docs/introduction.md
+6 −5 exercises/concept/take-a-number-deluxe/.docs/instructions.md
+2 −0 exercises/concept/take-a-number/.docs/instructions.md
+3 −0 exercises/concept/wine-cellar/.docs/instructions.md
+6 −0 exercises/practice/knapsack/.meta/tests.toml
+54 −0 exercises/practice/leap/.approaches/clauses/content.md
+4 −0 exercises/practice/leap/.approaches/clauses/snippet.txt
+36 −0 exercises/practice/leap/.approaches/config.json
+99 −0 exercises/practice/leap/.approaches/flow/content.md
+7 −0 exercises/practice/leap/.approaches/flow/snippet.txt
+83 −0 exercises/practice/leap/.approaches/introduction.md
+100 −0 exercises/practice/leap/.approaches/operators/content.md
+5 −0 exercises/practice/leap/.approaches/operators/snippet.txt
+1 −20 exercises/practice/leap/.docs/instructions.md
+16 −0 exercises/practice/leap/.docs/introduction.md
+2 −1 exercises/practice/leap/.meta/config.json
+5 −0 exercises/practice/ledger/.meta/tests.toml
+2 −2 exercises/practice/ledger/test/ledger_test.exs
+30 −14 exercises/practice/perfect-numbers/.docs/instructions.md
+1 −1 exercises/practice/pythagorean-triplet/.meta/config.json
+8 −12 exercises/practice/queen-attack/.docs/instructions.md
+15 −11 exercises/practice/raindrops/.docs/instructions.md
+3 −0 exercises/practice/raindrops/.docs/introduction.md
+1 −1 exercises/practice/spiral-matrix/.meta/config.json
+1 −1 exercises/practice/transpose/.meta/config.json
+5 −4 exercises/practice/zipper/hints/hint_3.md
+4 −1 mix.exs
+1 −0 mix.lock
2 changes: 1 addition & 1 deletion lib/elixir_analyzer/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ defmodule ElixirAnalyzer.Constants do
dna_encoding_use_tail_call_recursion: "elixir.dna-encoding.use_tail_call_recursion",

# File Sniffer Comments
file_sniffer_use_pattern_matching: "elixir.file-sniffer.use_pattern_matching",
file_sniffer_use_bitstring: "elixir.file-sniffer.use_bitstring",

# Freelancer Rates Comments
freelancer_rates_apply_discount_function_reuse:
Expand Down
154 changes: 8 additions & 146 deletions lib/elixir_analyzer/test_suite/file_sniffer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,155 +6,17 @@ defmodule ElixirAnalyzer.TestSuite.FileSniffer do
alias ElixirAnalyzer.Constants
use ElixirAnalyzer.ExerciseTest

feature "use pattern matching for bmp" do
assert_call "use <<>> to pattern match a bitstring in function body" do
type :essential
find :any
comment Constants.file_sniffer_use_pattern_matching()

form do
<<0x42, 0x4D, _ignore>>
end

form do
<<0x42, 0x4D>>
end

form do
<<0x42::8, 0x4D::8, _ignore>>
end

form do
<<0x42::8, 0x4D::8>>
end

form do
<<0x42::size(8), 0x4D::size(8), _ignore>>
end

form do
<<0x42::size(8), 0x4D::size(8)>>
end
end

feature "use pattern matching for png" do
type :essential
find :any
comment Constants.file_sniffer_use_pattern_matching()

form do
<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _ignore>>
end

form do
<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A>>
end

form do
<<0x89::8, 0x50::8, 0x4E::8, 0x47::8, 0x0D::8, 0x0A::8, 0x1A::8, 0x0A::8, _ignore>>
end

form do
<<0x89::8, 0x50::8, 0x4E::8, 0x47::8, 0x0D::8, 0x0A::8, 0x1A::8, 0x0A::8>>
end

form do
<<0x89::size(8), 0x50::size(8), 0x4E::size(8), 0x47::size(8), 0x0D::size(8), 0x0A::size(8),
0x1A::size(8), 0x0A::size(8), _ignore>>
end

form do
<<0x89::size(8), 0x50::size(8), 0x4E::size(8), 0x47::size(8), 0x0D::size(8), 0x0A::size(8),
0x1A::size(8), 0x0A::size(8)>>
end
comment Constants.file_sniffer_use_bitstring()
called_fn name: :<<>>
calling_fn module: FileSniffer, name: :type_from_binary
end

feature "use pattern matching for jpg" do
assert_call "use :: in bitstrings to specify segment type when pattern matching in function body" do
type :essential
find :any
comment Constants.file_sniffer_use_pattern_matching()

form do
<<0xFF, 0xD8, 0xFF, _ignore>>
end

form do
<<0xFF, 0xD8, 0xFF>>
end

form do
<<0xFF::8, 0xD8::8, 0xFF::8, _ignore>>
end

form do
<<0xFF::8, 0xD8::8, 0xFF::8>>
end

form do
<<0xFF::size(8), 0xD8::size(8), 0xFF::size(8), _ignore>>
end

form do
<<0xFF::size(8), 0xD8::size(8), 0xFF::size(8)>>
end
end

feature "use pattern matching for gif" do
type :essential
find :any
comment Constants.file_sniffer_use_pattern_matching()

form do
<<0x47, 0x49, 0x46, _ignore>>
end

form do
<<0x47, 0x49, 0x46>>
end

form do
<<0x47::8, 0x49::8, 0x46::8, _ignore>>
end

form do
<<0x47::8, 0x49::8, 0x46::8>>
end

form do
<<0x47::size(8), 0x49::size(8), 0x46::size(8), _ignore>>
end

form do
<<0x47::size(8), 0x49::size(8), 0x46::size(8)>>
end
end

feature "use pattern matching for exe" do
type :essential
find :any
comment Constants.file_sniffer_use_pattern_matching()

form do
<<0x7F, 0x45, 0x4C, 0x46, _ignore>>
end

form do
<<0x7F, 0x45, 0x4C, 0x46>>
end

form do
<<0x7F::8, 0x45::8, 0x4C::8, 0x46::8, _ignore>>
end

form do
<<0x7F::8, 0x45::8, 0x4C::8, 0x46::8>>
end

form do
<<0x7F::size(8), 0x45::size(8), 0x4C::size(8), 0x46::size(8), _ignore>>
end

form do
<<0x7F::size(8), 0x45::size(8), 0x4C::size(8), 0x46::size(8)>>
end
comment Constants.file_sniffer_use_bitstring()
called_fn name: :"::"
calling_fn module: FileSniffer, name: :type_from_binary
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
defmodule ElixirAnalyzer.ExerciseTest.AssertCall.FunctionHeadCallTest do
use ElixirAnalyzer.ExerciseTestCase,
exercise_test_module: ElixirAnalyzer.Support.AnalyzerVerification.AssertCall.FunctionHeadCall

test_exercise_analysis "calls in function head count", comments: [] do
[
defmodule AssertCallVerification do
def main_function(@jackpot) do
:win
end

def main_function(x) when x > 100 do
:too_much
end

def main_function([x | rest]) when is_integer(x) do
main_function(x)
end
end,
defmodule AssertCallVerification do
def main_function(@jackpot) do
:win
end

def main_function([x | rest]) when is_integer(x) and x > 100 do
:too_much
end
end,
defmodule AssertCallVerification do
def main_function(x) when x == @jackpot do
:win
end

def main_function(a, b, [x | rest]) when is_integer(x) and x > 100 do
:too_much
end
end,
defmodule AssertCallVerification do
def main_function(x) when x == @jackpot do
:win
end

def main_function(%{list: [x | rest]}) when is_integer(x) and x > 100 do
:too_much
end
end
]
end

test_exercise_analysis "indirect calls in function head count", comments: [] do
[
defmodule AssertCallVerification do
def main_function(x) do
helper_function(x)
end

def helper_function(@jackpot) do
:win
end

def helper_function([x | rest]) when is_integer(x) do
main_function(x)
end

defp helper_function(x) when x > 100 do
:too_much
end
end
]
end

test_exercise_analysis "calls from the wrong function",
comments: [
"didn't find any call to Kernel.is_integer/1 from main_function/1",
"didn't find any call to |/2 from main_function/1",
"didn't find any call to Kernel.@/1 from main_function/1"
] do
[
defmodule AssertCallVerification do
def main_function() do
nil
end

def wrong_function(@jackpot) do
:win
end

def wrong_function(x) when x > 100 do
:too_much
end

def wrong_function([x | rest]) when is_integer(x) do
wrong_function(x)
end
end
]
end

test_exercise_analysis "no calls",
comments: [
"didn't find any call to Kernel.>/2 from anywhere",
"didn't find any call to Kernel.is_integer/1 from main_function/1",
"didn't find any call to |/2 from main_function/1",
"didn't find any call to Kernel.@/1 from main_function/1"
] do
[
defmodule AssertCallVerification do
def main_function(x) when x >= 100 do
:too_much
end
end
]
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ defmodule ElixirAnalyzer.ExerciseTest.AssertCall.KernelTest do
"didn't find a call to Kernel.dbg/0",
"didn't find a call to dbg/0",
"didn't find a call to Kernel.self/0",
"didn't find a call to self/0"
"didn't find a call to self/0",
"didn't find a call to <<>>/1"
] do
defmodule AssertCallVerification do
def function() do
Expand Down Expand Up @@ -62,4 +63,29 @@ defmodule ElixirAnalyzer.ExerciseTest.AssertCall.KernelTest do
end
]
end

test_exercise_analysis "Calling <<>>/1",
comments_exclude: [
"didn't find a call to <<>>/1"
] do
[
defmodule AssertCallVerification do
def function() do
<<a, b, c>> = <<1, 2, 3>>
a
end
end,
defmodule AssertCallVerification do
def function() do
<<a, b, c>> = <<1, 2, 3>>

if b > 2 do
a + c
else
nil
end
end
end
]
end
end
Loading

0 comments on commit e3e7768

Please sign in to comment.