Class: Pinspec::Analyzer::TargetParser
- Inherits:
-
Object
- Object
- Pinspec::Analyzer::TargetParser
- Includes:
- Source
- Defined in:
- lib/pinspec/analyzer/target_parser.rb
Defined Under Namespace
Classes: Delegation, Descriptor, MethodDef, Scope
Constant Summary collapse
- MODEL_BASES =
["ApplicationRecord", "ActiveRecord::Base"].freeze
- INERT_BASES =
%w[Object BasicObject].freeze
- CLOCK_CALLS =
{ "Time" => %i[now new], "Date" => %i[today], "DateTime" => %i[now] }.freeze
- VISIBILITIES =
%i[public private protected].freeze
- HASH_SHAPED =
Names that are a bag of attributes rather than a model.
create_params,options,article_attributes. /\A(?:\w+_)?(?:params|options|attributes|attrs|opts)\z/- SCALARISH_TYPES =
These names are not models - but saying only that leaves the corpus with no value at all, and a required parameter then receives an invented nil. The target raises on it and pinspec pins that error as the application's behaviour, which is the one thing it promises never to do. So each name maps to a type the corpus can actually build.
{ "String" => %w[ name email phone title body text message description reason code key path url slug token label format mode scope type kind status state value data ], "Integer" => %w[count index number num limit offset size length quantity qty position], "Float" => %w[amount total subtotal price cost percent rate ratio sum] }.freeze
- SCALARISH_NAMES =
SCALARISH_TYPES.values.flatten.freeze
- DI_PATTERNS =
[ /\ARails\.application\.config\b/, /\ARails\.configuration\b/, /Container\b.*\.resolve\b/, /\AContainer\./ ].freeze
- PRUNE_AT =
[ Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode ].freeze
Class Method Summary collapse
- .application_scope_cache(root) ⇒ Object
-
.application_sources_for(root) ⇒ Object
Keyed by application root, because a batch run builds one parser per file and re-globbing the application for each would be quadratic.
- .parse(file_path, method_name) ⇒ Object
-
.reset_application_cache! ⇒ Object
Tests and long-lived processes need a way back to a clean slate.
-
.split_target(target) ⇒ Object
Returns [file, method] with method nil when the target named none.
Instance Method Summary collapse
-
#initialize(file_path, method_name, app_root: nil) ⇒ TargetParser
constructor
A new instance of TargetParser.
- #parse ⇒ Object
Methods included from Source
Constructor Details
#initialize(file_path, method_name, app_root: nil) ⇒ TargetParser
Returns a new instance of TargetParser.
116 117 118 119 120 |
# File 'lib/pinspec/analyzer/target_parser.rb', line 116 def initialize(file_path, method_name, app_root: nil) @app_root = app_root @file_path = file_path @descriptor = parse_descriptor(method_name) end |
Class Method Details
.application_scope_cache(root) ⇒ Object
104 105 106 107 |
# File 'lib/pinspec/analyzer/target_parser.rb', line 104 def application_scope_cache(root) @application_scopes ||= {} @application_scopes[root] ||= {} end |
.application_sources_for(root) ⇒ Object
Keyed by application root, because a batch run builds one parser per file and re-globbing the application for each would be quadratic.
96 97 98 99 100 101 102 |
# File 'lib/pinspec/analyzer/target_parser.rb', line 96 def application_sources_for(root) @application_sources ||= {} @application_sources[root] ||= begin dirs = %w[app lib].map { |d| File.join(root, d) }.select { |d| File.directory?(d) } dirs.flat_map { |dir| Dir.glob(File.join(dir, "**", "*.rb")) }.sort end end |
.parse(file_path, method_name) ⇒ Object
72 73 74 |
# File 'lib/pinspec/analyzer/target_parser.rb', line 72 def parse(file_path, method_name) new(file_path, method_name).parse end |
.reset_application_cache! ⇒ Object
Tests and long-lived processes need a way back to a clean slate.
110 111 112 113 |
# File 'lib/pinspec/analyzer/target_parser.rb', line 110 def reset_application_cache! @application_sources = {} @application_scopes = {} end |
.split_target(target) ⇒ Object
Returns [file, method] with method nil when the target named none. It is the caller's job to discover one, because that needs the file's contents and the surrounding directory's convention.
79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/pinspec/analyzer/target_parser.rb', line 79 def split_target(target) text = target.to_s return text.split("#", 2) if text.include?("#") unless text.end_with?(".rb") raise ArgumentError, "target must be a Ruby file, optionally with #METHOD " \ "(got #{target.inspect}); e.g. app/services/invoice_calculator.rb#call" end [text, nil] end |
Instance Method Details
#parse ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/pinspec/analyzer/target_parser.rb', line 122 def parse read_source build_index mdef = select_definition scope = @scopes[mdef.scope_name] unless scope raise TargetNotFound, "`#{@descriptor.method_name}` is defined at the top level of " \ "#{@file_path}, not inside a class or module; pinspec has no " \ "receiver to build." end guard_block!(mdef, scope) construction_kind, initializer_params = resolve_construction(scope, mdef) init_node = find_initialize(scope.name) scan = [mdef.node, init_node].compact TargetProfile.new( file_path: @file_path, class_name: scope.name, method_name: mdef.name, params: params_of(mdef.node), initializer_params: initializer_params, construction_kind: construction_kind, visibility: mdef.visibility, takes_block: false, source_range: [mdef.node.location.start_line, mdef.node.location.end_line], referenced_constants: referenced_constants(scan), clock_sites: clock_sites(scan), construction_source: @construction_source || :own ) end |