diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..c694b0a --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,45 @@ +name: Linter + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + clippy: + name: Clippy + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + core -> target + + - name: Run Clippy + run: cd core && cargo clippy --lib --all-features -- -W clippy::all + + rubocop: + name: RuboCop + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4' + bundler-cache: true + + - name: Run RuboCop + run: bundle exec rubocop diff --git a/.rubocop.yml b/.rubocop.yml index fe6dca4..73fc4b1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,3 +21,10 @@ Metrics/MethodLength: Metrics/AbcSize: Max: 25 + +Metrics/ClassLength: + Exclude: + - "test/**/*_test.rb" + +Metrics/CyclomaticComplexity: + Max: 10 diff --git a/core/src/rbs/method_loader.rb b/core/src/rbs/method_loader.rb index a0f8f05..594f000 100644 --- a/core/src/rbs/method_loader.rb +++ b/core/src/rbs/method_loader.rb @@ -65,11 +65,9 @@ def extract_block_param_types(method_type) block_func = method_type.block.type return nil unless block_func.is_a?(::RBS::Types::Function) - param_types = [] - # Required positional parameters - block_func.required_positionals.each do |param| - param_types << param.type.to_s + param_types = block_func.required_positionals.map do |param| + param.type.to_s end # Optional positional parameters diff --git a/test/array_test.rb b/test/array_test.rb index b8648c3..f8dbf4a 100644 --- a/test/array_test.rb +++ b/test/array_test.rb @@ -10,18 +10,18 @@ class ArrayTest < Minitest::Test # ============================================ def test_nested_array_integer - assert_type "x = [[1, 2], [3]]", "x", "Array[Array[Integer]]" + assert_type 'x = [[1, 2], [3]]', 'x', 'Array[Array[Integer]]' end def test_deeply_nested_array - assert_type "x = [[[1]]]", "x", "Array[Array[Array[Integer]]]" + assert_type 'x = [[[1]]]', 'x', 'Array[Array[Array[Integer]]]' end def test_nested_array_mixed types = infer('x = [[1], ["a"]]') assert_includes( - ["Array[Array[Integer] | Array[String]]", "Array[Array[String] | Array[Integer]]"], - types["x"] + ['Array[Array[Integer] | Array[String]]', 'Array[Array[String] | Array[Integer]]'], + types['x'] ) end diff --git a/test/conditional_test.rb b/test/conditional_test.rb index 7c0590d..ca03dbd 100644 --- a/test/conditional_test.rb +++ b/test/conditional_test.rb @@ -19,9 +19,9 @@ def test_if_else_union_type RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "Integer" - assert_includes type_str, "String" + type_str = types['x'] + assert_includes type_str, 'Integer' + assert_includes type_str, 'String' end def test_if_without_else_includes_nil @@ -32,9 +32,9 @@ def test_if_without_else_includes_nil RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "String" - assert_includes type_str, "nil" + type_str = types['x'] + assert_includes type_str, 'String' + assert_includes type_str, 'nil' end def test_if_elsif_else_union @@ -49,10 +49,10 @@ def test_if_elsif_else_union RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "String" - assert_includes type_str, "Integer" - assert_includes type_str, "Symbol" + type_str = types['x'] + assert_includes type_str, 'String' + assert_includes type_str, 'Integer' + assert_includes type_str, 'Symbol' end def test_unless_else_union_type @@ -65,9 +65,9 @@ def test_unless_else_union_type RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "String" - assert_includes type_str, "Integer" + type_str = types['x'] + assert_includes type_str, 'String' + assert_includes type_str, 'Integer' end def test_unless_without_else_includes_nil @@ -78,9 +78,9 @@ def test_unless_without_else_includes_nil RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "String" - assert_includes type_str, "nil" + type_str = types['x'] + assert_includes type_str, 'String' + assert_includes type_str, 'nil' end def test_case_when_else_union @@ -96,10 +96,10 @@ def test_case_when_else_union RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "String" - assert_includes type_str, "Integer" - assert_includes type_str, "Symbol" + type_str = types['x'] + assert_includes type_str, 'String' + assert_includes type_str, 'Integer' + assert_includes type_str, 'Symbol' end def test_case_without_else_includes_nil @@ -111,9 +111,9 @@ def test_case_without_else_includes_nil RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "String" - assert_includes type_str, "nil" + type_str = types['x'] + assert_includes type_str, 'String' + assert_includes type_str, 'nil' end def test_same_type_branches @@ -125,7 +125,7 @@ def test_same_type_branches end RUBY - assert_type source, "x", "String" + assert_type source, 'x', 'String' end # ============================================ @@ -226,7 +226,7 @@ def test_ternary_union_type RUBY types = infer(source) - assert_equal "(Integer | String)", types["x"] + assert_equal '(Integer | String)', types['x'] end def test_ternary_same_type @@ -234,7 +234,7 @@ def test_ternary_same_type x = true ? "hello" : "world" RUBY - assert_type source, "x", "String" + assert_type source, 'x', 'String' end def test_ternary_nil_branch @@ -243,7 +243,7 @@ def test_ternary_nil_branch RUBY types = infer(source) - assert_equal "(String | nil)", types["x"] + assert_equal '(String | nil)', types['x'] end # ============================================ diff --git a/test/exception_test.rb b/test/exception_test.rb index 5f5e355..c892ee6 100644 --- a/test/exception_test.rb +++ b/test/exception_test.rb @@ -19,9 +19,9 @@ def test_begin_rescue_union_type RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "Integer" - assert_includes type_str, "String" + type_str = types['x'] + assert_includes type_str, 'Integer' + assert_includes type_str, 'String' end def test_begin_rescue_else_excludes_begin_body @@ -36,10 +36,10 @@ def test_begin_rescue_else_excludes_begin_body RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "Symbol" - assert_includes type_str, "Integer" - refute_includes type_str, "String" + type_str = types['x'] + assert_includes type_str, 'Symbol' + assert_includes type_str, 'Integer' + refute_includes type_str, 'String' end def test_rescue_variable_typed_as_specific_exception @@ -52,8 +52,8 @@ def test_rescue_variable_typed_as_specific_exception RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "ArgumentError" + type_str = types['x'] + assert_includes type_str, 'ArgumentError' end def test_rescue_variable_typed_as_union_of_exceptions @@ -66,9 +66,9 @@ def test_rescue_variable_typed_as_union_of_exceptions RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "TypeError" - assert_includes type_str, "NameError" + type_str = types['x'] + assert_includes type_str, 'TypeError' + assert_includes type_str, 'NameError' end def test_rescue_without_exception_class_defaults_to_standard_error @@ -81,8 +81,8 @@ def test_rescue_without_exception_class_defaults_to_standard_error RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "StandardError" + type_str = types['x'] + assert_includes type_str, 'StandardError' end def test_rescue_modifier_union_type @@ -91,9 +91,9 @@ def test_rescue_modifier_union_type RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "Integer" - assert_includes type_str, "String" + type_str = types['x'] + assert_includes type_str, 'Integer' + assert_includes type_str, 'String' end # ============================================ diff --git a/test/float_test.rb b/test/float_test.rb index 2317acf..701dbf7 100644 --- a/test/float_test.rb +++ b/test/float_test.rb @@ -10,22 +10,22 @@ class FloatTest < Minitest::Test # ============================================ def test_float_literal - assert_type "x = 3.14", "x", "Float" + assert_type 'x = 3.14', 'x', 'Float' end def test_float_ceil_returns_integer types = infer("x = 3.14\na = x.ceil") - assert_equal "Integer", types["a"] + assert_equal 'Integer', types['a'] end def test_float_floor_returns_integer types = infer("x = 3.14\nb = x.floor") - assert_equal "Integer", types["b"] + assert_equal 'Integer', types['b'] end def test_float_abs_returns_float types = infer("x = 3.14\nc = x.abs") - assert_equal "Float", types["c"] + assert_equal 'Float', types['c'] end # ============================================ diff --git a/test/hash_test.rb b/test/hash_test.rb index 43fbb06..1343e59 100644 --- a/test/hash_test.rb +++ b/test/hash_test.rb @@ -10,27 +10,27 @@ class HashTest < Minitest::Test # ============================================ def test_hash_symbol_integer - assert_type "x = { a: 1, b: 2 }", "x", "Hash[Symbol, Integer]" + assert_type 'x = { a: 1, b: 2 }', 'x', 'Hash[Symbol, Integer]' end def test_hash_string_string - assert_type 'x = { "k" => "v" }', "x", "Hash[String, String]" + assert_type 'x = { "k" => "v" }', 'x', 'Hash[String, String]' end def test_hash_mixed_values types = infer('x = { a: 1, b: "x" }') assert_includes( - ["Hash[Symbol, Integer | String]", "Hash[Symbol, String | Integer]"], - types["x"] + ['Hash[Symbol, Integer | String]', 'Hash[Symbol, String | Integer]'], + types['x'] ) end def test_hash_empty - assert_type "x = {}", "x", "Hash" + assert_type 'x = {}', 'x', 'Hash' end def test_hash_nested - assert_type "x = { a: [1] }", "x", "Hash[Symbol, Array[Integer]]" + assert_type 'x = { a: [1] }', 'x', 'Hash[Symbol, Array[Integer]]' end # ============================================ diff --git a/test/integer_test.rb b/test/integer_test.rb index 9986bf0..ea02b7d 100644 --- a/test/integer_test.rb +++ b/test/integer_test.rb @@ -10,7 +10,7 @@ class IntegerTest < Minitest::Test # ============================================ def test_integer_literal - assert_type "x = 42", "x", "Integer" + assert_type 'x = 42', 'x', 'Integer' end # ============================================ diff --git a/test/multi_assign_test.rb b/test/multi_assign_test.rb index a86c578..2adc4ff 100644 --- a/test/multi_assign_test.rb +++ b/test/multi_assign_test.rb @@ -11,61 +11,61 @@ class MultiAssignTest < Minitest::Test def test_basic_multi_assign types = infer('a, b = 1, "hello"') - assert_equal "Integer", types["a"] - assert_equal "String", types["b"] + assert_equal 'Integer', types['a'] + assert_equal 'String', types['b'] end def test_lhs_longer_than_rhs - types = infer("a, b, c = 1, 2") - assert_equal "Integer", types["a"] - assert_equal "Integer", types["b"] - assert_equal "nil", types["c"] + types = infer('a, b, c = 1, 2') + assert_equal 'Integer', types['a'] + assert_equal 'Integer', types['b'] + assert_equal 'nil', types['c'] end def test_splat_basic - types = infer("first, *rest = 1, 2, 3") - assert_equal "Integer", types["first"] - assert_equal "Array[Integer]", types["rest"] + types = infer('first, *rest = 1, 2, 3') + assert_equal 'Integer', types['first'] + assert_equal 'Array[Integer]', types['rest'] end def test_splat_with_rights - types = infer("first, *rest, last = 1, 2, 3, 4") - assert_equal "Integer", types["first"] - assert_equal "Array[Integer]", types["rest"] - assert_equal "Integer", types["last"] + types = infer('first, *rest, last = 1, 2, 3, 4') + assert_equal 'Integer', types['first'] + assert_equal 'Array[Integer]', types['rest'] + assert_equal 'Integer', types['last'] end def test_splat_rights_no_lefts - types = infer("*rest, last = 1, 2, 3") - assert_equal "Array[Integer]", types["rest"] - assert_equal "Integer", types["last"] + types = infer('*rest, last = 1, 2, 3') + assert_equal 'Array[Integer]', types['rest'] + assert_equal 'Integer', types['last'] end def test_splat_empty - types = infer("first, *rest = 1") - assert_equal "Integer", types["first"] - assert_equal "Array[untyped]", types["rest"] + types = infer('first, *rest = 1') + assert_equal 'Integer', types['first'] + assert_equal 'Array[untyped]', types['rest'] end def test_splat_lefts_exceed_rhs - types = infer("a, b, c, *rest = 1, 2") - assert_equal "Integer", types["a"] - assert_equal "Integer", types["b"] - assert_equal "nil", types["c"] - assert_equal "Array[untyped]", types["rest"] + types = infer('a, b, c, *rest = 1, 2') + assert_equal 'Integer', types['a'] + assert_equal 'Integer', types['b'] + assert_equal 'nil', types['c'] + assert_equal 'Array[untyped]', types['rest'] end def test_splat_with_rights_insufficient_rhs - types = infer("a, *rest, z = 1") - assert_equal "Integer", types["a"] - assert_equal "Array[untyped]", types["rest"] - assert_equal "nil", types["z"] + types = infer('a, *rest, z = 1') + assert_equal 'Integer', types['a'] + assert_equal 'Array[untyped]', types['rest'] + assert_equal 'nil', types['z'] end def test_scalar_rhs - types = infer("a, b = 42") - assert_equal "Integer", types["a"] - assert_equal "nil", types["b"] + types = infer('a, b = 42') + assert_equal 'Integer', types['a'] + assert_equal 'nil', types['b'] end # ============================================ diff --git a/test/operator_test.rb b/test/operator_test.rb index 67bc5a7..e3cf7c8 100644 --- a/test/operator_test.rb +++ b/test/operator_test.rb @@ -15,9 +15,9 @@ def test_and_operator_union_type RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "TrueClass" - assert_includes type_str, "String" + type_str = types['x'] + assert_includes type_str, 'TrueClass' + assert_includes type_str, 'String' end def test_or_operator_union_type @@ -26,9 +26,9 @@ def test_or_operator_union_type RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "Integer" - assert_includes type_str, "String" + type_str = types['x'] + assert_includes type_str, 'Integer' + assert_includes type_str, 'String' end def test_arithmetic_operator_type @@ -36,7 +36,7 @@ def test_arithmetic_operator_type x = 1 + 2 RUBY - assert_type source, "x", "Integer" + assert_type source, 'x', 'Integer' end # ============================================ @@ -137,9 +137,9 @@ def test_not_operator_type RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "TrueClass" - assert_includes type_str, "FalseClass" + type_str = types['x'] + assert_includes type_str, 'TrueClass' + assert_includes type_str, 'FalseClass' end def test_not_operator_method_call_no_error @@ -176,8 +176,8 @@ def test_double_not_operator_type RUBY types = infer(source) - type_str = types["x"] - assert_includes type_str, "TrueClass" - assert_includes type_str, "FalseClass" + type_str = types['x'] + assert_includes type_str, 'TrueClass' + assert_includes type_str, 'FalseClass' end end diff --git a/test/parentheses_test.rb b/test/parentheses_test.rb index ea7281f..69aaf77 100644 --- a/test/parentheses_test.rb +++ b/test/parentheses_test.rb @@ -10,7 +10,7 @@ class ParenthesesTest < Minitest::Test # ============================================ def test_parenthesized_integer - assert_type 'x = (42)', "x", "Integer" + assert_type 'x = (42)', 'x', 'Integer' end # ============================================ diff --git a/test/range_test.rb b/test/range_test.rb index a00e683..39efac1 100644 --- a/test/range_test.rb +++ b/test/range_test.rb @@ -10,29 +10,29 @@ class RangeTest < Minitest::Test # ============================================ def test_range_integer - assert_type "x = 1..5", "x", "Range[Integer]" + assert_type 'x = 1..5', 'x', 'Range[Integer]' end def test_range_exclusive - assert_type "x = 1...5", "x", "Range[Integer]" + assert_type 'x = 1...5', 'x', 'Range[Integer]' end def test_range_string - assert_type 'x = "a".."z"', "x", "Range[String]" + assert_type 'x = "a".."z"', 'x', 'Range[String]' end def test_range_float - assert_type "x = 1.0..5.0", "x", "Range[Float]" + assert_type 'x = 1.0..5.0', 'x', 'Range[Float]' end def test_range_to_a_returns_array types = infer("x = 1..10\na = x.to_a") - assert_equal "Array[Elem]", types["a"] + assert_equal 'Array[Elem]', types['a'] end def test_range_size_returns_integer types = infer("x = 1..10\nb = x.size") - assert_equal "Integer | Float | nil", types["b"] + assert_equal 'Integer | Float | nil', types['b'] end # ============================================ diff --git a/test/regexp_test.rb b/test/regexp_test.rb index 47a4a57..bda3c6d 100644 --- a/test/regexp_test.rb +++ b/test/regexp_test.rb @@ -10,12 +10,12 @@ class RegexpTest < Minitest::Test # ============================================ def test_regexp_literal - assert_type "x = /hello/", "x", "Regexp" + assert_type 'x = /hello/', 'x', 'Regexp' end def test_regexp_source_returns_string types = infer("x = /hello/\na = x.source") - assert_equal "String", types["a"] + assert_equal 'String', types['a'] end # ============================================ diff --git a/test/string_test.rb b/test/string_test.rb index d4518c9..aff003c 100644 --- a/test/string_test.rb +++ b/test/string_test.rb @@ -10,7 +10,7 @@ class StringTest < Minitest::Test # ============================================ def test_string_literal - assert_type 'x = "hello"', "x", "String" + assert_type 'x = "hello"', 'x', 'String' end def test_multiple_vars @@ -19,8 +19,8 @@ def test_multiple_vars y = 42 RUBY - assert_equal "String", types["x"] - assert_equal "Integer", types["y"] + assert_equal 'String', types['x'] + assert_equal 'Integer', types['y'] end def test_method_call_return_type @@ -29,12 +29,12 @@ def test_method_call_return_type y = x.upcase RUBY - assert_equal "String", types["x"] - assert_equal "String", types["y"] + assert_equal 'String', types['x'] + assert_equal 'String', types['y'] end def test_interpolated_string_literal - assert_type 'x = "hello #{1 + 2}"', "x", "String" + assert_type 'x = "hello #{1 + 2}"', 'x', 'String' end # ============================================ @@ -86,5 +86,4 @@ def format RUBY assert_check_error(source, method_name: 'ceil', receiver_type: 'String') end - end