appmap 0.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +5 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +100 -0
- data/Dockerfile.appmap +5 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/Rakefile +117 -0
- data/appmap.gemspec +41 -0
- data/appmap.yml +8 -0
- data/examples/install.rb +76 -0
- data/examples/mock_webapp/Gemfile +1 -0
- data/examples/mock_webapp/appmap.yml +2 -0
- data/examples/mock_webapp/exe/mock_webapp_request +12 -0
- data/examples/mock_webapp/lib/mock_webapp/controller.rb +23 -0
- data/examples/mock_webapp/lib/mock_webapp/request.rb +12 -0
- data/examples/mock_webapp/lib/mock_webapp/user.rb +18 -0
- data/exe/_appmap-record-self +49 -0
- data/exe/appmap +168 -0
- data/lib/appmap/algorithm/prune_class_map.rb +65 -0
- data/lib/appmap/command/inspect.rb +11 -0
- data/lib/appmap/command/record.rb +91 -0
- data/lib/appmap/command/upload.rb +103 -0
- data/lib/appmap/config/directory.rb +65 -0
- data/lib/appmap/config/file.rb +13 -0
- data/lib/appmap/config/named_function.rb +21 -0
- data/lib/appmap/config/package_dir.rb +52 -0
- data/lib/appmap/config/path.rb +25 -0
- data/lib/appmap/config.rb +65 -0
- data/lib/appmap/feature.rb +262 -0
- data/lib/appmap/inspect/inspector.rb +99 -0
- data/lib/appmap/inspect/parse_node.rb +170 -0
- data/lib/appmap/inspect/parser.rb +15 -0
- data/lib/appmap/inspect.rb +91 -0
- data/lib/appmap/middleware/remote_recording.rb +122 -0
- data/lib/appmap/parser.rb +60 -0
- data/lib/appmap/rails/action_handler.rb +77 -0
- data/lib/appmap/rails/sql_handler.rb +148 -0
- data/lib/appmap/railtie.rb +32 -0
- data/lib/appmap/rspec/parse_node.rb +41 -0
- data/lib/appmap/rspec/parser.rb +15 -0
- data/lib/appmap/rspec.rb +288 -0
- data/lib/appmap/trace/event_handler/rack_handler_webrick.rb +65 -0
- data/lib/appmap/trace/tracer.rb +347 -0
- data/lib/appmap/version.rb +5 -0
- data/lib/appmap.rb +26 -0
- data/lore/pages/2019-05-21-install-and-record/index.pug +51 -0
- data/lore/pages/2019-05-21-install-and-record/install_example_appmap.png +0 -0
- data/lore/pages/2019-05-21-install-and-record/metadata.yml +5 -0
- data/lore/pages/layout.pug +66 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css +1912 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css +7 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-grid.min.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css +331 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css +8 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap-reboot.min.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css +9030 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.css.map +1 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css +7 -0
- data/lore/public/lib/bootstrap-4.1.3/css/bootstrap.min.css.map +1 -0
- data/lore/public/stylesheets/style.css +8 -0
- data/package-lock.json +1066 -0
- data/package.json +24 -0
- data/spec/abstract_controller4_base_spec.rb +58 -0
- data/spec/abstract_controller_base_spec.rb +59 -0
- data/spec/fixtures/rack_users_app/.dockerignore +2 -0
- data/spec/fixtures/rack_users_app/.gitignore +2 -0
- data/spec/fixtures/rack_users_app/Dockerfile +32 -0
- data/spec/fixtures/rack_users_app/Gemfile +10 -0
- data/spec/fixtures/rack_users_app/appmap.yml +3 -0
- data/spec/fixtures/rack_users_app/config.ru +2 -0
- data/spec/fixtures/rack_users_app/docker-compose.yml +9 -0
- data/spec/fixtures/rack_users_app/lib/app.rb +36 -0
- data/spec/fixtures/rails4_users_app/.gitignore +13 -0
- data/spec/fixtures/rails4_users_app/.rbenv-gemsets +2 -0
- data/spec/fixtures/rails4_users_app/.ruby-version +1 -0
- data/spec/fixtures/rails4_users_app/Dockerfile +30 -0
- data/spec/fixtures/rails4_users_app/Dockerfile.pg +3 -0
- data/spec/fixtures/rails4_users_app/Gemfile +77 -0
- data/spec/fixtures/rails4_users_app/README.rdoc +28 -0
- data/spec/fixtures/rails4_users_app/Rakefile +6 -0
- data/spec/fixtures/rails4_users_app/app/assets/images/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/assets/javascripts/application.js +16 -0
- data/spec/fixtures/rails4_users_app/app/assets/stylesheets/application.css +15 -0
- data/spec/fixtures/rails4_users_app/app/controllers/api/users_controller.rb +27 -0
- data/spec/fixtures/rails4_users_app/app/controllers/application_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/controllers/concerns/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/controllers/health_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/controllers/users_controller.rb +5 -0
- data/spec/fixtures/rails4_users_app/app/helpers/application_helper.rb +2 -0
- data/spec/fixtures/rails4_users_app/app/mailers/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/concerns/.keep +0 -0
- data/spec/fixtures/rails4_users_app/app/models/user.rb +18 -0
- data/spec/fixtures/rails4_users_app/app/views/layouts/application.html.haml +7 -0
- data/spec/fixtures/rails4_users_app/app/views/users/index.html.haml +7 -0
- data/spec/fixtures/rails4_users_app/appmap.yml +3 -0
- data/spec/fixtures/rails4_users_app/bin/rails +9 -0
- data/spec/fixtures/rails4_users_app/bin/setup +29 -0
- data/spec/fixtures/rails4_users_app/bin/spring +17 -0
- data/spec/fixtures/rails4_users_app/config/application.rb +26 -0
- data/spec/fixtures/rails4_users_app/config/boot.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/database.yml +17 -0
- data/spec/fixtures/rails4_users_app/config/environment.rb +5 -0
- data/spec/fixtures/rails4_users_app/config/environments/development.rb +41 -0
- data/spec/fixtures/rails4_users_app/config/environments/production.rb +79 -0
- data/spec/fixtures/rails4_users_app/config/environments/test.rb +42 -0
- data/spec/fixtures/rails4_users_app/config/initializers/assets.rb +11 -0
- data/spec/fixtures/rails4_users_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails4_users_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/fixtures/rails4_users_app/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails4_users_app/config/initializers/mime_types.rb +4 -0
- data/spec/fixtures/rails4_users_app/config/initializers/session_store.rb +3 -0
- data/spec/fixtures/rails4_users_app/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/spec/fixtures/rails4_users_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/fixtures/rails4_users_app/config/locales/en.yml +23 -0
- data/spec/fixtures/rails4_users_app/config/routes.rb +12 -0
- data/spec/fixtures/rails4_users_app/config/secrets.yml +22 -0
- data/spec/fixtures/rails4_users_app/config.ru +4 -0
- data/spec/fixtures/rails4_users_app/create_app +23 -0
- data/spec/fixtures/rails4_users_app/db/migrate/20191127112304_create_users.rb +10 -0
- data/spec/fixtures/rails4_users_app/db/schema.rb +26 -0
- data/spec/fixtures/rails4_users_app/db/seeds.rb +7 -0
- data/spec/fixtures/rails4_users_app/docker-compose.yml +24 -0
- data/spec/fixtures/rails4_users_app/lib/assets/.keep +0 -0
- data/spec/fixtures/rails4_users_app/lib/tasks/.keep +0 -0
- data/spec/fixtures/rails4_users_app/log/.keep +0 -0
- data/spec/fixtures/rails4_users_app/public/404.html +67 -0
- data/spec/fixtures/rails4_users_app/public/422.html +67 -0
- data/spec/fixtures/rails4_users_app/public/500.html +66 -0
- data/spec/fixtures/rails4_users_app/public/favicon.ico +0 -0
- data/spec/fixtures/rails4_users_app/public/robots.txt +5 -0
- data/spec/fixtures/rails4_users_app/spec/controllers/users_controller_api_spec.rb +49 -0
- data/spec/fixtures/rails4_users_app/spec/rails_helper.rb +95 -0
- data/spec/fixtures/rails4_users_app/spec/spec_helper.rb +96 -0
- data/spec/fixtures/rails4_users_app/test/fixtures/users.yml +9 -0
- data/spec/fixtures/rails_users_app/.dockerignore +1 -0
- data/spec/fixtures/rails_users_app/.gitignore +39 -0
- data/spec/fixtures/rails_users_app/.rspec +1 -0
- data/spec/fixtures/rails_users_app/.ruby-version +1 -0
- data/spec/fixtures/rails_users_app/Dockerfile +29 -0
- data/spec/fixtures/rails_users_app/Dockerfile.pg +3 -0
- data/spec/fixtures/rails_users_app/Gemfile +51 -0
- data/spec/fixtures/rails_users_app/Rakefile +6 -0
- data/spec/fixtures/rails_users_app/app/controllers/api/users_controller.rb +27 -0
- data/spec/fixtures/rails_users_app/app/controllers/application_controller.rb +2 -0
- data/spec/fixtures/rails_users_app/app/controllers/concerns/.keep +0 -0
- data/spec/fixtures/rails_users_app/app/controllers/health_controller.rb +5 -0
- data/spec/fixtures/rails_users_app/app/controllers/users_controller.rb +5 -0
- data/spec/fixtures/rails_users_app/app/models/activerecord/user.rb +18 -0
- data/spec/fixtures/rails_users_app/app/models/concerns/.keep +0 -0
- data/spec/fixtures/rails_users_app/app/models/sequel/user.rb +25 -0
- data/spec/fixtures/rails_users_app/app/views/layouts/application.html.haml +7 -0
- data/spec/fixtures/rails_users_app/app/views/users/index.html.haml +7 -0
- data/spec/fixtures/rails_users_app/appmap.yml +3 -0
- data/spec/fixtures/rails_users_app/bin/_appmap-record-self +29 -0
- data/spec/fixtures/rails_users_app/bin/appmap +29 -0
- data/spec/fixtures/rails_users_app/bin/byebug +29 -0
- data/spec/fixtures/rails_users_app/bin/gli +29 -0
- data/spec/fixtures/rails_users_app/bin/htmldiff +29 -0
- data/spec/fixtures/rails_users_app/bin/ldiff +29 -0
- data/spec/fixtures/rails_users_app/bin/nokogiri +29 -0
- data/spec/fixtures/rails_users_app/bin/rackup +29 -0
- data/spec/fixtures/rails_users_app/bin/rails +4 -0
- data/spec/fixtures/rails_users_app/bin/rake +29 -0
- data/spec/fixtures/rails_users_app/bin/rspec +29 -0
- data/spec/fixtures/rails_users_app/bin/ruby-parse +29 -0
- data/spec/fixtures/rails_users_app/bin/ruby-rewrite +29 -0
- data/spec/fixtures/rails_users_app/bin/sequel +29 -0
- data/spec/fixtures/rails_users_app/bin/setup +25 -0
- data/spec/fixtures/rails_users_app/bin/sprockets +29 -0
- data/spec/fixtures/rails_users_app/bin/thor +29 -0
- data/spec/fixtures/rails_users_app/bin/update +25 -0
- data/spec/fixtures/rails_users_app/config/application.rb +51 -0
- data/spec/fixtures/rails_users_app/config/boot.rb +3 -0
- data/spec/fixtures/rails_users_app/config/credentials.yml.enc +1 -0
- data/spec/fixtures/rails_users_app/config/database.yml +17 -0
- data/spec/fixtures/rails_users_app/config/environment.rb +5 -0
- data/spec/fixtures/rails_users_app/config/environments/development.rb +40 -0
- data/spec/fixtures/rails_users_app/config/environments/production.rb +68 -0
- data/spec/fixtures/rails_users_app/config/environments/test.rb +36 -0
- data/spec/fixtures/rails_users_app/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/fixtures/rails_users_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails_users_app/config/initializers/cors.rb +16 -0
- data/spec/fixtures/rails_users_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/fixtures/rails_users_app/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails_users_app/config/initializers/mime_types.rb +4 -0
- data/spec/fixtures/rails_users_app/config/initializers/record_button.rb +3 -0
- data/spec/fixtures/rails_users_app/config/initializers/wrap_parameters.rb +9 -0
- data/spec/fixtures/rails_users_app/config/locales/en.yml +33 -0
- data/spec/fixtures/rails_users_app/config/routes.rb +11 -0
- data/spec/fixtures/rails_users_app/config.ru +5 -0
- data/spec/fixtures/rails_users_app/create_app +11 -0
- data/spec/fixtures/rails_users_app/db/migrate/20190728211408_create_users.rb +9 -0
- data/spec/fixtures/rails_users_app/db/schema.rb +23 -0
- data/spec/fixtures/rails_users_app/docker-compose.yml +24 -0
- data/spec/fixtures/rails_users_app/lib/tasks/.keep +0 -0
- data/spec/fixtures/rails_users_app/log/.keep +0 -0
- data/spec/fixtures/rails_users_app/public/robots.txt +1 -0
- data/spec/fixtures/rails_users_app/spec/controllers/users_controller_api_spec.rb +29 -0
- data/spec/fixtures/rails_users_app/spec/models/user_spec.rb +39 -0
- data/spec/fixtures/rails_users_app/spec/rails_helper.rb +66 -0
- data/spec/fixtures/rails_users_app/spec/spec_helper.rb +96 -0
- data/spec/fixtures/rails_users_app/users_app/.gitignore +20 -0
- data/spec/rack_handler_webrick_spec.rb +59 -0
- data/spec/rails_spec_helper.rb +34 -0
- data/spec/railtie_spec.rb +35 -0
- data/spec/record_sql_rails4_pg_spec.rb +76 -0
- data/spec/record_sql_rails_pg_spec.rb +68 -0
- data/spec/rspec_feature_metadata_spec.rb +30 -0
- data/spec/spec_helper.rb +6 -0
- data/test/cli_test.rb +81 -0
- data/test/config_test.rb +149 -0
- data/test/explict_inspect_test.rb +29 -0
- data/test/fixtures/active_record_like/active_record/aggregations.rb +4 -0
- data/test/fixtures/active_record_like/active_record/association.rb +4 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_base.rb +8 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency/join_part.rb +8 -0
- data/test/fixtures/active_record_like/active_record/associations/join_dependency.rb +6 -0
- data/test/fixtures/active_record_like/active_record/caps/caps.rb +4 -0
- data/test/fixtures/active_record_like/active_record.rb +2 -0
- data/test/fixtures/cli_record_test/appmap.yml +2 -0
- data/test/fixtures/cli_record_test/lib/cli_record_test/main.rb +7 -0
- data/test/fixtures/ignore_non_ruby_file/class.rb +3 -0
- data/test/fixtures/ignore_non_ruby_file/non-ruby.txt +1 -0
- data/test/fixtures/includes_excludes/lib/a/a_1.rb +6 -0
- data/test/fixtures/includes_excludes/lib/a/a_2.rb +6 -0
- data/test/fixtures/includes_excludes/lib/a/x/x_1.rb +8 -0
- data/test/fixtures/includes_excludes/lib/b/b_1.rb +6 -0
- data/test/fixtures/includes_excludes/lib/root_1.rb +4 -0
- data/test/fixtures/inspect_multiple_subdirs/module_a/class_a.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_a.rb +2 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b/class_b.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b/class_c.rb +5 -0
- data/test/fixtures/inspect_multiple_subdirs/module_b.rb +2 -0
- data/test/fixtures/inspect_package/module_a/module_b/class_in_module.rb +6 -0
- data/test/fixtures/parse_file/defs_static_function.rb +96 -0
- data/test/fixtures/parse_file/function_within_class.rb +36 -0
- data/test/fixtures/parse_file/include_public_methods.rb +127 -0
- data/test/fixtures/parse_file/instance_function.rb +17 -0
- data/test/fixtures/parse_file/modules.rb +71 -0
- data/test/fixtures/parse_file/sclass_static_function.rb +88 -0
- data/test/fixtures/parse_file/toplevel_class.rb +13 -0
- data/test/fixtures/parse_file/toplevel_function.rb +14 -0
- data/test/fixtures/rspec_recorder/Gemfile +5 -0
- data/test/fixtures/rspec_recorder/appmap.yml +3 -0
- data/test/fixtures/rspec_recorder/lib/hello.rb +5 -0
- data/test/fixtures/rspec_recorder/spec/hello_spec.rb +9 -0
- data/test/fixtures/trace_test/trace_program_1.rb +44 -0
- data/test/implicit_inspect_test.rb +33 -0
- data/test/include_exclude_test.rb +48 -0
- data/test/prerecorded_trace_test.rb +76 -0
- data/test/rspec_test.rb +22 -0
- data/test/test_helper.rb +46 -0
- data/test/trace_test.rb +92 -0
- metadata +501 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'appmap/config/path'
|
2
|
+
require 'appmap/config/file'
|
3
|
+
require 'appmap/config/directory'
|
4
|
+
require 'appmap/config/package_dir'
|
5
|
+
require 'appmap/config/named_function'
|
6
|
+
|
7
|
+
module AppMap
|
8
|
+
module Config
|
9
|
+
class Configuration
|
10
|
+
attr_reader :name, :packages, :files, :named_functions
|
11
|
+
|
12
|
+
def initialize(name)
|
13
|
+
@name = name
|
14
|
+
@packages = []
|
15
|
+
@files = []
|
16
|
+
@named_functions = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def source_locations
|
20
|
+
packages + files + named_functions
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
NAMED_FUNCTIONS = [
|
26
|
+
Config::NamedFunction.new(:rack_handler_webrick, 'rack', 'lib/rack/handler/webrick.rb',
|
27
|
+
%w[Rack Handler WEBrick], 'service', false)
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
# Loads configuration data from a file, specified by the file name.
|
31
|
+
def load_from_file(config_file_name)
|
32
|
+
require 'yaml'
|
33
|
+
load YAML.safe_load(::File.read(config_file_name))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Loads configuration from a Hash.
|
37
|
+
def load(config_data)
|
38
|
+
Configuration.new(config_data['name']).tap do |config|
|
39
|
+
builders = Hash.new { |_, key| raise "Unknown config type #{key.inspect}" }
|
40
|
+
builders[:packages] = lambda { |path, options|
|
41
|
+
AppMap::Config::PackageDir.new(path).tap do |pdir|
|
42
|
+
pdir.package_name = options['name'] if options['name']
|
43
|
+
pdir.exclude = options['exclude'] if options['exclude']
|
44
|
+
end
|
45
|
+
}
|
46
|
+
builders[:files] = ->(path, _) { AppMap::Config::File.new(path) }
|
47
|
+
|
48
|
+
%i[packages files].each do |kind|
|
49
|
+
next unless (members = config_data[kind.to_s])
|
50
|
+
members.each do |member|
|
51
|
+
path = member.delete('path')
|
52
|
+
config.send(kind) << builders[kind].call(path, member)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
NAMED_FUNCTIONS.each do |dep|
|
57
|
+
next if config_data['named_functions'] && !config_data['named_functions'].member?(dep.gem_name)
|
58
|
+
|
59
|
+
config.named_functions << dep
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
module AppMap
|
2
|
+
# A Feature is a construct within the code that will be observed. Examples features include
|
3
|
+
# modules, classes and functions.
|
4
|
+
module Feature
|
5
|
+
TYPE_MAP = {
|
6
|
+
'cls' => 'class'
|
7
|
+
}.freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
FEATURE_BUILDERS = {
|
11
|
+
module: ->(_) { Module.new },
|
12
|
+
class: ->(_) { Cls.new },
|
13
|
+
function: lambda do |hash|
|
14
|
+
static = hash.delete('static')
|
15
|
+
class_name = hash.delete('class_name')
|
16
|
+
Function.new.tap do |e|
|
17
|
+
e.static = static
|
18
|
+
e.class_name = class_name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
# Deserialize a feature from a Hash. The Hash is typically a deserialized JSON dump of the feature.
|
24
|
+
def from_hash(hash)
|
25
|
+
builder = FEATURE_BUILDERS[hash['type'].to_sym]
|
26
|
+
raise "Unrecognized type of feature: #{type.inspect}" unless builder
|
27
|
+
|
28
|
+
feature = builder.call(hash)
|
29
|
+
feature.name = hash['name']
|
30
|
+
feature.location = hash['location']
|
31
|
+
feature.attributes = hash['attributes'] || {}
|
32
|
+
feature.children = (hash['children'] || []).map { |child| from_hash(child) }
|
33
|
+
feature
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
FeatureStruct = Struct.new(:name, :location, :attributes)
|
38
|
+
|
39
|
+
# Base is an abstract base class for features.
|
40
|
+
class Base < FeatureStruct
|
41
|
+
class << self
|
42
|
+
def expand_path(location)
|
43
|
+
path, lineno = location.split(':')
|
44
|
+
[ path, lineno ].compact.join(':')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :parent, :children
|
49
|
+
|
50
|
+
def initialize(name, location, attributes)
|
51
|
+
super(name, self.class.expand_path(location), attributes)
|
52
|
+
|
53
|
+
@parent = nil
|
54
|
+
@children = []
|
55
|
+
end
|
56
|
+
|
57
|
+
def remove_child(child)
|
58
|
+
# TODO: Encountered this indexing appland with active_dispatch
|
59
|
+
children.delete(child) or warn "Unable to remove #{name.inspect} from parent" # or raise "No such child : #{child}"
|
60
|
+
child.instance_variable_set('@parent', nil)
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_child(child)
|
64
|
+
@children << child
|
65
|
+
child.instance_variable_set('@parent', self)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Gets an array containing the type names which enclose this feature.
|
69
|
+
def enclosing_type_name
|
70
|
+
@enclosing_type_name ||= [].tap do |names|
|
71
|
+
p = self
|
72
|
+
while (p = p.parent) && p.type?
|
73
|
+
names << p.name
|
74
|
+
end
|
75
|
+
end.reverse
|
76
|
+
end
|
77
|
+
|
78
|
+
# true iff this feature has an enclosing type. An example of when this is false: when
|
79
|
+
# the parent of the feature is not a type (e.g. it's a location).
|
80
|
+
def enclosing_type_name?
|
81
|
+
!enclosing_type_name.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
# The 'include' attribute can indicate which elements of the parse subtree
|
85
|
+
# to automatically add as features. For example: public_classes, public_modules,
|
86
|
+
# public_methods.
|
87
|
+
def include_option
|
88
|
+
(attributes[:include] || '').split(',')
|
89
|
+
end
|
90
|
+
|
91
|
+
# yield each function to a block.
|
92
|
+
def collect_functions(accumulator = [])
|
93
|
+
accumulator.tap do |_|
|
94
|
+
accumulator << self if is_a?(Function)
|
95
|
+
children.each { |child| child.collect_functions(accumulator) }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def type?
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
def valid?
|
104
|
+
!name.blank? && !location.blank?
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_json(*opts)
|
108
|
+
to_h.to_json(*opts)
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_h
|
112
|
+
super.tap do |map|
|
113
|
+
map.delete(:parent)
|
114
|
+
class_name = self.class.name.underscore.split('/')[-1]
|
115
|
+
map[:type] = TYPE_MAP[class_name] || class_name
|
116
|
+
map[:children] = @children.map(&:to_h) unless @children.empty?
|
117
|
+
map.delete(:attributes) if map[:attributes].empty?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Determines if this feature should be dropped from the feature tree.
|
122
|
+
# A feature is dropped from the feature tree if it doesn't add useful information for the user.
|
123
|
+
# Performing this operation removes feature nodes that don't add anything useful to the user.
|
124
|
+
# For example, empty classes.
|
125
|
+
def prune(parent = nil)
|
126
|
+
should_prune = prune? && !parent.nil?
|
127
|
+
parent = self unless should_prune
|
128
|
+
children.dup.each do |child|
|
129
|
+
child.prune(parent)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Perform the prune in post-fix traversal order, otherwise the
|
133
|
+
# features will get confused about whether they should prune or not.
|
134
|
+
if should_prune
|
135
|
+
parent.remove_child(self)
|
136
|
+
children.each do |child|
|
137
|
+
parent.add_child(child)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Determines if this feature should be re-parented as a child of a different feature.
|
143
|
+
#
|
144
|
+
# A feature is re-parented if the enclosing type of the feature has already been defined in the tree.
|
145
|
+
#
|
146
|
+
# @param parent the parent of this feature in the compacted tree.
|
147
|
+
def reparent(parent = nil, features_by_type = {})
|
148
|
+
# Determine if the enclosing type of the feature is defined.
|
149
|
+
# Generally, it should be.
|
150
|
+
|
151
|
+
existing_enclosing_type = features_by_type[enclosing_type_name] if enclosing_type_name?
|
152
|
+
if existing_enclosing_type
|
153
|
+
parent = existing_enclosing_type
|
154
|
+
end
|
155
|
+
|
156
|
+
# Determine if this feature is a type which is already defined.
|
157
|
+
type_exists = true if type? && features_by_type.key?(type_name)
|
158
|
+
|
159
|
+
# If this feature is a type that's already defined, skip over it and
|
160
|
+
# add the children to the existing feature. Otherwise, clone this feature
|
161
|
+
# under the parent and use the cloned object as the parent of the compacted
|
162
|
+
# children.
|
163
|
+
if type_exists
|
164
|
+
features_by_type[type_name]
|
165
|
+
else
|
166
|
+
clone.tap do |f|
|
167
|
+
parent.add_child(f) if parent
|
168
|
+
features_by_type[type_name] = f if type?
|
169
|
+
end
|
170
|
+
end.tap do |updated_parent|
|
171
|
+
children.each do |child|
|
172
|
+
child.reparent(updated_parent, features_by_type)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def prune?
|
178
|
+
false
|
179
|
+
end
|
180
|
+
|
181
|
+
protected
|
182
|
+
|
183
|
+
def clone
|
184
|
+
self.class.new(name, location, attributes)
|
185
|
+
end
|
186
|
+
|
187
|
+
def child_classes
|
188
|
+
children.select { |c| c.is_a?(Cls) }
|
189
|
+
end
|
190
|
+
|
191
|
+
def child_nonclasses
|
192
|
+
children.reject { |c| c.is_a?(Cls) }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Package is a feature which represents the directory containing code.
|
197
|
+
class Package < Base
|
198
|
+
# prune a package if it's empty, or if it contains anything but packages.
|
199
|
+
def prune?
|
200
|
+
children.empty? || children.any? { |c| !c.is_a?(Package) }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Cls is a feature which represents a code class. A class defines a namespace which contains other
|
205
|
+
# features (such as member classes and functions), and it also usually encapsulates some data on which
|
206
|
+
# the member features operate.
|
207
|
+
class Cls < Base
|
208
|
+
# prune a class if it's empty.
|
209
|
+
def prune?
|
210
|
+
children.empty?
|
211
|
+
end
|
212
|
+
|
213
|
+
def type?
|
214
|
+
true
|
215
|
+
end
|
216
|
+
|
217
|
+
# Gets the type name of this class as an array.
|
218
|
+
def type_name
|
219
|
+
@type_name ||= enclosing_type_name + [ name ]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Function is a feature which represents a code function. It can be an instance function or static (aka 'class')
|
224
|
+
# function. Instance functions operate on the instance data of the class on which they are defined. Static
|
225
|
+
# functions are used to perform operations which don't have want or need of instance data.
|
226
|
+
#
|
227
|
+
# * `handler_id` If provided, identifies a trace handler which can apply specialized logic to the
|
228
|
+
# event data which is recorded for this function. For example, if the function represents a handler
|
229
|
+
# method for a web server, the custom handler can inspect and record the HTTP request method and path info.
|
230
|
+
class Function < Base
|
231
|
+
attr_accessor :static, :class_name, :handler_id
|
232
|
+
|
233
|
+
alias static? static
|
234
|
+
def instance?
|
235
|
+
!static?
|
236
|
+
end
|
237
|
+
|
238
|
+
# Static functions must have an enclosing class defined in order to be traced.
|
239
|
+
def valid?
|
240
|
+
super && (instance? || !class_name.blank?)
|
241
|
+
end
|
242
|
+
|
243
|
+
def to_h
|
244
|
+
super.tap do |h|
|
245
|
+
# Suppress the class name when it can be inferred from the enclosing type.
|
246
|
+
h[:class_name] = class_name if class_name && class_name != enclosing_type_name.join('::')
|
247
|
+
h[:static] = static?
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
protected
|
252
|
+
|
253
|
+
def clone
|
254
|
+
super.tap do |obj|
|
255
|
+
obj.static = static
|
256
|
+
obj.class_name = class_name
|
257
|
+
obj.handler_id = handler_id
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module AppMap
|
2
|
+
module Inspect
|
3
|
+
# Inspector is an abstract class for extracting features from a Ruby program.
|
4
|
+
class Inspector
|
5
|
+
attr_reader :file_path, :parse_nodes, :comments
|
6
|
+
|
7
|
+
def initialize(file_path, parse_nodes, comments)
|
8
|
+
@file_path = file_path
|
9
|
+
@parse_nodes = parse_nodes
|
10
|
+
@comments = comments
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# ImplicitInspector extracts features from a Ruby program, creating a feature for each public class and method.
|
15
|
+
class ImplicitInspector < Inspector
|
16
|
+
def inspect_file
|
17
|
+
features = []
|
18
|
+
features_by_ast_node = {}
|
19
|
+
parse_nodes.select(&:public?).each do |parse_node|
|
20
|
+
feature = parse_node.to_feature({})
|
21
|
+
features_by_ast_node[parse_node.node] = feature
|
22
|
+
if feature
|
23
|
+
if (enclosing_type_node = parse_node.enclosing_type_node) &&
|
24
|
+
(parent_feature = features_by_ast_node[enclosing_type_node])
|
25
|
+
parent_feature.add_child(feature)
|
26
|
+
else
|
27
|
+
features << feature
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
features.keep_if(&:valid?)
|
33
|
+
features
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# ExplicitInspector extracts features from a Ruby program, requiring the use of @appmap annotations to mark each
|
38
|
+
# relevant class and method.
|
39
|
+
class ExplicitInspector < Inspector
|
40
|
+
def inspect_file
|
41
|
+
nodes_by_line = parse_nodes.each_with_object({}) { |node, h| h[node.node.loc.line] = node }
|
42
|
+
|
43
|
+
features_by_ast_node = {}
|
44
|
+
features = []
|
45
|
+
|
46
|
+
comments.select { |c| c.text.index('@appmap') }.each do |c|
|
47
|
+
c.text.split("\n").select { |l| l.index('@appmap') }.each do |feature|
|
48
|
+
tokens = feature.split
|
49
|
+
tokens.delete_if { |t| %w[# @appmap].member?(t) }
|
50
|
+
attributes = tokens.inject({}) do |memo, token|
|
51
|
+
key, value = token.split('=')
|
52
|
+
memo.tap do |attrs|
|
53
|
+
attrs[key.to_sym] = value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
parse_node = nodes_by_line[c.location.last_line + 1]
|
58
|
+
if parse_node
|
59
|
+
feature = parse_node.to_feature(attributes)
|
60
|
+
features_by_ast_node[parse_node.node] = feature
|
61
|
+
if feature
|
62
|
+
if (enclosing_type_node = parse_node.enclosing_type_node) &&
|
63
|
+
(parent_feature = features_by_ast_node[enclosing_type_node])
|
64
|
+
parent_feature.add_child(feature)
|
65
|
+
else
|
66
|
+
features << feature
|
67
|
+
end
|
68
|
+
|
69
|
+
# At this point there's a parse_node and an associated feature.
|
70
|
+
# If the feature is a class which has the option 'include=public_methods',
|
71
|
+
# scan the rest of the class body for public methods and create features
|
72
|
+
# for them.
|
73
|
+
|
74
|
+
if parse_node.type == :class && feature.include_option.member?('public_methods')
|
75
|
+
begin_node = parse_node.node.children.find { |n| n.respond_to?(:type) && n.type == :begin }
|
76
|
+
if begin_node
|
77
|
+
public_methods = begin_node
|
78
|
+
.children
|
79
|
+
.select { |n| n.respond_to?(:type) && n.type == :def }
|
80
|
+
.map { |n| ParseNode.from_node(n, file_path, parse_node.ancestors + [ parse_node.node, begin_node ]) }
|
81
|
+
.select(&:public?)
|
82
|
+
public_methods.map { |m| m.to_feature([]) }.compact.each do |f|
|
83
|
+
feature.add_child(f)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
else
|
89
|
+
warn "No parse node found at #{file_path}:#{c.location.last_line + 1}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
features.keep_if(&:valid?)
|
95
|
+
features
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'appmap/feature'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module AppMap
|
5
|
+
module Inspect
|
6
|
+
# ParseNodeStruct wraps a generic AST parse node.
|
7
|
+
ParseNodeStruct = Struct.new(:node, :file_path, :ancestors) do
|
8
|
+
end
|
9
|
+
|
10
|
+
# ParseNode wraps a generic AST parse node.
|
11
|
+
class ParseNode < ParseNodeStruct
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
def_delegators :node, :type, :location
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# Build a ParseNode from an AST node.
|
18
|
+
def from_node(node, file_path, ancestors)
|
19
|
+
case node.type
|
20
|
+
when :class, :module
|
21
|
+
ClassParseNode.new(node, file_path, ancestors.dup)
|
22
|
+
when :def
|
23
|
+
InstanceMethodParseNode.new(node, file_path, ancestors.dup)
|
24
|
+
when :defs
|
25
|
+
StaticMethodParseNode.new(node, file_path, ancestors.dup) \
|
26
|
+
if StaticMethodParseNode.static?(node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def public?
|
32
|
+
preceding_send = preceding_sibling_nodes
|
33
|
+
.reverse
|
34
|
+
.select { |n| n.respond_to?(:type) && n.type == :send }
|
35
|
+
.find { |n| %i[public protected private].member?(n.children[1]) }
|
36
|
+
preceding_send.nil? || preceding_send.children[1] == :public
|
37
|
+
end
|
38
|
+
|
39
|
+
# Gets the AST node of the module or class which encloses this node.
|
40
|
+
def enclosing_type_node
|
41
|
+
ancestors.reverse.find do |a|
|
42
|
+
%i[class module].include?(a.type)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def parent_node
|
47
|
+
ancestors[-1]
|
48
|
+
end
|
49
|
+
|
50
|
+
def preceding_sibling_nodes
|
51
|
+
return [] unless parent_node
|
52
|
+
index_of_this_node = parent_node.children.index { |c| c == node }
|
53
|
+
parent_node.children[0...index_of_this_node]
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def extract_class_name(node)
|
59
|
+
node.children[0].children[1].to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_module_name(node)
|
63
|
+
node.children[0].children[1].to_s
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# A Ruby class.
|
68
|
+
class ClassParseNode < ParseNode
|
69
|
+
def to_feature(attributes)
|
70
|
+
AppMap::Feature::Cls.new(extract_class_name(node), "#{file_path}:#{location.line}", attributes)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Abstract representation of a method.
|
75
|
+
class MethodParseNode < ParseNode
|
76
|
+
def to_feature(attributes)
|
77
|
+
AppMap::Feature::Function.new(name, "#{file_path}:#{location.line}", attributes).tap do |a|
|
78
|
+
a.static = static?
|
79
|
+
a.class_name = class_name
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def enclosing_names
|
84
|
+
ancestors.select do |a|
|
85
|
+
%i[class module].include?(a.type)
|
86
|
+
end.map do |a|
|
87
|
+
send("extract_#{a.type}_name", a)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# A method defines as a :def AST node.
|
93
|
+
class InstanceMethodParseNode < MethodParseNode
|
94
|
+
def name
|
95
|
+
node.children[0].to_s
|
96
|
+
end
|
97
|
+
|
98
|
+
# class_name should be inferred from the enclosing type.
|
99
|
+
def class_name
|
100
|
+
enclosing_names.join('::')
|
101
|
+
end
|
102
|
+
|
103
|
+
# An instance method defined in an sclass is a static method.
|
104
|
+
#
|
105
|
+
# TODO: Well, not strictly true. A singleton class can be defined on a class or
|
106
|
+
# on an instance. In fact, to Ruby a class method is really just an instance method
|
107
|
+
# on a class. So, this needs fixing to try and determine if the singleton class is
|
108
|
+
# defined on an instance or on a class. This may actually be hard (impossible?) to do
|
109
|
+
# from static parsing.
|
110
|
+
def static?
|
111
|
+
result = ancestors[-1].type == :sclass ||
|
112
|
+
(ancestors[-1].type == :begin && ancestors[-2] && ancestors[-2].type == :sclass)
|
113
|
+
!!result
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# A method defines as a :defs AST node.
|
118
|
+
# For example:
|
119
|
+
#
|
120
|
+
# class Main
|
121
|
+
# def Main.main_func; end
|
122
|
+
# def explain
|
123
|
+
# some_func.tap do |s|
|
124
|
+
# def s.inspect; self; end
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
# end
|
128
|
+
class StaticMethodParseNode < MethodParseNode
|
129
|
+
class << self
|
130
|
+
def static?(node)
|
131
|
+
%i[self const].member?(node.children[0].type)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def name
|
136
|
+
node.children[1].to_s
|
137
|
+
end
|
138
|
+
|
139
|
+
# class_name is specified as `nil` if it should be inferred from the
|
140
|
+
# enclosing type.
|
141
|
+
def class_name
|
142
|
+
case (defs_type = node.children[0].type)
|
143
|
+
when :self
|
144
|
+
class_name_from_enclosing_type
|
145
|
+
when :const
|
146
|
+
class_name_from_declaration
|
147
|
+
else
|
148
|
+
raise "Unrecognized 'defs' method type : #{defs_type.inspect}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def static?
|
153
|
+
true
|
154
|
+
end
|
155
|
+
|
156
|
+
protected
|
157
|
+
|
158
|
+
def class_name_from_enclosing_type
|
159
|
+
enclosing_names.join('::')
|
160
|
+
end
|
161
|
+
|
162
|
+
def class_name_from_declaration
|
163
|
+
ancestor_names = enclosing_names
|
164
|
+
ancestor_names.pop
|
165
|
+
ancestor_names << node.children[0].children[1]
|
166
|
+
ancestor_names.join('::')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'appmap/parser'
|
2
|
+
require 'appmap/inspect/parse_node'
|
3
|
+
|
4
|
+
module AppMap
|
5
|
+
module Inspect
|
6
|
+
# Parser processes a Ruby into a list of parse nodes and a list of comments.
|
7
|
+
class Parser < ::AppMap::Parser
|
8
|
+
protected
|
9
|
+
|
10
|
+
def build_parse_node(node, file_path, ancestors)
|
11
|
+
ParseNode.from_node(node, file_path, ancestors)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'appmap/inspect/inspector'
|
2
|
+
require 'appmap/inspect/parser'
|
3
|
+
|
4
|
+
module AppMap
|
5
|
+
# Inspect identifies features from a Ruby file.
|
6
|
+
module Inspect
|
7
|
+
class << self
|
8
|
+
# Detect features from a source code repository. The manner in which the features are detected in the
|
9
|
+
# code is defined and tuned by a path configuration object. The path configuration tells the
|
10
|
+
# feature detector what it should do when it encounters code that may be a "sub-feature",
|
11
|
+
# for example a public instance method of a class.
|
12
|
+
#
|
13
|
+
# rubocop:disable Metrics/AbcSize
|
14
|
+
# rubocop:disable Metrics/MethodLength
|
15
|
+
# @appmap
|
16
|
+
def detect_features(config_spec)
|
17
|
+
child_features = -> { config_spec.children.map(&Inspect.method(:detect_features)).flatten.compact }
|
18
|
+
parse_file = -> { inspect_file(config_spec.mode, file_path: config_spec.path) }
|
19
|
+
|
20
|
+
feature_builders = Hash.new { |_, key| raise "Unable to build features for #{key.inspect}" }
|
21
|
+
feature_builders[AppMap::Config::Directory] = child_features
|
22
|
+
feature_builders[AppMap::Config::File] = parse_file
|
23
|
+
feature_builders[AppMap::Config::PackageDir] = lambda {
|
24
|
+
AppMap::Feature::Package.new(config_spec.package_name, config_spec.path, {}).tap do |package|
|
25
|
+
child_features.call.each do |child|
|
26
|
+
package.add_child(child)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
}
|
30
|
+
feature_builders[AppMap::Config::NamedFunction] = lambda {
|
31
|
+
# Loads named functions by finding the requested gem, finding the file within the gem,
|
32
|
+
# parsing that file, and then inspecting the module/class scope for the requested method.
|
33
|
+
# We can't 'require' the specified code, because if we do that, it can change the
|
34
|
+
# behavior of the program.
|
35
|
+
|
36
|
+
gem = Gem.loaded_specs[config_spec.gem_name]
|
37
|
+
return [] unless gem
|
38
|
+
|
39
|
+
gem_dir = gem.gem_dir
|
40
|
+
file_path = File.join(gem_dir, config_spec.file_path)
|
41
|
+
|
42
|
+
parse_nodes, comments = Parser.new(file_path: file_path).parse
|
43
|
+
features = ImplicitInspector.new(file_path, parse_nodes, comments).inspect_file
|
44
|
+
|
45
|
+
class_names = config_spec.class_names.dup
|
46
|
+
until class_names.empty?
|
47
|
+
class_name = class_names.shift
|
48
|
+
feature = features.find { |f| f.to_h[:type] == 'class' && f.name == class_name }
|
49
|
+
raise "#{class_name.inspect} not found" unless feature
|
50
|
+
|
51
|
+
features = feature.children
|
52
|
+
end
|
53
|
+
|
54
|
+
function = features.find { |f| f.to_h[:type] == 'function' && f.name == config_spec.method_name && f.static == config_spec.static }
|
55
|
+
|
56
|
+
# If the configuration specifier has an id, use it as the handler id.
|
57
|
+
# This is how we can associate custom handler logic with the named function.
|
58
|
+
function.handler_id = config_spec.id.to_s if config_spec.id
|
59
|
+
|
60
|
+
AppMap::Feature::Package.new(config_spec.gem_name, "#{gem_dir}:0", {}).tap do |pkg|
|
61
|
+
parent = pkg
|
62
|
+
class_names = config_spec.class_names.dup
|
63
|
+
until class_names.empty?
|
64
|
+
class_name = class_names.shift
|
65
|
+
cls = AppMap::Feature::Cls.new(class_name, "#{gem_dir}:0", {})
|
66
|
+
parent.children << cls
|
67
|
+
parent = cls
|
68
|
+
end
|
69
|
+
parent.children << function
|
70
|
+
end
|
71
|
+
}
|
72
|
+
|
73
|
+
feature_builders[config_spec.class].call
|
74
|
+
end
|
75
|
+
# rubocop:enable Metrics/AbcSize
|
76
|
+
# rubocop:enable Metrics/MethodLength
|
77
|
+
|
78
|
+
# Inspect a specific file for features.
|
79
|
+
#
|
80
|
+
# @appmap
|
81
|
+
def inspect_file(strategy, file_path: nil)
|
82
|
+
parse_nodes, comments = Parser.new(file_path: file_path).parse
|
83
|
+
inspector_class = {
|
84
|
+
implicit: ImplicitInspector,
|
85
|
+
explicit: ExplicitInspector
|
86
|
+
}[strategy] or raise "Invalid strategy : #{strategy.inspect}"
|
87
|
+
inspector_class.new(file_path, parse_nodes, comments).inspect_file
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|