From 85f61349c94fa27b4f6bddcdf80071e57c5ae001 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 7 Jan 2023 13:25:11 +0000 Subject: [PATCH 01/22] Only write files when content changes Previously the CLI would call `File.write` for every file, even if the contents was unchanged. This unnecessary filesystem churn can have a knock-on effect on other tools which may be watching directories for changes (e.g. IDEs). This commit updates the `stree write` command so that it only performs a write when the file contents has changed. --- lib/syntax_tree/cli.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index 392dd627..7e6f4067 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -303,10 +303,11 @@ def run(item) options.print_width, options: options.formatter_options ) + changed = source != formatted - File.write(filepath, formatted) if item.writable? + File.write(filepath, formatted) if item.writable? && changed - color = source == formatted ? Color.gray(filepath) : filepath + color = changed ? filepath : Color.gray(filepath) delta = ((Time.now - start) * 1000).round puts "#{color} #{delta}ms" From 9c8198969b8a8b4701a6bb487806d51745a39b74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:51:41 +0000 Subject: [PATCH 02/22] Bump rubocop from 1.42.0 to 1.43.0 Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.42.0 to 1.43.0. - [Release notes](https://github.com/rubocop/rubocop/releases) - [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](https://github.com/rubocop/rubocop/compare/v1.42.0...v1.43.0) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bb5e3663..b691d5e9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -19,16 +19,16 @@ GEM rake (13.0.6) regexp_parser (2.6.1) rexml (3.2.5) - rubocop (1.42.0) + rubocop (1.43.0) json (~> 2.3) parallel (~> 1.10) - parser (>= 3.1.2.1) + parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.24.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) + unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.24.1) parser (>= 3.1.1.0) ruby-progressbar (1.11.0) @@ -38,7 +38,7 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - unicode-display_width (2.4.1) + unicode-display_width (2.4.2) PLATFORMS arm64-darwin-21 From 6712db16c4bf7ad500ba9c653e0b876601134bd6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 10 Jan 2023 16:13:39 -0500 Subject: [PATCH 03/22] Use ruby-syntax-fixtures --- .gitmodules | 3 +++ test/ruby-syntax-fixtures | 1 + test/ruby_syntax_fixtures_test.rb | 13 +++++++++++++ 3 files changed, 17 insertions(+) create mode 160000 test/ruby-syntax-fixtures create mode 100644 test/ruby_syntax_fixtures_test.rb diff --git a/.gitmodules b/.gitmodules index f5477ea3..1a2c45cc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "spec"] path = spec/ruby url = git@github.com:ruby/spec.git +[submodule "test/ruby-syntax-fixtures"] + path = test/ruby-syntax-fixtures + url = https://github.com/ruby-syntax-tree/ruby-syntax-fixtures diff --git a/test/ruby-syntax-fixtures b/test/ruby-syntax-fixtures new file mode 160000 index 00000000..5b333f5a --- /dev/null +++ b/test/ruby-syntax-fixtures @@ -0,0 +1 @@ +Subproject commit 5b333f5a34d6fb08f88acc93b69c7d19b3fee8e7 diff --git a/test/ruby_syntax_fixtures_test.rb b/test/ruby_syntax_fixtures_test.rb new file mode 100644 index 00000000..9aae8cc8 --- /dev/null +++ b/test/ruby_syntax_fixtures_test.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SyntaxTree + class RubySyntaxFixturesTest < Minitest::Test + Dir[File.expand_path("ruby-syntax-fixtures/**/*.rb", __dir__)].each do |file| + define_method "test_ruby_syntax_fixtures_#{file}" do + refute_nil(SyntaxTree.parse(SyntaxTree.read(file))) + end + end + end +end From a116e97dc81b59370e62885c6ab3875bf54dc522 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 11 Jan 2023 10:19:15 -0500 Subject: [PATCH 04/22] Fix up formatting on main --- test/ruby_syntax_fixtures_test.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/ruby_syntax_fixtures_test.rb b/test/ruby_syntax_fixtures_test.rb index 9aae8cc8..0cf89310 100644 --- a/test/ruby_syntax_fixtures_test.rb +++ b/test/ruby_syntax_fixtures_test.rb @@ -4,7 +4,9 @@ module SyntaxTree class RubySyntaxFixturesTest < Minitest::Test - Dir[File.expand_path("ruby-syntax-fixtures/**/*.rb", __dir__)].each do |file| + Dir[ + File.expand_path("ruby-syntax-fixtures/**/*.rb", __dir__) + ].each do |file| define_method "test_ruby_syntax_fixtures_#{file}" do refute_nil(SyntaxTree.parse(SyntaxTree.read(file))) end From 46c0e00025c79339dd60c46dd32eba8430836f24 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Sat, 14 Jan 2023 18:21:46 +0900 Subject: [PATCH 05/22] Fix: Handle Fiddle::DLError --- lib/syntax_tree/yarv/instruction_sequence.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/syntax_tree/yarv/instruction_sequence.rb b/lib/syntax_tree/yarv/instruction_sequence.rb index c284221b..6aa7279e 100644 --- a/lib/syntax_tree/yarv/instruction_sequence.rb +++ b/lib/syntax_tree/yarv/instruction_sequence.rb @@ -70,7 +70,7 @@ def push(instruction) [Fiddle::TYPE_VOIDP] * 3, Fiddle::TYPE_VOIDP ) - rescue NameError + rescue NameError, Fiddle::DLError end # This object is used to track the size of the stack at any given time. It From e789ead77c274f3d1a9fa43a915ceb78f56804a6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 18 Jan 2023 12:59:14 -0500 Subject: [PATCH 06/22] Add interface test for instructions --- lib/syntax_tree/yarv/instructions.rb | 822 +++++++++++++++++++++++++++ lib/syntax_tree/yarv/legacy.rb | 34 ++ test/yarv_test.rb | 35 ++ 3 files changed, 891 insertions(+) diff --git a/lib/syntax_tree/yarv/instructions.rb b/lib/syntax_tree/yarv/instructions.rb index 5e1d116b..20068eac 100644 --- a/lib/syntax_tree/yarv/instructions.rb +++ b/lib/syntax_tree/yarv/instructions.rb @@ -91,6 +91,14 @@ def to_a(_iseq) [:adjuststack, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(AdjustStack) && other.number == number + end + def length 2 end @@ -139,6 +147,14 @@ def to_a(_iseq) [:anytostring] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(AnyToString) + end + def length 1 end @@ -197,6 +213,14 @@ def to_a(_iseq) [:branchif, label.name] end + def deconstruct_keys(keys) + { label: label } + end + + def ==(other) + other.is_a?(BranchIf) && other.label == label + end + def length 2 end @@ -250,6 +274,14 @@ def to_a(_iseq) [:branchnil, label.name] end + def deconstruct_keys(keys) + { label: label } + end + + def ==(other) + other.is_a?(BranchNil) && other.label == label + end + def length 2 end @@ -302,6 +334,14 @@ def to_a(_iseq) [:branchunless, label.name] end + def deconstruct_keys(keys) + { label: label } + end + + def ==(other) + other.is_a?(BranchUnless) && other.label == label + end + def length 2 end @@ -365,6 +405,16 @@ def to_a(iseq) ] end + def deconstruct_keys(keys) + { keyword_bits_index: keyword_bits_index, keyword_index: keyword_index } + end + + def ==(other) + other.is_a?(CheckKeyword) && + other.keyword_bits_index == keyword_bits_index && + other.keyword_index == keyword_index + end + def length 3 end @@ -419,6 +469,14 @@ def to_a(_iseq) [:checkmatch, type] end + def deconstruct_keys(keys) + { type: type } + end + + def ==(other) + other.is_a?(CheckMatch) && other.type == type + end + def length 2 end @@ -561,6 +619,14 @@ def to_a(_iseq) [:checktype, type] end + def deconstruct_keys(keys) + { type: type } + end + + def ==(other) + other.is_a?(CheckType) && other.type == type + end + def length 2 end @@ -656,6 +722,14 @@ def to_a(_iseq) [:concatarray] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(ConcatArray) + end + def length 1 end @@ -708,6 +782,14 @@ def to_a(_iseq) [:concatstrings, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(ConcatStrings) && other.number == number + end + def length 2 end @@ -771,6 +853,17 @@ def to_a(_iseq) [:defineclass, name, class_iseq.to_a, flags] end + def deconstruct_keys(keys) + { name: name, class_iseq: class_iseq, flags: flags } + end + + def ==(other) + other.is_a?(DefineClass) && + other.name == name && + other.class_iseq == class_iseq && + other.flags == flags + end + def length 4 end @@ -899,6 +992,17 @@ def to_a(_iseq) [:defined, type, name, message] end + def deconstruct_keys(keys) + { type: type, name: name, message: message } + end + + def ==(other) + other.is_a?(Defined) && + other.type == type && + other.name == name && + other.message == message + end + def length 4 end @@ -989,6 +1093,16 @@ def to_a(_iseq) [:definemethod, method_name, method_iseq.to_a] end + def deconstruct_keys(keys) + { method_name: method_name, method_iseq: method_iseq } + end + + def ==(other) + other.is_a?(DefineMethod) && + other.method_name == method_name && + other.method_iseq == method_iseq + end + def length 3 end @@ -1061,6 +1175,16 @@ def to_a(_iseq) [:definesmethod, method_name, method_iseq.to_a] end + def deconstruct_keys(keys) + { method_name: method_name, method_iseq: method_iseq } + end + + def ==(other) + other.is_a?(DefineSMethod) && + other.method_name == method_name && + other.method_iseq == method_iseq + end + def length 3 end @@ -1118,6 +1242,14 @@ def to_a(_iseq) [:dup] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(Dup) + end + def length 1 end @@ -1164,6 +1296,14 @@ def to_a(_iseq) [:duparray, object] end + def deconstruct_keys(keys) + { object: object } + end + + def ==(other) + other.is_a?(DupArray) && other.object == object + end + def length 2 end @@ -1210,6 +1350,14 @@ def to_a(_iseq) [:duphash, object] end + def deconstruct_keys(keys) + { object: object } + end + + def ==(other) + other.is_a?(DupHash) && other.object == object + end + def length 2 end @@ -1256,6 +1404,14 @@ def to_a(_iseq) [:dupn, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(DupN) && other.number == number + end + def length 2 end @@ -1307,6 +1463,16 @@ def to_a(_iseq) [:expandarray, number, flags] end + def deconstruct_keys(keys) + { number: number, flags: flags } + end + + def ==(other) + other.is_a?(ExpandArray) && + other.number == number && + other.flags == flags + end + def length 3 end @@ -1398,6 +1564,16 @@ def to_a(iseq) [:getblockparam, current.local_table.offset(index), level] end + def deconstruct_keys(keys) + { index: index, level: level } + end + + def ==(other) + other.is_a?(GetBlockParam) && + other.index == index && + other.level == level + end + def length 3 end @@ -1455,6 +1631,16 @@ def to_a(iseq) [:getblockparamproxy, current.local_table.offset(index), level] end + def deconstruct_keys(keys) + { index: index, level: level } + end + + def ==(other) + other.is_a?(GetBlockParamProxy) && + other.index == index && + other.level == level + end + def length 3 end @@ -1507,6 +1693,16 @@ def to_a(_iseq) [:getclassvariable, name, cache] end + def deconstruct_keys(keys) + { name: name, cache: cache } + end + + def ==(other) + other.is_a?(GetClassVariable) && + other.name == name && + other.cache == cache + end + def length 3 end @@ -1557,6 +1753,14 @@ def to_a(_iseq) [:getconstant, name] end + def deconstruct_keys(keys) + { name: name } + end + + def ==(other) + other.is_a?(GetConstant) && other.name == name + end + def length 2 end @@ -1619,6 +1823,14 @@ def to_a(_iseq) [:getglobal, name] end + def deconstruct_keys(keys) + { name: name } + end + + def ==(other) + other.is_a?(GetGlobal) && other.name == name + end + def length 2 end @@ -1678,6 +1890,16 @@ def to_a(_iseq) [:getinstancevariable, name, cache] end + def deconstruct_keys(keys) + { name: name, cache: cache } + end + + def ==(other) + other.is_a?(GetInstanceVariable) && + other.name == name && + other.cache == cache + end + def length 3 end @@ -1732,6 +1954,14 @@ def to_a(iseq) [:getlocal, current.local_table.offset(index), level] end + def deconstruct_keys(keys) + { index: index, level: level } + end + + def ==(other) + other.is_a?(GetLocal) && other.index == index && other.level == level + end + def length 3 end @@ -1781,6 +2011,14 @@ def to_a(iseq) [:getlocal_WC_0, iseq.local_table.offset(index)] end + def deconstruct_keys(keys) + { index: index } + end + + def ==(other) + other.is_a?(GetLocalWC0) && other.index == index + end + def length 2 end @@ -1830,6 +2068,14 @@ def to_a(iseq) [:getlocal_WC_1, iseq.parent_iseq.local_table.offset(index)] end + def deconstruct_keys(keys) + { index: index } + end + + def ==(other) + other.is_a?(GetLocalWC1) && other.index == index + end + def length 2 end @@ -1881,6 +2127,14 @@ def to_a(_iseq) [:getspecial, key, type] end + def deconstruct_keys(keys) + { key: key, type: type } + end + + def ==(other) + other.is_a?(GetSpecial) && other.key == key && other.type == type + end + def length 3 end @@ -1929,6 +2183,14 @@ def to_a(_iseq) [:intern] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(Intern) + end + def length 1 end @@ -1979,6 +2241,14 @@ def to_a(_iseq) [:invokeblock, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(InvokeBlock) && other.calldata == calldata + end + def length 2 end @@ -2034,6 +2304,16 @@ def to_a(_iseq) [:invokesuper, calldata.to_h, block_iseq&.to_a] end + def deconstruct_keys(keys) + { calldata: calldata, block_iseq: block_iseq } + end + + def ==(other) + other.is_a?(InvokeSuper) && + other.calldata == calldata && + other.block_iseq == block_iseq + end + def length 1 end @@ -2105,6 +2385,14 @@ def to_a(_iseq) [:jump, label.name] end + def deconstruct_keys(keys) + { label: label } + end + + def ==(other) + other.is_a?(Jump) && other.label == label + end + def length 2 end @@ -2145,6 +2433,14 @@ def to_a(_iseq) [:leave] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(Leave) + end + def length 1 end @@ -2195,6 +2491,14 @@ def to_a(_iseq) [:newarray, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(NewArray) && other.number == number + end + def length 2 end @@ -2243,6 +2547,14 @@ def to_a(_iseq) [:newarraykwsplat, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(NewArrayKwSplat) && other.number == number + end + def length 2 end @@ -2293,6 +2605,14 @@ def to_a(_iseq) [:newhash, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(NewHash) && other.number == number + end + def length 2 end @@ -2344,6 +2664,14 @@ def to_a(_iseq) [:newrange, exclude_end] end + def deconstruct_keys(keys) + { exclude_end: exclude_end } + end + + def ==(other) + other.is_a?(NewRange) && other.exclude_end == exclude_end + end + def length 2 end @@ -2385,6 +2713,14 @@ def to_a(_iseq) [:nop] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(Nop) + end + def length 1 end @@ -2434,6 +2770,14 @@ def to_a(_iseq) [:objtostring, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(ObjToString) && other.calldata == calldata + end + def length 2 end @@ -2485,6 +2829,14 @@ def to_a(_iseq) [:once, iseq.to_a, cache] end + def deconstruct_keys(keys) + { iseq: iseq, cache: cache } + end + + def ==(other) + other.is_a?(Once) && other.iseq == iseq && other.cache == cache + end + def length 3 end @@ -2536,6 +2888,14 @@ def to_a(_iseq) [:opt_and, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptAnd) && other.calldata == calldata + end + def length 2 end @@ -2584,6 +2944,14 @@ def to_a(_iseq) [:opt_aref, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptAref) && other.calldata == calldata + end + def length 2 end @@ -2637,6 +3005,16 @@ def to_a(_iseq) [:opt_aref_with, object, calldata.to_h] end + def deconstruct_keys(keys) + { object: object, calldata: calldata } + end + + def ==(other) + other.is_a?(OptArefWith) && + other.object == object && + other.calldata == calldata + end + def length 3 end @@ -2686,6 +3064,14 @@ def to_a(_iseq) [:opt_aset, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptAset) && other.calldata == calldata + end + def length 2 end @@ -2738,6 +3124,16 @@ def to_a(_iseq) [:opt_aset_with, object, calldata.to_h] end + def deconstruct_keys(keys) + { object: object, calldata: calldata } + end + + def ==(other) + other.is_a?(OptAsetWith) && + other.object == object && + other.calldata == calldata + end + def length 3 end @@ -2806,6 +3202,16 @@ def to_a(_iseq) ] end + def deconstruct_keys(keys) + { case_dispatch_hash: case_dispatch_hash, else_label: else_label } + end + + def ==(other) + other.is_a?(OptCaseDispatch) && + other.case_dispatch_hash == case_dispatch_hash && + other.else_label == else_label + end + def length 3 end @@ -2855,6 +3261,14 @@ def to_a(_iseq) [:opt_div, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptDiv) && other.calldata == calldata + end + def length 2 end @@ -2903,6 +3317,14 @@ def to_a(_iseq) [:opt_empty_p, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptEmptyP) && other.calldata == calldata + end + def length 2 end @@ -2952,6 +3374,14 @@ def to_a(_iseq) [:opt_eq, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptEq) && other.calldata == calldata + end + def length 2 end @@ -3001,6 +3431,14 @@ def to_a(_iseq) [:opt_ge, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptGE) && other.calldata == calldata + end + def length 2 end @@ -3050,6 +3488,14 @@ def to_a(_iseq) [:opt_getconstant_path, names] end + def deconstruct_keys(keys) + { names: names } + end + + def ==(other) + other.is_a?(OptGetConstantPath) && other.names == names + end + def length 2 end @@ -3106,6 +3552,14 @@ def to_a(_iseq) [:opt_gt, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptGT) && other.calldata == calldata + end + def length 2 end @@ -3155,6 +3609,14 @@ def to_a(_iseq) [:opt_le, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptLE) && other.calldata == calldata + end + def length 2 end @@ -3204,6 +3666,14 @@ def to_a(_iseq) [:opt_length, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptLength) && other.calldata == calldata + end + def length 2 end @@ -3253,6 +3723,14 @@ def to_a(_iseq) [:opt_lt, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptLT) && other.calldata == calldata + end + def length 2 end @@ -3302,6 +3780,14 @@ def to_a(_iseq) [:opt_ltlt, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptLTLT) && other.calldata == calldata + end + def length 2 end @@ -3352,6 +3838,14 @@ def to_a(_iseq) [:opt_minus, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptMinus) && other.calldata == calldata + end + def length 2 end @@ -3401,6 +3895,14 @@ def to_a(_iseq) [:opt_mod, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptMod) && other.calldata == calldata + end + def length 2 end @@ -3450,6 +3952,14 @@ def to_a(_iseq) [:opt_mult, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptMult) && other.calldata == calldata + end + def length 2 end @@ -3505,6 +4015,16 @@ def to_a(_iseq) [:opt_neq, eq_calldata.to_h, neq_calldata.to_h] end + def deconstruct_keys(keys) + { eq_calldata: eq_calldata, neq_calldata: neq_calldata } + end + + def ==(other) + other.is_a?(OptNEq) && + other.eq_calldata == eq_calldata && + other.neq_calldata == neq_calldata + end + def length 3 end @@ -3554,6 +4074,14 @@ def to_a(_iseq) [:opt_newarray_max, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(OptNewArrayMax) && other.number == number + end + def length 2 end @@ -3602,6 +4130,14 @@ def to_a(_iseq) [:opt_newarray_min, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(OptNewArrayMin) && other.number == number + end + def length 2 end @@ -3651,6 +4187,14 @@ def to_a(_iseq) [:opt_nil_p, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptNilP) && other.calldata == calldata + end + def length 2 end @@ -3698,6 +4242,14 @@ def to_a(_iseq) [:opt_not, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptNot) && other.calldata == calldata + end + def length 2 end @@ -3747,6 +4299,14 @@ def to_a(_iseq) [:opt_or, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptOr) && other.calldata == calldata + end + def length 2 end @@ -3796,6 +4356,14 @@ def to_a(_iseq) [:opt_plus, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptPlus) && other.calldata == calldata + end + def length 2 end @@ -3844,6 +4412,14 @@ def to_a(_iseq) [:opt_regexpmatch2, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptRegExpMatch2) && other.calldata == calldata + end + def length 2 end @@ -3892,6 +4468,14 @@ def to_a(_iseq) [:opt_send_without_block, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptSendWithoutBlock) && other.calldata == calldata + end + def length 2 end @@ -3941,6 +4525,14 @@ def to_a(_iseq) [:opt_size, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptSize) && other.calldata == calldata + end + def length 2 end @@ -3993,6 +4585,16 @@ def to_a(_iseq) [:opt_str_freeze, object, calldata.to_h] end + def deconstruct_keys(keys) + { object: object, calldata: calldata } + end + + def ==(other) + other.is_a?(OptStrFreeze) && + other.object == object && + other.calldata == calldata + end + def length 3 end @@ -4045,6 +4647,16 @@ def to_a(_iseq) [:opt_str_uminus, object, calldata.to_h] end + def deconstruct_keys(keys) + { object: object, calldata: calldata } + end + + def ==(other) + other.is_a?(OptStrUMinus) && + other.object == object && + other.calldata == calldata + end + def length 3 end @@ -4094,6 +4706,14 @@ def to_a(_iseq) [:opt_succ, calldata.to_h] end + def deconstruct_keys(keys) + { calldata: calldata } + end + + def ==(other) + other.is_a?(OptSucc) && other.calldata == calldata + end + def length 2 end @@ -4134,6 +4754,14 @@ def to_a(_iseq) [:pop] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(Pop) + end + def length 1 end @@ -4174,6 +4802,14 @@ def to_a(_iseq) [:putnil] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(PutNil) + end + def length 1 end @@ -4220,6 +4856,14 @@ def to_a(_iseq) [:putobject, object] end + def deconstruct_keys(keys) + { object: object } + end + + def ==(other) + other.is_a?(PutObject) && other.object == object + end + def length 2 end @@ -4262,6 +4906,14 @@ def to_a(_iseq) [:putobject_INT2FIX_0_] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(PutObjectInt2Fix0) + end + def length 1 end @@ -4304,6 +4956,14 @@ def to_a(_iseq) [:putobject_INT2FIX_1_] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(PutObjectInt2Fix1) + end + def length 1 end @@ -4344,6 +5004,14 @@ def to_a(_iseq) [:putself] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(PutSelf) + end + def length 1 end @@ -4396,6 +5064,14 @@ def to_a(_iseq) [:putspecialobject, object] end + def deconstruct_keys(keys) + { object: object } + end + + def ==(other) + other.is_a?(PutSpecialObject) && other.object == object + end + def length 2 end @@ -4451,6 +5127,14 @@ def to_a(_iseq) [:putstring, object] end + def deconstruct_keys(keys) + { object: object } + end + + def ==(other) + other.is_a?(PutString) && other.object == object + end + def length 2 end @@ -4505,6 +5189,16 @@ def to_a(_iseq) [:send, calldata.to_h, block_iseq&.to_a] end + def deconstruct_keys(keys) + { calldata: calldata, block_iseq: block_iseq } + end + + def ==(other) + other.is_a?(Send) && + other.calldata == calldata && + other.block_iseq == block_iseq + end + def length 3 end @@ -4582,6 +5276,16 @@ def to_a(iseq) [:setblockparam, current.local_table.offset(index), level] end + def deconstruct_keys(keys) + { index: index, level: level } + end + + def ==(other) + other.is_a?(SetBlockParam) && + other.index == index && + other.level == level + end + def length 3 end @@ -4635,6 +5339,16 @@ def to_a(_iseq) [:setclassvariable, name, cache] end + def deconstruct_keys(keys) + { name: name, cache: cache } + end + + def ==(other) + other.is_a?(SetClassVariable) && + other.name == name && + other.cache == cache + end + def length 3 end @@ -4684,6 +5398,14 @@ def to_a(_iseq) [:setconstant, name] end + def deconstruct_keys(keys) + { name: name } + end + + def ==(other) + other.is_a?(SetConstant) && other.name == name + end + def length 2 end @@ -4732,6 +5454,14 @@ def to_a(_iseq) [:setglobal, name] end + def deconstruct_keys(keys) + { name: name } + end + + def ==(other) + other.is_a?(SetGlobal) && other.name == name + end + def length 2 end @@ -4790,6 +5520,16 @@ def to_a(_iseq) [:setinstancevariable, name, cache] end + def deconstruct_keys(keys) + { name: name, cache: cache } + end + + def ==(other) + other.is_a?(SetInstanceVariable) && + other.name == name && + other.cache == cache + end + def length 3 end @@ -4844,6 +5584,14 @@ def to_a(iseq) [:setlocal, current.local_table.offset(index), level] end + def deconstruct_keys(keys) + { index: index, level: level } + end + + def ==(other) + other.is_a?(SetLocal) && other.index == index && other.level == level + end + def length 3 end @@ -4893,6 +5641,14 @@ def to_a(iseq) [:setlocal_WC_0, iseq.local_table.offset(index)] end + def deconstruct_keys(keys) + { index: index } + end + + def ==(other) + other.is_a?(SetLocalWC0) && other.index == index + end + def length 2 end @@ -4942,6 +5698,14 @@ def to_a(iseq) [:setlocal_WC_1, iseq.parent_iseq.local_table.offset(index)] end + def deconstruct_keys(keys) + { index: index } + end + + def ==(other) + other.is_a?(SetLocalWC1) && other.index == index + end + def length 2 end @@ -4989,6 +5753,14 @@ def to_a(_iseq) [:setn, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(SetN) && other.number == number + end + def length 2 end @@ -5037,6 +5809,14 @@ def to_a(_iseq) [:setspecial, key] end + def deconstruct_keys(keys) + { key: key } + end + + def ==(other) + other.is_a?(SetSpecial) && other.key == key + end + def length 2 end @@ -5092,6 +5872,14 @@ def to_a(_iseq) [:splatarray, flag] end + def deconstruct_keys(keys) + { flag: flag } + end + + def ==(other) + other.is_a?(SplatArray) && other.flag == flag + end + def length 2 end @@ -5156,6 +5944,14 @@ def to_a(_iseq) [:swap] end + def deconstruct_keys(keys) + {} + end + + def ==(other) + other.is_a?(Swap) + end + def length 1 end @@ -5218,6 +6014,14 @@ def to_a(_iseq) [:throw, type] end + def deconstruct_keys(keys) + { type: type } + end + + def ==(other) + other.is_a?(Throw) && other.type == type + end + def length 2 end @@ -5304,6 +6108,14 @@ def to_a(_iseq) [:topn, number] end + def deconstruct_keys(keys) + { number: number } + end + + def ==(other) + other.is_a?(TopN) && other.number == number + end + def length 2 end @@ -5352,6 +6164,16 @@ def to_a(_iseq) [:toregexp, options, length] end + def deconstruct_keys(keys) + { options: options, length: length } + end + + def ==(other) + other.is_a?(ToRegExp) && + other.options == options && + other.length == length + end + def pops length end diff --git a/lib/syntax_tree/yarv/legacy.rb b/lib/syntax_tree/yarv/legacy.rb index b2e33290..1ee8e0d5 100644 --- a/lib/syntax_tree/yarv/legacy.rb +++ b/lib/syntax_tree/yarv/legacy.rb @@ -34,6 +34,14 @@ def to_a(_iseq) [:getclassvariable, name] end + def deconstruct_keys(keys) + { name: name } + end + + def ==(other) + other.is_a?(GetClassVariable) && other.name == name + end + def length 2 end @@ -90,6 +98,16 @@ def to_a(_iseq) [:opt_getinlinecache, label.name, cache] end + def deconstruct_keys(keys) + { label: label, cache: cache } + end + + def ==(other) + other.is_a?(OptGetInlineCache) && + other.label == label && + other.cache == cache + end + def length 3 end @@ -141,6 +159,14 @@ def to_a(_iseq) [:opt_setinlinecache, cache] end + def deconstruct_keys(keys) + { cache: cache } + end + + def ==(other) + other.is_a?(OptSetInlineCache) && other.cache == cache + end + def length 2 end @@ -190,6 +216,14 @@ def to_a(_iseq) [:setclassvariable, name] end + def deconstruct_keys(keys) + { name: name } + end + + def ==(other) + other.is_a?(SetClassVariable) && other.name == name + end + def length 2 end diff --git a/test/yarv_test.rb b/test/yarv_test.rb index 6f60d74e..4efeae25 100644 --- a/test/yarv_test.rb +++ b/test/yarv_test.rb @@ -288,6 +288,41 @@ def value end end + instructions = + YARV.constants.map { YARV.const_get(_1) } + + YARV::Legacy.constants.map { YARV::Legacy.const_get(_1) } - + [ + YARV::Assembler, + YARV::Bf, + YARV::CallData, + YARV::Compiler, + YARV::Decompiler, + YARV::Disassembler, + YARV::InstructionSequence, + YARV::Legacy, + YARV::LocalTable, + YARV::VM + ] + + interface = %i[ + disasm + to_a + deconstruct_keys + length + pops + pushes + canonical + call + == + ] + + instructions.each do |instruction| + define_method("test_instruction_interface_#{instruction.name}") do + instance_methods = instruction.instance_methods(false) + assert_empty(interface - instance_methods) + end + end + private def assert_decompiles(expected, source) From b47eb46be8cb47fca1474d4c532a99484f59217c Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Wed, 18 Jan 2023 14:22:02 -0300 Subject: [PATCH 07/22] Add arity to Params, DefNode and BlockNode --- CHANGELOG.md | 4 + lib/syntax_tree/node.rb | 37 +++++++++ test/node_test.rb | 163 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b29fcbb..f71e5d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Added + +- Arity has been added to DefNode, BlockNode and Params. The method returns a range where the lower bound is the minimum and the upper bound is the maximum number of arguments that can be used to invoke that block/method definition. + ## [5.2.0] - 2023-01-04 ### Added diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index f19cfb2c..3e35bf41 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -4175,6 +4175,17 @@ def ===(other) def endless? !bodystmt.is_a?(BodyStmt) end + + def arity + case params + when Params + params.arity + when Paren + params.contents.arity + else + 0..0 + end + end end # Defined represents the use of the +defined?+ operator. It can be used with @@ -4362,6 +4373,15 @@ def keywords? opening.is_a?(Kw) end + def arity + case block_var + when BlockVar + block_var.params.arity + else + 0..0 + end + end + private # If this is nested anywhere inside certain nodes, then we can't change @@ -8325,6 +8345,23 @@ def ===(other) keyword_rest === other.keyword_rest && block === other.block end + # Returns a range representing the possible number of arguments accepted + # by this params node not including the block. For example: + # def foo(a, b = 1, c:, d: 2, &block) + # ... + # end + # has arity 2..4 + def arity + optional_keywords = keywords.count { |_label, value| value } + lower_bound = + requireds.length + posts.length + keywords.length - optional_keywords + + upper_bound = + lower_bound + optionals.length + + optional_keywords if keyword_rest.nil? && rest.nil? + lower_bound..upper_bound + end + private def format_contents(q, parts) diff --git a/test/node_test.rb b/test/node_test.rb index 3d700e73..8741f274 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -1058,6 +1058,169 @@ def test_root_class_raises_not_implemented_errors end end + def test_arity_no_args + source = <<~SOURCE + def foo + end + SOURCE + + at = location(chars: 0..11, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(0..0, node.arity) + node + end + end + + def test_arity_positionals + source = <<~SOURCE + def foo(a, b = 1) + end + SOURCE + + at = location(chars: 0..21, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(1..2, node.arity) + node + end + end + + def test_arity_rest + source = <<~SOURCE + def foo(a, *b) + end + SOURCE + + at = location(chars: 0..18, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(1.., node.arity) + node + end + end + + def test_arity_keyword_rest + source = <<~SOURCE + def foo(a, **b) + end + SOURCE + + at = location(chars: 0..19, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(1.., node.arity) + node + end + end + + def test_arity_keywords + source = <<~SOURCE + def foo(a:, b: 1) + end + SOURCE + + at = location(chars: 0..21, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(1..2, node.arity) + node + end + end + + def test_arity_mixed + source = <<~SOURCE + def foo(a, b = 1, c:, d: 2) + end + SOURCE + + at = location(chars: 0..31, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(2..4, node.arity) + node + end + end + + guard_version("2.7.3") do + def test_arity_arg_forward + source = <<~SOURCE + def foo(...) + end + SOURCE + + at = location(chars: 0..16, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(0.., node.arity) + node + end + end + end + + guard_version("3.0.0") do + def test_arity_positional_and_arg_forward + source = <<~SOURCE + def foo(a, ...) + end + SOURCE + + at = location(chars: 0..19, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(1.., node.arity) + node + end + end + end + + def test_arity_no_parenthesis + source = <<~SOURCE + def foo a, b = 1 + end + SOURCE + + at = location(chars: 0..20, columns: 0..3, lines: 1..2) + assert_node(DefNode, source, at: at) do |node| + assert_equal(1..2, node.arity) + node + end + end + + def test_block_arity_positionals + source = <<~SOURCE + [].each do |a, b, c| + end + SOURCE + + at = location(chars: 8..24, columns: 8..3, lines: 1..2) + assert_node(BlockNode, source, at: at) do |node| + block = node.block + assert_equal(3..3, block.arity) + block + end + end + + def test_block_arity_with_optional + source = <<~SOURCE + [].each do |a, b = 1| + end + SOURCE + + at = location(chars: 8..25, columns: 8..3, lines: 1..2) + assert_node(BlockNode, source, at: at) do |node| + block = node.block + assert_equal(1..2, block.arity) + block + end + end + + def test_block_arity_with_optional_keyword + source = <<~SOURCE + [].each do |a, b: 2| + end + SOURCE + + at = location(chars: 8..24, columns: 8..3, lines: 1..2) + assert_node(BlockNode, source, at: at) do |node| + block = node.block + assert_equal(1..2, block.arity) + block + end + end + private def location(lines: 1..1, chars: 0..0, columns: 0..0) From 2c12f9a55243b215a80660d64256c99b6e43ea7b Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Wed, 18 Jan 2023 15:14:18 -0300 Subject: [PATCH 08/22] Add arity to CallNode, VCall, CommandCall and Command --- CHANGELOG.md | 1 + lib/syntax_tree/node.rb | 40 ++++++++++ test/node_test.rb | 173 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f71e5d21..cf347efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added - Arity has been added to DefNode, BlockNode and Params. The method returns a range where the lower bound is the minimum and the upper bound is the maximum number of arguments that can be used to invoke that block/method definition. +- Arity has been added to CallNode, Command, CommandCall and VCall nodes. The method returns the number of arguments included in the invocation. For splats, double splats or argument forwards, this method returns Float::INFINITY. ## [5.2.0] - 2023-01-04 diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 3e35bf41..d1d40154 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -775,6 +775,10 @@ def ===(other) other.is_a?(ArgParen) && arguments === other.arguments end + def arity + arguments&.arity || 0 + end + private def trailing_comma? @@ -848,6 +852,22 @@ def format(q) def ===(other) other.is_a?(Args) && ArrayMatch.call(parts, other.parts) end + + def arity + accepts_infinite_arguments? ? Float::INFINITY : parts.length + end + + private + + def accepts_infinite_arguments? + parts.any? do |part| + part.is_a?(ArgStar) || part.is_a?(ArgsForward) || + ( + part.is_a?(BareAssocHash) && + part.assocs.any? { |p| p.is_a?(AssocSplat) } + ) + end + end end # ArgBlock represents using a block operator on an expression. @@ -1008,6 +1028,10 @@ def format(q) def ===(other) other.is_a?(ArgsForward) end + + def arity + Float::INFINITY + end end # ArrayLiteral represents an array literal, which can optionally contain @@ -3068,6 +3092,10 @@ def format_contents(q) end end end + + def arity + arguments&.arity || 0 + end end # Case represents the beginning of a case chain. @@ -3481,6 +3509,10 @@ def ===(other) arguments === other.arguments && block === other.block end + def arity + arguments.arity + end + private def align(q, node, &block) @@ -3646,6 +3678,10 @@ def ===(other) arguments === other.arguments && block === other.block end + def arity + arguments&.arity || 0 + end + private def argument_alignment(q, doc) @@ -11631,6 +11667,10 @@ def ===(other) def access_control? @access_control ||= %w[private protected public].include?(value.value) end + + def arity + 0 + end end # VoidStmt represents an empty lexical block of code. diff --git a/test/node_test.rb b/test/node_test.rb index 8741f274..7254c086 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -1221,6 +1221,179 @@ def test_block_arity_with_optional_keyword end end + def test_call_node_arity_positional_arguments + source = <<~SOURCE + foo(1, 2, 3) + SOURCE + + at = location(chars: 0..12, columns: 0..3, lines: 1..1) + assert_node(CallNode, source, at: at) do |node| + assert_equal(3, node.arity) + node + end + end + + def test_call_node_arity_keyword_arguments + source = <<~SOURCE + foo(bar, something: 123) + SOURCE + + at = location(chars: 0..24, columns: 0..24, lines: 1..1) + assert_node(CallNode, source, at: at) do |node| + assert_equal(2, node.arity) + node + end + end + + def test_call_node_arity_splat_arguments + source = <<~SOURCE + foo(*bar) + SOURCE + + at = location(chars: 0..9, columns: 0..9, lines: 1..1) + assert_node(CallNode, source, at: at) do |node| + assert_equal(Float::INFINITY, node.arity) + node + end + end + + def test_call_node_arity_keyword_rest_arguments + source = <<~SOURCE + foo(**bar) + SOURCE + + at = location(chars: 0..10, columns: 0..10, lines: 1..1) + assert_node(CallNode, source, at: at) do |node| + assert_equal(Float::INFINITY, node.arity) + node + end + end + + guard_version("2.7.3") do + def test_call_node_arity_arg_forward_arguments + source = <<~SOURCE + def foo(...) + bar(...) + end + SOURCE + + at = location(chars: 15..23, columns: 2..10, lines: 2..2) + assert_node(CallNode, source, at: at) do |node| + call = node.bodystmt.statements.body.first + assert_equal(Float::INFINITY, call.arity) + call + end + end + end + + def test_command_arity_positional_arguments + source = <<~SOURCE + foo 1, 2, 3 + SOURCE + + at = location(chars: 0..11, columns: 0..3, lines: 1..1) + assert_node(Command, source, at: at) do |node| + assert_equal(3, node.arity) + node + end + end + + def test_command_arity_keyword_arguments + source = <<~SOURCE + foo bar, something: 123 + SOURCE + + at = location(chars: 0..23, columns: 0..23, lines: 1..1) + assert_node(Command, source, at: at) do |node| + assert_equal(2, node.arity) + node + end + end + + def test_command_arity_splat_arguments + source = <<~SOURCE + foo *bar + SOURCE + + at = location(chars: 0..8, columns: 0..8, lines: 1..1) + assert_node(Command, source, at: at) do |node| + assert_equal(Float::INFINITY, node.arity) + node + end + end + + def test_command_arity_keyword_rest_arguments + source = <<~SOURCE + foo **bar + SOURCE + + at = location(chars: 0..9, columns: 0..9, lines: 1..1) + assert_node(Command, source, at: at) do |node| + assert_equal(Float::INFINITY, node.arity) + node + end + end + + def test_command_call_arity_positional_arguments + source = <<~SOURCE + object.foo 1, 2, 3 + SOURCE + + at = location(chars: 0..18, columns: 0..3, lines: 1..1) + assert_node(CommandCall, source, at: at) do |node| + assert_equal(3, node.arity) + node + end + end + + def test_command_call_arity_keyword_arguments + source = <<~SOURCE + object.foo bar, something: 123 + SOURCE + + at = location(chars: 0..30, columns: 0..30, lines: 1..1) + assert_node(CommandCall, source, at: at) do |node| + assert_equal(2, node.arity) + node + end + end + + def test_command_call_arity_splat_arguments + source = <<~SOURCE + object.foo *bar + SOURCE + + at = location(chars: 0..15, columns: 0..15, lines: 1..1) + assert_node(CommandCall, source, at: at) do |node| + assert_equal(Float::INFINITY, node.arity) + node + end + end + + def test_command_call_arity_keyword_rest_arguments + source = <<~SOURCE + object.foo **bar + SOURCE + + at = location(chars: 0..16, columns: 0..16, lines: 1..1) + assert_node(CommandCall, source, at: at) do |node| + assert_equal(Float::INFINITY, node.arity) + node + end + end + + def test_vcall_arity + source = <<~SOURCE + foo + SOURCE + + at = location(chars: 0..3, columns: 0..3, lines: 1..1) + assert_node(VCall, source, at: at) do |node| + assert_equal(0, node.arity) + node + end + end + private def location(lines: 1..1, chars: 0..0, columns: 0..0) From 66613acd533b7c01f008b50741e0d3c8f7b308d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Jan 2023 17:12:31 +0000 Subject: [PATCH 09/22] Bump actions/configure-pages from 2 to 3 Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 2 to 3. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](https://github.com/actions/configure-pages/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index fc02f2fe..6c64676d 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -27,7 +27,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Setup Pages - uses: actions/configure-pages@v2 + uses: actions/configure-pages@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 8d9fa5a2bd87a6a883c18e18c975837447888ab6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 18 Jan 2023 13:00:02 -0500 Subject: [PATCH 10/22] Enhance and test the interface for YARV instructions --- .rubocop.yml | 1 + lib/syntax_tree/yarv/instructions.rb | 258 ++++++++++++--------------- lib/syntax_tree/yarv/legacy.rb | 19 +- test/yarv_test.rb | 26 +-- 4 files changed, 141 insertions(+), 163 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 069041bd..0212027b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,6 +8,7 @@ AllCops: TargetRubyVersion: 2.7 Exclude: - '{.git,.github,bin,coverage,pkg,spec,test/fixtures,vendor,tmp}/**/*' + - test/ruby-syntax-fixtures/**/* - test.rb Layout/LineLength: diff --git a/lib/syntax_tree/yarv/instructions.rb b/lib/syntax_tree/yarv/instructions.rb index 20068eac..bba06f8d 100644 --- a/lib/syntax_tree/yarv/instructions.rb +++ b/lib/syntax_tree/yarv/instructions.rb @@ -91,7 +91,7 @@ def to_a(_iseq) [:adjuststack, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -147,7 +147,7 @@ def to_a(_iseq) [:anytostring] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -213,7 +213,7 @@ def to_a(_iseq) [:branchif, label.name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { label: label } end @@ -274,7 +274,7 @@ def to_a(_iseq) [:branchnil, label.name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { label: label } end @@ -334,7 +334,7 @@ def to_a(_iseq) [:branchunless, label.name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { label: label } end @@ -405,7 +405,7 @@ def to_a(iseq) ] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { keyword_bits_index: keyword_bits_index, keyword_index: keyword_index } end @@ -469,7 +469,7 @@ def to_a(_iseq) [:checkmatch, type] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { type: type } end @@ -619,7 +619,7 @@ def to_a(_iseq) [:checktype, type] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { type: type } end @@ -722,7 +722,7 @@ def to_a(_iseq) [:concatarray] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -782,7 +782,7 @@ def to_a(_iseq) [:concatstrings, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -853,15 +853,13 @@ def to_a(_iseq) [:defineclass, name, class_iseq.to_a, flags] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, class_iseq: class_iseq, flags: flags } end def ==(other) - other.is_a?(DefineClass) && - other.name == name && - other.class_iseq == class_iseq && - other.flags == flags + other.is_a?(DefineClass) && other.name == name && + other.class_iseq == class_iseq && other.flags == flags end def length @@ -992,14 +990,12 @@ def to_a(_iseq) [:defined, type, name, message] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { type: type, name: name, message: message } end def ==(other) - other.is_a?(Defined) && - other.type == type && - other.name == name && + other.is_a?(Defined) && other.type == type && other.name == name && other.message == message end @@ -1093,13 +1089,12 @@ def to_a(_iseq) [:definemethod, method_name, method_iseq.to_a] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { method_name: method_name, method_iseq: method_iseq } end def ==(other) - other.is_a?(DefineMethod) && - other.method_name == method_name && + other.is_a?(DefineMethod) && other.method_name == method_name && other.method_iseq == method_iseq end @@ -1175,13 +1170,12 @@ def to_a(_iseq) [:definesmethod, method_name, method_iseq.to_a] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { method_name: method_name, method_iseq: method_iseq } end def ==(other) - other.is_a?(DefineSMethod) && - other.method_name == method_name && + other.is_a?(DefineSMethod) && other.method_name == method_name && other.method_iseq == method_iseq end @@ -1242,7 +1236,7 @@ def to_a(_iseq) [:dup] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -1296,7 +1290,7 @@ def to_a(_iseq) [:duparray, object] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object } end @@ -1350,7 +1344,7 @@ def to_a(_iseq) [:duphash, object] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object } end @@ -1404,7 +1398,7 @@ def to_a(_iseq) [:dupn, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -1463,13 +1457,12 @@ def to_a(_iseq) [:expandarray, number, flags] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number, flags: flags } end def ==(other) - other.is_a?(ExpandArray) && - other.number == number && + other.is_a?(ExpandArray) && other.number == number && other.flags == flags end @@ -1564,13 +1557,12 @@ def to_a(iseq) [:getblockparam, current.local_table.offset(index), level] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index, level: level } end def ==(other) - other.is_a?(GetBlockParam) && - other.index == index && + other.is_a?(GetBlockParam) && other.index == index && other.level == level end @@ -1631,13 +1623,12 @@ def to_a(iseq) [:getblockparamproxy, current.local_table.offset(index), level] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index, level: level } end def ==(other) - other.is_a?(GetBlockParamProxy) && - other.index == index && + other.is_a?(GetBlockParamProxy) && other.index == index && other.level == level end @@ -1693,13 +1684,12 @@ def to_a(_iseq) [:getclassvariable, name, cache] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, cache: cache } end def ==(other) - other.is_a?(GetClassVariable) && - other.name == name && + other.is_a?(GetClassVariable) && other.name == name && other.cache == cache end @@ -1753,7 +1743,7 @@ def to_a(_iseq) [:getconstant, name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name } end @@ -1823,7 +1813,7 @@ def to_a(_iseq) [:getglobal, name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name } end @@ -1890,13 +1880,12 @@ def to_a(_iseq) [:getinstancevariable, name, cache] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, cache: cache } end def ==(other) - other.is_a?(GetInstanceVariable) && - other.name == name && + other.is_a?(GetInstanceVariable) && other.name == name && other.cache == cache end @@ -1954,7 +1943,7 @@ def to_a(iseq) [:getlocal, current.local_table.offset(index), level] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index, level: level } end @@ -2011,7 +2000,7 @@ def to_a(iseq) [:getlocal_WC_0, iseq.local_table.offset(index)] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index } end @@ -2068,7 +2057,7 @@ def to_a(iseq) [:getlocal_WC_1, iseq.parent_iseq.local_table.offset(index)] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index } end @@ -2127,7 +2116,7 @@ def to_a(_iseq) [:getspecial, key, type] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { key: key, type: type } end @@ -2183,7 +2172,7 @@ def to_a(_iseq) [:intern] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -2241,7 +2230,7 @@ def to_a(_iseq) [:invokeblock, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -2304,13 +2293,12 @@ def to_a(_iseq) [:invokesuper, calldata.to_h, block_iseq&.to_a] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata, block_iseq: block_iseq } end def ==(other) - other.is_a?(InvokeSuper) && - other.calldata == calldata && + other.is_a?(InvokeSuper) && other.calldata == calldata && other.block_iseq == block_iseq end @@ -2385,7 +2373,7 @@ def to_a(_iseq) [:jump, label.name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { label: label } end @@ -2433,7 +2421,7 @@ def to_a(_iseq) [:leave] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -2491,7 +2479,7 @@ def to_a(_iseq) [:newarray, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -2547,7 +2535,7 @@ def to_a(_iseq) [:newarraykwsplat, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -2605,7 +2593,7 @@ def to_a(_iseq) [:newhash, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -2664,7 +2652,7 @@ def to_a(_iseq) [:newrange, exclude_end] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { exclude_end: exclude_end } end @@ -2713,7 +2701,7 @@ def to_a(_iseq) [:nop] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -2770,7 +2758,7 @@ def to_a(_iseq) [:objtostring, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -2829,7 +2817,7 @@ def to_a(_iseq) [:once, iseq.to_a, cache] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { iseq: iseq, cache: cache } end @@ -2888,7 +2876,7 @@ def to_a(_iseq) [:opt_and, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -2944,7 +2932,7 @@ def to_a(_iseq) [:opt_aref, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3005,13 +2993,12 @@ def to_a(_iseq) [:opt_aref_with, object, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object, calldata: calldata } end def ==(other) - other.is_a?(OptArefWith) && - other.object == object && + other.is_a?(OptArefWith) && other.object == object && other.calldata == calldata end @@ -3064,7 +3051,7 @@ def to_a(_iseq) [:opt_aset, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3124,13 +3111,12 @@ def to_a(_iseq) [:opt_aset_with, object, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object, calldata: calldata } end def ==(other) - other.is_a?(OptAsetWith) && - other.object == object && + other.is_a?(OptAsetWith) && other.object == object && other.calldata == calldata end @@ -3202,7 +3188,7 @@ def to_a(_iseq) ] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { case_dispatch_hash: case_dispatch_hash, else_label: else_label } end @@ -3261,7 +3247,7 @@ def to_a(_iseq) [:opt_div, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3317,7 +3303,7 @@ def to_a(_iseq) [:opt_empty_p, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3374,7 +3360,7 @@ def to_a(_iseq) [:opt_eq, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3431,7 +3417,7 @@ def to_a(_iseq) [:opt_ge, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3488,7 +3474,7 @@ def to_a(_iseq) [:opt_getconstant_path, names] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { names: names } end @@ -3552,7 +3538,7 @@ def to_a(_iseq) [:opt_gt, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3609,7 +3595,7 @@ def to_a(_iseq) [:opt_le, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3666,7 +3652,7 @@ def to_a(_iseq) [:opt_length, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3723,7 +3709,7 @@ def to_a(_iseq) [:opt_lt, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3780,7 +3766,7 @@ def to_a(_iseq) [:opt_ltlt, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3838,7 +3824,7 @@ def to_a(_iseq) [:opt_minus, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3895,7 +3881,7 @@ def to_a(_iseq) [:opt_mod, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -3952,7 +3938,7 @@ def to_a(_iseq) [:opt_mult, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4015,13 +4001,12 @@ def to_a(_iseq) [:opt_neq, eq_calldata.to_h, neq_calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { eq_calldata: eq_calldata, neq_calldata: neq_calldata } end def ==(other) - other.is_a?(OptNEq) && - other.eq_calldata == eq_calldata && + other.is_a?(OptNEq) && other.eq_calldata == eq_calldata && other.neq_calldata == neq_calldata end @@ -4074,7 +4059,7 @@ def to_a(_iseq) [:opt_newarray_max, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -4130,7 +4115,7 @@ def to_a(_iseq) [:opt_newarray_min, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -4187,7 +4172,7 @@ def to_a(_iseq) [:opt_nil_p, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4242,7 +4227,7 @@ def to_a(_iseq) [:opt_not, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4299,7 +4284,7 @@ def to_a(_iseq) [:opt_or, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4356,7 +4341,7 @@ def to_a(_iseq) [:opt_plus, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4412,7 +4397,7 @@ def to_a(_iseq) [:opt_regexpmatch2, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4468,7 +4453,7 @@ def to_a(_iseq) [:opt_send_without_block, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4525,7 +4510,7 @@ def to_a(_iseq) [:opt_size, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4585,13 +4570,12 @@ def to_a(_iseq) [:opt_str_freeze, object, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object, calldata: calldata } end def ==(other) - other.is_a?(OptStrFreeze) && - other.object == object && + other.is_a?(OptStrFreeze) && other.object == object && other.calldata == calldata end @@ -4647,13 +4631,12 @@ def to_a(_iseq) [:opt_str_uminus, object, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object, calldata: calldata } end def ==(other) - other.is_a?(OptStrUMinus) && - other.object == object && + other.is_a?(OptStrUMinus) && other.object == object && other.calldata == calldata end @@ -4706,7 +4689,7 @@ def to_a(_iseq) [:opt_succ, calldata.to_h] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata } end @@ -4754,7 +4737,7 @@ def to_a(_iseq) [:pop] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -4802,7 +4785,7 @@ def to_a(_iseq) [:putnil] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -4856,7 +4839,7 @@ def to_a(_iseq) [:putobject, object] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object } end @@ -4906,7 +4889,7 @@ def to_a(_iseq) [:putobject_INT2FIX_0_] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -4956,7 +4939,7 @@ def to_a(_iseq) [:putobject_INT2FIX_1_] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -5004,7 +4987,7 @@ def to_a(_iseq) [:putself] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -5064,7 +5047,7 @@ def to_a(_iseq) [:putspecialobject, object] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object } end @@ -5127,7 +5110,7 @@ def to_a(_iseq) [:putstring, object] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { object: object } end @@ -5189,13 +5172,12 @@ def to_a(_iseq) [:send, calldata.to_h, block_iseq&.to_a] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { calldata: calldata, block_iseq: block_iseq } end def ==(other) - other.is_a?(Send) && - other.calldata == calldata && + other.is_a?(Send) && other.calldata == calldata && other.block_iseq == block_iseq end @@ -5276,13 +5258,12 @@ def to_a(iseq) [:setblockparam, current.local_table.offset(index), level] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index, level: level } end def ==(other) - other.is_a?(SetBlockParam) && - other.index == index && + other.is_a?(SetBlockParam) && other.index == index && other.level == level end @@ -5339,13 +5320,12 @@ def to_a(_iseq) [:setclassvariable, name, cache] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, cache: cache } end def ==(other) - other.is_a?(SetClassVariable) && - other.name == name && + other.is_a?(SetClassVariable) && other.name == name && other.cache == cache end @@ -5398,7 +5378,7 @@ def to_a(_iseq) [:setconstant, name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name } end @@ -5454,7 +5434,7 @@ def to_a(_iseq) [:setglobal, name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name } end @@ -5520,13 +5500,12 @@ def to_a(_iseq) [:setinstancevariable, name, cache] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name, cache: cache } end def ==(other) - other.is_a?(SetInstanceVariable) && - other.name == name && + other.is_a?(SetInstanceVariable) && other.name == name && other.cache == cache end @@ -5584,7 +5563,7 @@ def to_a(iseq) [:setlocal, current.local_table.offset(index), level] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index, level: level } end @@ -5641,7 +5620,7 @@ def to_a(iseq) [:setlocal_WC_0, iseq.local_table.offset(index)] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index } end @@ -5698,7 +5677,7 @@ def to_a(iseq) [:setlocal_WC_1, iseq.parent_iseq.local_table.offset(index)] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { index: index } end @@ -5753,7 +5732,7 @@ def to_a(_iseq) [:setn, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -5809,7 +5788,7 @@ def to_a(_iseq) [:setspecial, key] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { key: key } end @@ -5872,7 +5851,7 @@ def to_a(_iseq) [:splatarray, flag] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { flag: flag } end @@ -5944,7 +5923,7 @@ def to_a(_iseq) [:swap] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) {} end @@ -6014,7 +5993,7 @@ def to_a(_iseq) [:throw, type] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { type: type } end @@ -6108,7 +6087,7 @@ def to_a(_iseq) [:topn, number] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { number: number } end @@ -6164,13 +6143,12 @@ def to_a(_iseq) [:toregexp, options, length] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { options: options, length: length } end def ==(other) - other.is_a?(ToRegExp) && - other.options == options && + other.is_a?(ToRegExp) && other.options == options && other.length == length end diff --git a/lib/syntax_tree/yarv/legacy.rb b/lib/syntax_tree/yarv/legacy.rb index 1ee8e0d5..ab9b00df 100644 --- a/lib/syntax_tree/yarv/legacy.rb +++ b/lib/syntax_tree/yarv/legacy.rb @@ -34,10 +34,10 @@ def to_a(_iseq) [:getclassvariable, name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name } end - + def ==(other) other.is_a?(GetClassVariable) && other.name == name end @@ -98,13 +98,12 @@ def to_a(_iseq) [:opt_getinlinecache, label.name, cache] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { label: label, cache: cache } end - + def ==(other) - other.is_a?(OptGetInlineCache) && - other.label == label && + other.is_a?(OptGetInlineCache) && other.label == label && other.cache == cache end @@ -159,10 +158,10 @@ def to_a(_iseq) [:opt_setinlinecache, cache] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { cache: cache } end - + def ==(other) other.is_a?(OptSetInlineCache) && other.cache == cache end @@ -216,10 +215,10 @@ def to_a(_iseq) [:setclassvariable, name] end - def deconstruct_keys(keys) + def deconstruct_keys(_keys) { name: name } end - + def ==(other) other.is_a?(SetClassVariable) && other.name == name end diff --git a/test/yarv_test.rb b/test/yarv_test.rb index 4efeae25..be7c4c2d 100644 --- a/test/yarv_test.rb +++ b/test/yarv_test.rb @@ -290,19 +290,19 @@ def value instructions = YARV.constants.map { YARV.const_get(_1) } + - YARV::Legacy.constants.map { YARV::Legacy.const_get(_1) } - - [ - YARV::Assembler, - YARV::Bf, - YARV::CallData, - YARV::Compiler, - YARV::Decompiler, - YARV::Disassembler, - YARV::InstructionSequence, - YARV::Legacy, - YARV::LocalTable, - YARV::VM - ] + YARV::Legacy.constants.map { YARV::Legacy.const_get(_1) } - + [ + YARV::Assembler, + YARV::Bf, + YARV::CallData, + YARV::Compiler, + YARV::Decompiler, + YARV::Disassembler, + YARV::InstructionSequence, + YARV::Legacy, + YARV::LocalTable, + YARV::VM + ] interface = %i[ disasm From c1cd547451e04809c1a261e59c58c75641410baf Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 20 Jan 2023 09:48:08 -0500 Subject: [PATCH 11/22] A couple of convenience APIs --- lib/syntax_tree/yarv/assembler.rb | 16 +++++++----- lib/syntax_tree/yarv/compiler.rb | 2 +- lib/syntax_tree/yarv/decompiler.rb | 2 +- lib/syntax_tree/yarv/vm.rb | 4 +++ test/yarv_test.rb | 42 +++++++++++++++--------------- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/lib/syntax_tree/yarv/assembler.rb b/lib/syntax_tree/yarv/assembler.rb index ec467b58..ac400506 100644 --- a/lib/syntax_tree/yarv/assembler.rb +++ b/lib/syntax_tree/yarv/assembler.rb @@ -62,22 +62,26 @@ def visit_string_literal(node) "constant-from" ].freeze - attr_reader :filepath + attr_reader :lines - def initialize(filepath) - @filepath = filepath + def initialize(lines) + @lines = lines end def assemble iseq = InstructionSequence.new("
", "", 1, :top) - assemble_iseq(iseq, File.readlines(filepath, chomp: true)) + assemble_iseq(iseq, lines) iseq.compile! iseq end - def self.assemble(filepath) - new(filepath).assemble + def self.assemble(source) + new(source.lines(chomp: true)).assemble + end + + def self.assemble_file(filepath) + new(File.readlines(filepath, chomp: true)).assemble end private diff --git a/lib/syntax_tree/yarv/compiler.rb b/lib/syntax_tree/yarv/compiler.rb index 4c9a4d50..c1b4d6dd 100644 --- a/lib/syntax_tree/yarv/compiler.rb +++ b/lib/syntax_tree/yarv/compiler.rb @@ -285,7 +285,7 @@ def visit_unsupported(_node) # if we need to return the value of the last statement. attr_reader :last_statement - def initialize(options) + def initialize(options = Options.new) @options = options @iseq = nil @last_statement = false diff --git a/lib/syntax_tree/yarv/decompiler.rb b/lib/syntax_tree/yarv/decompiler.rb index 47d2a2df..753ba80a 100644 --- a/lib/syntax_tree/yarv/decompiler.rb +++ b/lib/syntax_tree/yarv/decompiler.rb @@ -97,7 +97,7 @@ def decompile(iseq) clause << Next(Args([])) when Leave value = Args([clause.pop]) - clause << (iseq.type == :top ? Break(value) : ReturnNode(value)) + clause << (iseq.type != :top ? Break(value) : ReturnNode(value)) when OptAnd, OptDiv, OptEq, OptGE, OptGT, OptLE, OptLT, OptLTLT, OptMinus, OptMod, OptMult, OptOr, OptPlus left, right = clause.pop(2) diff --git a/lib/syntax_tree/yarv/vm.rb b/lib/syntax_tree/yarv/vm.rb index 1bbb82ed..b303944d 100644 --- a/lib/syntax_tree/yarv/vm.rb +++ b/lib/syntax_tree/yarv/vm.rb @@ -219,6 +219,10 @@ def initialize(events = NullEvents.new) @frame = nil end + def self.run(iseq) + new.run_top_frame(iseq) + end + ########################################################################## # Helper methods for frames ########################################################################## diff --git a/test/yarv_test.rb b/test/yarv_test.rb index be7c4c2d..e3995435 100644 --- a/test/yarv_test.rb +++ b/test/yarv_test.rb @@ -6,27 +6,27 @@ module SyntaxTree class YARVTest < Minitest::Test CASES = { - "0" => "break 0\n", - "1" => "break 1\n", - "2" => "break 2\n", - "1.0" => "break 1.0\n", - "1 + 2" => "break 1 + 2\n", - "1 - 2" => "break 1 - 2\n", - "1 * 2" => "break 1 * 2\n", - "1 / 2" => "break 1 / 2\n", - "1 % 2" => "break 1 % 2\n", - "1 < 2" => "break 1 < 2\n", - "1 <= 2" => "break 1 <= 2\n", - "1 > 2" => "break 1 > 2\n", - "1 >= 2" => "break 1 >= 2\n", - "1 == 2" => "break 1 == 2\n", - "1 != 2" => "break 1 != 2\n", - "1 & 2" => "break 1 & 2\n", - "1 | 2" => "break 1 | 2\n", - "1 << 2" => "break 1 << 2\n", - "1 >> 2" => "break 1.>>(2)\n", - "1 ** 2" => "break 1.**(2)\n", - "a = 1; a" => "a = 1\nbreak a\n" + "0" => "return 0\n", + "1" => "return 1\n", + "2" => "return 2\n", + "1.0" => "return 1.0\n", + "1 + 2" => "return 1 + 2\n", + "1 - 2" => "return 1 - 2\n", + "1 * 2" => "return 1 * 2\n", + "1 / 2" => "return 1 / 2\n", + "1 % 2" => "return 1 % 2\n", + "1 < 2" => "return 1 < 2\n", + "1 <= 2" => "return 1 <= 2\n", + "1 > 2" => "return 1 > 2\n", + "1 >= 2" => "return 1 >= 2\n", + "1 == 2" => "return 1 == 2\n", + "1 != 2" => "return 1 != 2\n", + "1 & 2" => "return 1 & 2\n", + "1 | 2" => "return 1 | 2\n", + "1 << 2" => "return 1 << 2\n", + "1 >> 2" => "return 1.>>(2)\n", + "1 ** 2" => "return 1.**(2)\n", + "a = 1; a" => "a = 1\nreturn a\n" }.freeze CASES.each do |source, expected| From 68fa0ad5987ded34d58e4f1c3c932cb75d6f5a04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:10:47 +0000 Subject: [PATCH 12/22] Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.3.5 to 1.3.6. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.3.5...v1.3.6) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 514ac27a..e54c9100 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.3.5 + uses: dependabot/fetch-metadata@v1.3.6 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs From bce6b87b0ab8b0c02de62b86da0c75f680ea5df6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 25 Jan 2023 10:44:50 -0500 Subject: [PATCH 13/22] Handle invalid byte sequences in UTF-8 --- lib/syntax_tree/parser.rb | 21 +++++++++++++++++++-- test/parser_test.rb | 9 +++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index 602bb98f..99b703d0 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -1103,6 +1103,7 @@ def on_command_call(receiver, operator, message, arguments) # :call-seq: # on_comment: (String value) -> Comment def on_comment(value) + # char is the index of the # character in the source. char = char_pos location = Location.token( @@ -1112,8 +1113,24 @@ def on_comment(value) size: value.size - 1 ) - index = source.rindex(/[^\t ]/, char - 1) if char != 0 - inline = index && (source[index] != "\n") + # Loop backward in the source string, starting from the beginning of the + # comment, and find the first character that is not a space or a tab. If + # index is -1, this indicates that we've checked all of the characters + # back to the start of the source, so this comment must be at the + # beginning of the file. + # + # We are purposefully not using rindex or regular expressions here because + # they check if there are invalid characters, which is actually possible + # with the use of __END__ syntax. + index = char - 1 + while index > -1 && (source[index] == "\t" || source[index] == " ") + index -= 1 + end + + # If we found a character that was not a space or a tab before the comment + # and it's a newline, then this comment is inline. Otherwise, it stands on + # its own and can be attached as its own node in the tree. + inline = index != -1 && source[index] != "\n" comment = Comment.new(value: value.chomp, inline: inline, location: location) diff --git a/test/parser_test.rb b/test/parser_test.rb index 6048cf11..8d6c0a16 100644 --- a/test/parser_test.rb +++ b/test/parser_test.rb @@ -65,5 +65,14 @@ def foo end RUBY end + + def test_does_not_choke_on_invalid_characters_in_source_string + SyntaxTree.parse(<<~RUBY) + # comment + # comment + __END__ + \xC5 + RUBY + end end end From bc9e665798b68081c0cb14c75cb2fddc7c331d40 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 25 Jan 2023 11:38:38 -0500 Subject: [PATCH 14/22] Indexing functionality --- lib/syntax_tree.rb | 15 +++ lib/syntax_tree/index.rb | 223 +++++++++++++++++++++++++++++++++++++++ test/index_test.rb | 59 +++++++++++ 3 files changed, 297 insertions(+) create mode 100644 lib/syntax_tree/index.rb create mode 100644 test/index_test.rb diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index f1217ac3..f5c71aba 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -26,6 +26,7 @@ require_relative "syntax_tree/parser" require_relative "syntax_tree/pattern" require_relative "syntax_tree/search" +require_relative "syntax_tree/index" require_relative "syntax_tree/yarv" require_relative "syntax_tree/yarv/bf" @@ -116,4 +117,18 @@ def self.read(filepath) def self.search(source, query, &block) Search.new(Pattern.new(query).compile).scan(parse(source), &block) end + + # Indexes the given source code to return a list of all class, module, and + # method definitions. Used to quickly provide indexing capability for IDEs or + # documentation generation. + def self.index(source) + Index.index(source) + end + + # Indexes the given file to return a list of all class, module, and method + # definitions. Used to quickly provide indexing capability for IDEs or + # documentation generation. + def self.index_file(filepath) + Index.index_file(filepath) + end end diff --git a/lib/syntax_tree/index.rb b/lib/syntax_tree/index.rb new file mode 100644 index 00000000..60158314 --- /dev/null +++ b/lib/syntax_tree/index.rb @@ -0,0 +1,223 @@ +# frozen_string_literal: true + +module SyntaxTree + # This class can be used to build an index of the structure of Ruby files. We + # define an index as the list of constants and methods defined within a file. + # + # This index strives to be as fast as possible to better support tools like + # IDEs. Because of that, it has different backends depending on what + # functionality is available. + module Index + # This is a location for an index entry. + class Location + attr_reader :line, :column + + def initialize(line, column) + @line = line + @column = column + end + end + + # This entry represents a class definition using the class keyword. + class ClassDefinition + attr_reader :nesting, :name, :location + + def initialize(nesting, name, location) + @nesting = nesting + @name = name + @location = location + end + end + + # This entry represents a module definition using the module keyword. + class ModuleDefinition + attr_reader :nesting, :name, :location + + def initialize(nesting, name, location) + @nesting = nesting + @name = name + @location = location + end + end + + # This entry represents a method definition using the def keyword. + class MethodDefinition + attr_reader :nesting, :name, :location + + def initialize(nesting, name, location) + @nesting = nesting + @name = name + @location = location + end + end + + # This entry represents a singleton method definition using the def keyword + # with a specified target. + class SingletonMethodDefinition + attr_reader :nesting, :name, :location + + def initialize(nesting, name, location) + @nesting = nesting + @name = name + @location = location + end + end + + # This backend creates the index using RubyVM::InstructionSequence, which is + # faster than using the Syntax Tree parser, but is not available on all + # runtimes. + class ISeqBackend + VM_DEFINECLASS_TYPE_CLASS = 0x00 + VM_DEFINECLASS_TYPE_SINGLETON_CLASS = 0x01 + VM_DEFINECLASS_TYPE_MODULE = 0x02 + VM_DEFINECLASS_FLAG_SCOPED = 0x08 + VM_DEFINECLASS_FLAG_HAS_SUPERCLASS = 0x10 + + def index(source) + index_iseq(RubyVM::InstructionSequence.compile(source).to_a) + end + + def index_file(filepath) + index_iseq(RubyVM::InstructionSequence.compile_file(filepath).to_a) + end + + private + + def index_iseq(iseq) + results = [] + queue = [[iseq, []]] + + while (current_iseq, current_nesting = queue.shift) + current_iseq[13].each_with_index do |insn, index| + next unless insn.is_a?(Array) + + case insn[0] + when :defineclass + _, name, class_iseq, flags = insn + + if flags == VM_DEFINECLASS_TYPE_SINGLETON_CLASS + # At the moment, we don't support singletons that aren't + # defined on self. We could, but it would require more + # emulation. + if current_iseq[13][index - 2] != [:putself] + raise NotImplementedError, + "singleton class with non-self receiver" + end + elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0 + code_location = class_iseq[4][:code_location] + location = Location.new(code_location[0], code_location[1]) + results << ModuleDefinition.new(current_nesting, name, location) + else + code_location = class_iseq[4][:code_location] + location = Location.new(code_location[0], code_location[1]) + results << ClassDefinition.new(current_nesting, name, location) + end + + queue << [class_iseq, current_nesting + [name]] + when :definemethod + _, name, method_iseq = insn + + code_location = method_iseq[4][:code_location] + location = Location.new(code_location[0], code_location[1]) + results << SingletonMethodDefinition.new( + current_nesting, + name, + location + ) + when :definesmethod + _, name, method_iseq = insn + + code_location = method_iseq[4][:code_location] + location = Location.new(code_location[0], code_location[1]) + results << MethodDefinition.new(current_nesting, name, location) + end + end + end + + results + end + end + + # This backend creates the index using the Syntax Tree parser and a visitor. + # It is not as fast as using the instruction sequences directly, but is + # supported on all runtimes. + class ParserBackend + class IndexVisitor < Visitor + attr_reader :results, :nesting + + def initialize + @results = [] + @nesting = [] + end + + def visit_class(node) + name = visit(node.constant).to_sym + location = + Location.new(node.location.start_line, node.location.start_column) + + results << ClassDefinition.new(nesting.dup, name, location) + nesting << name + + super + nesting.pop + end + + def visit_const_ref(node) + node.constant.value + end + + def visit_def(node) + name = node.name.value.to_sym + location = + Location.new(node.location.start_line, node.location.start_column) + + results << if node.target.nil? + MethodDefinition.new(nesting.dup, name, location) + else + SingletonMethodDefinition.new(nesting.dup, name, location) + end + end + + def visit_module(node) + name = visit(node.constant).to_sym + location = + Location.new(node.location.start_line, node.location.start_column) + + results << ModuleDefinition.new(nesting.dup, name, location) + nesting << name + + super + nesting.pop + end + + def visit_program(node) + super + results + end + end + + def index(source) + SyntaxTree.parse(source).accept(IndexVisitor.new) + end + + def index_file(filepath) + index(SyntaxTree.read(filepath)) + end + end + + # The class defined here is used to perform the indexing, depending on what + # functionality is available from the runtime. + INDEX_BACKEND = + defined?(RubyVM::InstructionSequence) ? ISeqBackend : ParserBackend + + # This method accepts source code and then indexes it. + def self.index(source) + INDEX_BACKEND.new.index(source) + end + + # This method accepts a filepath and then indexes it. + def self.index_file(filepath) + INDEX_BACKEND.new.index_file(filepath) + end + end +end diff --git a/test/index_test.rb b/test/index_test.rb new file mode 100644 index 00000000..3ea02a20 --- /dev/null +++ b/test/index_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SyntaxTree + class IndexTest < Minitest::Test + def test_module + index_each("module Foo; end") do |entry| + assert_equal :Foo, entry.name + assert_empty entry.nesting + end + end + + def test_module_nested + index_each("module Foo; module Bar; end; end") do |entry| + assert_equal :Bar, entry.name + assert_equal [:Foo], entry.nesting + end + end + + def test_class + index_each("class Foo; end") do |entry| + assert_equal :Foo, entry.name + assert_empty entry.nesting + end + end + + def test_class_nested + index_each("class Foo; class Bar; end; end") do |entry| + assert_equal :Bar, entry.name + assert_equal [:Foo], entry.nesting + end + end + + def test_method + index_each("def foo; end") do |entry| + assert_equal :foo, entry.name + assert_empty entry.nesting + end + end + + def test_method_nested + index_each("class Foo; def foo; end; end") do |entry| + assert_equal :foo, entry.name + assert_equal [:Foo], entry.nesting + end + end + + private + + def index_each(source) + yield SyntaxTree::Index::ParserBackend.new.index(source).last + + if defined?(RubyVM::InstructionSequence) + yield SyntaxTree::Index::ISeqBackend.new.index(source).last + end + end + end +end From 4d659883264ffd831572e84ef437e94e88b3b7a6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 25 Jan 2023 13:19:05 -0500 Subject: [PATCH 15/22] Comments on index entries --- lib/syntax_tree/index.rb | 196 ++++++++++++++++++++++++++++++++++----- test/index_test.rb | 21 +++++ 2 files changed, 195 insertions(+), 22 deletions(-) diff --git a/lib/syntax_tree/index.rb b/lib/syntax_tree/index.rb index 60158314..6956ae9c 100644 --- a/lib/syntax_tree/index.rb +++ b/lib/syntax_tree/index.rb @@ -20,46 +20,128 @@ def initialize(line, column) # This entry represents a class definition using the class keyword. class ClassDefinition - attr_reader :nesting, :name, :location + attr_reader :nesting, :name, :location, :comments - def initialize(nesting, name, location) + def initialize(nesting, name, location, comments) @nesting = nesting @name = name @location = location + @comments = comments end end # This entry represents a module definition using the module keyword. class ModuleDefinition - attr_reader :nesting, :name, :location + attr_reader :nesting, :name, :location, :comments - def initialize(nesting, name, location) + def initialize(nesting, name, location, comments) @nesting = nesting @name = name @location = location + @comments = comments end end # This entry represents a method definition using the def keyword. class MethodDefinition - attr_reader :nesting, :name, :location + attr_reader :nesting, :name, :location, :comments - def initialize(nesting, name, location) + def initialize(nesting, name, location, comments) @nesting = nesting @name = name @location = location + @comments = comments end end # This entry represents a singleton method definition using the def keyword # with a specified target. class SingletonMethodDefinition - attr_reader :nesting, :name, :location + attr_reader :nesting, :name, :location, :comments - def initialize(nesting, name, location) + def initialize(nesting, name, location, comments) @nesting = nesting @name = name @location = location + @comments = comments + end + end + + # When you're using the instruction sequence backend, this class is used to + # lazily parse comments out of the source code. + class FileComments + # We use the ripper library to pull out source comments. + class Parser < Ripper + attr_reader :comments + + def initialize(*) + super + @comments = {} + end + + def on_comment(value) + comments[lineno] = value.chomp + end + end + + # This represents the Ruby source in the form of a file. When it needs to + # be read we'll read the file. + class FileSource + attr_reader :filepath + + def initialize(filepath) + @filepath = filepath + end + + def source + File.read(filepath) + end + end + + # This represents the Ruby source in the form of a string. When it needs + # to be read the string is returned. + class StringSource + attr_reader :source + + def initialize(source) + @source = source + end + end + + attr_reader :source + + def initialize(source) + @source = source + end + + def comments + @comments ||= Parser.new(source.source).tap(&:parse).comments + end + end + + # This class handles parsing comments from Ruby source code in the case that + # we use the instruction sequence backend. Because the instruction sequence + # backend doesn't provide comments (since they are dropped) we provide this + # interface to lazily parse them out. + class EntryComments + include Enumerable + attr_reader :file_comments, :location + + def initialize(file_comments, location) + @file_comments = file_comments + @location = location + end + + def each(&block) + line = location.line - 1 + result = [] + + while line >= 0 && (comment = file_comments.comments[line]) + result.unshift(comment) + line -= 1 + end + + result.each(&block) end end @@ -74,16 +156,22 @@ class ISeqBackend VM_DEFINECLASS_FLAG_HAS_SUPERCLASS = 0x10 def index(source) - index_iseq(RubyVM::InstructionSequence.compile(source).to_a) + index_iseq( + RubyVM::InstructionSequence.compile(source).to_a, + FileComments.new(FileComments::StringSource.new(source)) + ) end def index_file(filepath) - index_iseq(RubyVM::InstructionSequence.compile_file(filepath).to_a) + index_iseq( + RubyVM::InstructionSequence.compile_file(filepath).to_a, + FileComments.new(FileComments::FileSource.new(filepath)) + ) end private - def index_iseq(iseq) + def index_iseq(iseq, file_comments) results = [] queue = [[iseq, []]] @@ -106,11 +194,23 @@ def index_iseq(iseq) elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0 code_location = class_iseq[4][:code_location] location = Location.new(code_location[0], code_location[1]) - results << ModuleDefinition.new(current_nesting, name, location) + + results << ModuleDefinition.new( + current_nesting, + name, + location, + EntryComments.new(file_comments, location) + ) else code_location = class_iseq[4][:code_location] location = Location.new(code_location[0], code_location[1]) - results << ClassDefinition.new(current_nesting, name, location) + + results << ClassDefinition.new( + current_nesting, + name, + location, + EntryComments.new(file_comments, location) + ) end queue << [class_iseq, current_nesting + [name]] @@ -122,14 +222,21 @@ def index_iseq(iseq) results << SingletonMethodDefinition.new( current_nesting, name, - location + location, + EntryComments.new(file_comments, location) ) when :definesmethod _, name, method_iseq = insn code_location = method_iseq[4][:code_location] location = Location.new(code_location[0], code_location[1]) - results << MethodDefinition.new(current_nesting, name, location) + + results << MethodDefinition.new( + current_nesting, + name, + location, + EntryComments.new(file_comments, location) + ) end end end @@ -143,11 +250,12 @@ def index_iseq(iseq) # supported on all runtimes. class ParserBackend class IndexVisitor < Visitor - attr_reader :results, :nesting + attr_reader :results, :nesting, :statements def initialize @results = [] @nesting = [] + @statements = nil end def visit_class(node) @@ -155,9 +263,14 @@ def visit_class(node) location = Location.new(node.location.start_line, node.location.start_column) - results << ClassDefinition.new(nesting.dup, name, location) - nesting << name + results << ClassDefinition.new( + nesting.dup, + name, + location, + comments_for(node) + ) + nesting << name super nesting.pop end @@ -172,9 +285,19 @@ def visit_def(node) Location.new(node.location.start_line, node.location.start_column) results << if node.target.nil? - MethodDefinition.new(nesting.dup, name, location) + MethodDefinition.new( + nesting.dup, + name, + location, + comments_for(node) + ) else - SingletonMethodDefinition.new(nesting.dup, name, location) + SingletonMethodDefinition.new( + nesting.dup, + name, + location, + comments_for(node) + ) end end @@ -183,9 +306,14 @@ def visit_module(node) location = Location.new(node.location.start_line, node.location.start_column) - results << ModuleDefinition.new(nesting.dup, name, location) - nesting << name + results << ModuleDefinition.new( + nesting.dup, + name, + location, + comments_for(node) + ) + nesting << name super nesting.pop end @@ -194,6 +322,30 @@ def visit_program(node) super results end + + def visit_statements(node) + @statements = node + super + end + + private + + def comments_for(node) + comments = [] + + body = statements.body + line = node.location.start_line - 1 + index = body.index(node) - 1 + + while index >= 0 && body[index].is_a?(Comment) && + (line - body[index].location.start_line < 2) + comments.unshift(body[index].value) + line = body[index].location.start_line + index -= 1 + end + + comments + end end def index(source) diff --git a/test/index_test.rb b/test/index_test.rb index 3ea02a20..91dfcc76 100644 --- a/test/index_test.rb +++ b/test/index_test.rb @@ -18,6 +18,13 @@ def test_module_nested end end + def test_module_comments + index_each("# comment1\n# comment2\nmodule Foo; end") do |entry| + assert_equal :Foo, entry.name + assert_equal ["# comment1", "# comment2"], entry.comments.to_a + end + end + def test_class index_each("class Foo; end") do |entry| assert_equal :Foo, entry.name @@ -32,6 +39,13 @@ def test_class_nested end end + def test_class_comments + index_each("# comment1\n# comment2\nclass Foo; end") do |entry| + assert_equal :Foo, entry.name + assert_equal ["# comment1", "# comment2"], entry.comments.to_a + end + end + def test_method index_each("def foo; end") do |entry| assert_equal :foo, entry.name @@ -46,6 +60,13 @@ def test_method_nested end end + def test_method_comments + index_each("# comment1\n# comment2\ndef foo; end") do |entry| + assert_equal :foo, entry.name + assert_equal ["# comment1", "# comment2"], entry.comments.to_a + end + end + private def index_each(source) From 7731c6d6721b3a733c6cd9fbf7725d3b3f257427 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 25 Jan 2023 20:16:58 -0500 Subject: [PATCH 16/22] More test coverage for indexing --- lib/syntax_tree/index.rb | 43 ++++++++++++++++++++-------------------- test/index_test.rb | 35 ++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/lib/syntax_tree/index.rb b/lib/syntax_tree/index.rb index 6956ae9c..8b33f785 100644 --- a/lib/syntax_tree/index.rb +++ b/lib/syntax_tree/index.rb @@ -171,6 +171,11 @@ def index_file(filepath) private + def location_for(iseq) + code_location = iseq[4][:code_location] + Location.new(code_location[0], code_location[1]) + end + def index_iseq(iseq, file_comments) results = [] queue = [[iseq, []]] @@ -192,9 +197,7 @@ def index_iseq(iseq, file_comments) "singleton class with non-self receiver" end elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0 - code_location = class_iseq[4][:code_location] - location = Location.new(code_location[0], code_location[1]) - + location = location_for(class_iseq) results << ModuleDefinition.new( current_nesting, name, @@ -202,9 +205,7 @@ def index_iseq(iseq, file_comments) EntryComments.new(file_comments, location) ) else - code_location = class_iseq[4][:code_location] - location = Location.new(code_location[0], code_location[1]) - + location = location_for(class_iseq) results << ClassDefinition.new( current_nesting, name, @@ -215,25 +216,23 @@ def index_iseq(iseq, file_comments) queue << [class_iseq, current_nesting + [name]] when :definemethod - _, name, method_iseq = insn - - code_location = method_iseq[4][:code_location] - location = Location.new(code_location[0], code_location[1]) - results << SingletonMethodDefinition.new( + location = location_for(insn[2]) + results << MethodDefinition.new( current_nesting, - name, + insn[1], location, EntryComments.new(file_comments, location) ) when :definesmethod - _, name, method_iseq = insn - - code_location = method_iseq[4][:code_location] - location = Location.new(code_location[0], code_location[1]) + if current_iseq[13][index - 1] != [:putself] + raise NotImplementedError, + "singleton method with non-self receiver" + end - results << MethodDefinition.new( + location = location_for(insn[2]) + results << SingletonMethodDefinition.new( current_nesting, - name, + insn[1], location, EntryComments.new(file_comments, location) ) @@ -363,13 +362,13 @@ def index_file(filepath) defined?(RubyVM::InstructionSequence) ? ISeqBackend : ParserBackend # This method accepts source code and then indexes it. - def self.index(source) - INDEX_BACKEND.new.index(source) + def self.index(source, backend: INDEX_BACKEND.new) + backend.index(source) end # This method accepts a filepath and then indexes it. - def self.index_file(filepath) - INDEX_BACKEND.new.index_file(filepath) + def self.index_file(filepath, backend: INDEX_BACKEND.new) + backend.index_file(filepath) end end end diff --git a/test/index_test.rb b/test/index_test.rb index 91dfcc76..6bb83881 100644 --- a/test/index_test.rb +++ b/test/index_test.rb @@ -67,13 +67,44 @@ def test_method_comments end end + def test_singleton_method + index_each("def self.foo; end") do |entry| + assert_equal :foo, entry.name + assert_empty entry.nesting + end + end + + def test_singleton_method_nested + index_each("class Foo; def self.foo; end; end") do |entry| + assert_equal :foo, entry.name + assert_equal [:Foo], entry.nesting + end + end + + def test_singleton_method_comments + index_each("# comment1\n# comment2\ndef self.foo; end") do |entry| + assert_equal :foo, entry.name + assert_equal ["# comment1", "# comment2"], entry.comments.to_a + end + end + + def test_this_file + entries = Index.index_file(__FILE__, backend: Index::ParserBackend.new) + + if defined?(RubyVM::InstructionSequence) + entries += Index.index_file(__FILE__, backend: Index::ISeqBackend.new) + end + + entries.map { |entry| entry.comments.to_a } + end + private def index_each(source) - yield SyntaxTree::Index::ParserBackend.new.index(source).last + yield Index.index(source, backend: Index::ParserBackend.new).last if defined?(RubyVM::InstructionSequence) - yield SyntaxTree::Index::ISeqBackend.new.index(source).last + yield Index.index(source, backend: Index::ISeqBackend.new).last end end end From 98a6b73017ace48f3e9d17aa189e73edaefbcf7c Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 26 Jan 2023 10:36:58 -0500 Subject: [PATCH 17/22] Bump deps --- .rubocop.yml | 3 +++ Gemfile.lock | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 0212027b..bc98a43a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -11,6 +11,9 @@ AllCops: - test/ruby-syntax-fixtures/**/* - test.rb +Gemspec/DevelopmentDependencies: + Enabled: false + Layout/LineLength: Max: 80 diff --git a/Gemfile.lock b/Gemfile.lock index b691d5e9..4ebe14d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,9 +17,9 @@ GEM prettier_print (1.2.0) rainbow (3.1.1) rake (13.0.6) - regexp_parser (2.6.1) + regexp_parser (2.6.2) rexml (3.2.5) - rubocop (1.43.0) + rubocop (1.44.1) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) From 5a9bf11e0060d9e583468a3fa5c8fd0abc4999e4 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 26 Jan 2023 11:06:24 -0500 Subject: [PATCH 18/22] Small arity refactor --- lib/syntax_tree/node.rb | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index d1d40154..ee00940d 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -854,18 +854,17 @@ def ===(other) end def arity - accepts_infinite_arguments? ? Float::INFINITY : parts.length - end - - private - - def accepts_infinite_arguments? - parts.any? do |part| - part.is_a?(ArgStar) || part.is_a?(ArgsForward) || - ( - part.is_a?(BareAssocHash) && - part.assocs.any? { |p| p.is_a?(AssocSplat) } - ) + parts.sum do |part| + case part + when ArgStar, ArgsForward + Float::INFINITY + when BareAssocHash + part.assocs.sum do |assoc| + assoc.is_a?(AssocSplat) ? Float::INFINITY : 1 + end + else + 1 + end end end end @@ -8383,18 +8382,24 @@ def ===(other) # Returns a range representing the possible number of arguments accepted # by this params node not including the block. For example: - # def foo(a, b = 1, c:, d: 2, &block) - # ... - # end - # has arity 2..4 + # + # def foo(a, b = 1, c:, d: 2, &block) + # ... + # end + # + # has arity 2..4. + # def arity optional_keywords = keywords.count { |_label, value| value } + lower_bound = requireds.length + posts.length + keywords.length - optional_keywords upper_bound = - lower_bound + optionals.length + - optional_keywords if keyword_rest.nil? && rest.nil? + if keyword_rest.nil? && rest.nil? + lower_bound + optionals.length + optional_keywords + end + lower_bound..upper_bound end From a8ae2af6f6c54d48b1ba7033d4277c5adf6c89bb Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 26 Jan 2023 11:11:01 -0500 Subject: [PATCH 19/22] Remove VarRef array formatting --- lib/syntax_tree/node.rb | 140 +++++++++++---------------------- test/fixtures/array_literal.rb | 13 ++- 2 files changed, 54 insertions(+), 99 deletions(-) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index ee00940d..b954152d 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -1103,58 +1103,6 @@ def format(q) end end - # Formats an array that contains only a list of variable references. To make - # things simpler, if there are a bunch, we format them all using the "fill" - # algorithm as opposed to breaking them into a ton of lines. For example, - # - # [foo, bar, baz] - # - # instead of becoming: - # - # [ - # foo, - # bar, - # baz - # ] - # - # would instead become: - # - # [ - # foo, bar, - # baz - # ] - # - # provided the line length was hit between `bar` and `baz`. - class VarRefsFormatter - # The separator for the fill algorithm. - class Separator - def call(q) - q.text(",") - q.fill_breakable - end - end - - # [Args] the contents of the array - attr_reader :contents - - def initialize(contents) - @contents = contents - end - - def format(q) - q.text("[") - q.group do - q.indent do - q.breakable_empty - q.seplist(contents.parts, Separator.new) { |part| q.format(part) } - q.if_break { q.text(",") } if q.trailing_comma? - end - q.breakable_empty - end - q.text("]") - end - end - # This is a special formatter used if the array literal contains no values # but _does_ contain comments. In this case we do some special formatting to # make sure the comments gets indented properly. @@ -1229,19 +1177,17 @@ def deconstruct_keys(_keys) end def format(q) - if qwords? - QWordsFormatter.new(contents).format(q) - return - end - - if qsymbols? - QSymbolsFormatter.new(contents).format(q) - return - end + if lbracket.comments.empty? && contents && contents.comments.empty? && + contents.parts.length > 1 + if qwords? + QWordsFormatter.new(contents).format(q) + return + end - if var_refs?(q) - VarRefsFormatter.new(contents).format(q) - return + if qsymbols? + QSymbolsFormatter.new(contents).format(q) + return + end end if empty_with_comments? @@ -1273,39 +1219,24 @@ def ===(other) private def qwords? - lbracket.comments.empty? && contents && contents.comments.empty? && - contents.parts.length > 1 && - contents.parts.all? do |part| - case part - when StringLiteral - part.comments.empty? && part.parts.length == 1 && - part.parts.first.is_a?(TStringContent) && - !part.parts.first.value.match?(/[\s\[\]\\]/) - when CHAR - !part.value.match?(/[\[\]\\]/) - else - false - end + contents.parts.all? do |part| + case part + when StringLiteral + part.comments.empty? && part.parts.length == 1 && + part.parts.first.is_a?(TStringContent) && + !part.parts.first.value.match?(/[\s\[\]\\]/) + when CHAR + !part.value.match?(/[\[\]\\]/) + else + false end + end end def qsymbols? - lbracket.comments.empty? && contents && contents.comments.empty? && - contents.parts.length > 1 && - contents.parts.all? do |part| - part.is_a?(SymbolLiteral) && part.comments.empty? - end - end - - def var_refs?(q) - lbracket.comments.empty? && contents && contents.comments.empty? && - contents.parts.all? do |part| - part.is_a?(VarRef) && part.comments.empty? - end && - ( - contents.parts.sum { |part| part.value.value.length + 2 } > - q.maxwidth * 2 - ) + contents.parts.all? do |part| + part.is_a?(SymbolLiteral) && part.comments.empty? + end end # If we have an empty array that contains only comments, then we're going @@ -6551,9 +6482,26 @@ def deconstruct_keys(_keys) def format(q) force_flat = [ - AliasNode, Assign, Break, Command, CommandCall, Heredoc, IfNode, IfOp, - Lambda, MAssign, Next, OpAssign, RescueMod, ReturnNode, Super, Undef, - UnlessNode, VoidStmt, YieldNode, ZSuper + AliasNode, + Assign, + Break, + Command, + CommandCall, + Heredoc, + IfNode, + IfOp, + Lambda, + MAssign, + Next, + OpAssign, + RescueMod, + ReturnNode, + Super, + Undef, + UnlessNode, + VoidStmt, + YieldNode, + ZSuper ] if q.parent.is_a?(Paren) || force_flat.include?(truthy.class) || diff --git a/test/fixtures/array_literal.rb b/test/fixtures/array_literal.rb index df807728..391d2eae 100644 --- a/test/fixtures/array_literal.rb +++ b/test/fixtures/array_literal.rb @@ -24,9 +24,16 @@ - fooooooooooooooooo = 1 [ - fooooooooooooooooo, fooooooooooooooooo, fooooooooooooooooo, - fooooooooooooooooo, fooooooooooooooooo, fooooooooooooooooo, - fooooooooooooooooo, fooooooooooooooooo, fooooooooooooooooo, fooooooooooooooooo + fooooooooooooooooo, + fooooooooooooooooo, + fooooooooooooooooo, + fooooooooooooooooo, + fooooooooooooooooo, + fooooooooooooooooo, + fooooooooooooooooo, + fooooooooooooooooo, + fooooooooooooooooo, + fooooooooooooooooo ] % [ From 82dc9b7ce91030dbf4ed2474853558d1bf05de1c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 7 Jan 2023 13:46:14 +0000 Subject: [PATCH 20/22] Introduce `plugin/disable_ternary` This will prevent the automatic conversion of `if ... else` to ternary expressions. --- README.md | 1 + lib/syntax_tree/formatter.rb | 21 +++++++++++++-- lib/syntax_tree/node.rb | 1 + lib/syntax_tree/plugin/disable_ternary.rb | 7 +++++ test/plugin/disable_ternary_test.rb | 32 +++++++++++++++++++++++ 5 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 lib/syntax_tree/plugin/disable_ternary.rb create mode 100644 test/plugin/disable_ternary_test.rb diff --git a/README.md b/README.md index 7a943ca8..7bb731e2 100644 --- a/README.md +++ b/README.md @@ -658,6 +658,7 @@ To register plugins, define a file somewhere in your load path named `syntax_tre * `plugin/single_quotes` - This will change all of your string literals to use single quotes instead of the default double quotes. * `plugin/trailing_comma` - This will put trailing commas into multiline array literals, hash literals, and method calls that can support trailing commas. +* `plugin/disable_ternary` - This will prevent the automatic conversion of `if ... else` to ternary expressions. If you're using Syntax Tree as a library, you can require those files directly or manually pass those options to the formatter initializer through the `SyntaxTree::Formatter::Options` class. diff --git a/lib/syntax_tree/formatter.rb b/lib/syntax_tree/formatter.rb index fddc06fe..067de7ee 100644 --- a/lib/syntax_tree/formatter.rb +++ b/lib/syntax_tree/formatter.rb @@ -21,11 +21,15 @@ def initialize(version) # that folks have become entrenched in their ways, we decided to provide a # small amount of configurability. class Options - attr_reader :quote, :trailing_comma, :target_ruby_version + attr_reader :quote, + :trailing_comma, + :disable_ternary, + :target_ruby_version def initialize( quote: :default, trailing_comma: :default, + disable_ternary: :default, target_ruby_version: :default ) @quote = @@ -50,6 +54,17 @@ def initialize( trailing_comma end + @disable_ternary = + if disable_ternary == :default + # We ship with a disable ternary plugin that will define this + # constant. That constant is responsible for determining the default + # disable ternary value. If it's defined, then we default to true. + # Otherwise we default to false. + defined?(DISABLE_TERNARY) + else + disable_ternary + end + @target_ruby_version = if target_ruby_version == :default # The default target Ruby version is the current version of Ruby. @@ -69,8 +84,9 @@ def initialize( # These options are overridden in plugins to we need to make sure they are # available here. - attr_reader :quote, :trailing_comma, :target_ruby_version + attr_reader :quote, :trailing_comma, :disable_ternary, :target_ruby_version alias trailing_comma? trailing_comma + alias disable_ternary? disable_ternary def initialize(source, *args, options: Options.new) super(*args) @@ -81,6 +97,7 @@ def initialize(source, *args, options: Options.new) # Memoizing these values to make access faster. @quote = options.quote @trailing_comma = options.trailing_comma + @disable_ternary = options.disable_ternary @target_ruby_version = options.target_ruby_version end diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index f19cfb2c..119180ec 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -6154,6 +6154,7 @@ module Ternaryable class << self def call(q, node) return false if ENV["STREE_FAST_FORMAT"] + return false if q.disable_ternary? # If this is a conditional inside of a parentheses as the only content, # then we don't want to transform it into a ternary. Presumably the user diff --git a/lib/syntax_tree/plugin/disable_ternary.rb b/lib/syntax_tree/plugin/disable_ternary.rb new file mode 100644 index 00000000..0cb48d84 --- /dev/null +++ b/lib/syntax_tree/plugin/disable_ternary.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module SyntaxTree + class Formatter + DISABLE_TERNARY = true + end +end diff --git a/test/plugin/disable_ternary_test.rb b/test/plugin/disable_ternary_test.rb new file mode 100644 index 00000000..ac27ea5a --- /dev/null +++ b/test/plugin/disable_ternary_test.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module SyntaxTree + class DisableTernaryTest < Minitest::Test + def test_short_if_else_unchanged + assert_format(<<~RUBY) + if true + 1 + else + 2 + end + RUBY + end + + def test_short_ternary_unchanged + assert_format("true ? 1 : 2\n") + end + + private + + def assert_format(expected, source = expected) + options = Formatter::Options.new(disable_ternary: true) + formatter = Formatter.new(source, [], options: options) + SyntaxTree.parse(source).format(formatter) + + formatter.flush + assert_equal(expected, formatter.output.join) + end + end +end From ee84d7cb33b00e8bd4c9991df30a0a50cfe5cb8b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 26 Jan 2023 11:27:28 -0500 Subject: [PATCH 21/22] Rename plugin/disable_ternary to plugin/disable_auto_ternary --- README.md | 2 +- lib/syntax_tree/formatter.rb | 20 ++++++++++++-------- lib/syntax_tree/node.rb | 3 +-- test/plugin/disable_ternary_test.rb | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7bb731e2..3c437947 100644 --- a/README.md +++ b/README.md @@ -658,7 +658,7 @@ To register plugins, define a file somewhere in your load path named `syntax_tre * `plugin/single_quotes` - This will change all of your string literals to use single quotes instead of the default double quotes. * `plugin/trailing_comma` - This will put trailing commas into multiline array literals, hash literals, and method calls that can support trailing commas. -* `plugin/disable_ternary` - This will prevent the automatic conversion of `if ... else` to ternary expressions. +* `plugin/disable_auto_ternary` - This will prevent the automatic conversion of `if ... else` to ternary expressions. If you're using Syntax Tree as a library, you can require those files directly or manually pass those options to the formatter initializer through the `SyntaxTree::Formatter::Options` class. diff --git a/lib/syntax_tree/formatter.rb b/lib/syntax_tree/formatter.rb index 067de7ee..c64cf7d1 100644 --- a/lib/syntax_tree/formatter.rb +++ b/lib/syntax_tree/formatter.rb @@ -23,13 +23,13 @@ def initialize(version) class Options attr_reader :quote, :trailing_comma, - :disable_ternary, + :disable_auto_ternary, :target_ruby_version def initialize( quote: :default, trailing_comma: :default, - disable_ternary: :default, + disable_auto_ternary: :default, target_ruby_version: :default ) @quote = @@ -54,15 +54,15 @@ def initialize( trailing_comma end - @disable_ternary = - if disable_ternary == :default + @disable_auto_ternary = + if disable_auto_ternary == :default # We ship with a disable ternary plugin that will define this # constant. That constant is responsible for determining the default # disable ternary value. If it's defined, then we default to true. # Otherwise we default to false. defined?(DISABLE_TERNARY) else - disable_ternary + disable_auto_ternary end @target_ruby_version = @@ -84,9 +84,13 @@ def initialize( # These options are overridden in plugins to we need to make sure they are # available here. - attr_reader :quote, :trailing_comma, :disable_ternary, :target_ruby_version + attr_reader :quote, + :trailing_comma, + :disable_auto_ternary, + :target_ruby_version + alias trailing_comma? trailing_comma - alias disable_ternary? disable_ternary + alias disable_auto_ternary? disable_auto_ternary def initialize(source, *args, options: Options.new) super(*args) @@ -97,7 +101,7 @@ def initialize(source, *args, options: Options.new) # Memoizing these values to make access faster. @quote = options.quote @trailing_comma = options.trailing_comma - @disable_ternary = options.disable_ternary + @disable_auto_ternary = options.disable_auto_ternary @target_ruby_version = options.target_ruby_version end diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 7ddfc710..fc5517cf 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -6139,8 +6139,7 @@ def self.call(parent) module Ternaryable class << self def call(q, node) - return false if ENV["STREE_FAST_FORMAT"] - return false if q.disable_ternary? + return false if ENV["STREE_FAST_FORMAT"] || q.disable_auto_ternary? # If this is a conditional inside of a parentheses as the only content, # then we don't want to transform it into a ternary. Presumably the user diff --git a/test/plugin/disable_ternary_test.rb b/test/plugin/disable_ternary_test.rb index ac27ea5a..b2af9d35 100644 --- a/test/plugin/disable_ternary_test.rb +++ b/test/plugin/disable_ternary_test.rb @@ -21,7 +21,7 @@ def test_short_ternary_unchanged private def assert_format(expected, source = expected) - options = Formatter::Options.new(disable_ternary: true) + options = Formatter::Options.new(disable_auto_ternary: true) formatter = Formatter.new(source, [], options: options) SyntaxTree.parse(source).format(formatter) From c497724afdd34ed00a4ac4804b1d26da973b4c95 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 26 Jan 2023 11:58:30 -0500 Subject: [PATCH 22/22] Bump to version 5.3.0 --- CHANGELOG.md | 18 +++++++++++++++--- Gemfile.lock | 2 +- lib/syntax_tree/version.rb | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf347efb..c39bed36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [5.3.0] - 2023-01-26 + ### Added -- Arity has been added to DefNode, BlockNode and Params. The method returns a range where the lower bound is the minimum and the upper bound is the maximum number of arguments that can be used to invoke that block/method definition. -- Arity has been added to CallNode, Command, CommandCall and VCall nodes. The method returns the number of arguments included in the invocation. For splats, double splats or argument forwards, this method returns Float::INFINITY. +- `#arity` has been added to `DefNode`, `BlockNode`, and `Params`. The method returns a range where the lower bound is the minimum and the upper bound is the maximum number of arguments that can be used to invoke that block/method definition. +- `#arity` has been added to `CallNode`, `Command`, `CommandCall`, and `VCall` nodes. The method returns the number of arguments included in the invocation. For splats, double splats, or argument forwards, this method returns `Float::INFINITY`. +- `SyntaxTree::index` and `SyntaxTree::index_file` APIs have been added to collect a list of classes, modules, and methods defined in a given source string or file, respectively. These APIs are experimental and subject to change. +- A `plugin/disable_auto_ternary` plugin has been added the disables the formatted that automatically changes permissable `if/else` clauses into ternaries. + +### Changed + +- Files are now only written from the CLI if the content of them changes, which should match watching files less chaotic. +- In the case that `rb_iseq_load` cannot be found, `Fiddle::DLError` is now rescued. +- Previously if there were invalid UTF-8 byte sequences after the `__END__` keyword the parser could potentially have crashed when parsing comments. This has been fixed. +- Previously there was special formatting for array literals that contained only variable references (either locals, method calls, or constants). For consistency, this has been removed and all array literals are now formatted the same way. ## [5.2.0] - 2023-01-04 @@ -486,7 +497,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - 🎉 Initial release! 🎉 -[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v5.2.0...HEAD +[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v5.3.0...HEAD +[5.3.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v5.2.0...v5.3.0 [5.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v5.1.0...v5.2.0 [5.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v5.0.1...v5.1.0 [5.0.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v5.0.0...v5.0.1 diff --git a/Gemfile.lock b/Gemfile.lock index 4ebe14d0..799bd891 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - syntax_tree (5.2.0) + syntax_tree (5.3.0) prettier_print (>= 1.2.0) GEM diff --git a/lib/syntax_tree/version.rb b/lib/syntax_tree/version.rb index a97f5e43..6cb1fccf 100644 --- a/lib/syntax_tree/version.rb +++ b/lib/syntax_tree/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SyntaxTree - VERSION = "5.2.0" + VERSION = "5.3.0" end