From 6d1eb56fa061af99826c6de6ff967f29863cf174 Mon Sep 17 00:00:00 2001 From: FraSanga Date: Tue, 16 Sep 2025 23:57:53 +0200 Subject: [PATCH 1/3] added erlang ast test in captains-log --- lib/elixir_analyzer/constants.ex | 466 +++++++++--------- .../test_suite/captains_log.ex | 125 +++-- .../test_suite/captains_log_test.exs | 224 ++++----- .../no_erlang_solution/.meta/config.json | 20 + .../no_erlang_solution/.meta/exemplar.ex | 20 + .../no_erlang_solution/expected_analysis.json | 1 + .../no_erlang_solution/lib/captains_log.ex | 20 + .../perfect_solution/.meta/config.json | 20 + .../perfect_solution/.meta/exemplar.ex | 20 + .../perfect_solution/expected_analysis.json | 1 + .../perfect_solution/lib/captains_log.ex | 20 + 11 files changed, 540 insertions(+), 397 deletions(-) create mode 100644 test_data/captains-log/no_erlang_solution/.meta/config.json create mode 100644 test_data/captains-log/no_erlang_solution/.meta/exemplar.ex create mode 100644 test_data/captains-log/no_erlang_solution/expected_analysis.json create mode 100644 test_data/captains-log/no_erlang_solution/lib/captains_log.ex create mode 100644 test_data/captains-log/perfect_solution/.meta/config.json create mode 100644 test_data/captains-log/perfect_solution/.meta/exemplar.ex create mode 100644 test_data/captains-log/perfect_solution/expected_analysis.json create mode 100644 test_data/captains-log/perfect_solution/lib/captains_log.ex diff --git a/lib/elixir_analyzer/constants.ex b/lib/elixir_analyzer/constants.ex index 19247b35..771b092d 100644 --- a/lib/elixir_analyzer/constants.ex +++ b/lib/elixir_analyzer/constants.ex @@ -1,233 +1,233 @@ -defmodule ElixirAnalyzer.Constants do - @moduledoc """ - A list of Elixir analyzer comments, in the format: - ``` - elixir.[directory].[filename] - ``` - - `[directory]` must correspond to a directory in https://github.com/exercism/website-copy/tree/main/analyzer-comments/elixir - and `[filename].md` must be a file in that directory. - """ - - @constants [ - general_feedback_request: "elixir.general.feedback_request", - - # General Error Comments - general_file_not_found: "elixir.general.file_not_found", - general_parsing_error: "elixir.general.parsing_error", - - # General Solution Error / Warning Comments - solution_use_moduledoc: "elixir.solution.use_module_doc", - solution_use_specification: "elixir.solution.use_specification", - solution_raise_fn_clause_error: "elixir.solution.raise_fn_clause_error", - solution_module_attribute_name_snake_case: "elixir.solution.module_attribute_name_snake_case", - solution_module_pascal_case: "elixir.solution.module_pascal_case", - solution_function_name_snake_case: "elixir.solution.function_name_snake_case", - solution_variable_name_snake_case: "elixir.solution.variable_name_snake_case", - solution_indentation: "elixir.solution.indentation", - solution_debug_functions: "elixir.solution.debug_functions", - solution_last_line_assignment: "elixir.solution.last_line_assignment", - solution_compiler_warnings: "elixir.solution.compiler_warnings", - solution_def_with_is: "elixir.solution.def_with_is", - solution_defguard_with_question_mark: "elixir.solution.defguard_with_question_mark", - solution_defmacro_with_is_and_question_mark: - "elixir.solution.defmacro_with_is_and_question_mark", - solution_same_as_exemplar: "elixir.solution.same_as_exemplar", - solution_list_prepend_head: "elixir.solution.list_prepend_head", - solution_function_annotation_order: "elixir.solution.function_annotation_order", - solution_no_integer_literal: "elixir.solution.no_integer_literal", - solution_boilerplate_comment: "elixir.solution.boilerplate_comment", - solution_todo_comment: "elixir.solution.todo_comment", - solution_private_helper_functions: "elixir.solution.private_helper_functions", - solution_unless_with_else: "elixir.solution.unless_with_else", - solution_use_function_capture: "elixir.solution.use_function_capture", - solution_deprecated_random_module: "elixir.solution.deprecated_random_module", - solution_no_rescue: "elixir.solution.no_rescue", - - # Concept exercises - - # Basketball Website comments - basketball_website_no_map: "elixir.basketball-website.no_map", - basketball_website_get_in: "elixir.basketball-website.get_in", - - # Bird Count Comments - bird_count_use_recursion: "elixir.bird-count.use_recursion", - - # Boutique Inventory Comments - boutique_inventory_use_enum_sort_by: "elixir.boutique-inventory.use_enum_sort_by", - boutique_inventory_use_enum_filter_or_enum_reject: - "elixir.boutique-inventory.use_enum_filter_or_enum_reject", - boutique_inventory_use_enum_map: "elixir.boutique-inventory.use_enum_map", - boutique_inventory_use_enum_reduce: "elixir.boutique-inventory.use_enum_reduce", - boutique_inventory_increase_quantity_best_function_choice: - "elixir.boutique-inventory.increase_quantity_best_function_choice", - - # Boutique Suggestions Comments - boutique_suggestions_use_list_comprehensions: - "elixir.boutique-suggestions.use_list_comprehensions", - - # Chessboard Comments - chessboard_function_reuse: "elixir.chessboard.function_reuse", - chessboard_change_codepoint_to_string_directly: - "elixir.chessboard.change_codepoint_to_string_directly", - - # Captains Log Comments - captains_log_use_enum_random: "elixir.captains-log.use_enum_random", - captains_log_do_not_use_enum_random: "elixir.captains-log.do_not_use_enum_random", - captains_log_do_not_use_rand_uniform_real: "elixir.captains-log.do_not_use_rand_uniform_real", - captains_log_use_rand_uniform: "elixir.captains-log.use_rand_uniform", - captains_log_use_io_lib: "elixir.captains-log.use_io_lib", - - # Community Garden Comments - community_garden_use_get_and_update: "elixir.community-garden.use_get_and_update", - - # Dancing Dots Comments - dancing_dots_annotate_impl_animation: "elixir.dancing-dots.annotate_impl_animation", - dancing_dots_do_not_reimplement_init: "elixir.dancing-dots.do_not_reimplement_init", - - # DNA Encoding Comments - dna_encoding_use_recursion: "elixir.dna-encoding.use_recursion", - dna_encoding_use_tail_call_recursion: "elixir.dna-encoding.use_tail_call_recursion", - - # File Sniffer Comments - file_sniffer_use_bitstring: "elixir.file-sniffer.use_bitstring", - - # Freelancer Rates Comments - freelancer_rates_apply_discount_function_reuse: - "elixir.freelancer-rates.apply_discount_function_reuse", - - # German Sysadmin Comments - german_sysadmin_no_string: "elixir.german-sysadmin.no_string", - german_sysadmin_use_case: "elixir.german-sysadmin.use_case", - - # Guessing Game Comments - guessing_game_use_default_argument: "elixir.guessing-game.use_default_argument", - guessing_game_use_multiple_clause_functions: - "elixir.guessing-game.use_multiple_clause_functions", - guessing_game_use_guards: "elixir.guessing-game.use_guards", - - # High Score Comments - high_score_use_module_attribute: "elixir.high-score.use_module_attribute", - high_score_use_default_argument_with_module_attribute: - "elixir.high-score.use_default_argument_with_module_attribute", - high_score_use_map_update: "elixir.high-score.use_map_update", - - # High School Sweetheart Comments - high_school_sweetheart_function_reuse: "elixir.high-school-sweetheart.function_reuse", - high_school_sweetheart_multiline_string: "elixir.high-school-sweetheart.multiline_string", - - # Language List Comments - language_list_do_not_use_enum: "elixir.language-list.do_not_use_enum", - - # Lasagna Comments - lasagna_function_reuse: "elixir.lasagna.function_reuse", - - # Leap Comments - leap_erlang_calendar: "elixir.leap.erlang_calendar", - - # Library Fees Comments - library_fees_function_reuse: "elixir.library-fees.function_reuse", - - # Log Level Comments - log_level_use_cond: "elixir.log-level.use_cond", - - # Name Badge Comments - name_badge_use_if: "elixir.name-badge.use_if", - - # Need For Speed Comments - need_for_speed_import_IO_with_only: "elixir.need-for-speed.import_IO_with_only", - need_for_speed_import_ANSI_with_except: "elixir.need-for-speed.import_ANSI_with_except", - need_for_speed_do_not_modify_code: "elixir.need-for-speed.do_not_modify_code", - - # Newsletter Comments - newsletter_close_log_returns_implicitly: "elixir.newsletter.close_log_returns_implicitly", - newsletter_log_sent_email_prefer_io_puts: "elixir.newsletter.log_sent_email_prefer_io_puts", - newsletter_log_sent_email_returns_implicitly: - "elixir.newsletter.log_sent_email_returns_implicitly", - newsletter_send_newsletter_returns_implicitly: - "elixir.newsletter.send_newsletter_returns_implicitly", - newsletter_open_log_uses_option_write: "elixir.newsletter.open_log_uses_option_write", - newsletter_send_newsletter_reuses_functions: - "elixir.newsletter.send_newsletter_reuses_functions", - - # New Passport Comments - new_passport_use_with: "elixir.new-passport.use_with", - new_passport_use_with_else: "elixir.new-passport.use_with_else", - new_passport_do_not_modify_code: "elixir.new-passport.do_not_modify_code", - - # Pacman Rules Comments - pacman_rules_use_strictly_boolean_operators: - "elixir.pacman-rules.use_strictly_boolean_operators", - - # Remote Control Car Comments - remote_control_car_use_default_argument: "elixir.remote-control-car.use_default_argument", - - # RPG Character Sheet - rpg_character_sheet_welcome_ends_with_IO_puts: - "elixir.rpg-character-sheet.welcome_ends_with_IO_puts", - rpg_character_sheet_run_uses_other_functions: - "elixir.rpg-character-sheet.run_uses_other_functions", - rpg_character_sheet_run_ends_with_IO_inspect: - "elixir.rpg-character-sheet.ends_with_IO_inspect", - rpg_character_sheet_IO_inspect_uses_label: "elixir.rpg-character-sheet.IO_inspect_uses_label", - - # RPN Calculator Inspection - rpn_calculator_inspection_use_start_link: "elixir.rpn-calculator-inspection.use_start_link", - - # RPN Calculator Output - rpn_calculator_output_try_rescue_else_after: - "elixir.rpn-calculator-output.try_rescue_else_after", - rpn_calculator_output_open_before_try: "elixir.rpn-calculator-output.open_before_try", - rpn_calculator_output_write_in_try: "elixir.rpn-calculator-output.write_in_try", - rpn_calculator_output_output_in_else: "elixir.rpn-calculator-output.output_in_else", - rpn_calculator_output_close_in_after: "elixir.rpn-calculator-output.close_in_after", - - # Take A Number Comments - take_a_number_do_not_use_abstractions: "elixir.take-a-number.do_not_use_abstractions", - - # Take A Number Deluxe Comments - take_a_number_deluxe_use_genserver: "elixir.take-a-number-deluxe.use_genserver", - take_a_number_deluxe_annotate_impl_genserver: - "elixir.take-a-number-deluxe.annotate_impl_genserver", - - # Top Secret Comments - top_secret_function_reuse: "elixir.top-secret.function_reuse", - - # Wine Cellar Comments - wine_cellar_use_keyword_get_values: "elixir.wine-cellar.use_keyword_get_values", - - # Practice exercises - - # Accumulate Comments - accumulate_use_recursion: "elixir.accumulate.use_recursion", - - # List Ops Comments - list_ops_do_not_use_list_functions: "elixir.list-ops.do_not_use_list_functions", - - # Sieve Comments - sieve_do_not_use_div_rem: "elixir.sieve.do_not_use_div_rem", - # - # Strain Comments - strain_use_recursion: "elixir.strain.use_recursion", - - # Square Root Comments - square_root_do_not_use_built_in_sqrt: "elixir.square-root.do_not_use_built_in_sqrt", - - # Two-fer Error Comments - two_fer_use_default_parameter: "elixir.two-fer.use_default_param", - two_fer_use_guards: "elixir.two-fer.use_guards", - two_fer_use_string_interpolation: "elixir.two-fer.use_string_interpolation", - two_fer_wrong_specification: "elixir.two-fer.wrong_specification", - two_fer_use_function_level_guard: "elixir.two-fer.use_function_level_guard", - two_fer_use_of_aux_functions: "elixir.two-fer.use_of_aux_functions", - two_fer_use_of_function_header: "elixir.two-fer.use_of_function_header" - ] - - for {constant, markdown} <- @constants do - def unquote(constant)(), do: unquote(markdown) - end - - def list_of_all_comments() do - Enum.map(@constants, &Kernel.elem(&1, 1)) - end -end +defmodule ElixirAnalyzer.Constants do + @moduledoc """ + A list of Elixir analyzer comments, in the format: + ``` + elixir.[directory].[filename] + ``` + + `[directory]` must correspond to a directory in https://github.com/exercism/website-copy/tree/main/analyzer-comments/elixir + and `[filename].md` must be a file in that directory. + """ + + @constants [ + general_feedback_request: "elixir.general.feedback_request", + + # General Error Comments + general_file_not_found: "elixir.general.file_not_found", + general_parsing_error: "elixir.general.parsing_error", + + # General Solution Error / Warning Comments + solution_use_moduledoc: "elixir.solution.use_module_doc", + solution_use_specification: "elixir.solution.use_specification", + solution_raise_fn_clause_error: "elixir.solution.raise_fn_clause_error", + solution_module_attribute_name_snake_case: "elixir.solution.module_attribute_name_snake_case", + solution_module_pascal_case: "elixir.solution.module_pascal_case", + solution_function_name_snake_case: "elixir.solution.function_name_snake_case", + solution_variable_name_snake_case: "elixir.solution.variable_name_snake_case", + solution_indentation: "elixir.solution.indentation", + solution_debug_functions: "elixir.solution.debug_functions", + solution_last_line_assignment: "elixir.solution.last_line_assignment", + solution_compiler_warnings: "elixir.solution.compiler_warnings", + solution_def_with_is: "elixir.solution.def_with_is", + solution_defguard_with_question_mark: "elixir.solution.defguard_with_question_mark", + solution_defmacro_with_is_and_question_mark: + "elixir.solution.defmacro_with_is_and_question_mark", + solution_same_as_exemplar: "elixir.solution.same_as_exemplar", + solution_list_prepend_head: "elixir.solution.list_prepend_head", + solution_function_annotation_order: "elixir.solution.function_annotation_order", + solution_no_integer_literal: "elixir.solution.no_integer_literal", + solution_boilerplate_comment: "elixir.solution.boilerplate_comment", + solution_todo_comment: "elixir.solution.todo_comment", + solution_private_helper_functions: "elixir.solution.private_helper_functions", + solution_unless_with_else: "elixir.solution.unless_with_else", + solution_use_function_capture: "elixir.solution.use_function_capture", + solution_deprecated_random_module: "elixir.solution.deprecated_random_module", + solution_no_rescue: "elixir.solution.no_rescue", + + # Concept exercises + + # Basketball Website comments + basketball_website_no_map: "elixir.basketball-website.no_map", + basketball_website_get_in: "elixir.basketball-website.get_in", + + # Bird Count Comments + bird_count_use_recursion: "elixir.bird-count.use_recursion", + + # Boutique Inventory Comments + boutique_inventory_use_enum_sort_by: "elixir.boutique-inventory.use_enum_sort_by", + boutique_inventory_use_enum_filter_or_enum_reject: + "elixir.boutique-inventory.use_enum_filter_or_enum_reject", + boutique_inventory_use_enum_map: "elixir.boutique-inventory.use_enum_map", + boutique_inventory_use_enum_reduce: "elixir.boutique-inventory.use_enum_reduce", + boutique_inventory_increase_quantity_best_function_choice: + "elixir.boutique-inventory.increase_quantity_best_function_choice", + + # Boutique Suggestions Comments + boutique_suggestions_use_list_comprehensions: + "elixir.boutique-suggestions.use_list_comprehensions", + + # Chessboard Comments + chessboard_function_reuse: "elixir.chessboard.function_reuse", + chessboard_change_codepoint_to_string_directly: + "elixir.chessboard.change_codepoint_to_string_directly", + + # Captains Log Comments + captains_log_use_enum_random: "elixir.captains-log.use_enum_random", + captains_log_do_not_use_enum_random: "elixir.captains-log.do_not_use_enum_random", + captains_log_do_not_use_rand_uniform_real: "elixir.captains-log.do_not_use_rand_uniform_real", + captains_log_use_rand_uniform: "elixir.captains-log.use_rand_uniform", + captains_log_use_erlang: "elixir.captains-log.use_erlang", + + # Community Garden Comments + community_garden_use_get_and_update: "elixir.community-garden.use_get_and_update", + + # Dancing Dots Comments + dancing_dots_annotate_impl_animation: "elixir.dancing-dots.annotate_impl_animation", + dancing_dots_do_not_reimplement_init: "elixir.dancing-dots.do_not_reimplement_init", + + # DNA Encoding Comments + dna_encoding_use_recursion: "elixir.dna-encoding.use_recursion", + dna_encoding_use_tail_call_recursion: "elixir.dna-encoding.use_tail_call_recursion", + + # File Sniffer Comments + file_sniffer_use_bitstring: "elixir.file-sniffer.use_bitstring", + + # Freelancer Rates Comments + freelancer_rates_apply_discount_function_reuse: + "elixir.freelancer-rates.apply_discount_function_reuse", + + # German Sysadmin Comments + german_sysadmin_no_string: "elixir.german-sysadmin.no_string", + german_sysadmin_use_case: "elixir.german-sysadmin.use_case", + + # Guessing Game Comments + guessing_game_use_default_argument: "elixir.guessing-game.use_default_argument", + guessing_game_use_multiple_clause_functions: + "elixir.guessing-game.use_multiple_clause_functions", + guessing_game_use_guards: "elixir.guessing-game.use_guards", + + # High Score Comments + high_score_use_module_attribute: "elixir.high-score.use_module_attribute", + high_score_use_default_argument_with_module_attribute: + "elixir.high-score.use_default_argument_with_module_attribute", + high_score_use_map_update: "elixir.high-score.use_map_update", + + # High School Sweetheart Comments + high_school_sweetheart_function_reuse: "elixir.high-school-sweetheart.function_reuse", + high_school_sweetheart_multiline_string: "elixir.high-school-sweetheart.multiline_string", + + # Language List Comments + language_list_do_not_use_enum: "elixir.language-list.do_not_use_enum", + + # Lasagna Comments + lasagna_function_reuse: "elixir.lasagna.function_reuse", + + # Leap Comments + leap_erlang_calendar: "elixir.leap.erlang_calendar", + + # Library Fees Comments + library_fees_function_reuse: "elixir.library-fees.function_reuse", + + # Log Level Comments + log_level_use_cond: "elixir.log-level.use_cond", + + # Name Badge Comments + name_badge_use_if: "elixir.name-badge.use_if", + + # Need For Speed Comments + need_for_speed_import_IO_with_only: "elixir.need-for-speed.import_IO_with_only", + need_for_speed_import_ANSI_with_except: "elixir.need-for-speed.import_ANSI_with_except", + need_for_speed_do_not_modify_code: "elixir.need-for-speed.do_not_modify_code", + + # Newsletter Comments + newsletter_close_log_returns_implicitly: "elixir.newsletter.close_log_returns_implicitly", + newsletter_log_sent_email_prefer_io_puts: "elixir.newsletter.log_sent_email_prefer_io_puts", + newsletter_log_sent_email_returns_implicitly: + "elixir.newsletter.log_sent_email_returns_implicitly", + newsletter_send_newsletter_returns_implicitly: + "elixir.newsletter.send_newsletter_returns_implicitly", + newsletter_open_log_uses_option_write: "elixir.newsletter.open_log_uses_option_write", + newsletter_send_newsletter_reuses_functions: + "elixir.newsletter.send_newsletter_reuses_functions", + + # New Passport Comments + new_passport_use_with: "elixir.new-passport.use_with", + new_passport_use_with_else: "elixir.new-passport.use_with_else", + new_passport_do_not_modify_code: "elixir.new-passport.do_not_modify_code", + + # Pacman Rules Comments + pacman_rules_use_strictly_boolean_operators: + "elixir.pacman-rules.use_strictly_boolean_operators", + + # Remote Control Car Comments + remote_control_car_use_default_argument: "elixir.remote-control-car.use_default_argument", + + # RPG Character Sheet + rpg_character_sheet_welcome_ends_with_IO_puts: + "elixir.rpg-character-sheet.welcome_ends_with_IO_puts", + rpg_character_sheet_run_uses_other_functions: + "elixir.rpg-character-sheet.run_uses_other_functions", + rpg_character_sheet_run_ends_with_IO_inspect: + "elixir.rpg-character-sheet.ends_with_IO_inspect", + rpg_character_sheet_IO_inspect_uses_label: "elixir.rpg-character-sheet.IO_inspect_uses_label", + + # RPN Calculator Inspection + rpn_calculator_inspection_use_start_link: "elixir.rpn-calculator-inspection.use_start_link", + + # RPN Calculator Output + rpn_calculator_output_try_rescue_else_after: + "elixir.rpn-calculator-output.try_rescue_else_after", + rpn_calculator_output_open_before_try: "elixir.rpn-calculator-output.open_before_try", + rpn_calculator_output_write_in_try: "elixir.rpn-calculator-output.write_in_try", + rpn_calculator_output_output_in_else: "elixir.rpn-calculator-output.output_in_else", + rpn_calculator_output_close_in_after: "elixir.rpn-calculator-output.close_in_after", + + # Take A Number Comments + take_a_number_do_not_use_abstractions: "elixir.take-a-number.do_not_use_abstractions", + + # Take A Number Deluxe Comments + take_a_number_deluxe_use_genserver: "elixir.take-a-number-deluxe.use_genserver", + take_a_number_deluxe_annotate_impl_genserver: + "elixir.take-a-number-deluxe.annotate_impl_genserver", + + # Top Secret Comments + top_secret_function_reuse: "elixir.top-secret.function_reuse", + + # Wine Cellar Comments + wine_cellar_use_keyword_get_values: "elixir.wine-cellar.use_keyword_get_values", + + # Practice exercises + + # Accumulate Comments + accumulate_use_recursion: "elixir.accumulate.use_recursion", + + # List Ops Comments + list_ops_do_not_use_list_functions: "elixir.list-ops.do_not_use_list_functions", + + # Sieve Comments + sieve_do_not_use_div_rem: "elixir.sieve.do_not_use_div_rem", + # + # Strain Comments + strain_use_recursion: "elixir.strain.use_recursion", + + # Square Root Comments + square_root_do_not_use_built_in_sqrt: "elixir.square-root.do_not_use_built_in_sqrt", + + # Two-fer Error Comments + two_fer_use_default_parameter: "elixir.two-fer.use_default_param", + two_fer_use_guards: "elixir.two-fer.use_guards", + two_fer_use_string_interpolation: "elixir.two-fer.use_string_interpolation", + two_fer_wrong_specification: "elixir.two-fer.wrong_specification", + two_fer_use_function_level_guard: "elixir.two-fer.use_function_level_guard", + two_fer_use_of_aux_functions: "elixir.two-fer.use_of_aux_functions", + two_fer_use_of_function_header: "elixir.two-fer.use_of_function_header" + ] + + for {constant, markdown} <- @constants do + def unquote(constant)(), do: unquote(markdown) + end + + def list_of_all_comments() do + Enum.map(@constants, &Kernel.elem(&1, 1)) + end +end diff --git a/lib/elixir_analyzer/test_suite/captains_log.ex b/lib/elixir_analyzer/test_suite/captains_log.ex index 05a1256e..a872532c 100644 --- a/lib/elixir_analyzer/test_suite/captains_log.ex +++ b/lib/elixir_analyzer/test_suite/captains_log.ex @@ -1,52 +1,73 @@ -defmodule ElixirAnalyzer.TestSuite.CaptainsLog do - @moduledoc """ - This is an exercise analyzer extension module for the concept exercise Captains Log - """ - use ElixirAnalyzer.ExerciseTest - alias ElixirAnalyzer.Constants - - assert_call "random_planet_class uses Enum.random" do - type :essential - calling_fn module: CaptainsLog, name: :random_planet_class - called_fn module: Enum, name: :random - comment Constants.captains_log_use_enum_random() - end - - assert_call "random_ship_registry_number uses Enum.random" do - type :essential - calling_fn module: CaptainsLog, name: :random_ship_registry_number - called_fn module: Enum, name: :random - comment Constants.captains_log_use_enum_random() - end - - assert_no_call "random_stardate does not use Enum.random" do - type :essential - calling_fn module: CaptainsLog, name: :random_stardate - called_fn module: Enum, name: :random - comment Constants.captains_log_do_not_use_enum_random() - end - - assert_no_call "random_stardate does not use :rand.uniform_real" do - type :essential - calling_fn module: CaptainsLog, name: :random_stardate - called_fn module: :rand, name: :uniform_real - comment Constants.captains_log_do_not_use_rand_uniform_real() - end - - assert_call "random_stardate uses :rand.uniform" do - type :essential - calling_fn module: CaptainsLog, name: :random_stardate - called_fn module: :rand, name: :uniform - comment Constants.captains_log_use_rand_uniform() - suppress_if Constants.solution_deprecated_random_module(), :fail - suppress_if "random_stardate does not use :rand.uniform_real", :fail - suppress_if "random_stardate does not use Enum.random", :fail - end - - assert_call "format_stardate uses :io_lib" do - type :essential - calling_fn module: CaptainsLog, name: :format_stardate - called_fn module: :io_lib, name: :_ - comment Constants.captains_log_use_io_lib() - end -end +defmodule ElixirAnalyzer.TestSuite.CaptainsLog do + @moduledoc """ + This is an exercise analyzer extension module for the concept exercise Captains Log + """ + use ElixirAnalyzer.ExerciseTest + alias ElixirAnalyzer.Constants + alias ElixirAnalyzer.Source + + assert_call "random_planet_class uses Enum.random" do + type :essential + calling_fn module: CaptainsLog, name: :random_planet_class + called_fn module: Enum, name: :random + comment Constants.captains_log_use_enum_random() + end + + assert_call "random_ship_registry_number uses Enum.random" do + type :essential + calling_fn module: CaptainsLog, name: :random_ship_registry_number + called_fn module: Enum, name: :random + comment Constants.captains_log_use_enum_random() + end + + assert_no_call "random_stardate does not use Enum.random" do + type :essential + calling_fn module: CaptainsLog, name: :random_stardate + called_fn module: Enum, name: :random + comment Constants.captains_log_do_not_use_enum_random() + end + + assert_no_call "random_stardate does not use :rand.uniform_real" do + type :essential + calling_fn module: CaptainsLog, name: :random_stardate + called_fn module: :rand, name: :uniform_real + comment Constants.captains_log_do_not_use_rand_uniform_real() + end + + assert_call "random_stardate uses :rand.uniform" do + type :essential + calling_fn module: CaptainsLog, name: :random_stardate + called_fn module: :rand, name: :uniform + comment Constants.captains_log_use_rand_uniform() + suppress_if Constants.solution_deprecated_random_module(), :fail + suppress_if "random_stardate does not use :rand.uniform_real", :fail + suppress_if "random_stardate does not use Enum.random", :fail +end + + + check_source "format_stardate uses erlang" do + type :essential + comment Constants.captains_log_use_erlang() + + check(%Source{code_ast: code_ast}) do + {_, erlang?} = + Macro.prewalk(code_ast, false, fn node, acc -> + case node do + # usage :io_lib.format/2 + {{:., _, [:io_lib, :format]}, _, _} -> + {node, true} + + # usage :erlang.function_name/arity + # matches any function call from :erlang module + {{:., _, [:erlang, _]}, _, _} -> + {node, true} + + _ -> + {node, acc} + end + end) + + erlang? + end + end +end diff --git a/test/elixir_analyzer/test_suite/captains_log_test.exs b/test/elixir_analyzer/test_suite/captains_log_test.exs index b3b783fd..f09c3e4e 100644 --- a/test/elixir_analyzer/test_suite/captains_log_test.exs +++ b/test/elixir_analyzer/test_suite/captains_log_test.exs @@ -1,112 +1,112 @@ -defmodule ElixirAnalyzer.ExerciseTest.CaptainsLogTest do - use ElixirAnalyzer.ExerciseTestCase, - exercise_test_module: ElixirAnalyzer.TestSuite.CaptainsLog - - alias ElixirAnalyzer.Constants - - test_exercise_analysis "example solution", - comments: [Constants.solution_same_as_exemplar()] do - defmodule CaptainsLog do - @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] - - def random_planet_class() do - Enum.random(@planetary_classes) - end - - def random_ship_registry_number() do - number = Enum.random(1000..9999) - "NCC-#{number}" - end - - def random_stardate() do - :rand.uniform() * 1000 + 41_000 - end - - def format_stardate(stardate) do - to_string(:io_lib.format("~.1f", [stardate])) - end - end - end - - describe "Expects random_planet_class and random_ship_registry_number to use Enum.random" do - test_exercise_analysis "random_planet_class does not use Enum.random", - comments_include: [Constants.captains_log_use_enum_random()] do - defmodule CaptainsLog do - @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] - - def random_planet_class() do - @planetary_classes - |> Enum.shuffle() - |> hd - end - end - end - - test_exercise_analysis "random_ship_registry_number does not use Enum.random", - comments_include: [Constants.captains_log_use_enum_random()] do - defmodule CaptainsLog do - def random_ship_registry_number() do - number = - 1000..9999 - |> Enum.shuffle() - |> hd - - "NCC-#{number}" - end - end - end - end - - describe "random_stardate uses :rand.uniform" do - test_exercise_analysis "when using Enum.random instead", - comments_include: [ - Constants.captains_log_do_not_use_enum_random() - ], - comments_exclude: [ - Constants.captains_log_use_rand_uniform() - ] do - defmodule CaptainsLog do - def random_stardate do - Enum.random(4_100_000..4_200_000) |> Kernel./(100) - end - end - end - - test_exercise_analysis "when using :rand.uniform_real instead", - comments_include: [ - Constants.captains_log_do_not_use_rand_uniform_real() - ], - comments_exclude: [ - Constants.captains_log_use_rand_uniform() - ] do - defmodule CaptainsLog do - def random_stardate do - :rand.uniform_real() * 1000 + 41_000 - end - end - end - - test_exercise_analysis "when using deprecated :random module", - comments_include: [Constants.solution_deprecated_random_module()], - comments_exclude: [Constants.captains_log_use_rand_uniform()] do - defmodule CaptainsLog do - def random_stardate() do - :random.uniform() * 1000 + 41_000 - end - end - end - end - - test_exercise_analysis "format_stardate uses Float.round", - comments_include: [Constants.captains_log_use_io_lib()] do - defmodule CaptainsLog do - def format_stardate(stardate) do - if is_float(stardate) do - Float.round(stardate, 1) |> to_string() - else - raise ArgumentError - end - end - end - end -end +defmodule ElixirAnalyzer.ExerciseTest.CaptainsLogTest do + use ElixirAnalyzer.ExerciseTestCase, + exercise_test_module: ElixirAnalyzer.TestSuite.CaptainsLog + + alias ElixirAnalyzer.Constants + + test_exercise_analysis "example solution", + comments: [Constants.solution_same_as_exemplar()] do + defmodule CaptainsLog do + @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] + + def random_planet_class() do + Enum.random(@planetary_classes) + end + + def random_ship_registry_number() do + number = Enum.random(1000..9999) + "NCC-#{number}" + end + + def random_stardate() do + :rand.uniform() * 1000 + 41_000 + end + + def format_stardate(stardate) do + to_string(:io_lib.format("~.1f", [stardate])) + end + end + end + + describe "Expects random_planet_class and random_ship_registry_number to use Enum.random" do + test_exercise_analysis "random_planet_class does not use Enum.random", + comments_include: [Constants.captains_log_use_enum_random()] do + defmodule CaptainsLog do + @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] + + def random_planet_class() do + @planetary_classes + |> Enum.shuffle() + |> hd + end + end + end + + test_exercise_analysis "random_ship_registry_number does not use Enum.random", + comments_include: [Constants.captains_log_use_enum_random()] do + defmodule CaptainsLog do + def random_ship_registry_number() do + number = + 1000..9999 + |> Enum.shuffle() + |> hd + + "NCC-#{number}" + end + end + end + end + + describe "random_stardate uses :rand.uniform" do + test_exercise_analysis "when using Enum.random instead", + comments_include: [ + Constants.captains_log_do_not_use_enum_random() + ], + comments_exclude: [ + Constants.captains_log_use_rand_uniform() + ] do + defmodule CaptainsLog do + def random_stardate do + Enum.random(4_100_000..4_200_000) |> Kernel./(100) + end + end + end + + test_exercise_analysis "when using :rand.uniform_real instead", + comments_include: [ + Constants.captains_log_do_not_use_rand_uniform_real() + ], + comments_exclude: [ + Constants.captains_log_use_rand_uniform() + ] do + defmodule CaptainsLog do + def random_stardate do + :rand.uniform_real() * 1000 + 41_000 + end + end + end + + test_exercise_analysis "when using deprecated :random module", + comments_include: [Constants.solution_deprecated_random_module()], + comments_exclude: [Constants.captains_log_use_rand_uniform()] do + defmodule CaptainsLog do + def random_stardate() do + :random.uniform() * 1000 + 41_000 + end + end + end + end + + test_exercise_analysis "format_stardate uses Float.round", + comments_include: [Constants.captains_log_use_erlang()] do + defmodule CaptainsLog do + def format_stardate(stardate) do + if is_float(stardate) do + Float.round(stardate, 1) |> to_string() + else + raise ArgumentError + end + end + end + end +end diff --git a/test_data/captains-log/no_erlang_solution/.meta/config.json b/test_data/captains-log/no_erlang_solution/.meta/config.json new file mode 100644 index 00000000..d279010d --- /dev/null +++ b/test_data/captains-log/no_erlang_solution/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "angelikatyborska" + ], + "contributors": [ + "neenjaw" + ], + "files": { + "solution": [ + "lib/captains_log.ex" + ], + "test": [ + "test/captains_log_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "blurb": "Learn about randomness and using Erlang libraries from Elixir by helping Mary generate stardates and starship registry numbers for her Star Trek themed pen-and-paper role playing sessions." +} \ No newline at end of file diff --git a/test_data/captains-log/no_erlang_solution/.meta/exemplar.ex b/test_data/captains-log/no_erlang_solution/.meta/exemplar.ex new file mode 100644 index 00000000..faaff587 --- /dev/null +++ b/test_data/captains-log/no_erlang_solution/.meta/exemplar.ex @@ -0,0 +1,20 @@ +defmodule CaptainsLog do + @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] + + def random_planet_class() do + Enum.random(@planetary_classes) + end + + def random_ship_registry_number() do + number = Enum.random(1000..9999) + "NCC-#{number}" + end + + def random_stardate() do + :rand.uniform() * 1000 + 41_000 + end + + def format_stardate(stardate) do + to_string(:io_lib.format("~.1f", [stardate])) + end +end diff --git a/test_data/captains-log/no_erlang_solution/expected_analysis.json b/test_data/captains-log/no_erlang_solution/expected_analysis.json new file mode 100644 index 00000000..b8b95c54 --- /dev/null +++ b/test_data/captains-log/no_erlang_solution/expected_analysis.json @@ -0,0 +1 @@ +{"summary":"Check the comments for things to fix. 🛠","comments":[{"type":"essential","comment":"elixir.captains-log.use_erlang"},{"type":"informative","params":{"mentoring_request_url":"https://exercism.org/tracks/elixir/exercises/captains-log/mentor_discussions"},"comment":"elixir.general.feedback_request"}]} \ No newline at end of file diff --git a/test_data/captains-log/no_erlang_solution/lib/captains_log.ex b/test_data/captains-log/no_erlang_solution/lib/captains_log.ex new file mode 100644 index 00000000..4666af3a --- /dev/null +++ b/test_data/captains-log/no_erlang_solution/lib/captains_log.ex @@ -0,0 +1,20 @@ +defmodule CaptainsLog do + @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] + + def random_planet_class() do + Enum.random(@planetary_classes) + end + + def random_ship_registry_number() do + number = Enum.random(1000..9999) + "NCC-#{number}" + end + + def random_stardate() do + :rand.uniform() * 1000 + 41_000 + end + + def format_stardate(stardate) do + Float.round(stardate, 1) |> to_string() + end +end diff --git a/test_data/captains-log/perfect_solution/.meta/config.json b/test_data/captains-log/perfect_solution/.meta/config.json new file mode 100644 index 00000000..d279010d --- /dev/null +++ b/test_data/captains-log/perfect_solution/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "angelikatyborska" + ], + "contributors": [ + "neenjaw" + ], + "files": { + "solution": [ + "lib/captains_log.ex" + ], + "test": [ + "test/captains_log_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "blurb": "Learn about randomness and using Erlang libraries from Elixir by helping Mary generate stardates and starship registry numbers for her Star Trek themed pen-and-paper role playing sessions." +} \ No newline at end of file diff --git a/test_data/captains-log/perfect_solution/.meta/exemplar.ex b/test_data/captains-log/perfect_solution/.meta/exemplar.ex new file mode 100644 index 00000000..faaff587 --- /dev/null +++ b/test_data/captains-log/perfect_solution/.meta/exemplar.ex @@ -0,0 +1,20 @@ +defmodule CaptainsLog do + @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] + + def random_planet_class() do + Enum.random(@planetary_classes) + end + + def random_ship_registry_number() do + number = Enum.random(1000..9999) + "NCC-#{number}" + end + + def random_stardate() do + :rand.uniform() * 1000 + 41_000 + end + + def format_stardate(stardate) do + to_string(:io_lib.format("~.1f", [stardate])) + end +end diff --git a/test_data/captains-log/perfect_solution/expected_analysis.json b/test_data/captains-log/perfect_solution/expected_analysis.json new file mode 100644 index 00000000..9dfbf803 --- /dev/null +++ b/test_data/captains-log/perfect_solution/expected_analysis.json @@ -0,0 +1 @@ +{"comments":[],"summary":"Submission analyzed. No automated suggestions found."} diff --git a/test_data/captains-log/perfect_solution/lib/captains_log.ex b/test_data/captains-log/perfect_solution/lib/captains_log.ex new file mode 100644 index 00000000..86678ca8 --- /dev/null +++ b/test_data/captains-log/perfect_solution/lib/captains_log.ex @@ -0,0 +1,20 @@ +defmodule CaptainsLog do + @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] + + def random_planet_class() do + Enum.random(@planetary_classes) + end + + def random_ship_registry_number() do + number = Enum.random(1000..9999) + "NCC-#{number}" + end + + def random_stardate() do + :rand.uniform() * 1000 + 41_000 + end + + def format_stardate(stardate) do + :io_lib.format("~.1f", [stardate]) |> to_string() + end +end From 1a5b13507d7e98c3192f26ec0365ddf3eb99dcdb Mon Sep 17 00:00:00 2001 From: FraSanga Date: Sat, 27 Sep 2025 13:36:26 +0200 Subject: [PATCH 2/3] crlf to lf (end of line) --- lib/elixir_analyzer/constants.ex | 466 +++++++++--------- .../test_suite/captains_log.ex | 146 +++--- .../test_suite/captains_log_test.exs | 224 ++++----- 3 files changed, 418 insertions(+), 418 deletions(-) diff --git a/lib/elixir_analyzer/constants.ex b/lib/elixir_analyzer/constants.ex index 771b092d..5496a545 100644 --- a/lib/elixir_analyzer/constants.ex +++ b/lib/elixir_analyzer/constants.ex @@ -1,233 +1,233 @@ -defmodule ElixirAnalyzer.Constants do - @moduledoc """ - A list of Elixir analyzer comments, in the format: - ``` - elixir.[directory].[filename] - ``` - - `[directory]` must correspond to a directory in https://github.com/exercism/website-copy/tree/main/analyzer-comments/elixir - and `[filename].md` must be a file in that directory. - """ - - @constants [ - general_feedback_request: "elixir.general.feedback_request", - - # General Error Comments - general_file_not_found: "elixir.general.file_not_found", - general_parsing_error: "elixir.general.parsing_error", - - # General Solution Error / Warning Comments - solution_use_moduledoc: "elixir.solution.use_module_doc", - solution_use_specification: "elixir.solution.use_specification", - solution_raise_fn_clause_error: "elixir.solution.raise_fn_clause_error", - solution_module_attribute_name_snake_case: "elixir.solution.module_attribute_name_snake_case", - solution_module_pascal_case: "elixir.solution.module_pascal_case", - solution_function_name_snake_case: "elixir.solution.function_name_snake_case", - solution_variable_name_snake_case: "elixir.solution.variable_name_snake_case", - solution_indentation: "elixir.solution.indentation", - solution_debug_functions: "elixir.solution.debug_functions", - solution_last_line_assignment: "elixir.solution.last_line_assignment", - solution_compiler_warnings: "elixir.solution.compiler_warnings", - solution_def_with_is: "elixir.solution.def_with_is", - solution_defguard_with_question_mark: "elixir.solution.defguard_with_question_mark", - solution_defmacro_with_is_and_question_mark: - "elixir.solution.defmacro_with_is_and_question_mark", - solution_same_as_exemplar: "elixir.solution.same_as_exemplar", - solution_list_prepend_head: "elixir.solution.list_prepend_head", - solution_function_annotation_order: "elixir.solution.function_annotation_order", - solution_no_integer_literal: "elixir.solution.no_integer_literal", - solution_boilerplate_comment: "elixir.solution.boilerplate_comment", - solution_todo_comment: "elixir.solution.todo_comment", - solution_private_helper_functions: "elixir.solution.private_helper_functions", - solution_unless_with_else: "elixir.solution.unless_with_else", - solution_use_function_capture: "elixir.solution.use_function_capture", - solution_deprecated_random_module: "elixir.solution.deprecated_random_module", - solution_no_rescue: "elixir.solution.no_rescue", - - # Concept exercises - - # Basketball Website comments - basketball_website_no_map: "elixir.basketball-website.no_map", - basketball_website_get_in: "elixir.basketball-website.get_in", - - # Bird Count Comments - bird_count_use_recursion: "elixir.bird-count.use_recursion", - - # Boutique Inventory Comments - boutique_inventory_use_enum_sort_by: "elixir.boutique-inventory.use_enum_sort_by", - boutique_inventory_use_enum_filter_or_enum_reject: - "elixir.boutique-inventory.use_enum_filter_or_enum_reject", - boutique_inventory_use_enum_map: "elixir.boutique-inventory.use_enum_map", - boutique_inventory_use_enum_reduce: "elixir.boutique-inventory.use_enum_reduce", - boutique_inventory_increase_quantity_best_function_choice: - "elixir.boutique-inventory.increase_quantity_best_function_choice", - - # Boutique Suggestions Comments - boutique_suggestions_use_list_comprehensions: - "elixir.boutique-suggestions.use_list_comprehensions", - - # Chessboard Comments - chessboard_function_reuse: "elixir.chessboard.function_reuse", - chessboard_change_codepoint_to_string_directly: - "elixir.chessboard.change_codepoint_to_string_directly", - - # Captains Log Comments - captains_log_use_enum_random: "elixir.captains-log.use_enum_random", - captains_log_do_not_use_enum_random: "elixir.captains-log.do_not_use_enum_random", - captains_log_do_not_use_rand_uniform_real: "elixir.captains-log.do_not_use_rand_uniform_real", - captains_log_use_rand_uniform: "elixir.captains-log.use_rand_uniform", - captains_log_use_erlang: "elixir.captains-log.use_erlang", - - # Community Garden Comments - community_garden_use_get_and_update: "elixir.community-garden.use_get_and_update", - - # Dancing Dots Comments - dancing_dots_annotate_impl_animation: "elixir.dancing-dots.annotate_impl_animation", - dancing_dots_do_not_reimplement_init: "elixir.dancing-dots.do_not_reimplement_init", - - # DNA Encoding Comments - dna_encoding_use_recursion: "elixir.dna-encoding.use_recursion", - dna_encoding_use_tail_call_recursion: "elixir.dna-encoding.use_tail_call_recursion", - - # File Sniffer Comments - file_sniffer_use_bitstring: "elixir.file-sniffer.use_bitstring", - - # Freelancer Rates Comments - freelancer_rates_apply_discount_function_reuse: - "elixir.freelancer-rates.apply_discount_function_reuse", - - # German Sysadmin Comments - german_sysadmin_no_string: "elixir.german-sysadmin.no_string", - german_sysadmin_use_case: "elixir.german-sysadmin.use_case", - - # Guessing Game Comments - guessing_game_use_default_argument: "elixir.guessing-game.use_default_argument", - guessing_game_use_multiple_clause_functions: - "elixir.guessing-game.use_multiple_clause_functions", - guessing_game_use_guards: "elixir.guessing-game.use_guards", - - # High Score Comments - high_score_use_module_attribute: "elixir.high-score.use_module_attribute", - high_score_use_default_argument_with_module_attribute: - "elixir.high-score.use_default_argument_with_module_attribute", - high_score_use_map_update: "elixir.high-score.use_map_update", - - # High School Sweetheart Comments - high_school_sweetheart_function_reuse: "elixir.high-school-sweetheart.function_reuse", - high_school_sweetheart_multiline_string: "elixir.high-school-sweetheart.multiline_string", - - # Language List Comments - language_list_do_not_use_enum: "elixir.language-list.do_not_use_enum", - - # Lasagna Comments - lasagna_function_reuse: "elixir.lasagna.function_reuse", - - # Leap Comments - leap_erlang_calendar: "elixir.leap.erlang_calendar", - - # Library Fees Comments - library_fees_function_reuse: "elixir.library-fees.function_reuse", - - # Log Level Comments - log_level_use_cond: "elixir.log-level.use_cond", - - # Name Badge Comments - name_badge_use_if: "elixir.name-badge.use_if", - - # Need For Speed Comments - need_for_speed_import_IO_with_only: "elixir.need-for-speed.import_IO_with_only", - need_for_speed_import_ANSI_with_except: "elixir.need-for-speed.import_ANSI_with_except", - need_for_speed_do_not_modify_code: "elixir.need-for-speed.do_not_modify_code", - - # Newsletter Comments - newsletter_close_log_returns_implicitly: "elixir.newsletter.close_log_returns_implicitly", - newsletter_log_sent_email_prefer_io_puts: "elixir.newsletter.log_sent_email_prefer_io_puts", - newsletter_log_sent_email_returns_implicitly: - "elixir.newsletter.log_sent_email_returns_implicitly", - newsletter_send_newsletter_returns_implicitly: - "elixir.newsletter.send_newsletter_returns_implicitly", - newsletter_open_log_uses_option_write: "elixir.newsletter.open_log_uses_option_write", - newsletter_send_newsletter_reuses_functions: - "elixir.newsletter.send_newsletter_reuses_functions", - - # New Passport Comments - new_passport_use_with: "elixir.new-passport.use_with", - new_passport_use_with_else: "elixir.new-passport.use_with_else", - new_passport_do_not_modify_code: "elixir.new-passport.do_not_modify_code", - - # Pacman Rules Comments - pacman_rules_use_strictly_boolean_operators: - "elixir.pacman-rules.use_strictly_boolean_operators", - - # Remote Control Car Comments - remote_control_car_use_default_argument: "elixir.remote-control-car.use_default_argument", - - # RPG Character Sheet - rpg_character_sheet_welcome_ends_with_IO_puts: - "elixir.rpg-character-sheet.welcome_ends_with_IO_puts", - rpg_character_sheet_run_uses_other_functions: - "elixir.rpg-character-sheet.run_uses_other_functions", - rpg_character_sheet_run_ends_with_IO_inspect: - "elixir.rpg-character-sheet.ends_with_IO_inspect", - rpg_character_sheet_IO_inspect_uses_label: "elixir.rpg-character-sheet.IO_inspect_uses_label", - - # RPN Calculator Inspection - rpn_calculator_inspection_use_start_link: "elixir.rpn-calculator-inspection.use_start_link", - - # RPN Calculator Output - rpn_calculator_output_try_rescue_else_after: - "elixir.rpn-calculator-output.try_rescue_else_after", - rpn_calculator_output_open_before_try: "elixir.rpn-calculator-output.open_before_try", - rpn_calculator_output_write_in_try: "elixir.rpn-calculator-output.write_in_try", - rpn_calculator_output_output_in_else: "elixir.rpn-calculator-output.output_in_else", - rpn_calculator_output_close_in_after: "elixir.rpn-calculator-output.close_in_after", - - # Take A Number Comments - take_a_number_do_not_use_abstractions: "elixir.take-a-number.do_not_use_abstractions", - - # Take A Number Deluxe Comments - take_a_number_deluxe_use_genserver: "elixir.take-a-number-deluxe.use_genserver", - take_a_number_deluxe_annotate_impl_genserver: - "elixir.take-a-number-deluxe.annotate_impl_genserver", - - # Top Secret Comments - top_secret_function_reuse: "elixir.top-secret.function_reuse", - - # Wine Cellar Comments - wine_cellar_use_keyword_get_values: "elixir.wine-cellar.use_keyword_get_values", - - # Practice exercises - - # Accumulate Comments - accumulate_use_recursion: "elixir.accumulate.use_recursion", - - # List Ops Comments - list_ops_do_not_use_list_functions: "elixir.list-ops.do_not_use_list_functions", - - # Sieve Comments - sieve_do_not_use_div_rem: "elixir.sieve.do_not_use_div_rem", - # - # Strain Comments - strain_use_recursion: "elixir.strain.use_recursion", - - # Square Root Comments - square_root_do_not_use_built_in_sqrt: "elixir.square-root.do_not_use_built_in_sqrt", - - # Two-fer Error Comments - two_fer_use_default_parameter: "elixir.two-fer.use_default_param", - two_fer_use_guards: "elixir.two-fer.use_guards", - two_fer_use_string_interpolation: "elixir.two-fer.use_string_interpolation", - two_fer_wrong_specification: "elixir.two-fer.wrong_specification", - two_fer_use_function_level_guard: "elixir.two-fer.use_function_level_guard", - two_fer_use_of_aux_functions: "elixir.two-fer.use_of_aux_functions", - two_fer_use_of_function_header: "elixir.two-fer.use_of_function_header" - ] - - for {constant, markdown} <- @constants do - def unquote(constant)(), do: unquote(markdown) - end - - def list_of_all_comments() do - Enum.map(@constants, &Kernel.elem(&1, 1)) - end -end +defmodule ElixirAnalyzer.Constants do + @moduledoc """ + A list of Elixir analyzer comments, in the format: + ``` + elixir.[directory].[filename] + ``` + + `[directory]` must correspond to a directory in https://github.com/exercism/website-copy/tree/main/analyzer-comments/elixir + and `[filename].md` must be a file in that directory. + """ + + @constants [ + general_feedback_request: "elixir.general.feedback_request", + + # General Error Comments + general_file_not_found: "elixir.general.file_not_found", + general_parsing_error: "elixir.general.parsing_error", + + # General Solution Error / Warning Comments + solution_use_moduledoc: "elixir.solution.use_module_doc", + solution_use_specification: "elixir.solution.use_specification", + solution_raise_fn_clause_error: "elixir.solution.raise_fn_clause_error", + solution_module_attribute_name_snake_case: "elixir.solution.module_attribute_name_snake_case", + solution_module_pascal_case: "elixir.solution.module_pascal_case", + solution_function_name_snake_case: "elixir.solution.function_name_snake_case", + solution_variable_name_snake_case: "elixir.solution.variable_name_snake_case", + solution_indentation: "elixir.solution.indentation", + solution_debug_functions: "elixir.solution.debug_functions", + solution_last_line_assignment: "elixir.solution.last_line_assignment", + solution_compiler_warnings: "elixir.solution.compiler_warnings", + solution_def_with_is: "elixir.solution.def_with_is", + solution_defguard_with_question_mark: "elixir.solution.defguard_with_question_mark", + solution_defmacro_with_is_and_question_mark: + "elixir.solution.defmacro_with_is_and_question_mark", + solution_same_as_exemplar: "elixir.solution.same_as_exemplar", + solution_list_prepend_head: "elixir.solution.list_prepend_head", + solution_function_annotation_order: "elixir.solution.function_annotation_order", + solution_no_integer_literal: "elixir.solution.no_integer_literal", + solution_boilerplate_comment: "elixir.solution.boilerplate_comment", + solution_todo_comment: "elixir.solution.todo_comment", + solution_private_helper_functions: "elixir.solution.private_helper_functions", + solution_unless_with_else: "elixir.solution.unless_with_else", + solution_use_function_capture: "elixir.solution.use_function_capture", + solution_deprecated_random_module: "elixir.solution.deprecated_random_module", + solution_no_rescue: "elixir.solution.no_rescue", + + # Concept exercises + + # Basketball Website comments + basketball_website_no_map: "elixir.basketball-website.no_map", + basketball_website_get_in: "elixir.basketball-website.get_in", + + # Bird Count Comments + bird_count_use_recursion: "elixir.bird-count.use_recursion", + + # Boutique Inventory Comments + boutique_inventory_use_enum_sort_by: "elixir.boutique-inventory.use_enum_sort_by", + boutique_inventory_use_enum_filter_or_enum_reject: + "elixir.boutique-inventory.use_enum_filter_or_enum_reject", + boutique_inventory_use_enum_map: "elixir.boutique-inventory.use_enum_map", + boutique_inventory_use_enum_reduce: "elixir.boutique-inventory.use_enum_reduce", + boutique_inventory_increase_quantity_best_function_choice: + "elixir.boutique-inventory.increase_quantity_best_function_choice", + + # Boutique Suggestions Comments + boutique_suggestions_use_list_comprehensions: + "elixir.boutique-suggestions.use_list_comprehensions", + + # Chessboard Comments + chessboard_function_reuse: "elixir.chessboard.function_reuse", + chessboard_change_codepoint_to_string_directly: + "elixir.chessboard.change_codepoint_to_string_directly", + + # Captains Log Comments + captains_log_use_enum_random: "elixir.captains-log.use_enum_random", + captains_log_do_not_use_enum_random: "elixir.captains-log.do_not_use_enum_random", + captains_log_do_not_use_rand_uniform_real: "elixir.captains-log.do_not_use_rand_uniform_real", + captains_log_use_rand_uniform: "elixir.captains-log.use_rand_uniform", + captains_log_use_erlang: "elixir.captains-log.use_erlang", + + # Community Garden Comments + community_garden_use_get_and_update: "elixir.community-garden.use_get_and_update", + + # Dancing Dots Comments + dancing_dots_annotate_impl_animation: "elixir.dancing-dots.annotate_impl_animation", + dancing_dots_do_not_reimplement_init: "elixir.dancing-dots.do_not_reimplement_init", + + # DNA Encoding Comments + dna_encoding_use_recursion: "elixir.dna-encoding.use_recursion", + dna_encoding_use_tail_call_recursion: "elixir.dna-encoding.use_tail_call_recursion", + + # File Sniffer Comments + file_sniffer_use_bitstring: "elixir.file-sniffer.use_bitstring", + + # Freelancer Rates Comments + freelancer_rates_apply_discount_function_reuse: + "elixir.freelancer-rates.apply_discount_function_reuse", + + # German Sysadmin Comments + german_sysadmin_no_string: "elixir.german-sysadmin.no_string", + german_sysadmin_use_case: "elixir.german-sysadmin.use_case", + + # Guessing Game Comments + guessing_game_use_default_argument: "elixir.guessing-game.use_default_argument", + guessing_game_use_multiple_clause_functions: + "elixir.guessing-game.use_multiple_clause_functions", + guessing_game_use_guards: "elixir.guessing-game.use_guards", + + # High Score Comments + high_score_use_module_attribute: "elixir.high-score.use_module_attribute", + high_score_use_default_argument_with_module_attribute: + "elixir.high-score.use_default_argument_with_module_attribute", + high_score_use_map_update: "elixir.high-score.use_map_update", + + # High School Sweetheart Comments + high_school_sweetheart_function_reuse: "elixir.high-school-sweetheart.function_reuse", + high_school_sweetheart_multiline_string: "elixir.high-school-sweetheart.multiline_string", + + # Language List Comments + language_list_do_not_use_enum: "elixir.language-list.do_not_use_enum", + + # Lasagna Comments + lasagna_function_reuse: "elixir.lasagna.function_reuse", + + # Leap Comments + leap_erlang_calendar: "elixir.leap.erlang_calendar", + + # Library Fees Comments + library_fees_function_reuse: "elixir.library-fees.function_reuse", + + # Log Level Comments + log_level_use_cond: "elixir.log-level.use_cond", + + # Name Badge Comments + name_badge_use_if: "elixir.name-badge.use_if", + + # Need For Speed Comments + need_for_speed_import_IO_with_only: "elixir.need-for-speed.import_IO_with_only", + need_for_speed_import_ANSI_with_except: "elixir.need-for-speed.import_ANSI_with_except", + need_for_speed_do_not_modify_code: "elixir.need-for-speed.do_not_modify_code", + + # Newsletter Comments + newsletter_close_log_returns_implicitly: "elixir.newsletter.close_log_returns_implicitly", + newsletter_log_sent_email_prefer_io_puts: "elixir.newsletter.log_sent_email_prefer_io_puts", + newsletter_log_sent_email_returns_implicitly: + "elixir.newsletter.log_sent_email_returns_implicitly", + newsletter_send_newsletter_returns_implicitly: + "elixir.newsletter.send_newsletter_returns_implicitly", + newsletter_open_log_uses_option_write: "elixir.newsletter.open_log_uses_option_write", + newsletter_send_newsletter_reuses_functions: + "elixir.newsletter.send_newsletter_reuses_functions", + + # New Passport Comments + new_passport_use_with: "elixir.new-passport.use_with", + new_passport_use_with_else: "elixir.new-passport.use_with_else", + new_passport_do_not_modify_code: "elixir.new-passport.do_not_modify_code", + + # Pacman Rules Comments + pacman_rules_use_strictly_boolean_operators: + "elixir.pacman-rules.use_strictly_boolean_operators", + + # Remote Control Car Comments + remote_control_car_use_default_argument: "elixir.remote-control-car.use_default_argument", + + # RPG Character Sheet + rpg_character_sheet_welcome_ends_with_IO_puts: + "elixir.rpg-character-sheet.welcome_ends_with_IO_puts", + rpg_character_sheet_run_uses_other_functions: + "elixir.rpg-character-sheet.run_uses_other_functions", + rpg_character_sheet_run_ends_with_IO_inspect: + "elixir.rpg-character-sheet.ends_with_IO_inspect", + rpg_character_sheet_IO_inspect_uses_label: "elixir.rpg-character-sheet.IO_inspect_uses_label", + + # RPN Calculator Inspection + rpn_calculator_inspection_use_start_link: "elixir.rpn-calculator-inspection.use_start_link", + + # RPN Calculator Output + rpn_calculator_output_try_rescue_else_after: + "elixir.rpn-calculator-output.try_rescue_else_after", + rpn_calculator_output_open_before_try: "elixir.rpn-calculator-output.open_before_try", + rpn_calculator_output_write_in_try: "elixir.rpn-calculator-output.write_in_try", + rpn_calculator_output_output_in_else: "elixir.rpn-calculator-output.output_in_else", + rpn_calculator_output_close_in_after: "elixir.rpn-calculator-output.close_in_after", + + # Take A Number Comments + take_a_number_do_not_use_abstractions: "elixir.take-a-number.do_not_use_abstractions", + + # Take A Number Deluxe Comments + take_a_number_deluxe_use_genserver: "elixir.take-a-number-deluxe.use_genserver", + take_a_number_deluxe_annotate_impl_genserver: + "elixir.take-a-number-deluxe.annotate_impl_genserver", + + # Top Secret Comments + top_secret_function_reuse: "elixir.top-secret.function_reuse", + + # Wine Cellar Comments + wine_cellar_use_keyword_get_values: "elixir.wine-cellar.use_keyword_get_values", + + # Practice exercises + + # Accumulate Comments + accumulate_use_recursion: "elixir.accumulate.use_recursion", + + # List Ops Comments + list_ops_do_not_use_list_functions: "elixir.list-ops.do_not_use_list_functions", + + # Sieve Comments + sieve_do_not_use_div_rem: "elixir.sieve.do_not_use_div_rem", + # + # Strain Comments + strain_use_recursion: "elixir.strain.use_recursion", + + # Square Root Comments + square_root_do_not_use_built_in_sqrt: "elixir.square-root.do_not_use_built_in_sqrt", + + # Two-fer Error Comments + two_fer_use_default_parameter: "elixir.two-fer.use_default_param", + two_fer_use_guards: "elixir.two-fer.use_guards", + two_fer_use_string_interpolation: "elixir.two-fer.use_string_interpolation", + two_fer_wrong_specification: "elixir.two-fer.wrong_specification", + two_fer_use_function_level_guard: "elixir.two-fer.use_function_level_guard", + two_fer_use_of_aux_functions: "elixir.two-fer.use_of_aux_functions", + two_fer_use_of_function_header: "elixir.two-fer.use_of_function_header" + ] + + for {constant, markdown} <- @constants do + def unquote(constant)(), do: unquote(markdown) + end + + def list_of_all_comments() do + Enum.map(@constants, &Kernel.elem(&1, 1)) + end +end diff --git a/lib/elixir_analyzer/test_suite/captains_log.ex b/lib/elixir_analyzer/test_suite/captains_log.ex index a872532c..65c9eca0 100644 --- a/lib/elixir_analyzer/test_suite/captains_log.ex +++ b/lib/elixir_analyzer/test_suite/captains_log.ex @@ -1,73 +1,73 @@ -defmodule ElixirAnalyzer.TestSuite.CaptainsLog do - @moduledoc """ - This is an exercise analyzer extension module for the concept exercise Captains Log - """ - use ElixirAnalyzer.ExerciseTest - alias ElixirAnalyzer.Constants - alias ElixirAnalyzer.Source - - assert_call "random_planet_class uses Enum.random" do - type :essential - calling_fn module: CaptainsLog, name: :random_planet_class - called_fn module: Enum, name: :random - comment Constants.captains_log_use_enum_random() - end - - assert_call "random_ship_registry_number uses Enum.random" do - type :essential - calling_fn module: CaptainsLog, name: :random_ship_registry_number - called_fn module: Enum, name: :random - comment Constants.captains_log_use_enum_random() - end - - assert_no_call "random_stardate does not use Enum.random" do - type :essential - calling_fn module: CaptainsLog, name: :random_stardate - called_fn module: Enum, name: :random - comment Constants.captains_log_do_not_use_enum_random() - end - - assert_no_call "random_stardate does not use :rand.uniform_real" do - type :essential - calling_fn module: CaptainsLog, name: :random_stardate - called_fn module: :rand, name: :uniform_real - comment Constants.captains_log_do_not_use_rand_uniform_real() - end - - assert_call "random_stardate uses :rand.uniform" do - type :essential - calling_fn module: CaptainsLog, name: :random_stardate - called_fn module: :rand, name: :uniform - comment Constants.captains_log_use_rand_uniform() - suppress_if Constants.solution_deprecated_random_module(), :fail - suppress_if "random_stardate does not use :rand.uniform_real", :fail - suppress_if "random_stardate does not use Enum.random", :fail -end - - - check_source "format_stardate uses erlang" do - type :essential - comment Constants.captains_log_use_erlang() - - check(%Source{code_ast: code_ast}) do - {_, erlang?} = - Macro.prewalk(code_ast, false, fn node, acc -> - case node do - # usage :io_lib.format/2 - {{:., _, [:io_lib, :format]}, _, _} -> - {node, true} - - # usage :erlang.function_name/arity - # matches any function call from :erlang module - {{:., _, [:erlang, _]}, _, _} -> - {node, true} - - _ -> - {node, acc} - end - end) - - erlang? - end - end -end +defmodule ElixirAnalyzer.TestSuite.CaptainsLog do + @moduledoc """ + This is an exercise analyzer extension module for the concept exercise Captains Log + """ + use ElixirAnalyzer.ExerciseTest + alias ElixirAnalyzer.Constants + alias ElixirAnalyzer.Source + + assert_call "random_planet_class uses Enum.random" do + type :essential + calling_fn module: CaptainsLog, name: :random_planet_class + called_fn module: Enum, name: :random + comment Constants.captains_log_use_enum_random() + end + + assert_call "random_ship_registry_number uses Enum.random" do + type :essential + calling_fn module: CaptainsLog, name: :random_ship_registry_number + called_fn module: Enum, name: :random + comment Constants.captains_log_use_enum_random() + end + + assert_no_call "random_stardate does not use Enum.random" do + type :essential + calling_fn module: CaptainsLog, name: :random_stardate + called_fn module: Enum, name: :random + comment Constants.captains_log_do_not_use_enum_random() + end + + assert_no_call "random_stardate does not use :rand.uniform_real" do + type :essential + calling_fn module: CaptainsLog, name: :random_stardate + called_fn module: :rand, name: :uniform_real + comment Constants.captains_log_do_not_use_rand_uniform_real() + end + + assert_call "random_stardate uses :rand.uniform" do + type :essential + calling_fn module: CaptainsLog, name: :random_stardate + called_fn module: :rand, name: :uniform + comment Constants.captains_log_use_rand_uniform() + suppress_if Constants.solution_deprecated_random_module(), :fail + suppress_if "random_stardate does not use :rand.uniform_real", :fail + suppress_if "random_stardate does not use Enum.random", :fail +end + + + check_source "format_stardate uses erlang" do + type :essential + comment Constants.captains_log_use_erlang() + + check(%Source{code_ast: code_ast}) do + {_, erlang?} = + Macro.prewalk(code_ast, false, fn node, acc -> + case node do + # usage :io_lib.format/2 + {{:., _, [:io_lib, :format]}, _, _} -> + {node, true} + + # usage :erlang.function_name/arity + # matches any function call from :erlang module + {{:., _, [:erlang, _]}, _, _} -> + {node, true} + + _ -> + {node, acc} + end + end) + + erlang? + end + end +end diff --git a/test/elixir_analyzer/test_suite/captains_log_test.exs b/test/elixir_analyzer/test_suite/captains_log_test.exs index f09c3e4e..fb07b638 100644 --- a/test/elixir_analyzer/test_suite/captains_log_test.exs +++ b/test/elixir_analyzer/test_suite/captains_log_test.exs @@ -1,112 +1,112 @@ -defmodule ElixirAnalyzer.ExerciseTest.CaptainsLogTest do - use ElixirAnalyzer.ExerciseTestCase, - exercise_test_module: ElixirAnalyzer.TestSuite.CaptainsLog - - alias ElixirAnalyzer.Constants - - test_exercise_analysis "example solution", - comments: [Constants.solution_same_as_exemplar()] do - defmodule CaptainsLog do - @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] - - def random_planet_class() do - Enum.random(@planetary_classes) - end - - def random_ship_registry_number() do - number = Enum.random(1000..9999) - "NCC-#{number}" - end - - def random_stardate() do - :rand.uniform() * 1000 + 41_000 - end - - def format_stardate(stardate) do - to_string(:io_lib.format("~.1f", [stardate])) - end - end - end - - describe "Expects random_planet_class and random_ship_registry_number to use Enum.random" do - test_exercise_analysis "random_planet_class does not use Enum.random", - comments_include: [Constants.captains_log_use_enum_random()] do - defmodule CaptainsLog do - @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] - - def random_planet_class() do - @planetary_classes - |> Enum.shuffle() - |> hd - end - end - end - - test_exercise_analysis "random_ship_registry_number does not use Enum.random", - comments_include: [Constants.captains_log_use_enum_random()] do - defmodule CaptainsLog do - def random_ship_registry_number() do - number = - 1000..9999 - |> Enum.shuffle() - |> hd - - "NCC-#{number}" - end - end - end - end - - describe "random_stardate uses :rand.uniform" do - test_exercise_analysis "when using Enum.random instead", - comments_include: [ - Constants.captains_log_do_not_use_enum_random() - ], - comments_exclude: [ - Constants.captains_log_use_rand_uniform() - ] do - defmodule CaptainsLog do - def random_stardate do - Enum.random(4_100_000..4_200_000) |> Kernel./(100) - end - end - end - - test_exercise_analysis "when using :rand.uniform_real instead", - comments_include: [ - Constants.captains_log_do_not_use_rand_uniform_real() - ], - comments_exclude: [ - Constants.captains_log_use_rand_uniform() - ] do - defmodule CaptainsLog do - def random_stardate do - :rand.uniform_real() * 1000 + 41_000 - end - end - end - - test_exercise_analysis "when using deprecated :random module", - comments_include: [Constants.solution_deprecated_random_module()], - comments_exclude: [Constants.captains_log_use_rand_uniform()] do - defmodule CaptainsLog do - def random_stardate() do - :random.uniform() * 1000 + 41_000 - end - end - end - end - - test_exercise_analysis "format_stardate uses Float.round", - comments_include: [Constants.captains_log_use_erlang()] do - defmodule CaptainsLog do - def format_stardate(stardate) do - if is_float(stardate) do - Float.round(stardate, 1) |> to_string() - else - raise ArgumentError - end - end - end - end -end +defmodule ElixirAnalyzer.ExerciseTest.CaptainsLogTest do + use ElixirAnalyzer.ExerciseTestCase, + exercise_test_module: ElixirAnalyzer.TestSuite.CaptainsLog + + alias ElixirAnalyzer.Constants + + test_exercise_analysis "example solution", + comments: [Constants.solution_same_as_exemplar()] do + defmodule CaptainsLog do + @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] + + def random_planet_class() do + Enum.random(@planetary_classes) + end + + def random_ship_registry_number() do + number = Enum.random(1000..9999) + "NCC-#{number}" + end + + def random_stardate() do + :rand.uniform() * 1000 + 41_000 + end + + def format_stardate(stardate) do + to_string(:io_lib.format("~.1f", [stardate])) + end + end + end + + describe "Expects random_planet_class and random_ship_registry_number to use Enum.random" do + test_exercise_analysis "random_planet_class does not use Enum.random", + comments_include: [Constants.captains_log_use_enum_random()] do + defmodule CaptainsLog do + @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"] + + def random_planet_class() do + @planetary_classes + |> Enum.shuffle() + |> hd + end + end + end + + test_exercise_analysis "random_ship_registry_number does not use Enum.random", + comments_include: [Constants.captains_log_use_enum_random()] do + defmodule CaptainsLog do + def random_ship_registry_number() do + number = + 1000..9999 + |> Enum.shuffle() + |> hd + + "NCC-#{number}" + end + end + end + end + + describe "random_stardate uses :rand.uniform" do + test_exercise_analysis "when using Enum.random instead", + comments_include: [ + Constants.captains_log_do_not_use_enum_random() + ], + comments_exclude: [ + Constants.captains_log_use_rand_uniform() + ] do + defmodule CaptainsLog do + def random_stardate do + Enum.random(4_100_000..4_200_000) |> Kernel./(100) + end + end + end + + test_exercise_analysis "when using :rand.uniform_real instead", + comments_include: [ + Constants.captains_log_do_not_use_rand_uniform_real() + ], + comments_exclude: [ + Constants.captains_log_use_rand_uniform() + ] do + defmodule CaptainsLog do + def random_stardate do + :rand.uniform_real() * 1000 + 41_000 + end + end + end + + test_exercise_analysis "when using deprecated :random module", + comments_include: [Constants.solution_deprecated_random_module()], + comments_exclude: [Constants.captains_log_use_rand_uniform()] do + defmodule CaptainsLog do + def random_stardate() do + :random.uniform() * 1000 + 41_000 + end + end + end + end + + test_exercise_analysis "format_stardate uses Float.round", + comments_include: [Constants.captains_log_use_erlang()] do + defmodule CaptainsLog do + def format_stardate(stardate) do + if is_float(stardate) do + Float.round(stardate, 1) |> to_string() + else + raise ArgumentError + end + end + end + end +end From ff21ba62b3e0044a6fcc68a243c3c1280fbe64f4 Mon Sep 17 00:00:00 2001 From: FraSanga Date: Sat, 27 Sep 2025 13:38:16 +0200 Subject: [PATCH 3/3] fix indent --- lib/elixir_analyzer/test_suite/captains_log.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir_analyzer/test_suite/captains_log.ex b/lib/elixir_analyzer/test_suite/captains_log.ex index 65c9eca0..e21b6d91 100644 --- a/lib/elixir_analyzer/test_suite/captains_log.ex +++ b/lib/elixir_analyzer/test_suite/captains_log.ex @@ -42,7 +42,7 @@ defmodule ElixirAnalyzer.TestSuite.CaptainsLog do suppress_if Constants.solution_deprecated_random_module(), :fail suppress_if "random_stardate does not use :rand.uniform_real", :fail suppress_if "random_stardate does not use Enum.random", :fail -end + end check_source "format_stardate uses erlang" do