ast_transform 1.0.0.pre.alpha.pre.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 48a95ba22654bc424c664e918b4d05d96938aa9647adfa742de94f9fb21ea6ce
4
+ data.tar.gz: 42611d110dbf44a11603d05b83b1eadfafb49fb0733dbb1063ec0f204af0fa3a
5
+ SHA512:
6
+ metadata.gz: 84cd1fb8c9a63f0f565cc1cf6bf2cf75e10d0d6e8920372049f147aa6ddb7cc48e1324178a3358993bb826521d29bdb80ff7d7095a4150f6cf2d6ec71fda024b
7
+ data.tar.gz: a6a1411fbcd95e50ae9834d82a29093707feaac26e604364bd04e4e8c7f0510db44a8b205ac67422bb33e90ad9d11c7bb78dbcb1cb941715e27fb7255e6e87a6
data/.gitignore ADDED
@@ -0,0 +1,53 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
51
+
52
+ # RubyMine
53
+ /.idea
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+ rvm:
3
+ - '2.2'
4
+ - '2.3'
5
+ - '2.4'
6
+ - '2.5'
7
+ - '2.6'
8
+ - '2.7'
9
+ install: ./bin/setup
10
+ deploy:
11
+ provider: rubygems
12
+ gemspec: ast_transform.gemspec
13
+ gem: ast_transform
14
+ on:
15
+ branch: master
16
+ tag: true
17
+ api_key:
18
+ secure: a+D7+wUWmgdk39C+oDFAqjLDAE9s8uaN08xPgf0/IaxFy3PGeuxKSi+3ZFaB0f6zI203mMe7m0sNVD77PxyPqKToKal5K9240+m9yZGoS4seMOJExcBmhiMXk8KOj9upw5Fk0PY91LY5lU13m6u40MtHT9r8da8AysO21PGhkrDkYZNGZ/0nCpV3hnux6DOlNguUhHuuKIKCY6xN9tldwUhVZJvDjSKLe0aRMbP0PZhM3JzMz9BfosirkdE9u666ryI5kIFfHgzDCekiU1sbEugOKyHyu90kaVhgRAXA81LHirj6U9pA0cfJOq84EAE2di6A0xswlbM8GBXdJpRIh706A6sZ7ByYG2SlxyF9vHogqhpg8aU5IMk+sNbrg6++qyTW8zSTksoJN/kMAHcEvMloK4Ja2rRPqhIJkawcQRd5unxHnt4jc6ED2ryDbXhAaBWX4G80jpg5lSeHIQH/S9v+PWcG9UB8Rv08oiJadEid+26r4rWsRA0f/hBP6K+KMq3rACa2Xapgo9ZbSE4xBBu+Yc+4ooiKQ42y+10A5TlTI6iSxKUDcmbH/qVZIchzlDv4MFYoE50BHVn9p0J6dbf8q+WQXsRi1ojEcVy95hd00wdmeMGItE7VuW8zDObJ5W8LNaKuKD358o+4v5CrammfNNz/MynRXhfmPCwyy8s=
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.4] 2019-06-20
8
+ ### Fixed
9
+ - Source mapping for transformations wrapping source nodes into virtual nodes now work.
10
+
11
+ ## [0.1.3] 2019-05-28
12
+ ### Added
13
+ - Changing the output path for transformed files is now supported through `ASTTransform.output_path=`.
14
+
15
+ ## [0.1.2] 2018-12-21
16
+ ### Fixed
17
+ - Bumped and relaxed Unparser dependency to ~> 0.4
18
+
19
+ ## [0.1.1] 2018-12-06
20
+ ### Added
21
+ - ASTTransform::Transformer now supports passing a custom AST::Node Builder.
22
+
23
+ ## [0.1.0] 2018-11-08
24
+ ### Initial Release!
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ast_transform.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,69 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ast_transform (1.0.0)
5
+ parser (~> 2.7)
6
+ unparser (~> 0.4)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ abstract_type (0.0.7)
12
+ adamantium (0.2.0)
13
+ ice_nine (~> 0.11.0)
14
+ memoizable (~> 0.4.0)
15
+ ansi (1.5.0)
16
+ ast (2.4.1)
17
+ builder (3.2.4)
18
+ byebug (11.1.3)
19
+ coderay (1.1.3)
20
+ concord (0.1.5)
21
+ adamantium (~> 0.2.0)
22
+ equalizer (~> 0.0.9)
23
+ diff-lcs (1.4.4)
24
+ equalizer (0.0.11)
25
+ ice_nine (0.11.2)
26
+ memoizable (0.4.2)
27
+ thread_safe (~> 0.3, >= 0.3.1)
28
+ method_source (1.0.0)
29
+ minitest (5.14.1)
30
+ minitest-reporters (1.4.2)
31
+ ansi
32
+ builder
33
+ minitest (>= 5.0)
34
+ ruby-progressbar
35
+ parser (2.7.1.4)
36
+ ast (~> 2.4.1)
37
+ procto (0.0.3)
38
+ pry (0.13.1)
39
+ coderay (~> 1.1)
40
+ method_source (~> 1.0)
41
+ pry-byebug (3.9.0)
42
+ byebug (~> 11.0)
43
+ pry (~> 0.13.0)
44
+ rake (13.0.1)
45
+ ruby-progressbar (1.10.1)
46
+ thread_safe (0.3.6)
47
+ unparser (0.4.7)
48
+ abstract_type (~> 0.0.7)
49
+ adamantium (~> 0.2.0)
50
+ concord (~> 0.1.5)
51
+ diff-lcs (~> 1.3)
52
+ equalizer (~> 0.0.9)
53
+ parser (>= 2.6.5)
54
+ procto (~> 0.0.2)
55
+
56
+ PLATFORMS
57
+ ruby
58
+
59
+ DEPENDENCIES
60
+ ast_transform!
61
+ bundler (~> 2.1)
62
+ minitest (~> 5.14)
63
+ minitest-reporters (~> 1.4.2)
64
+ pry
65
+ pry-byebug
66
+ rake (~> 13.0)
67
+
68
+ BUNDLED WITH
69
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Jean-Philippe Duchesne
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,185 @@
1
+ [![Build Status](https://travis-ci.org/rspockframework/ast-transform.svg?branch=master)](https://travis-ci.org/rspockframework/ast-transform)
2
+
3
+ # ASTTransform
4
+
5
+ ASTTransform is an Abstract Syntax Tree (AST) transformation framework. It hooks into the compilation process and allows to perform AST transformations using an annotation: `transform!`.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'ast_transform'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install ast_transform
22
+
23
+ Add this to the very beginning of your script or application to install the ASTTransform hook:
24
+
25
+ ```ruby
26
+ require 'ast_transform'
27
+ ASTTransform.install
28
+ ```
29
+
30
+ ### Compatibility with Bootsnap
31
+
32
+ ASTTransform is compatible with [Bootsnap](https://github.com/Shopify/bootsnap/). The only requirement is to install the above hook after Bootsnap, and ASTTransform does the rest for you.
33
+
34
+ ## Usage
35
+
36
+ Getting started using ASTTransform is extremely easy! All you need is to use the `transform!` annotation:
37
+
38
+ ```ruby
39
+ transform!(MyTransformation)
40
+ class MyClass
41
+ # ...
42
+ end
43
+ ```
44
+
45
+ When your class is required and loaded into the runtime, ASTTransform will run the `MyTransformation` transformation on the annotated code.
46
+
47
+ ### Supported annotated code
48
+
49
+ The following expressions can be annotated, which will pass only the annotated AST node to the transformation:
50
+
51
+ #### Class definitions
52
+
53
+ ```ruby
54
+ transform!(MyTransformation)
55
+ class Foo
56
+ # ...
57
+ end
58
+ ```
59
+
60
+ #### Constant assignments
61
+
62
+ ```ruby
63
+ transform!(MyTransformation)
64
+ Foo = Class.new do
65
+ # ...
66
+ end
67
+ ```
68
+
69
+ ### Running multiple transformations
70
+
71
+ #### On the same AST node
72
+
73
+ You can run multiple transformations on the same code, by passing multiple transformations to the annotation:
74
+
75
+ ```ruby
76
+ transform!(MyTransformation1, MyTransformation2)
77
+ class Foo
78
+ # ...
79
+ end
80
+ ```
81
+
82
+ **Note**: The transformations will be executed in order, the output of the previous transformation being fed into the next, etc...
83
+
84
+ #### On different AST nodes
85
+
86
+ Because each `transform!` annotation runs transformations in isolated scope, it is possible to have multiple annotated nodes in the same file:
87
+
88
+ ```ruby
89
+ transform!(MyTransformation)
90
+ class Foo
91
+ # ...
92
+ end
93
+
94
+ transform!(MyTransformation)
95
+ class Bar
96
+ # ...
97
+ end
98
+ ```
99
+
100
+ You can even have nested `transform!` annotations:
101
+
102
+ ```ruby
103
+ transform!(FooTransformation)
104
+ class Foo
105
+ transform!(BarTransformation)
106
+ class Bar
107
+ # ...
108
+ end
109
+
110
+ # ...
111
+ end
112
+ ```
113
+
114
+ The above code would first process class `Foo` using `FooTransformation` (which could even make modifications to `Bar` on its own), and then `BarTransformation` would be run against `Bar`.
115
+
116
+ ### Writing Transformations
117
+
118
+ For more in-depth information regarding processing AST nodes, we recommend looking at https://github.com/whitequark/ast, as transformations are built on top of `Parser::AST::Processor`, which in turn is built on top of the `ast` gem.
119
+
120
+ Transformations should derive from `ASTTransform::AbstractTransformation`:
121
+
122
+ ```ruby
123
+ require 'ast_transform/abstract_transformation'
124
+
125
+ class MyTransformation < ASTTransformation::AbstractTransformation
126
+ # ...
127
+ end
128
+ ```
129
+
130
+ #### Transformation discoverability
131
+
132
+ ASTTransform automatically loads your transformations at compile time. As such, we expect your files to be located at a known path.
133
+
134
+ Transformations are required using the following scheme, i.e. for `MyNamespace::MyTransformation`, it will make the following call, so your file must be placed accordingly for ASTTransform to find it:
135
+ ```ruby
136
+ require 'my_namespace/my_transformation'
137
+ ```
138
+
139
+ #### Processing each node
140
+
141
+ To do some processing on each node, override the `process_node` private method. If you do this, make sure to also process the children nodes if required.
142
+
143
+ ```ruby
144
+ require 'ast_transform/abstract_transformation'
145
+
146
+ class MyTransformation < ASTTransformation::AbstractTransformation
147
+ private
148
+
149
+ def process_node(node)
150
+ # ... processing
151
+ node.updated(nil, process_all(node.children))
152
+ end
153
+ end
154
+ ```
155
+
156
+ In the above, `node#updated` allows updating the node, either its type or its children. Each node is immutable, so updating nodes requires recursively re-creating the tree from the deepest modified nodes. Passing `nil` as the first argument keeps the same node type.
157
+
158
+ #### Processing on certain types of nodes only
159
+
160
+ The [ast gem](https://github.com/whitequark/ast) uses a pattern in which a Transformation may implement a method matching a node type, i.e. `on_class`, `on_send`, `on_lvar`, etc... This is very useful when transformations should process all nodes of this type.
161
+
162
+ ### Parameterizable transformations
163
+
164
+ If you want your transformation to be customizable, accept the parameters in the constructor. The annotation can the be changed accordingly:
165
+
166
+ ```ruby
167
+ class FooTransformation < ASTTransform::AbstractTransformation
168
+ def initialize(param1, params2: false)
169
+ # ...
170
+ end
171
+
172
+ # ...
173
+ end
174
+
175
+ transform!(FooTransformation.new(param1, param2: true))
176
+ class Foo
177
+ # ...
178
+ end
179
+ ```
180
+
181
+ ## Development
182
+
183
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
184
+
185
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ # Ensure we load test_loader first for ASTTransform.install
7
+ filepath = File.expand_path('test/test_loader.rb', __dir__)
8
+ t.ruby_opts << "-r #{filepath}"
9
+ t.warning = false
10
+
11
+ t.libs << 'test'
12
+ t.libs << 'lib'
13
+ t.test_files = FileList['test/**/*_test.rb']
14
+ end
15
+
16
+ task :default => :test
@@ -0,0 +1,43 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "ast_transform/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ast_transform"
8
+ spec.version = ASTTransform::VERSION
9
+ spec.authors = ["Jean-Philippe Duchesne"]
10
+ spec.email = ["jpduchesne89@gmail.com"]
11
+
12
+ spec.summary = 'An AST transformation framework.'
13
+ spec.description = spec.summary
14
+ spec.homepage = "https://github.com/rspockframework/ast-transform"
15
+ spec.license = "MIT"
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+ spec.required_ruby_version = '~> 2.2'
23
+
24
+ if ENV['TRAVIS']
25
+ if ENV['TRAVIS_TAG'].nil? || ENV['TRAVIS_TAG'].empty?
26
+ spec.version = "#{spec.version}-alpha-#{ENV['TRAVIS_BUILD_NUMBER']}"
27
+ elsif ENV['TRAVIS_TAG'] != spec.version.to_s
28
+ raise "Tag name (#{ENV['TRAVIS_TAG']}) and Gem version (#{spec.version}) are different"
29
+ end
30
+ end
31
+
32
+ # Development dependencies
33
+ spec.add_development_dependency "bundler", "~> 2.1"
34
+ spec.add_development_dependency "rake", "~> 13.0"
35
+ spec.add_development_dependency "minitest", "~> 5.14"
36
+ spec.add_development_dependency "minitest-reporters", "~> 1.4.2"
37
+ spec.add_development_dependency "pry"
38
+ spec.add_development_dependency "pry-byebug"
39
+
40
+ # Runtime dependencies
41
+ spec.add_runtime_dependency "parser", "~> 2.7"
42
+ spec.add_runtime_dependency "unparser", "~> 0.4"
43
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ast_transform"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ require 'ast_transform/transformation_helper'
3
+
4
+ module ASTTransform
5
+ class AbstractTransformation < Parser::AST::Processor
6
+ include TransformationHelper
7
+
8
+ # Runs this transformation on +node+.
9
+ # Note: If you want to add one-time checks to the transformation, override this, then call super.
10
+ #
11
+ # @param node [Parser::AST::Node] The node to be transformed.
12
+ #
13
+ # @return [Parser::AST::Node] The transformed node.
14
+ def run(node)
15
+ process(node)
16
+ end
17
+
18
+ # Used internally by Parser::AST::Processor to process each node. DO NOT OVERRIDE.
19
+ def process(node)
20
+ return node unless node.is_a?(Parser::AST::Node)
21
+
22
+ process_node(node)
23
+ end
24
+
25
+ private
26
+
27
+ # Processes the given +node+.
28
+ # Note: If you want to do processing on each node, override this.
29
+ #
30
+ # @param node [Parser::AST::Node] The node to be transformed.
31
+ #
32
+ # @return [Parser::AST::Node] The transformed node.
33
+ def process_node(node)
34
+ method(:process).super_method.call(node)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ast_transform/transformer'
4
+ require 'ast_transform/instruction_sequence/mixin_utils'
5
+ require 'pathname'
6
+
7
+ module ASTTransform
8
+ module InstructionSequence
9
+ module BootsnapMixin
10
+ def input_to_storage(source, source_path)
11
+ return ASTTransform::MixinUtils.try_super(self, :input_to_storage, source, source_path) if source_path == __FILE__
12
+ return ASTTransform::MixinUtils.try_super(self, :input_to_storage, source, source_path) unless source =~ /transform!/
13
+
14
+ iseq = ASTTransform::InstructionSequence.source_to_transformed_iseq(source, source_path)
15
+ iseq.to_binary
16
+ rescue SyntaxError
17
+ raise ::Bootsnap::CompileCache::Uncompilable, 'syntax error'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ require 'pathname'
3
+ require 'ast_transform/transformer'
4
+ require 'ast_transform/transformation'
5
+ require 'ast_transform/instruction_sequence/mixin_utils'
6
+
7
+ module ASTTransform
8
+ module InstructionSequence
9
+ module Mixin
10
+ def load_iseq(source_path)
11
+ return ASTTransform::MixinUtils.try_super(self, :load_iseq, source_path) if source_path == __FILE__
12
+
13
+ source = File.read(source_path)
14
+
15
+ return ASTTransform::MixinUtils.try_super(self, :load_iseq, source_path) unless source =~ /transform!/
16
+
17
+ ASTTransform::InstructionSequence.source_to_transformed_iseq(source, source_path)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ASTTransform
4
+ module MixinUtils
5
+ class << self
6
+ def try_super(target, method_sym, *args, &block)
7
+ super_method = target.method(method_sym).super_method
8
+ super_method ? super_method.call(*args, &block) : nil
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ module ASTTransform
3
+ module InstructionSequence
4
+ class << self
5
+ def using_bootsnap_compilation?
6
+ filepath, = RubyVM::InstructionSequence.method(:load_iseq).source_location
7
+ filepath =~ %r{/bootsnap/}
8
+ rescue NameError
9
+ false
10
+ end
11
+
12
+ def source_to_transformed_iseq(source, source_path)
13
+ transformer = ASTTransform::Transformer.new(ASTTransform::Transformation.new)
14
+ rewritten_file_pathname = write_pathname(source_path)
15
+
16
+ rewritten_source = transformer.transform_file_source(source, source_path, rewritten_file_pathname.to_s)
17
+ write(rewritten_source, rewritten_file_pathname)
18
+
19
+ RubyVM::InstructionSequence.compile(rewritten_source, rewritten_file_pathname.to_s, rewritten_file_pathname.to_s)
20
+ end
21
+
22
+ def write_pathname(file_path)
23
+ project_path = File.expand_path("")
24
+ relative_source_file_pathname = Pathname.new(file_path).relative_path_from(Pathname.new(project_path))
25
+ Pathname.new("").join(project_path, 'tmp', 'ast_transform', relative_source_file_pathname)
26
+ end
27
+
28
+ def write(string, pathname)
29
+ FileUtils.mkdir_p(pathname.dirname)
30
+ File.open(pathname, 'w') do |file|
31
+ file.write(string)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+ require 'parser/current'
3
+
4
+ module ASTTransform
5
+ class SourceMap
6
+ class << self
7
+ # Registers the given SourceMap.
8
+ #
9
+ # @param source_map [SourceMap] The source map to be registered.
10
+ #
11
+ # @return [void]
12
+ def register_source_map(source_map)
13
+ source_maps[source_map.transformed_file_path] = source_map
14
+
15
+ nil
16
+ end
17
+
18
+ # Retrieves the SourceMap for the given +file_path+.
19
+ #
20
+ # @param file_path [String] The transformed file path.
21
+ #
22
+ # @return [SourceMap|nil] The associated source map.
23
+ def for_file_path(file_path)
24
+ source_maps[file_path]
25
+ end
26
+
27
+ private
28
+
29
+ def source_maps
30
+ @@source_maps ||= {}
31
+ end
32
+ end
33
+
34
+ # Constructs a new SourceMap instance.
35
+ #
36
+ # Note: +source_ranges_ast+ and +transformed_ranges_ast+ must be equivalent ASTs.
37
+ #
38
+ # @param source_file_path [String] The path to the source file.
39
+ # @param transformed_file_path [String] The path to the transformed file.
40
+ # @param source_ranges_ast [Parser::AST::Node] A transformed AST that contains the source code ranges.
41
+ # @param transformed_ranges_ast [Parser::AST::Node] A transformed AST that contains the ranges for the executed
42
+ # code.
43
+ def initialize(source_file_path, transformed_file_path, source_ranges_ast, transformed_ranges_ast)
44
+ @source_file_path = source_file_path
45
+ @transformed_file_path = transformed_file_path
46
+ @source_ranges_ast = source_ranges_ast
47
+ @transformed_ranges_ast = transformed_ranges_ast
48
+
49
+ @lines = Hash.new { |hash, key| hash[key] = [] }
50
+ extract_source_map_data(@transformed_ranges_ast, [])
51
+ @source_map = build_source_map.freeze
52
+ end
53
+
54
+ attr_reader :source_file_path, :transformed_file_path, :source_map
55
+
56
+ # Retrieves the mapped line number for the given +line_number+.
57
+ #
58
+ # @param line_number [Integer] The line number in the executed code to be mapped to the source.
59
+ #
60
+ # @return [Integer|nil] The mapped line number, otherwise nil if not found.
61
+ def line(line_number)
62
+ @source_map[line_number]
63
+ end
64
+
65
+ # Retrieves the line count for the executed code.
66
+ #
67
+ # @return [Integer] The line count.
68
+ def line_count
69
+ @transformed_ranges_ast&.loc&.expression.last_line || 0
70
+ end
71
+
72
+ private
73
+
74
+ # Extracts SourceMap data from the given node.
75
+ #
76
+ # @param node [Parser::AST::Node] The node containing ranges for the executed code.
77
+ #
78
+ # @return [void]
79
+ def extract_source_map_data(node, indexes)
80
+ return false unless node&.is_a?(Parser::AST::Node)
81
+
82
+ range = node.loc&.expression
83
+
84
+ if range && range.line == range.last_line
85
+ @lines[range.line] << indexes.dup
86
+ end
87
+
88
+ node.children.each.with_index do |child, index|
89
+ extract_source_map_data(child, indexes.dup << index)
90
+ end
91
+
92
+ nil
93
+ end
94
+
95
+ # Builds the source map.
96
+ #
97
+ # @return [Hash] A Hash containing line numbers from executed code to source code.
98
+ def build_source_map
99
+ (1..line_count).each.with_object({}) {|it, hash| hash[it] = source_line(it) }
100
+ end
101
+
102
+ # Retrieves the source line for the given +line_number+ in the executed code.
103
+ #
104
+ # @param line_number [Integer] The line number in the executed code.
105
+ #
106
+ # @return [Integer|nil] The line number in the source code, or nil if cannot be mapped.
107
+ def source_line(line_number)
108
+ if @lines.key?(line_number)
109
+ @lines[line_number].each do |dig_array|
110
+ source_node = approximate_dig_last_valid_node(@source_ranges_ast, dig_array)
111
+ next unless source_node
112
+
113
+ range = search_range(source_node, 1)
114
+ return range.line if range
115
+ end
116
+ end
117
+
118
+ nil
119
+ end
120
+
121
+ # Recursively look for node represented by +indexes+ in +node+. If not found, goes back +depth+ nodes and search for
122
+ # the node pointed to by +indexes+.
123
+ #
124
+ # @param node [Parser::AST::Node] The node to search into. This must be a node in the +@source_ranges_ast+.
125
+ # @param indexes [Array<Integer>] Child indexes pointing to the node we're looking for in +node+.
126
+ # @param depth [Integer] Number of nodes to go up to search for the node pointed to by +indexes+.
127
+ #
128
+ # @return [Parser::AST::Node|nil] The node found, nil otherwise.
129
+ def approximate_dig_last_valid_node(node, indexes, depth = 1)
130
+ return node if indexes.empty?
131
+
132
+ result = dig_node(node, indexes)
133
+ return result if result.is_a?(Parser::AST::Node) || depth <= 0
134
+
135
+ queried_node = dig_last_valid_node(@transformed_ranges_ast, indexes)
136
+
137
+ last_known_index = dig_last_valid_node_index(node, indexes[0...-depth])
138
+ query_indexes = indexes[0...last_known_index]
139
+
140
+ last_known_node = dig_node(node, query_indexes)
141
+
142
+ search_node(last_known_node, queried_node)
143
+ end
144
+
145
+ # Recursively search the children of +node+ for an equivalent +queried_node+.
146
+ #
147
+ # @param node [Parser::AST::Node] The current node to search in.
148
+ # @param queried_node [Parser::AST::Node] The equivalent node to search for.
149
+ #
150
+ # @return [Parser::AST::Node|nil] The found node from the +node+ graph, nil otherwise.
151
+ def search_node(node, queried_node)
152
+ return unless node&.is_a?(Parser::AST::Node)
153
+ return node if node == queried_node
154
+
155
+ node.children.each do |child_node|
156
+ result = search_node(child_node, queried_node)
157
+ return result if result
158
+ end
159
+
160
+ nil
161
+ end
162
+
163
+ # Recursively search the given +node+ for a range.
164
+ #
165
+ # @param node [Parser::AST::Node] The current node to search in.
166
+ # @param max_range [Integer|nil] The max range to consider valid. Nil means any range is valid. If 1, only ranges
167
+ # which span one line will be considered, etc...
168
+ #
169
+ # @return [Parser::Source::Range|nil] The range, or nil if no range was found. This occurs when the tree contains
170
+ # no ranges, i.e. they're all virtually built nodes.
171
+ def search_range(node, max_range = nil)
172
+ return unless node&.is_a?(Parser::AST::Node)
173
+
174
+ range = node.loc&.expression
175
+ if range && max_range && range.last_line - range.line < max_range || range && max_range.nil?
176
+ return range
177
+ else
178
+ node.children.each do |child_node|
179
+ result = search_range(child_node, max_range)
180
+ return result if result
181
+ end
182
+ end
183
+
184
+ nil
185
+ end
186
+
187
+ # Finds the index for the last valid node represented by +indexes+ in the children of +node+.
188
+ #
189
+ # @param node [Parser::AST::Node] The current node to search in.
190
+ # @param indexes [Array<Integer>] The array of indexes pointing to the child node to be retrieved from +node+.
191
+ #
192
+ # @return [Integer|nil] The index of the node if found, nil otherwise.
193
+ def dig_last_valid_node_index(node, indexes)
194
+ return if indexes.empty?
195
+
196
+ result = dig_node(node, indexes)
197
+ current_index = indexes&.size
198
+ return current_index if result.is_a?(Parser::AST::Node)
199
+
200
+ dig_last_valid_node_index(node, indexes[0...-1])
201
+ end
202
+
203
+ # Recursively look for the node represented by +indexes+ in the children of +node+. If not found, returns the last
204
+ # valid node.
205
+ #
206
+ # @param node [Parser::AST::Node] The node to look into.
207
+ # @param indexes [Array<Integer>] The array of indexes pointing to the child node to be retrieved from +node+.
208
+ #
209
+ # @return [Parser::AST::Node|nil] The node if found, nil otherwise.
210
+ def dig_last_valid_node(node, indexes)
211
+ return node if indexes.empty?
212
+
213
+ result = dig_node(node, indexes)
214
+ return result if result.is_a?(Parser::AST::Node)
215
+
216
+ dig_last_valid_node(node, indexes[0...-1])
217
+ end
218
+
219
+ # Recursively look for the node represented by +indexes+ in the children of +node+.
220
+ #
221
+ # @param node [Parser::AST::Node] The node to look into.
222
+ # @param indexes [Array<Integer>] The array of indexes pointing to the child node to be retrieved from +node+.
223
+ #
224
+ # @return [Parser::AST::Node|nil] The node if found, nil otherwise.
225
+ def dig_node(node, indexes)
226
+ indexes.inject(node) do |node, index|
227
+ return nil unless node.is_a?(Parser::AST::Node)
228
+ node.children[index]
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+ require 'ast_transform'
3
+ require 'ast_transform/abstract_transformation'
4
+ require 'ast_transform/transformer'
5
+ require 'unparser'
6
+
7
+ module ASTTransform
8
+ class Transformation < ASTTransform::AbstractTransformation
9
+ TRANSFORM_AST = s(:send, nil, :transform!)
10
+
11
+ private
12
+
13
+ def process_node(node)
14
+ children = node.children.map.with_index do |child_node, index|
15
+ previous_sibling = previous_child(node, index)
16
+ process_node_helper(child_node, previous_sibling)
17
+ end
18
+
19
+ children.reject!.with_index { |child_node, index|
20
+ transform_node?(child_node) && transformable_node?(next_child(node, index))
21
+ }
22
+
23
+ node.updated(nil, process_all(children))
24
+ end
25
+
26
+ def previous_child(node, index)
27
+ index > 0 ? node.children[index - 1] : nil
28
+ end
29
+
30
+ def next_child(node, index)
31
+ index >= 0 ? node.children[index + 1] : nil
32
+ end
33
+
34
+ def process_node_helper(node, previous_node)
35
+ if transform_node?(previous_node) && transformable_node?(node)
36
+ transformations = extract_transformations(previous_node)
37
+ ASTTransform::Transformer.new(*transformations).transform_ast(node)
38
+ else
39
+ node
40
+ end
41
+ end
42
+
43
+ def transform_node?(node)
44
+ return false unless node.is_a?(Parser::AST::Node)
45
+
46
+ node.type == :send && node.children.count >= 3 && node.children[0].nil? && node.children[1] == :transform!
47
+ end
48
+
49
+ def transformable_node?(node)
50
+ return false unless node.is_a?(Parser::AST::Node)
51
+
52
+ [:class, :casgn].include?(node.type)
53
+ end
54
+
55
+ def extract_transformations(node)
56
+ node.children.map do |child_node|
57
+ extract_transformation(child_node)
58
+ end.compact!
59
+ end
60
+
61
+ def extract_transformation(node)
62
+ return unless node.is_a?(Parser::AST::Node)
63
+ return unless node.children.count >= 2
64
+
65
+ if node.children[1] == :new
66
+ require_transformation(node)
67
+ code = Unparser.unparse(node)
68
+
69
+ TOPLEVEL_BINDING.eval(code)
70
+ else
71
+ require_transformation(node)
72
+ code = "#{Unparser.unparse(node)}.new"
73
+
74
+ TOPLEVEL_BINDING.eval(code)
75
+ end
76
+ end
77
+
78
+ def require_transformation(node)
79
+ const_node = node.children.first
80
+ const_name = Unparser.unparse(const_node)
81
+
82
+ constant = try_const_get(const_name)
83
+ unless constant
84
+ require_path = require_path(const_name)
85
+ require(require_path)
86
+ end
87
+
88
+ nil
89
+ end
90
+
91
+ def require_path(const_name)
92
+ acronyms = ASTTransform.acronyms
93
+ acronym_regex = acronyms.empty? ? /(?=a)b/ : /#{acronyms.join("|")}/
94
+ return const_name unless /[A-Z-]|::/.match?(const_name)
95
+ word = const_name.to_s.gsub("::".freeze, "/".freeze)
96
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
97
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
98
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
99
+ word.tr!("-".freeze, "_".freeze)
100
+ word.downcase!
101
+ word
102
+ end
103
+
104
+ def try_const_get(const_name)
105
+ Object.const_get(const_name)
106
+ rescue NameError
107
+ nil
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require 'parser'
3
+
4
+ module ASTTransform
5
+ module TransformationHelper
6
+ def self.included(base)
7
+ base.extend(Methods)
8
+ base.include(Methods)
9
+ end
10
+
11
+ module Methods
12
+ def s(type, *children, **properties)
13
+ Parser::AST::Node.new(type, children, properties)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+ require 'parser/current'
3
+ require 'unparser'
4
+ require 'ast_transform/source_map'
5
+
6
+ module ASTTransform
7
+ class Transformer
8
+ # Constructs a new Transformer instance.
9
+ #
10
+ # @param transformations [Array<ASTTransform::AbstractTransformation>] The transformations to be run.
11
+ # @param builder [Parser::Builders::Default] The AST Node builder.
12
+ def initialize(*transformations, builder: Parser::Builders::Default.new)
13
+ @transformations = transformations
14
+ @builder = builder
15
+ end
16
+
17
+ # Builds the AST for the given +source+.
18
+ #
19
+ # @param source [String] The input source code.
20
+ # @param file_path [String] The file_path. This is important for source mapping in backtraces.
21
+ #
22
+ # @return [Parser::AST::Node] The AST.
23
+ def build_ast(source, file_path: 'tmp')
24
+ buffer = create_buffer(source, file_path)
25
+ parser.parse(buffer)
26
+ end
27
+
28
+ # Builds the AST for the given +file_path+.
29
+ #
30
+ # @param file_path [String] The input file path.
31
+ #
32
+ # @return [Parser::AST::Node] The AST.
33
+ def build_ast_from_file(file_path)
34
+ source = File.read(file_path)
35
+ build_ast(source, file_path: file_path)
36
+ end
37
+
38
+ # Transforms the given +source+.
39
+ #
40
+ # @param source [String] The input source code to be transformed.
41
+ #
42
+ # @return [String] The transformed code.
43
+ def transform(source)
44
+ ast = build_ast(source)
45
+ transformed_ast = transform_ast(ast)
46
+ Unparser.unparse(transformed_ast)
47
+ end
48
+
49
+ # Transforms the give +file_path+.
50
+ #
51
+ # @param file_path [String] The input file to be transformed. This is required for source mapping in backtraces.
52
+ # @param transformed_file_path [String] The file path to the transformed file.
53
+ #
54
+ # @return [String] The transformed code.
55
+ def transform_file(file_path, transformed_file_path)
56
+ source = File.read(file_path)
57
+ transform_file_source(source, file_path, transformed_file_path)
58
+ end
59
+
60
+ # Transforms the given +source+ in +file_path+.
61
+ #
62
+ # @param source [String] The input source code to be transformed.
63
+ # @param file_path [String] The file path for the input +source+. This is required for source mapping in backtraces.
64
+ # @param transformed_file_path [String] The file path to the transformed filed. This is required to register the
65
+ # SourceMap.
66
+ #
67
+ # @return [String] The transformed code.
68
+ def transform_file_source(source, file_path, transformed_file_path)
69
+ source_ast = build_ast(source, file_path: file_path)
70
+ # At this point, the transformed_ast contains line number mappings for the original +source+.
71
+ transformed_ast = transform_ast(source_ast)
72
+
73
+ transformed_source = Unparser.unparse(transformed_ast)
74
+
75
+ register_source_map(file_path, transformed_file_path, transformed_ast, transformed_source)
76
+
77
+ transformed_source
78
+ end
79
+
80
+ # Transforms the given +ast+.
81
+ #
82
+ # @param ast [Parser::AST::Node] The input AST to be transformed.
83
+ #
84
+ # @return [Parser::AST::Node] The transformed AST.
85
+ def transform_ast(ast)
86
+ @transformations.inject(ast) do |ast, transformation|
87
+ transformation.run(ast)
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def create_buffer(source, file_path)
94
+ buffer = Parser::Source::Buffer.new(file_path)
95
+ buffer.source = source.dup.force_encoding(parser.default_encoding)
96
+
97
+ buffer
98
+ end
99
+
100
+ def parser
101
+ @parser&.reset
102
+ @parser ||= Parser::CurrentRuby.new(@builder)
103
+ end
104
+
105
+ def register_source_map(source_file_path, transformed_file_path, transformed_ast, transformed_source)
106
+ # The transformed_source is re-parsed to get the correct line numbers for the transformed_ast, which is the code
107
+ # that will run.
108
+ rewritten_ast = build_ast(transformed_source)
109
+ source_map = ASTTransform::SourceMap.new(source_file_path, transformed_file_path, transformed_ast, rewritten_ast)
110
+ ASTTransform::SourceMap.register_source_map(source_map)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,3 @@
1
+ module ASTTransform
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ast_transform/version"
4
+ require 'ast_transform/instruction_sequence'
5
+ require 'ast_transform/instruction_sequence/mixin'
6
+ require 'ast_transform/instruction_sequence/bootsnap_mixin'
7
+
8
+ module ASTTransform
9
+ DEFAULT_OUTPUT_PATH = Pathname.new("").join("tmp", "ast_transform").to_s
10
+
11
+ class << self
12
+ def acronyms
13
+ @acronyms ||= []
14
+ end
15
+
16
+ def acronym(acronym)
17
+ acronyms << acronym
18
+ acronyms.uniq!
19
+ end
20
+
21
+ def install
22
+ @installed ||= begin
23
+ if defined?(Bootsnap) && ASTTransform::InstructionSequence.using_bootsnap_compilation?
24
+ class << Bootsnap::CompileCache::ISeq
25
+ prepend ::ASTTransform::InstructionSequence::BootsnapMixin
26
+ end
27
+ else
28
+ class << RubyVM::InstructionSequence
29
+ prepend ::ASTTransform::InstructionSequence::Mixin
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def output_path=(path)
36
+ @output_path = path
37
+ end
38
+
39
+ def output_path
40
+ @output_path || DEFAULT_OUTPUT_PATH
41
+ end
42
+ end
43
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ast_transform
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre.alpha.pre.15
5
+ platform: ruby
6
+ authors:
7
+ - Jean-Philippe Duchesne
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-07-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.4.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: parser
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.7'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.7'
111
+ - !ruby/object:Gem::Dependency
112
+ name: unparser
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.4'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.4'
125
+ description: An AST transformation framework.
126
+ email:
127
+ - jpduchesne89@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".travis.yml"
134
+ - CHANGELOG.md
135
+ - Gemfile
136
+ - Gemfile.lock
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - ast_transform.gemspec
141
+ - bin/console
142
+ - bin/setup
143
+ - lib/ast_transform.rb
144
+ - lib/ast_transform/abstract_transformation.rb
145
+ - lib/ast_transform/instruction_sequence.rb
146
+ - lib/ast_transform/instruction_sequence/bootsnap_mixin.rb
147
+ - lib/ast_transform/instruction_sequence/mixin.rb
148
+ - lib/ast_transform/instruction_sequence/mixin_utils.rb
149
+ - lib/ast_transform/source_map.rb
150
+ - lib/ast_transform/transformation.rb
151
+ - lib/ast_transform/transformation_helper.rb
152
+ - lib/ast_transform/transformer.rb
153
+ - lib/ast_transform/version.rb
154
+ homepage: https://github.com/rspockframework/ast-transform
155
+ licenses:
156
+ - MIT
157
+ metadata: {}
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.2'
167
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">"
170
+ - !ruby/object:Gem::Version
171
+ version: 1.3.1
172
+ requirements: []
173
+ rubyforge_project:
174
+ rubygems_version: 2.7.7
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: An AST transformation framework.
178
+ test_files: []