preval 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a68492a84d002ce1287b7494fdeb2f7da2c1bf40f9001830c189b012679fff82
4
- data.tar.gz: ee652180924518b7c95ecc4dd3d1453ad05bc72b87f550c8921b765fc1b17418
3
+ metadata.gz: 38f2a5ee7be77fb0aac3f27ff453882026d53c50dfa3358dbcdf5f2a022486e2
4
+ data.tar.gz: 4dc976e6da2f87d5b18cd588d1c11e86e0a3aa08a4adca073629144984d5de40
5
5
  SHA512:
6
- metadata.gz: 79755ecb9e6b832a030d31052ffac72eccf86620562b7e82ec1b0f16a0dd5a9009e89c4c219ebad42a03984e144fbce042bd603b269b6970cdaf847e5c2969f4
7
- data.tar.gz: 974140ac97093237681db2d8369a9ef295001bc6135dd6802ab55f35888627dbd571c4935a2d8091d6d2a6db17136eb9b3e5e55c1ac379c5388ceffc7c0d9ac7
6
+ metadata.gz: d0387f1656097fe72b7eef9a83b98bf5c8cd38b65225c5931dc7d179416630577a7e3248fa57e4f4453cecca91fcd864c48e7e181aa74178cacf3fce4cdfe692
7
+ data.tar.gz: 9d623940a4710c8e83c1f9ac5eccc797280d99db3d4ace3750d01577965198302df92f80f7336525583d9b3b55a4ba3e68201b6d8d6e2343cbf7929d5f4ab1b0
@@ -0,0 +1,30 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+ DisplayStyleGuide: true
4
+ TargetRubyVersion: 2.6
5
+ Exclude:
6
+ - lib/preval/format.rb
7
+
8
+ Layout/CommentIndentation:
9
+ Enabled: false
10
+
11
+ Lint/InheritException:
12
+ Enabled: false
13
+
14
+ Metrics/AbcSize:
15
+ Enabled: false
16
+
17
+ Metrics/CyclomaticComplexity:
18
+ Enabled: false
19
+
20
+ Metrics/MethodLength:
21
+ Enabled: false
22
+
23
+ Metrics/PerceivedComplexity:
24
+ Enabled: false
25
+
26
+ Style/Documentation:
27
+ Enabled: false
28
+
29
+ Style/NumericPredicate:
30
+ Enabled: false
@@ -2,3 +2,6 @@ language: ruby
2
2
  cache: bundler
3
3
  rvm: 2.6.0
4
4
  before_install: gem install bundler -v 2.0.0
5
+ script:
6
+ - bundle exec rake
7
+ - bundle exec rubocop
@@ -6,23 +6,37 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] - 2019-06-13
10
+
11
+ ### Added
12
+
13
+ - Replace `.sort.first` with `.min`.
14
+ - Replace `.sort.last` with `.max`.
15
+
9
16
  ## [0.4.1] - 2019-04-20
17
+
10
18
  ### Changed
19
+
11
20
  - Support `attr_writer` transformations even if there is a void statement at the beginning of the method.
12
21
 
13
22
  ## [0.4.0] - 2019-04-19
23
+
14
24
  ### Added
25
+
15
26
  - Replace `def foo=(value); @foo = value; end` with `attr_writer :foo`
16
27
  - Replace `while false ... end` loops with nothing
17
28
  - Replace `until false ... end` loops with `loop do ... end` loops
18
29
  - Replace `until true ... end` loops with nothing
19
30
 
20
31
  ### Changed
32
+
21
33
  - Extracted out the `Preval::Visitors::AttrAccessor` visitor.
22
34
  - Renamed the `Preval::Visitors::Micro` visitor to `Preval::Visitors::Fasterer`.
23
35
 
24
36
  ## [0.3.0] - 2019-04-19
37
+
25
38
  ### Added
39
+
26
40
  - Fold constant for exponentiation if exponent is 0 and value is an integer.
27
41
  - Replace `.reverse.each` usage with `.reverse_each`.
28
42
  - Replace `foo ... in` loops with `.each do` loops.
@@ -33,14 +47,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
33
47
  - Replace `def foo=(value); @foo = value; end` with `attr_writer :foo`.
34
48
 
35
49
  ## [0.2.0] - 2019-04-18
50
+
36
51
  ### Added
52
+
37
53
  - Hook into the `bootsnap` gem if it's loaded.
38
54
 
39
55
  ## [0.1.0] - 2019-03-08
56
+
40
57
  ### Added
58
+
41
59
  - Initial release. 🎉
42
60
 
43
- [Unreleased]: https://github.com/kddeisz/preval/compare/v0.4.1...HEAD
61
+ [unreleased]: https://github.com/kddeisz/preval/compare/v0.5.0...HEAD
62
+ [0.5.0]: https://github.com/kddeisz/preval/compare/v0.4.1...v0.5.0
44
63
  [0.4.1]: https://github.com/kddeisz/preval/compare/v0.4.0...v0.4.1
45
64
  [0.4.0]: https://github.com/kddeisz/preval/compare/v0.3.0...v0.4.0
46
65
  [0.3.0]: https://github.com/kddeisz/preval/compare/v0.2.0...v0.3.0
@@ -0,0 +1,76 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, sex characteristics, gender identity and expression,
9
+ level of experience, education, socio-economic status, nationality, personal
10
+ appearance, race, religion, or sexual identity and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ - Using welcoming and inclusive language
18
+ - Being respectful of differing viewpoints and experiences
19
+ - Gracefully accepting constructive criticism
20
+ - Focusing on what is best for the community
21
+ - Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ - The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ - Trolling, insulting/derogatory comments, and personal or political attacks
28
+ - Public or private harassment
29
+ - Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ - Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at kevin.deisz@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72
+
73
+ [homepage]: https://www.contributor-covenant.org
74
+
75
+ For answers to common questions about this code of conduct, see
76
+ https://www.contributor-covenant.org/faq
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
@@ -1,26 +1,41 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- preval (0.4.1)
4
+ preval (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- bootsnap (1.4.3)
9
+ ast (2.4.0)
10
+ bootsnap (1.4.4)
10
11
  msgpack (~> 1.0)
12
+ jaro_winkler (1.5.2)
11
13
  minitest (5.11.3)
12
14
  msgpack (1.2.10)
13
15
  mustermann (1.0.3)
16
+ parallel (1.17.0)
17
+ parser (2.6.3.0)
18
+ ast (~> 2.4.0)
14
19
  rack (2.0.6)
15
20
  rack-protection (2.0.5)
16
21
  rack
22
+ rainbow (3.0.0)
17
23
  rake (12.3.2)
24
+ rubocop (0.71.0)
25
+ jaro_winkler (~> 1.5.1)
26
+ parallel (~> 1.10)
27
+ parser (>= 2.6)
28
+ rainbow (>= 2.2.2, < 4.0)
29
+ ruby-progressbar (~> 1.7)
30
+ unicode-display_width (>= 1.4.0, < 1.7)
31
+ ruby-progressbar (1.10.1)
18
32
  sinatra (2.0.5)
19
33
  mustermann (~> 1.0)
20
34
  rack (~> 2.0)
21
35
  rack-protection (= 2.0.5)
22
36
  tilt (~> 2.0)
23
37
  tilt (2.0.9)
38
+ unicode-display_width (1.6.0)
24
39
 
25
40
  PLATFORMS
26
41
  ruby
@@ -31,6 +46,7 @@ DEPENDENCIES
31
46
  minitest (~> 5.11)
32
47
  preval!
33
48
  rake (~> 12.3)
49
+ rubocop (~> 0.71)
34
50
  sinatra
35
51
 
36
52
  BUNDLED WITH
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Preval
2
2
 
3
+ [![Build Status](https://travis-ci.com/kddeisz/preval.svg?branch=master)](https://travis-ci.com/kddeisz/preval)
4
+
3
5
  `preval` is a gem that hooks into the Ruby compilation process and runs optimizations before it gets loaded by the virtual machine.
4
6
 
5
7
  Certain optimizations that are common in compilers are not immediately possible with Ruby on account of Ruby's flexibility. For example, most compilers will run through a process called [constant folding](https://en.wikipedia.org/wiki/Constant_folding) to eliminate the need to perform extraneous operations at runtime (e.g., `5 + 2` in the source can be replaced with `7`). However, because Ruby allows you to override the `Integer#+` method, it's possible that `5 + 2` would not evaluate to `7`. `preval` assumes that most developers will not override the `Integer#+` method, and performs optimizations under that assumption.
@@ -26,27 +28,60 @@ Or install it yourself as:
26
28
 
27
29
  If you're using the `bootsnap` gem, `preval` will automatically hook into its compilation step. Otherwise, you'll need to manually call `Preval.process(source)` with your own iseq loader (you can check out [yomikomu](https://github.com/ko1/yomikomu) for an example).
28
30
 
29
- Each optimization is generally named for the function it performs, and can be enabled through the `enable!` method on the visitor class. If you do not explicitly call `enable!` on any optimizations, nothing will change with your source.
30
-
31
- * `Preval::Visitors::Arithmetic` replaces:
32
- * constant expressions with their evaluation (e.g., `5 + 2` becomes `7`)
33
- * arithmetic identities with their evaluation (e.g., `a * 1` becomes `a`)
34
- * `Preval::Visitors::AttrAccessor` replaces:
35
- * `def foo; @foo; end` with `attr_reader :foo`
36
- * `def foo=(value); @foo = value; end` with `attr_writer :foo`
37
- * `Preval::Visitors::Fasterer` replaces:
38
- * `.gsub('...', '...')` with `.tr('...', '...')` if the arguments are strings and are both of length 1
39
- * `.map { ... }.flatten(1)` with `.flat_map { ... }`
40
- * `.reverse.each` with `.reverse_each`
41
- * `.shuffle.first` with `.sample`
42
- * `Preval::Visitors::Loops` replaces:
43
- * `for ... in ... end` loops with `... each do ... end` loops
44
- * `while true ... end` loops with `loop do ... end` loops
45
- * `while false ... end` loops with nothing
46
- * `until false ... end` loops with `loop do ... end` loops
47
- * `until true ... end` loops with nothing
48
-
49
- You can also call `Preval.enable_all!` which will enable every built-in visitor. Be especially careful when doing this.
31
+ Each optimization is generally named for the function it performs, and can be enabled through the `enable!` method on the visitor class. If you do not explicitly call `enable!` on any optimizations, nothing will change with your source. You can also call `Preval.enable_all!` which will enable every built-in visitor. Be especially careful when doing this.
32
+
33
+ ### `Preval::Visitors::Arithmetic`
34
+
35
+ Replaces:
36
+
37
+ - constant expressions with their evaluation (e.g., `5 + 2` becomes `7`)
38
+ - arithmetic identities with their evaluation (e.g., `a * 1` becomes `a`)
39
+
40
+ Unsafe if:
41
+
42
+ - you overload any of the `Integer` operator methods
43
+
44
+ ### `Preval::Visitors::AttrAccessor`
45
+
46
+ Replaces:
47
+
48
+ - `def foo; @foo; end` with `attr_reader :foo`
49
+ - `def foo=(value); @foo = value; end` with `attr_writer :foo`
50
+
51
+ Unsafe if:
52
+
53
+ - you overload the `attr_reader` method
54
+ - you overload the `attr_writer` method
55
+ - you have custom complex `method_added` logic
56
+
57
+ ### `Preval::Visitors::Fasterer`
58
+
59
+ Replaces:
60
+
61
+ - `.gsub('...', '...')` with `.tr('...', '...')` if the arguments are strings and are both of length 1
62
+ - `.map { ... }.flatten(1)` with `.flat_map { ... }`
63
+ - `.reverse.each` with `.reverse_each`
64
+ - `.shuffle.first` with `.sample`
65
+ - `.sort.first` with `.min`
66
+ - `.sort.last` with `.max`
67
+
68
+ Unsafe if:
69
+
70
+ - any of the effected methods are overwritten or custom (`gsub` and `tr` for the first one, `map`, `flatten`, and `flat_map` for the second, etc.)
71
+
72
+ ### `Preval::Visitors::Loops`
73
+
74
+ Replaces:
75
+
76
+ - `for ... in ... end` loops with `... each do ... end` loops
77
+ - `while true ... end` loops with `loop do ... end` loops
78
+ - `while false ... end` loops with nothing
79
+ - `until false ... end` loops with `loop do ... end` loops
80
+ - `until true ... end` loops with nothing
81
+
82
+ Unsafe if:
83
+
84
+ - the object over which you're iterating with a `for` loop has a custom `each` method that doesn't do what you'd expect it to do
50
85
 
51
86
  ## Development
52
87
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rake/testtask'
3
5
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'preval'
data/bin/parse CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'ripper'
4
5
 
data/bin/print CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'preval'
@@ -42,28 +42,104 @@ textarea, code {
42
42
  <code></code>
43
43
  </main>
44
44
  <script>
45
- const textarea = document.querySelector("textarea");
46
- const code = document.querySelector("code");
45
+ (() => {
46
+ const textarea = document.querySelector("textarea");
47
+ const code = document.querySelector("code");
47
48
 
48
- const fetchCode = () => {
49
- var xhr = new XMLHttpRequest();
50
- xhr.open("POST", "/", true);
49
+ const fetchCode = () => {
50
+ var xhr = new XMLHttpRequest();
51
+ xhr.open("POST", "/", true);
51
52
 
52
- xhr.onreadystatechange = () => {
53
- if (xhr.readyState === XMLHttpRequest.DONE) {
54
- code.innerText = xhr.status === 200 ? xhr.responseText : "";
53
+ xhr.onreadystatechange = () => {
54
+ if (xhr.readyState === XMLHttpRequest.DONE) {
55
+ code.innerText = xhr.status === 200 ? xhr.responseText : "";
56
+ }
57
+ };
58
+
59
+ xhr.send(textarea.value);
60
+ };
61
+
62
+ let timeout = 0;
63
+
64
+ textarea.addEventListener("input", () => {
65
+ clearTimeout(timeout);
66
+ timeout = setTimeout(fetchCode, 300);
67
+ });
68
+
69
+ const handleTab = event => {
70
+ event.preventDefault();
71
+
72
+ const { selectionStart, selectionEnd, value } = textarea;
73
+
74
+ const preSpace = value.substring(0, selectionStart);
75
+ const postSpace = value.substring(selectionEnd);
76
+
77
+ textarea.value = `${preSpace} ${postSpace}`;
78
+ textarea.selectionStart = textarea.selectionEnd = selectionStart + 2;
79
+ };
80
+
81
+ const indentLine = line => ` ${line}`;
82
+ const dedentLine = line => {
83
+ if (line.startsWith(" ")) {
84
+ return line.slice(2);
85
+ }
86
+ if (line.startsWith(" ")) {
87
+ return line.slice(1);
55
88
  }
89
+ return line;
56
90
  };
57
91
 
58
- xhr.send(textarea.value);
59
- };
92
+ const handleDent = event => {
93
+ event.preventDefault();
60
94
 
61
- let timeout = 0;
95
+ const { selectionStart, selectionEnd, value } = textarea;
96
+ const lines = value.split("\n");
62
97
 
63
- textarea.addEventListener("input", () => {
64
- clearTimeout(timeout);
65
- timeout = setTimeout(fetchCode, 300);
66
- });
98
+ let currentStart = selectionStart;
99
+ let currentEnd = selectionEnd;
100
+ let startLine = null;
101
+ let endLine = null;
102
+
103
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx += 1) {
104
+ currentStart -= (lines[lineIdx].length + 1);
105
+ if (startLine === null && currentStart < 0) {
106
+ startLine = lineIdx;
107
+ }
108
+
109
+ currentEnd -= (lines[lineIdx].length + 1);
110
+ if (endLine === null && currentEnd < 0) {
111
+ endLine = lineIdx;
112
+ }
113
+
114
+ if (currentStart < 0 && currentEnd < 0) {
115
+ break;
116
+ }
117
+ }
118
+
119
+ const nextLines = (
120
+ lines.slice(0, startLine)
121
+ .concat(lines.slice(startLine, endLine + 1).map(
122
+ event.key === "]" ? indentLine : dedentLine
123
+ ))
124
+ .concat(lines.slice(endLine + 1))
125
+ );
126
+
127
+ textarea.value = nextLines.join("\n");
128
+
129
+ const buffer = nextLines.slice(0, startLine + 1).join("\n").length;
130
+ textarea.selectionStart = textarea.selectionEnd = (
131
+ buffer + currentStart + 1
132
+ );
133
+ };
134
+
135
+ textarea.addEventListener("keydown", event => {
136
+ if (event.key === "Tab") {
137
+ handleTab(event);
138
+ } else if (["[", "]"].includes(event.key) && event.metaKey) {
139
+ handleDent(event);
140
+ }
141
+ });
142
+ })();
67
143
  </script>
68
144
  </body>
69
145
  </html>
@@ -14,4 +14,6 @@ post '/' do
14
14
  Preval.process(request.body.read || '').tap do |response|
15
15
  halt 422 unless response
16
16
  end
17
+ rescue NoMethodError
18
+ halt 422
17
19
  end
@@ -3,7 +3,7 @@
3
3
  require 'ripper'
4
4
 
5
5
  module Preval
6
- SyntaxError = Class.new(SyntaxError)
6
+ SyntaxError = Class.new(::SyntaxError)
7
7
 
8
8
  class << self
9
9
  attr_reader :visitors
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Preval
2
- VERSION = '0.4.1'
4
+ VERSION = '0.5.0'
3
5
  end
@@ -6,15 +6,15 @@ module Preval
6
6
  def on_def(node)
7
7
  # auto create attr_readers
8
8
  if node.type_match?(:@ident, :params, :bodystmt) &&
9
- # def foo; @foo; end
9
+ # def foo; @foo; end
10
10
  node[1].body.none? &&
11
- # there are no params to this method
11
+ # there are no params to this method
12
12
  node[2, 0].type_match?(:stmts_new, :var_ref) &&
13
- # there is only one statement in the body and its a var reference
13
+ # there is only one statement in the body and its a var reference
14
14
  node[2, 0, 1, 0].is?(:@ivar) &&
15
- # the var reference is referencing an instance variable
15
+ # the var reference is referencing an instance variable
16
16
  node[0].body == node[2, 0, 1, 0].body[1..-1]
17
- # the name of the variable matches the name of the method
17
+ # the name of the variable matches the name of the method
18
18
 
19
19
  ast = Parser.parse("attr_reader :#{node[0].body}")
20
20
  node.update(:stmts_add, ast.body[0].body)
@@ -22,28 +22,28 @@ module Preval
22
22
 
23
23
  # auto create attr_writers
24
24
  if node.type_match?(:@ident, :paren, :bodystmt) &&
25
- # def foo=(value); @foo = value; end
25
+ # def foo=(value); @foo = value; end
26
26
  node[0].body.end_with?('=') &&
27
- # this is a setter method
27
+ # this is a setter method
28
28
  node[1, 0, 0].length == 1 &&
29
- # there is exactly one required param
29
+ # there is exactly one required param
30
30
  node[1, 0].body[1..-1].none? &&
31
- # there are no other params
31
+ # there are no other params
32
32
  (
33
33
  node[2, 0].type_match?(:stmts_new, :assign) || (
34
34
  node[2, 0, 0].type_match?(:stmts_new, :void_stmt) &&
35
35
  node[2, 0].type_match?(:stmts_add, :assign)
36
36
  )
37
37
  ) &&
38
- # there is only one statement in the body and it's an assignment
38
+ # there is only one statement in the body and it's an assignment
39
39
  node[2, 0, 1].type_match?(:var_field, :var_ref) &&
40
- # assigning a variable
40
+ # assigning a variable
41
41
  node[2, 0, 1, 0, 0].is?(:@ivar) &&
42
- # assigning to an instance variable
42
+ # assigning to an instance variable
43
43
  node[0].body[0..-2] == node[2, 0, 1, 0, 0].body[1..-1] &&
44
- # variable name matches the method name
44
+ # variable name matches the method name
45
45
  node[1, 0, 0][0].body == node[2, 0, 1, 1, 0].body
46
- # assignment variable matches the argument name
46
+ # assignment variable matches the argument name
47
47
 
48
48
  ast = Parser.parse("attr_writer :#{node[0].body[0..-2]}")
49
49
  node.update(:stmts_add, ast.body[0].body)
@@ -9,16 +9,24 @@ module Preval
9
9
 
10
10
  # replace `.reverse.each` with `.reverse_each`
11
11
  # replace `.shuffle.first` with `.sample`
12
+ # replace `.sort.first` with `min`
13
+ # replace `.sort.last` with `max`
12
14
  if node.type_match?(:call, :@period, :@ident) &&
13
- # foo.each
15
+ # foo.each
14
16
  left.type_match?(%i[array vcall], :@period, :@ident)
15
- # foo.reverse
17
+ # foo.reverse
16
18
 
17
19
  callleft, callperiod, callright = left.body
18
20
 
19
21
  if callright.body == 'reverse' && right.body == 'each'
20
22
  callright.update(:@ident, 'reverse_each')
21
23
  node.update(:call, [callleft, callperiod, callright])
24
+ elsif callright.body == 'sort' && right.body == 'first'
25
+ callright.update(:@ident, 'min')
26
+ node.update(:call, [callleft, callperiod, callright])
27
+ elsif callright.body == 'sort' && right.body == 'last'
28
+ callright.update(:@ident, 'max')
29
+ node.update(:call, [callleft, callperiod, callright])
22
30
  elsif callright.body == 'shuffle' && right.body == 'first'
23
31
  callright.update(:@ident, 'sample')
24
32
  node.update(:call, [callleft, callperiod, callright])
@@ -29,20 +37,20 @@ module Preval
29
37
  def on_method_add_arg(node)
30
38
  # replace `.gsub('...', '...')` with `.tr('...', '...')`
31
39
  if node.type_match?(:call, :arg_paren) &&
32
- # foo.gsub()
40
+ # foo.gsub()
33
41
  node[0].type_match?(:vcall, :@period, :@ident) &&
34
- # foo.gsub
42
+ # foo.gsub
35
43
  node[0, 2].body == 'gsub'
36
- # the method being called is gsub
44
+ # the method being called is gsub
37
45
 
38
46
  left = node[1, 0, 0, 0, 1]
39
47
  right = node[1, 0, 0, 1]
40
48
 
41
49
  if left.is?(:string_literal) &&
42
50
  right.is?(:string_literal) &&
43
- [left, right].all? do |node|
44
- node[0, 1].is?(:@tstring_content) &&
45
- node[0, 1].body.length == 1
51
+ [left, right].all? do |string|
52
+ string[0, 1].is?(:@tstring_content) &&
53
+ string[0, 1].body.length == 1
46
54
  end
47
55
 
48
56
  node[0, 2].update(:@ident, 'tr')
@@ -50,22 +58,22 @@ module Preval
50
58
  end
51
59
 
52
60
  # replace `.map { ... }.flatten(1)` with `.flat_map { ... }`
53
- if node.type_match?(:call, :arg_paren) &&
54
- # foo.flatten()
61
+ if node.type_match?(:call, :arg_paren) &&
62
+ # foo.flatten()
55
63
  node[0].type_match?(:method_add_block, :@period, :@ident) &&
56
- # foo.map {}
64
+ # foo.map {}
57
65
  node[0, 0, 0].type_match?(%i[array vcall], :@period, :@ident) &&
58
- # foo.flatten
66
+ # foo.flatten
59
67
  node[0, 0, 0, 2].body == 'map' &&
60
- # the inner call is a call to map
68
+ # the inner call is a call to map
61
69
  node[0, 2].body == 'flatten' &&
62
- # the outer call is a call to flatten
70
+ # the outer call is a call to flatten
63
71
  node[1].is?(:arg_paren) &&
64
- # flatten has a param
72
+ # flatten has a param
65
73
  node[1, 0, 0].type_match?(:args_new, :@int) &&
66
- # there is only one argument to flatten and it is an integer
74
+ # there is only one argument to flatten and it is an integer
67
75
  node[1, 0, 0, 1].body == '1'
68
- # the value of the argument to flatten is 1
76
+ # the value of the argument to flatten is 1
69
77
 
70
78
  node[0, 0, 0, 2].update(:@ident, 'flat_map')
71
79
  node.update(:method_add_block, node[0, 0].body)
@@ -16,11 +16,11 @@ module Preval
16
16
  def on_while(node)
17
17
  # auto replace `while true` with `loop do`
18
18
  if node[0].is?(:var_ref) &&
19
- # the predicate to the while is a variable reference
19
+ # the predicate to the while is a variable reference
20
20
  node[0, 0].is?(:@kw) &&
21
- # the variable reference is a keyword
21
+ # the variable reference is a keyword
22
22
  node[0, 0].body == 'true'
23
- # the keyword is "true"
23
+ # the keyword is "true"
24
24
 
25
25
  ast = Parser.parse(<<~CODE)
26
26
  loop do
@@ -33,11 +33,11 @@ module Preval
33
33
 
34
34
  # ignore `while false`
35
35
  if node[0].is?(:var_ref) &&
36
- # the predicate to the while is a variable reference
36
+ # the predicate to the while is a variable reference
37
37
  node[0, 0].is?(:@kw) &&
38
- # the variable reference is a keyword
38
+ # the variable reference is a keyword
39
39
  node[0, 0].body == 'false'
40
- # the kwyword is "false"
40
+ # the kwyword is "false"
41
41
 
42
42
  node.update(:void_stmt, [])
43
43
  end
@@ -46,11 +46,11 @@ module Preval
46
46
  def on_until(node)
47
47
  # auto replace `until false` with `loop do`
48
48
  if node[0].is?(:var_ref) &&
49
- # the predicate to the while is a variable reference
49
+ # the predicate to the while is a variable reference
50
50
  node[0, 0].is?(:@kw) &&
51
- # the variable reference is a keyword
51
+ # the variable reference is a keyword
52
52
  node[0, 0].body == 'false'
53
- # the keyword is "false"
53
+ # the keyword is "false"
54
54
 
55
55
  ast = Parser.parse(<<~CODE)
56
56
  loop do
@@ -63,11 +63,11 @@ module Preval
63
63
 
64
64
  # ignore `until true`
65
65
  if node[0].is?(:var_ref) &&
66
- # the predicate to the until is a variable reference
66
+ # the predicate to the until is a variable reference
67
67
  node[0, 0].is?(:@kw) &&
68
- # the variable reference is a keyword
68
+ # the variable reference is a keyword
69
69
  node[0, 0].body == 'true'
70
- # the kwyword is "true"
70
+ # the kwyword is "true"
71
71
 
72
72
  node.update(:void_stmt, [])
73
73
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'preval/version'
@@ -12,14 +14,19 @@ Gem::Specification.new do |spec|
12
14
  spec.homepage = 'https://github.com/kddeisz/preval'
13
15
  spec.license = 'MIT'
14
16
 
15
- spec.files = Dir.chdir(__dir__) do
16
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- end
17
+ spec.files =
18
+ Dir.chdir(__dir__) do
19
+ `git ls-files -z`.split("\x0").reject do |f|
20
+ f.match(%r{^(test|spec|features)/})
21
+ end
22
+ end
23
+
18
24
  spec.bindir = 'exe'
19
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
26
  spec.require_paths = ['lib']
21
27
 
22
28
  spec.add_development_dependency 'bundler', '~> 2.0'
23
- spec.add_development_dependency 'rake', '~> 12.3'
24
29
  spec.add_development_dependency 'minitest', '~> 5.11'
30
+ spec.add_development_dependency 'rake', '~> 12.3'
31
+ spec.add_development_dependency 'rubocop', '~> 0.71'
25
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: preval
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Deisz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-20 00:00:00.000000000 Z
11
+ date: 2019-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.11'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -39,19 +53,19 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '12.3'
41
55
  - !ruby/object:Gem::Dependency
42
- name: minitest
56
+ name: rubocop
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '5.11'
61
+ version: '0.71'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '5.11'
68
+ version: '0.71'
55
69
  description:
56
70
  email:
57
71
  - kevin.deisz@gmail.com
@@ -60,8 +74,10 @@ extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
62
76
  - ".gitignore"
77
+ - ".rubocop.yml"
63
78
  - ".travis.yml"
64
79
  - CHANGELOG.md
80
+ - CODE_OF_CONDUCT.md
65
81
  - Gemfile
66
82
  - Gemfile.lock
67
83
  - LICENSE