preval 0.4.0 → 0.6.1

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: 9e8e8b1cc9579dafac71315c8138e9a07ece0a40e8c0e0ccdb3f9345574c4446
4
- data.tar.gz: 5bc5ccf467619084b2243f082466881e1f491ecb844cd622ff612b9c7242ae42
3
+ metadata.gz: d26654a804f989edced31f92fd9617a20aee3c7411329b38ed108c905f215e73
4
+ data.tar.gz: 2653cba29f3e0bb72fd514cf75a56c9b95c75f435ce66809812ac02b0015b63e
5
5
  SHA512:
6
- metadata.gz: aebce20a3d9332bf8e8eb20ea9f71789531b1462398713a22f9fb027c860aaee0d060bb94ddc6f400ceb1a6a94e0619638c54bedadcfa6eaea4a5cf3254cbb32
7
- data.tar.gz: 515570a0d77a859529530b9113a8309b974c9bf09c7117afc2eee605406725155d3f35995bcac674cd0d3577e4da00891faa189925a573dde08fdd3b2fc9cda6
6
+ metadata.gz: fd486370dde1064f95b4c311dd7ee37e43a1468a54d3e6af68ec93e9192c045eef2c5cf3d0961ef63aa6c2a1d9f00ba12cf540ba0d8f0cd50436a2522cd93d4f
7
+ data.tar.gz: dd459fd9b480dab7d6a520af82d87540316f9c4950f2a30a4e5fe74d3e44bcb288f80edffcf13e2c6ab3e6ba7f7fb9617397c815aa236d5294bae252714478a3
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "bundler"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "daily"
@@ -0,0 +1,34 @@
1
+ name: Main
2
+ on:
3
+ - push
4
+ - pull_request_target
5
+ jobs:
6
+ ci:
7
+ name: CI
8
+ runs-on: ubuntu-latest
9
+ env:
10
+ CI: true
11
+ steps:
12
+ - uses: actions/checkout@master
13
+ - uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: 3.0
16
+ bundler-cache: true
17
+ - name: Lint and test
18
+ run: |
19
+ bundle exec rubocop --parallel
20
+ bundle exec rake test
21
+ automerge:
22
+ name: AutoMerge
23
+ needs: ci
24
+ runs-on: ubuntu-latest
25
+ if: github.event_name == 'pull_request_target' && (github.actor == github.repository_owner || github.actor == 'dependabot[bot]')
26
+ steps:
27
+ - uses: actions/github-script@v3
28
+ with:
29
+ script: |
30
+ github.pulls.merge({
31
+ owner: context.payload.repository.owner.login,
32
+ repo: context.payload.repository.name,
33
+ pull_number: context.payload.pull_request.number
34
+ })
data/.rubocop.yml ADDED
@@ -0,0 +1,58 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+ DisplayStyleGuide: true
4
+ TargetRubyVersion: 2.7
5
+ Exclude:
6
+ - '{tmp,vendor,yard}/**/*'
7
+ - lib/preval/format.rb
8
+
9
+ Gemspec/RequiredRubyVersion:
10
+ Enabled: false
11
+
12
+ Layout/LineLength:
13
+ Max: 80
14
+
15
+ Lint/AmbiguousBlockAssociation:
16
+ Enabled: false
17
+
18
+ Layout/CommentIndentation:
19
+ Enabled: false
20
+
21
+ Lint/InheritException:
22
+ Enabled: false
23
+
24
+ Metrics/AbcSize:
25
+ Enabled: false
26
+
27
+ Metrics/CyclomaticComplexity:
28
+ Enabled: false
29
+
30
+ Metrics/MethodLength:
31
+ Enabled: false
32
+
33
+ Metrics/PerceivedComplexity:
34
+ Enabled: false
35
+
36
+ Naming/RescuedExceptionsVariableName:
37
+ Enabled: false
38
+
39
+ Style/Documentation:
40
+ Enabled: false
41
+
42
+ Style/FormatString:
43
+ EnforcedStyle: percent
44
+
45
+ Style/FormatStringToken:
46
+ Enabled: false
47
+
48
+ Style/NumericPredicate:
49
+ Enabled: false
50
+
51
+ Style/OptionalBooleanParameter:
52
+ Enabled: false
53
+
54
+ Style/PerlBackrefs:
55
+ Enabled: false
56
+
57
+ Style/SlicingWithRange:
58
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -6,19 +6,49 @@ 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.6.1] - 2021-11-17
10
+
11
+ ### Changed
12
+
13
+ - Require MFA for releasing.
14
+
15
+ ## [0.6.0] - 2019-12-31
16
+
17
+ ### Added
18
+
19
+ - Support for `nokw_param`, `args_forward`, and `in` nodes.
20
+
21
+ ## [0.5.0] - 2019-06-13
22
+
23
+ ### Added
24
+
25
+ - Replace `.sort.first` with `.min`.
26
+ - Replace `.sort.last` with `.max`.
27
+
28
+ ## [0.4.1] - 2019-04-20
29
+
30
+ ### Changed
31
+
32
+ - Support `attr_writer` transformations even if there is a void statement at the beginning of the method.
33
+
9
34
  ## [0.4.0] - 2019-04-19
35
+
10
36
  ### Added
37
+
11
38
  - Replace `def foo=(value); @foo = value; end` with `attr_writer :foo`
12
39
  - Replace `while false ... end` loops with nothing
13
40
  - Replace `until false ... end` loops with `loop do ... end` loops
14
41
  - Replace `until true ... end` loops with nothing
15
42
 
16
43
  ### Changed
44
+
17
45
  - Extracted out the `Preval::Visitors::AttrAccessor` visitor.
18
46
  - Renamed the `Preval::Visitors::Micro` visitor to `Preval::Visitors::Fasterer`.
19
47
 
20
48
  ## [0.3.0] - 2019-04-19
49
+
21
50
  ### Added
51
+
22
52
  - Fold constant for exponentiation if exponent is 0 and value is an integer.
23
53
  - Replace `.reverse.each` usage with `.reverse_each`.
24
54
  - Replace `foo ... in` loops with `.each do` loops.
@@ -29,15 +59,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
29
59
  - Replace `def foo=(value); @foo = value; end` with `attr_writer :foo`.
30
60
 
31
61
  ## [0.2.0] - 2019-04-18
62
+
32
63
  ### Added
64
+
33
65
  - Hook into the `bootsnap` gem if it's loaded.
34
66
 
35
67
  ## [0.1.0] - 2019-03-08
68
+
36
69
  ### Added
70
+
37
71
  - Initial release. 🎉
38
72
 
39
- [Unreleased]: https://github.com/kddeisz/preval/compare/v0.4.0...HEAD
40
- [0.4.0]: https://github.com/kddeisz/preval/compare/v0.3.0...v0.4.0
41
- [0.3.0]: https://github.com/kddeisz/preval/compare/v0.2.0...v0.3.0
42
- [0.2.0]: https://github.com/kddeisz/preval/compare/v0.1.0...v0.2.0
43
- [0.1.0]: https://github.com/kddeisz/preval/compare/49c899...v0.1.0
73
+ [unreleased]: https://github.com/kddnewton/preval/compare/v0.6.1...HEAD
74
+ [0.6.1]: https://github.com/kddnewton/preval/compare/v0.6.0...v0.6.1
75
+ [0.6.0]: https://github.com/kddnewton/preval/compare/v0.5.0...v0.6.0
76
+ [0.5.0]: https://github.com/kddnewton/preval/compare/v0.4.1...v0.5.0
77
+ [0.4.1]: https://github.com/kddnewton/preval/compare/v0.4.0...v0.4.1
78
+ [0.4.0]: https://github.com/kddnewton/preval/compare/v0.3.0...v0.4.0
79
+ [0.3.0]: https://github.com/kddnewton/preval/compare/v0.2.0...v0.3.0
80
+ [0.2.0]: https://github.com/kddnewton/preval/compare/v0.1.0...v0.2.0
81
+ [0.1.0]: https://github.com/kddnewton/preval/compare/49c899...v0.1.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 kddnewton@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
data/Gemfile.lock CHANGED
@@ -1,26 +1,48 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- preval (0.4.0)
4
+ preval (0.6.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- bootsnap (1.4.3)
9
+ ast (2.4.2)
10
+ bootsnap (1.9.1)
10
11
  msgpack (~> 1.0)
11
- minitest (5.11.3)
12
- msgpack (1.2.10)
13
- mustermann (1.0.3)
14
- rack (2.0.6)
15
- rack-protection (2.0.5)
12
+ minitest (5.14.4)
13
+ msgpack (1.4.2)
14
+ mustermann (1.1.1)
15
+ ruby2_keywords (~> 0.0.1)
16
+ parallel (1.21.0)
17
+ parser (3.0.2.0)
18
+ ast (~> 2.4.1)
19
+ rack (2.2.3)
20
+ rack-protection (2.1.0)
16
21
  rack
17
- rake (12.3.2)
18
- sinatra (2.0.5)
22
+ rainbow (3.0.0)
23
+ rake (13.0.6)
24
+ regexp_parser (2.1.1)
25
+ rexml (3.2.5)
26
+ rubocop (1.23.0)
27
+ parallel (~> 1.10)
28
+ parser (>= 3.0.0.0)
29
+ rainbow (>= 2.2.2, < 4.0)
30
+ regexp_parser (>= 1.8, < 3.0)
31
+ rexml
32
+ rubocop-ast (>= 1.12.0, < 2.0)
33
+ ruby-progressbar (~> 1.7)
34
+ unicode-display_width (>= 1.4.0, < 3.0)
35
+ rubocop-ast (1.13.0)
36
+ parser (>= 3.0.1.1)
37
+ ruby-progressbar (1.11.0)
38
+ ruby2_keywords (0.0.5)
39
+ sinatra (2.1.0)
19
40
  mustermann (~> 1.0)
20
- rack (~> 2.0)
21
- rack-protection (= 2.0.5)
41
+ rack (~> 2.2)
42
+ rack-protection (= 2.1.0)
22
43
  tilt (~> 2.0)
23
- tilt (2.0.9)
44
+ tilt (2.0.10)
45
+ unicode-display_width (2.1.0)
24
46
 
25
47
  PLATFORMS
26
48
  ruby
@@ -30,8 +52,9 @@ DEPENDENCIES
30
52
  bundler (~> 2.0)
31
53
  minitest (~> 5.11)
32
54
  preval!
33
- rake (~> 12.3)
55
+ rake (~> 13.0)
56
+ rubocop (~> 1.0)
34
57
  sinatra
35
58
 
36
59
  BUNDLED WITH
37
- 2.0.1
60
+ 2.2.3
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019 Kevin Deisz
3
+ Copyright (c) 2019-present Kevin Newton
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Preval
2
2
 
3
+ [![Build Status](https://github.com/kddnewton/preval/workflows/Main/badge.svg)](https://github.com/kddnewton/preval/actions)
4
+ [![Gem Version](https://img.shields.io/gem/v/preval.svg)](https://rubygems.org/gems/preval)
5
+
3
6
  `preval` is a gem that hooks into the Ruby compilation process and runs optimizations before it gets loaded by the virtual machine.
4
7
 
5
8
  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 +29,60 @@ Or install it yourself as:
26
29
 
27
30
  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
31
 
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.
32
+ 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.
33
+
34
+ ### `Preval::Visitors::Arithmetic`
35
+
36
+ Replaces:
37
+
38
+ - constant expressions with their evaluation (e.g., `5 + 2` becomes `7`)
39
+ - arithmetic identities with their evaluation (e.g., `a * 1` becomes `a`)
40
+
41
+ Unsafe if:
42
+
43
+ - you overload any of the `Integer` operator methods
44
+
45
+ ### `Preval::Visitors::AttrAccessor`
46
+
47
+ Replaces:
48
+
49
+ - `def foo; @foo; end` with `attr_reader :foo`
50
+ - `def foo=(value); @foo = value; end` with `attr_writer :foo`
51
+
52
+ Unsafe if:
53
+
54
+ - you overload the `attr_reader` method
55
+ - you overload the `attr_writer` method
56
+ - you have custom complex `method_added` logic
57
+
58
+ ### `Preval::Visitors::Fasterer`
59
+
60
+ Replaces:
61
+
62
+ - `.gsub('...', '...')` with `.tr('...', '...')` if the arguments are strings and are both of length 1
63
+ - `.map { ... }.flatten(1)` with `.flat_map { ... }`
64
+ - `.reverse.each` with `.reverse_each`
65
+ - `.shuffle.first` with `.sample`
66
+ - `.sort.first` with `.min`
67
+ - `.sort.last` with `.max`
68
+
69
+ Unsafe if:
70
+
71
+ - 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.)
72
+
73
+ ### `Preval::Visitors::Loops`
74
+
75
+ Replaces:
76
+
77
+ - `for ... in ... end` loops with `... each do ... end` loops
78
+ - `while true ... end` loops with `loop do ... end` loops
79
+ - `while false ... end` loops with nothing
80
+ - `until false ... end` loops with `loop do ... end` loops
81
+ - `until true ... end` loops with nothing
82
+
83
+ Unsafe if:
84
+
85
+ - 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
86
 
51
87
  ## Development
52
88
 
@@ -56,7 +92,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
56
92
 
57
93
  ## Contributing
58
94
 
59
- Bug reports and pull requests are welcome on GitHub at https://github.com/kddeisz/preval.
95
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kddnewton/preval.
60
96
 
61
97
  ## License
62
98
 
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
 
data/bin/console 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'
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'
data/docs/index.html CHANGED
@@ -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>
data/docs/server.rb CHANGED
@@ -11,7 +11,9 @@ get '/' do
11
11
  end
12
12
 
13
13
  post '/' do
14
- Preval.process(request.body.read).tap do |response|
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
data/lib/preval/format.rb CHANGED
@@ -20,6 +20,7 @@ module Preval
20
20
  parts.join
21
21
  end
22
22
  to(:args_add_star) { starts_with?(:args_new) ? "*#{source(1)}" : "#{source(0)}, *#{source(1)}" }
23
+ to(:args_forward) { '...' }
23
24
  to(:args_new) { '' }
24
25
  to(:assign) { "#{source(0)} = #{source(1)}" }
25
26
  to(:array) { body[0].nil? ? '[]' : "#{starts_with?(:args_add) ? '[' : ''}#{source(0)}]" }
@@ -72,6 +73,7 @@ module Preval
72
73
  to(:if) { "if #{source(0)}\n#{source(1)}\n#{body[2] ? "#{source(2)}\n" : ''}end" }
73
74
  to(:if_mod) { "#{source(1)} if #{source(0)}" }
74
75
  to(:ifop) { "#{source(0)} ? #{source(1)} : #{source(2)}"}
76
+ to(:in) { "in #{source(0)}\n#{source(1)}" }
75
77
  to(:kwrest_param) { "**#{body[0] ? source(0) : ''}" }
76
78
  to(:lambda) { "->(#{starts_with?(:paren) ? body[0].body[0].to_source : source(0)}) { #{source(1)} }" }
77
79
  to(:massign) { join(' = ') }
@@ -99,7 +101,7 @@ module Preval
99
101
  parts << rest.to_source if rest
100
102
  parts += post.map(&:to_source) if post
101
103
  parts += kwargs.map { |(kwarg, value)| value ? "#{kwarg.to_source} #{value.to_source}" : kwarg.to_source } if kwargs
102
- parts << kwarg_rest.to_source if kwarg_rest
104
+ parts << (kwarg_rest.is_a?(Symbol) ? "**#{kwarg_rest}" : kwarg_rest.to_source) if kwarg_rest
103
105
  parts << block.to_source if block
104
106
 
105
107
  parts.join(', ')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Preval
2
- VERSION = '0.4.0'
4
+ VERSION = '0.6.1'
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,25 +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
32
- node[2, 0, 0, 0]&.is?(:stmts_new) &&
33
- # there is only one statement in the body
34
- node[2, 0, 1].is?(:assign) &&
35
- # the only statement is an assignment
31
+ # there are no other params
32
+ (
33
+ node[2, 0].type_match?(:stmts_new, :assign) || (
34
+ node[2, 0, 0].type_match?(:stmts_new, :void_stmt) &&
35
+ node[2, 0].type_match?(:stmts_add, :assign)
36
+ )
37
+ ) &&
38
+ # there is only one statement in the body and it's an assignment
36
39
  node[2, 0, 1].type_match?(:var_field, :var_ref) &&
37
- # assigning a variable
40
+ # assigning a variable
38
41
  node[2, 0, 1, 0, 0].is?(:@ivar) &&
39
- # assigning to an instance variable
42
+ # assigning to an instance variable
40
43
  node[0].body[0..-2] == node[2, 0, 1, 0, 0].body[1..-1] &&
41
- # variable name matches the method name
44
+ # variable name matches the method name
42
45
  node[1, 0, 0][0].body == node[2, 0, 1, 1, 0].body
43
- # assignment variable matches the argument name
46
+ # assignment variable matches the argument name
44
47
 
45
48
  ast = Parser.parse("attr_writer :#{node[0].body[0..-2]}")
46
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
data/lib/preval.rb CHANGED
@@ -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
data/preval.gemspec CHANGED
@@ -1,25 +1,40 @@
1
- lib = File.expand_path('lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'preval/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/preval/version'
4
+
5
+ version = Preval::VERSION
6
+ repository = 'https://github.com/kddnewton/preval'
4
7
 
5
8
  Gem::Specification.new do |spec|
6
9
  spec.name = 'preval'
7
- spec.version = Preval::VERSION
8
- spec.authors = ['Kevin Deisz']
9
- spec.email = ['kevin.deisz@gmail.com']
10
+ spec.version = version
11
+ spec.authors = ['Kevin Newton']
12
+ spec.email = ['kddnewton@gmail.com']
10
13
 
11
14
  spec.summary = 'Automatically optimizes your Ruby code'
12
- spec.homepage = 'https://github.com/kddeisz/preval'
15
+ spec.homepage = repository
13
16
  spec.license = 'MIT'
14
17
 
15
- spec.files = Dir.chdir(__dir__) do
16
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- end
18
+ spec.metadata = {
19
+ 'bug_tracker_uri' => "#{repository}/issues",
20
+ 'changelog_uri' => "#{repository}/blob/v#{version}/CHANGELOG.md",
21
+ 'source_code_uri' => repository,
22
+ 'rubygems_mfa_required' => 'true'
23
+ }
24
+
25
+ spec.files =
26
+ Dir.chdir(__dir__) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ f.match(%r{^(test|spec|features)/})
29
+ end
30
+ end
31
+
18
32
  spec.bindir = 'exe'
19
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
34
  spec.require_paths = ['lib']
21
35
 
22
36
  spec.add_development_dependency 'bundler', '~> 2.0'
23
- spec.add_development_dependency 'rake', '~> 12.3'
24
37
  spec.add_development_dependency 'minitest', '~> 5.11'
38
+ spec.add_development_dependency 'rake', '~> 13.0'
39
+ spec.add_development_dependency 'rubocop', '~> 1.0'
25
40
  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.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
- - Kevin Deisz
8
- autorequire:
7
+ - Kevin Newton
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-19 00:00:00.000000000 Z
11
+ date: 2021-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,44 +24,61 @@ 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
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '12.3'
47
+ version: '13.0'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '12.3'
54
+ version: '13.0'
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: '1.0'
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'
55
- description:
68
+ version: '1.0'
69
+ description:
56
70
  email:
57
- - kevin.deisz@gmail.com
71
+ - kddnewton@gmail.com
58
72
  executables: []
59
73
  extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
76
+ - ".github/dependabot.yml"
77
+ - ".github/workflows/main.yml"
62
78
  - ".gitignore"
63
- - ".travis.yml"
79
+ - ".rubocop.yml"
64
80
  - CHANGELOG.md
81
+ - CODE_OF_CONDUCT.md
65
82
  - Gemfile
66
83
  - Gemfile.lock
67
84
  - LICENSE
@@ -86,11 +103,15 @@ files:
86
103
  - lib/preval/visitors/fasterer.rb
87
104
  - lib/preval/visitors/loops.rb
88
105
  - preval.gemspec
89
- homepage: https://github.com/kddeisz/preval
106
+ homepage: https://github.com/kddnewton/preval
90
107
  licenses:
91
108
  - MIT
92
- metadata: {}
93
- post_install_message:
109
+ metadata:
110
+ bug_tracker_uri: https://github.com/kddnewton/preval/issues
111
+ changelog_uri: https://github.com/kddnewton/preval/blob/v0.6.1/CHANGELOG.md
112
+ source_code_uri: https://github.com/kddnewton/preval
113
+ rubygems_mfa_required: 'true'
114
+ post_install_message:
94
115
  rdoc_options: []
95
116
  require_paths:
96
117
  - lib
@@ -105,8 +126,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
126
  - !ruby/object:Gem::Version
106
127
  version: '0'
107
128
  requirements: []
108
- rubygems_version: 3.0.3
109
- signing_key:
129
+ rubygems_version: 3.2.3
130
+ signing_key:
110
131
  specification_version: 4
111
132
  summary: Automatically optimizes your Ruby code
112
133
  test_files: []
data/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: ruby
2
- cache: bundler
3
- rvm: 2.6.0
4
- before_install: gem install bundler -v 2.0.0