preval 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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