Class: RSpec::Signal::Project

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec/signal/project.rb

Overview

Knows which paths belong to the project under test ("first party") and how to render any path compactly.

First-party means: the host application's own code, plus code from gems that are developed alongside it (Bundler path: sources, local Rails engines). Those are things the agent can actually open and edit.

Constant Summary collapse

VENDORED =

Directories that live inside the project root but are not project code.

%w[
  vendor/bundle vendor/cache vendor/ruby .bundle node_modules
  tmp/ .git/ coverage/
].freeze
GEM_PATH =

Matches ".../gems/capybara-3.40.0/lib/capybara/node/finders.rb".

The leading .*/ is greedy on purpose: a RubyGems install path contains "/gems/" twice ("/lib/ruby/gems/3.3.0/gems/capybara-3.40.0/..."), and it is the last one that introduces the gem.

%r{
  \A.*/(?:gems|bundler/gems)/
  (?<name>[A-Za-z0-9_.-]+?)
  # A release version, or the short SHA Bundler uses for git sources.
  (?:-(?<version>\d[\w.]*(?:-[a-z0-9]+)?|[0-9a-f]{12,40}))?
  /(?<rest>.*)\z
}x.freeze
STDLIB_PATH =

Matches ".../lib/ruby/3.3.0/json/common.rb" and ".../ruby/3.3.0/x86_64-linux/..."

%r{
  /lib/ruby/(?:\d+\.\d+\.\d+|site_ruby|vendor_ruby)/
  (?:[a-z0-9_]+-[a-z0-9_-]+/)?(?<rest>.*)\z
}x.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root: Dir.pwd, extra_first_party: []) ⇒ Project

Returns a new instance of Project.

Parameters:

  • root (String) (defaults to: Dir.pwd)

    absolute path to the project root

  • extra_first_party (Array<String>) (defaults to: [])

    additional absolute path prefixes to treat as first party (e.g. a sibling engine checkout)



42
43
44
45
46
47
# File 'lib/rspec/signal/project.rb', line 42

def initialize(root: Dir.pwd, extra_first_party: [])
  @root = File.expand_path(root)
  @root_prefix = "#{@root}/"
  @extra = extra_first_party.map { |p| "#{File.expand_path(p)}/" }
  @cache = {}
end

Instance Attribute Details

#rootObject (readonly)

Returns the value of attribute root.



37
38
39
# File 'lib/rspec/signal/project.rb', line 37

def root
  @root
end

Instance Method Details

#absolutize(path) ⇒ Object



90
91
92
93
94
95
96
97
98
# File 'lib/rspec/signal/project.rb', line 90

def absolutize(path)
  stripped = path.to_s.sub(%r{\A\./}, "")
  # Ruby emits pseudo-frames that are not file paths at all: "<internal:...>",
  # "-e", "(eval)", "(irb)". Joining those to the project root would wrongly
  # make them look first party.
  return stripped if stripped.start_with?("/", "<", "-", "(")

  File.join(@root, stripped)
end

#display_path(path) ⇒ Object

A short, stable, human/agent readable rendering of a path.

/app/spec/models/user_spec.rb          -> spec/models/user_spec.rb
.../gems/capybara-3.40.0/lib/capybara/node/finders.rb -> capybara/node/finders.rb
.../lib/ruby/3.3.0/json/common.rb      -> ruby/json/common.rb


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rspec/signal/project.rb', line 67

def display_path(path)
  return path if path.nil? || path.empty?

  absolute = absolutize(path)
  return relative_to_root(absolute) if under?(absolute, @root_prefix) || first_party?(path)

  if (m = GEM_PATH.match(absolute))
    return "#{m[:name]}/#{strip_redundant_prefix(m[:name], m[:rest].sub(%r{\Alib/}, ""))}"
  end

  if (stdlib = STDLIB_PATH.match(absolute))
    return "ruby/#{stdlib[:rest]}"
  end

  relative_to_root(absolute)
end

#first_party?(path) ⇒ Boolean

Returns:

  • (Boolean)


56
57
58
59
60
# File 'lib/rspec/signal/project.rb', line 56

def first_party?(path)
  return false if path.nil? || path.empty?

  @cache[path] ||= compute_first_party(path)
end

#gem_name(path) ⇒ Object

The gem a path belongs to, or nil.



85
86
87
88
# File 'lib/rspec/signal/project.rb', line 85

def gem_name(path)
  m = GEM_PATH.match(absolutize(path))
  m && m[:name]
end

#local_gem_prefixesObject

Absolute path prefixes of Bundler path: gems (local gems and engines). Discovered lazily and defensively: any Bundler problem simply means we fall back to plain root-relative detection.



52
53
54
# File 'lib/rspec/signal/project.rb', line 52

def local_gem_prefixes
  @local_gem_prefixes ||= discover_local_gem_prefixes
end

#relative_to_root(absolute) ⇒ Object



100
101
102
103
104
# File 'lib/rspec/signal/project.rb', line 100

def relative_to_root(absolute)
  return absolute[@root_prefix.length..] if under?(absolute, @root_prefix)

  absolute
end