preval 0.4.0 → 0.6.1

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: 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