diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb
index 0100de7b2721a7b1a475eac6e42820c8f54c1e03..7f6a2e2f819961c7786450c7f150dc3d94e41880 100644
--- a/Library/Homebrew/dev-cmd/audit.rb
+++ b/Library/Homebrew/dev-cmd/audit.rb
@@ -1019,7 +1019,7 @@ module Homebrew
         problem "missing version"
       elsif !version.detected_from_url?
         version_text = version
-        version_url = Version.detect(url, specs)
+        version_url = Version.detect(url, **specs)
         if version_url.to_s == version_text.to_s && version.instance_of?(Version)
           problem "version #{version_text} is redundant with version scanned from URL"
         end
diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb
index 4789167e99f702d42a03a10365cb7f2c9124778f..5463ca934fc4aa054648eab70933c4c1805bb9be 100644
--- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb
+++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb
@@ -468,7 +468,7 @@ module Homebrew
     unless version
       specs = {}
       specs[:tag] = tag if tag
-      version = Version.detect(url, specs)
+      version = Version.detect(url, **specs)
     end
     # if we haven't already found open requests, try for an exact match across closed requests
     GitHub.check_for_duplicate_pull_requests("#{formula.name} #{version}", tap_full_name, state: "closed", args: args)
diff --git a/Library/Homebrew/formula_creator.rb b/Library/Homebrew/formula_creator.rb
index 0be15b4f881cba4e29c55f770f180dbbe1a3268a..9d424cb0cb9b10e6caa22762f9793b66a428d18e 100644
--- a/Library/Homebrew/formula_creator.rb
+++ b/Library/Homebrew/formula_creator.rb
@@ -34,7 +34,7 @@ module Homebrew
       @version = if @version
         Version.create(@version)
       else
-        Version.detect(url, {})
+        Version.detect(url)
       end
     end
 
diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb
index e498069eaefe919163d915b22180977ab5d4e442..dd11a9f981f287ce575a65f2264bf3f65ea48dc2 100644
--- a/Library/Homebrew/resource.rb
+++ b/Library/Homebrew/resource.rb
@@ -203,7 +203,7 @@ class Resource
     return Version::NULL if val.nil? && url.nil?
 
     case val
-    when nil     then Version.detect(url, specs)
+    when nil     then Version.detect(url, **specs)
     when String  then Version.create(val)
     when Version then val
     else
diff --git a/Library/Homebrew/test/version_spec.rb b/Library/Homebrew/test/version_spec.rb
index 51a8641b820798eb91d7897603b56a18f220acb5..38bcf00cb4c474f32a54c736727bac2b8bfe7447 100644
--- a/Library/Homebrew/test/version_spec.rb
+++ b/Library/Homebrew/test/version_spec.rb
@@ -63,17 +63,19 @@ describe Version::NULL do
   end
 end
 
-describe Version::NullToken do
-  specify "#inspect" do
-    expect(subject.inspect).to eq("#<Version::NullToken>")
-  end
+describe Version do
+  describe "::NULL_TOKEN" do
+    subject { described_class::NULL_TOKEN }
 
-  it "is equal to itself" do
-    expect(subject).to be == described_class.new
+    specify "#inspect" do
+      expect(subject.inspect).to eq("#<Version::NullToken>")
+    end
+
+    it "is equal to itself" do
+      expect(subject).to be == described_class::NULL_TOKEN
+    end
   end
-end
 
-describe Version do
   specify "comparison" do
     expect(described_class.create("0.1")).to be == described_class.create("0.1.0")
     expect(described_class.create("0.1")).to be < described_class.create("0.2")
@@ -259,9 +261,17 @@ describe Version do
     end
   end
 
-  specify "#detected_from_url?" do
-    expect(described_class.create("1.0")).not_to be_detected_from_url
-    expect(Version::FromURL.new("1.0")).to be_detected_from_url
+  describe "#detected_from_url?" do
+    it "is false if created explicitly" do
+      expect(described_class.new("1.0.0")).not_to be_detected_from_url
+    end
+
+    it "is true if the version was detected from a URL" do
+      version = described_class.detect("https://example.org/archive-1.0.0.tar.gz")
+
+      expect(version).to eq "1.0.0"
+      expect(version).to be_detected_from_url
+    end
   end
 
   specify "#head?" do
@@ -363,9 +373,9 @@ describe Version do
   end
 
   describe "::detect" do
-    matcher :be_detected_from do |url, specs = {}|
+    matcher :be_detected_from do |url, **specs|
       match do |expected|
-        @detected = described_class.detect(url, specs)
+        @detected = described_class.detect(url, **specs)
         @detected == expected
       end
 
diff --git a/Library/Homebrew/version.rb b/Library/Homebrew/version.rb
index ae2aec8bb1bc61cf33436a2de4654cafeb537611..daa83bf73e57d5fc7edb4a408f1b6948f0219952 100644
--- a/Library/Homebrew/version.rb
+++ b/Library/Homebrew/version.rb
@@ -2,6 +2,9 @@
 
 require "version/null"
 
+# A formula's version.
+#
+# @api private
 class Version
   include Comparable
 
@@ -9,6 +12,7 @@ class Version
     /#{"^" if full}#{Regexp.escape(name)}(@\d[\d.]*)?#{"$" if full}/
   end
 
+  # A part of a `Version`.
   class Token
     include Comparable
 
@@ -67,9 +71,10 @@ class Version
     end
   end
 
+  # A pseudo-token representing the absence of a token.
   class NullToken < Token
-    def initialize(value = nil)
-      super
+    def initialize
+      super(nil)
     end
 
     def <=>(other)
@@ -95,9 +100,12 @@ class Version
       "#<#{self.class.name}>"
     end
   end
+  private_constant :NullToken
 
+  # Represents the absence of a token.
   NULL_TOKEN = NullToken.new.freeze
 
+  # A token string.
   class StringToken < Token
     PATTERN = /[a-z]+/i.freeze
 
@@ -119,6 +127,7 @@ class Version
     end
   end
 
+  # A token consisting of only numbers.
   class NumericToken < Token
     PATTERN = /[0-9]+/i.freeze
 
@@ -146,12 +155,14 @@ class Version
     end
   end
 
+  # A token consisting of an alphabetic and a numeric part.
   class CompositeToken < StringToken
     def rev
       value[/[0-9]+/].to_i
     end
   end
 
+  # A token representing the part of a version designating it is an alpha release.
   class AlphaToken < CompositeToken
     PATTERN = /alpha[0-9]*|a[0-9]+/i.freeze
 
@@ -169,6 +180,7 @@ class Version
     end
   end
 
+  # A token representing the part of a version designating it is a beta release.
   class BetaToken < CompositeToken
     PATTERN = /beta[0-9]*|b[0-9]+/i.freeze
 
@@ -188,6 +200,7 @@ class Version
     end
   end
 
+  # A token representing the part of a version designating it is a pre-release.
   class PreToken < CompositeToken
     PATTERN = /pre[0-9]*/i.freeze
 
@@ -207,6 +220,7 @@ class Version
     end
   end
 
+  # A token representing the part of a version designating it is a release-candidate.
   class RCToken < CompositeToken
     PATTERN = /rc[0-9]*/i.freeze
 
@@ -226,6 +240,7 @@ class Version
     end
   end
 
+  # A token representing the part of a version designating it is a patch release.
   class PatchToken < CompositeToken
     PATTERN = /p[0-9]*/i.freeze
 
@@ -252,19 +267,10 @@ class Version
     NumericToken::PATTERN,
     StringToken::PATTERN,
   ).freeze
+  private_constant :SCAN_PATTERN
 
-  class FromURL < Version
-    def detected_from_url?
-      true
-    end
-  end
-
-  def self.detect(url, specs)
-    if specs.key?(:tag)
-      FromURL.parse(specs[:tag])
-    else
-      FromURL.parse(url)
-    end
+  def self.detect(url, **specs)
+    parse(specs.fetch(:tag, url), detected_from_url: true)
   end
 
   def self.create(val)
@@ -277,9 +283,9 @@ class Version
     end
   end
 
-  def self.parse(spec)
+  def self.parse(spec, detected_from_url: false)
     version = _parse(spec)
-    version.nil? ? NULL : new(version)
+    version.nil? ? NULL : new(version, detected_from_url: detected_from_url)
   end
 
   def self._parse(spec)
@@ -424,14 +430,15 @@ class Version
 
   private_class_method :_parse
 
-  def initialize(val)
+  def initialize(val, detected_from_url: false)
     raise TypeError, "Version value must be a string; got a #{val.class} (#{val})" unless val.respond_to?(:to_str)
 
     @version = val.to_str
+    @detected_from_url = detected_from_url
   end
 
   def detected_from_url?
-    false
+    @detected_from_url
   end
 
   def head?
@@ -550,10 +557,13 @@ class Version
   end
 end
 
+# A formula's [HEAD version](https://docs.brew.sh/Formula-Cookbook#unstable-versions-head).
+#
+# @api private
 class HeadVersion < Version
   attr_reader :commit
 
-  def initialize(val)
+  def initialize(*)
     super
     @commit = @version[/^HEAD-(.+)$/, 1]
   end
diff --git a/Library/Homebrew/version/null.rb b/Library/Homebrew/version/null.rb
index b15546a6cb914b09cb4dc6a676bd663e8cbb1063..7b63523b43c446c8781cfff6df4ecebf9df1fe9f 100644
--- a/Library/Homebrew/version/null.rb
+++ b/Library/Homebrew/version/null.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 class Version
+  # Represents the absence of a version.
   NULL = Class.new do
     include Comparable