ast_transform 1.0.0.pre.alpha.pre.15

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