diff --git a/src/core/jsonschema/CMakeLists.txt b/src/core/jsonschema/CMakeLists.txt
index 403cd15a5..637c6acfc 100644
--- a/src/core/jsonschema/CMakeLists.txt
+++ b/src/core/jsonschema/CMakeLists.txt
@@ -4,11 +4,11 @@ include(./official_resolver.cmake)
 
 sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME jsonschema
   FOLDER "Core/JSON Schema"
-  PRIVATE_HEADERS anchor.h bundle.h resolver.h
+  PRIVATE_HEADERS bundle.h resolver.h
     walker.h reference.h frame.h error.h unevaluated.h
     keywords.h transform.h
   SOURCES jsonschema.cc default_walker.cc frame.cc
-    anchor.cc resolver.cc walker.cc bundle.cc
+    resolver.cc walker.cc bundle.cc
     unevaluated.cc relativize.cc unidentify.cc
     transform_rule.cc transformer.cc
     "${CMAKE_CURRENT_BINARY_DIR}/official_resolver.cc")
diff --git a/src/core/jsonschema/anchor.cc b/src/core/jsonschema/anchor.cc
deleted file mode 100644
index 115290210..000000000
--- a/src/core/jsonschema/anchor.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-#include <sourcemeta/core/jsonschema.h>
-#include <sourcemeta/core/uri.h>
-
-#include <cassert> // assert
-#include <utility> // std::move
-
-namespace sourcemeta::core {
-
-auto anchors(const JSON &schema, const SchemaResolver &resolver,
-             const std::optional<std::string> &default_dialect)
-    -> std::map<std::string, AnchorType> {
-  const std::map<std::string, bool> vocabularies{
-      sourcemeta::core::vocabularies(schema, resolver, default_dialect)};
-  return anchors(schema, vocabularies);
-}
-
-auto anchors(const JSON &schema,
-             const std::map<std::string, bool> &vocabularies)
-    -> std::map<std::string, AnchorType> {
-  std::map<std::string, AnchorType> result;
-
-  // 2020-12
-  if (schema.is_object() &&
-      vocabularies.contains(
-          "https://json-schema.org/draft/2020-12/vocab/core")) {
-    if (schema.defines("$dynamicAnchor")) {
-      const auto &anchor{schema.at("$dynamicAnchor")};
-      assert(anchor.is_string());
-      result.insert({anchor.to_string(), AnchorType::Dynamic});
-    }
-
-    if (schema.defines("$anchor")) {
-      const auto &anchor{schema.at("$anchor")};
-      assert(anchor.is_string());
-      const auto anchor_string{anchor.to_string()};
-      const auto success = result.insert({anchor_string, AnchorType::Static});
-      assert(success.second || result.contains(anchor_string));
-      if (!success.second) {
-        result[anchor_string] = AnchorType::All;
-      }
-    }
-  }
-
-  // 2019-09
-  if (schema.is_object() &&
-      vocabularies.contains(
-          "https://json-schema.org/draft/2019-09/vocab/core")) {
-    if (schema.defines("$recursiveAnchor")) {
-      const auto &anchor{schema.at("$recursiveAnchor")};
-      assert(anchor.is_boolean());
-      if (anchor.to_boolean()) {
-        // We store a 2019-09 recursive anchor as an empty anchor
-        result.insert({"", AnchorType::Dynamic});
-      }
-    }
-
-    if (schema.defines("$anchor")) {
-      const auto &anchor{schema.at("$anchor")};
-      assert(anchor.is_string());
-      const auto anchor_string{anchor.to_string()};
-      const auto success = result.insert({anchor_string, AnchorType::Static});
-      assert(success.second || result.contains(anchor_string));
-      if (!success.second) {
-        result[anchor_string] = AnchorType::All;
-      }
-    }
-  }
-
-  // Draft 7 and 6
-  // Old `$id` anchor form
-  if (schema.is_object() &&
-      (vocabularies.contains("http://json-schema.org/draft-07/schema#") ||
-       vocabularies.contains("http://json-schema.org/draft-06/schema#"))) {
-    if (schema.defines("$id")) {
-      assert(schema.at("$id").is_string());
-      const URI identifier(schema.at("$id").to_string());
-      if (identifier.is_fragment_only()) {
-        result.insert(
-            {std::string{identifier.fragment().value()}, AnchorType::Static});
-      }
-    }
-  }
-
-  // Draft 4
-  // Old `id` anchor form
-  if (schema.is_object() &&
-      vocabularies.contains("http://json-schema.org/draft-04/schema#")) {
-    if (schema.defines("id")) {
-      assert(schema.at("id").is_string());
-      const URI identifier(schema.at("id").to_string());
-      if (identifier.is_fragment_only()) {
-        result.insert(
-            {std::string{identifier.fragment().value()}, AnchorType::Static});
-      }
-    }
-  }
-
-  return result;
-}
-
-} // namespace sourcemeta::core
diff --git a/src/core/jsonschema/frame.cc b/src/core/jsonschema/frame.cc
index 21f0948a1..cffe597a0 100644
--- a/src/core/jsonschema/frame.cc
+++ b/src/core/jsonschema/frame.cc
@@ -13,6 +13,92 @@
 #include <utility>    // std::pair, std::move
 #include <vector>     // std::vector
 
+enum class AnchorType : std::uint8_t { Static, Dynamic, All };
+
+static auto find_anchors(const sourcemeta::core::JSON &schema,
+                         const std::map<std::string, bool> &vocabularies)
+    -> std::map<std::string, AnchorType> {
+  std::map<std::string, AnchorType> result;
+
+  // 2020-12
+  if (schema.is_object() &&
+      vocabularies.contains(
+          "https://json-schema.org/draft/2020-12/vocab/core")) {
+    if (schema.defines("$dynamicAnchor")) {
+      const auto &anchor{schema.at("$dynamicAnchor")};
+      assert(anchor.is_string());
+      result.insert({anchor.to_string(), AnchorType::Dynamic});
+    }
+
+    if (schema.defines("$anchor")) {
+      const auto &anchor{schema.at("$anchor")};
+      assert(anchor.is_string());
+      const auto anchor_string{anchor.to_string()};
+      const auto success = result.insert({anchor_string, AnchorType::Static});
+      assert(success.second || result.contains(anchor_string));
+      if (!success.second) {
+        result[anchor_string] = AnchorType::All;
+      }
+    }
+  }
+
+  // 2019-09
+  if (schema.is_object() &&
+      vocabularies.contains(
+          "https://json-schema.org/draft/2019-09/vocab/core")) {
+    if (schema.defines("$recursiveAnchor")) {
+      const auto &anchor{schema.at("$recursiveAnchor")};
+      assert(anchor.is_boolean());
+      if (anchor.to_boolean()) {
+        // We store a 2019-09 recursive anchor as an empty anchor
+        result.insert({"", AnchorType::Dynamic});
+      }
+    }
+
+    if (schema.defines("$anchor")) {
+      const auto &anchor{schema.at("$anchor")};
+      assert(anchor.is_string());
+      const auto anchor_string{anchor.to_string()};
+      const auto success = result.insert({anchor_string, AnchorType::Static});
+      assert(success.second || result.contains(anchor_string));
+      if (!success.second) {
+        result[anchor_string] = AnchorType::All;
+      }
+    }
+  }
+
+  // Draft 7 and 6
+  // Old `$id` anchor form
+  if (schema.is_object() &&
+      (vocabularies.contains("http://json-schema.org/draft-07/schema#") ||
+       vocabularies.contains("http://json-schema.org/draft-06/schema#"))) {
+    if (schema.defines("$id")) {
+      assert(schema.at("$id").is_string());
+      const sourcemeta::core::URI identifier(schema.at("$id").to_string());
+      if (identifier.is_fragment_only()) {
+        result.insert(
+            {std::string{identifier.fragment().value()}, AnchorType::Static});
+      }
+    }
+  }
+
+  // Draft 4
+  // Old `id` anchor form
+  if (schema.is_object() &&
+      vocabularies.contains("http://json-schema.org/draft-04/schema#")) {
+    if (schema.defines("id")) {
+      assert(schema.at("id").is_string());
+      const sourcemeta::core::URI identifier(schema.at("id").to_string());
+      if (identifier.is_fragment_only()) {
+        result.insert(
+            {std::string{identifier.fragment().value()}, AnchorType::Static});
+      }
+    }
+  }
+
+  return result;
+}
+
 static auto find_nearest_bases(
     const std::map<sourcemeta::core::Pointer, std::vector<std::string>> &bases,
     const sourcemeta::core::Pointer &pointer,
@@ -254,8 +340,8 @@ auto internal_analyse(const sourcemeta::core::JSON &schema,
     }
 
     // Handle schema anchors
-    for (const auto &[name, type] : sourcemeta::core::anchors(
-             entry.common.value, entry.common.vocabularies)) {
+    for (const auto &[name, type] :
+         find_anchors(entry.common.value, entry.common.vocabularies)) {
       const auto bases{
           find_nearest_bases(base_uris, entry.common.pointer, entry.id)};
 
@@ -263,8 +349,7 @@ auto internal_analyse(const sourcemeta::core::JSON &schema,
         const auto anchor_uri{sourcemeta::core::URI::from_fragment(name)};
         const auto relative_anchor_uri{anchor_uri.recompose()};
 
-        if (type == sourcemeta::core::AnchorType::Static ||
-            type == sourcemeta::core::AnchorType::All) {
+        if (type == AnchorType::Static || type == AnchorType::All) {
           store(frame, ReferenceType::Static, Frame::LocationType::Anchor,
                 relative_anchor_uri, root_id, "", entry.common.pointer,
                 entry.common.pointer.resolve_from(bases.second),
@@ -272,8 +357,7 @@ auto internal_analyse(const sourcemeta::core::JSON &schema,
                 entry.common.base_dialect.value());
         }
 
-        if (type == sourcemeta::core::AnchorType::Dynamic ||
-            type == sourcemeta::core::AnchorType::All) {
+        if (type == AnchorType::Dynamic || type == AnchorType::All) {
           store(frame, ReferenceType::Dynamic, Frame::LocationType::Anchor,
                 relative_anchor_uri, root_id, "", entry.common.pointer,
                 entry.common.pointer.resolve_from(bases.second),
@@ -310,8 +394,7 @@ auto internal_analyse(const sourcemeta::core::JSON &schema,
             continue;
           }
 
-          if (type == sourcemeta::core::AnchorType::Static ||
-              type == sourcemeta::core::AnchorType::All) {
+          if (type == AnchorType::Static || type == AnchorType::All) {
             store(frame, sourcemeta::core::ReferenceType::Static,
                   Frame::LocationType::Anchor, anchor_uri, root_id, base_string,
                   entry.common.pointer,
@@ -320,8 +403,7 @@ auto internal_analyse(const sourcemeta::core::JSON &schema,
                   entry.common.base_dialect.value());
           }
 
-          if (type == sourcemeta::core::AnchorType::Dynamic ||
-              type == sourcemeta::core::AnchorType::All) {
+          if (type == AnchorType::Dynamic || type == AnchorType::All) {
             store(frame, sourcemeta::core::ReferenceType::Dynamic,
                   Frame::LocationType::Anchor, anchor_uri, root_id, base_string,
                   entry.common.pointer,
diff --git a/src/core/jsonschema/include/sourcemeta/core/jsonschema.h b/src/core/jsonschema/include/sourcemeta/core/jsonschema.h
index c8bfad80f..7007e5055 100644
--- a/src/core/jsonschema/include/sourcemeta/core/jsonschema.h
+++ b/src/core/jsonschema/include/sourcemeta/core/jsonschema.h
@@ -6,7 +6,6 @@
 #endif
 
 #include <sourcemeta/core/json.h>
-#include <sourcemeta/core/jsonschema_anchor.h>
 #include <sourcemeta/core/jsonschema_bundle.h>
 #include <sourcemeta/core/jsonschema_error.h>
 #include <sourcemeta/core/jsonschema_frame.h>
diff --git a/src/core/jsonschema/include/sourcemeta/core/jsonschema_anchor.h b/src/core/jsonschema/include/sourcemeta/core/jsonschema_anchor.h
deleted file mode 100644
index 1d117831d..000000000
--- a/src/core/jsonschema/include/sourcemeta/core/jsonschema_anchor.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#ifndef SOURCEMETA_CORE_JSONSCHEMA_ANCHOR_H_
-#define SOURCEMETA_CORE_JSONSCHEMA_ANCHOR_H_
-
-#ifndef SOURCEMETA_CORE_JSONSCHEMA_EXPORT
-#include <sourcemeta/core/jsonschema_export.h>
-#endif
-
-#include <sourcemeta/core/json.h>
-#include <sourcemeta/core/jsonschema_resolver.h>
-
-#include <cstdint>  // std::uint8_t
-#include <map>      // std::map
-#include <optional> // std::optional
-#include <string>   // std::string
-
-namespace sourcemeta::core {
-
-/// @ingroup jsonschema
-/// The anchor type
-enum class AnchorType : std::uint8_t { Static, Dynamic, All };
-
-/// @ingroup jsonschema
-///
-/// This function returns the anchors of the given schema, if any. This function
-/// also supports the old pre 2019-09 `id` and `$id` anchor form. A given
-/// subschema might have more than two anchors if, for example, declares both
-/// `$anchor` and `$dynamicAnchor` (in 2020-12). For example:
-///
-/// ```cpp
-/// #include <sourcemeta/core/json.h>
-/// #include <sourcemeta/core/jsonschema.h>
-/// #include <cassert>
-///
-/// const sourcemeta::core::JSON document =
-///     sourcemeta::core::parse_json(R"JSON({
-///   "$schema": "https://json-schema.org/draft/2020-12/schema",
-///   "$id": "https://sourcemeta.com/example-schema",
-///   "$anchor": "foo"
-/// })JSON");
-///
-/// const auto anchors{sourcemeta::core::anchors(
-///   document, sourcemeta::core::official_resolver)};
-/// assert(anchors.size() == 1);
-/// assert(anchors.contains("foo"));
-/// // This is a static anchor
-/// assert(anchors.at("foo") == sourcemeta::core::AnchorType::Static);
-/// ```
-SOURCEMETA_CORE_JSONSCHEMA_EXPORT
-auto anchors(const JSON &schema, const SchemaResolver &resolver,
-             const std::optional<std::string> &default_dialect = std::nullopt)
-    -> std::map<std::string, AnchorType>;
-
-/// @ingroup jsonschema
-///
-/// This function is a shortcut to sourcemeta::core::anchors if you have
-/// already computed the vocabularies that the given schema makes use of.
-SOURCEMETA_CORE_JSONSCHEMA_EXPORT
-auto anchors(const JSON &schema,
-             const std::map<std::string, bool> &vocabularies)
-    -> std::map<std::string, AnchorType>;
-
-} // namespace sourcemeta::core
-
-#endif
diff --git a/test/jsonschema/CMakeLists.txt b/test/jsonschema/CMakeLists.txt
index 1bb3e6076..18315c895 100644
--- a/test/jsonschema/CMakeLists.txt
+++ b/test/jsonschema/CMakeLists.txt
@@ -2,11 +2,6 @@ sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME jsonschema
   FOLDER "Core/JSON Schema"
   SOURCES
     jsonschema_test_utils.h
-    jsonschema_anchor_2020_12_test.cc
-    jsonschema_anchor_2019_09_test.cc
-    jsonschema_anchor_draft7_test.cc
-    jsonschema_anchor_draft6_test.cc
-    jsonschema_anchor_draft4_test.cc
     jsonschema_identify_2020_12_test.cc
     jsonschema_identify_2019_09_test.cc
     jsonschema_identify_draft7_test.cc
diff --git a/test/jsonschema/jsonschema_anchor_2019_09_test.cc b/test/jsonschema/jsonschema_anchor_2019_09_test.cc
deleted file mode 100644
index 09b4b9185..000000000
--- a/test/jsonschema/jsonschema_anchor_2019_09_test.cc
+++ /dev/null
@@ -1,125 +0,0 @@
-#include <gtest/gtest.h>
-#include <sourcemeta/core/json.h>
-#include <sourcemeta/core/jsonschema.h>
-
-TEST(JSONSchema_anchor_2019_09, boolean_schema) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json("true");
-  const auto anchors{sourcemeta::core::anchors(
-      document, sourcemeta::core::official_resolver,
-      "https://json-schema.org/draft/2019-09/schema")};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_2019_09, top_level_no_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2019-09/schema"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_2019_09, top_level_static_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2019-09/schema",
-    "$anchor": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_2019_09, top_level_dynamic_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2019-09/schema",
-    "$dynamicAnchor": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_2019_09, top_level_recursive_anchor_false) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2019-09/schema",
-    "$recursiveAnchor": false
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_2019_09, top_level_recursive_anchor_true) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2019-09/schema",
-    "$recursiveAnchor": true
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains(""));
-  EXPECT_EQ(anchors.at(""), sourcemeta::core::AnchorType::Dynamic);
-}
-
-TEST(JSONSchema_anchor_2019_09, top_level_recursive_anchor_true_and_empty) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2019-09/schema",
-    "$recursiveAnchor": true,
-    "$anchor": ""
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains(""));
-  EXPECT_EQ(anchors.at(""), sourcemeta::core::AnchorType::All);
-}
-
-TEST(JSONSchema_anchor_2019_09, nested_static_with_default_dialect) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$anchor": "foo"
-  })JSON");
-
-  const auto anchors{sourcemeta::core::anchors(
-      document, sourcemeta::core::official_resolver,
-      "https://json-schema.org/draft/2019-09/schema")};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_2019_09, vocabularies_shortcut) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2019-09/schema",
-    "$anchor": "foo"
-  })JSON");
-
-  const std::map<std::string, bool> vocabularies{sourcemeta::core::vocabularies(
-      document, sourcemeta::core::official_resolver)};
-
-  const auto anchors{sourcemeta::core::anchors(document, vocabularies)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_2019_09, old_id_anchor_not_recognized) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2019-09/schema",
-    "$id": "#foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
diff --git a/test/jsonschema/jsonschema_anchor_2020_12_test.cc b/test/jsonschema/jsonschema_anchor_2020_12_test.cc
deleted file mode 100644
index 4563ac770..000000000
--- a/test/jsonschema/jsonschema_anchor_2020_12_test.cc
+++ /dev/null
@@ -1,119 +0,0 @@
-#include <gtest/gtest.h>
-#include <sourcemeta/core/json.h>
-#include <sourcemeta/core/jsonschema.h>
-
-TEST(JSONSchema_anchor_2020_12, boolean_schema) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json("true");
-  const auto anchors{sourcemeta::core::anchors(
-      document, sourcemeta::core::official_resolver,
-      "https://json-schema.org/draft/2020-12/schema")};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_2020_12, top_level_no_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2020-12/schema"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_2020_12, top_level_static_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2020-12/schema",
-    "$anchor": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_2020_12, top_level_dynamic_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2020-12/schema",
-    "$dynamicAnchor": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Dynamic);
-}
-
-TEST(JSONSchema_anchor_2020_12, top_level_static_and_dynamic_anchor_different) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2020-12/schema",
-    "$anchor": "bar",
-    "$dynamicAnchor": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_EQ(anchors.size(), 2);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_TRUE(anchors.contains("bar"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Dynamic);
-  EXPECT_EQ(anchors.at("bar"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_2020_12, top_level_static_and_dynamic_anchor_same) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2020-12/schema",
-    "$anchor": "foo",
-    "$dynamicAnchor": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::All);
-}
-
-TEST(JSONSchema_anchor_2020_12, nested_static_with_default_dialect) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$anchor": "foo"
-  })JSON");
-
-  const auto anchors{sourcemeta::core::anchors(
-      document, sourcemeta::core::official_resolver,
-      "https://json-schema.org/draft/2020-12/schema")};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_2020_12, vocabularies_shortcut) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2020-12/schema",
-    "$anchor": "foo",
-    "$dynamicAnchor": "bar"
-  })JSON");
-
-  const std::map<std::string, bool> vocabularies{sourcemeta::core::vocabularies(
-      document, sourcemeta::core::official_resolver)};
-
-  const auto anchors{sourcemeta::core::anchors(document, vocabularies)};
-  EXPECT_EQ(anchors.size(), 2);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_TRUE(anchors.contains("bar"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-  EXPECT_EQ(anchors.at("bar"), sourcemeta::core::AnchorType::Dynamic);
-}
-
-TEST(JSONSchema_anchor_2020_12, old_id_anchor_not_recognized) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "https://json-schema.org/draft/2020-12/schema",
-    "$id": "#foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
diff --git a/test/jsonschema/jsonschema_anchor_draft4_test.cc b/test/jsonschema/jsonschema_anchor_draft4_test.cc
deleted file mode 100644
index c3f2ba3d1..000000000
--- a/test/jsonschema/jsonschema_anchor_draft4_test.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-#include <gtest/gtest.h>
-
-#include <sourcemeta/core/json.h>
-#include <sourcemeta/core/jsonschema.h>
-
-TEST(JSONSchema_anchor_draft4, boolean_schema) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json("true");
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver,
-                                "http://json-schema.org/draft-04/schema#")};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft4, top_level_no_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-04/schema#"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft4, top_level_static_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-04/schema#",
-    "id": "#foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft4, nested_static_with_default_dialect) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "id": "#foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver,
-                                "http://json-schema.org/draft-04/schema#")};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft4, vocabularies_shortcut) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-04/schema#",
-    "id": "#foo"
-  })JSON");
-
-  const std::map<std::string, bool> vocabularies{sourcemeta::core::vocabularies(
-      document, sourcemeta::core::official_resolver)};
-
-  const auto anchors{sourcemeta::core::anchors(document, vocabularies)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft4, non_fragment_relative_id) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-04/schema#",
-    "id": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft4, not_only_fragment) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-04/schema#",
-    "id": "foo#bar"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
diff --git a/test/jsonschema/jsonschema_anchor_draft6_test.cc b/test/jsonschema/jsonschema_anchor_draft6_test.cc
deleted file mode 100644
index 628bcfac1..000000000
--- a/test/jsonschema/jsonschema_anchor_draft6_test.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-#include <gtest/gtest.h>
-
-#include <sourcemeta/core/json.h>
-#include <sourcemeta/core/jsonschema.h>
-
-TEST(JSONSchema_anchor_draft6, boolean_schema) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json("true");
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver,
-                                "http://json-schema.org/draft-06/schema#")};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft6, top_level_no_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-06/schema#"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft6, top_level_static_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-06/schema#",
-    "$id": "#foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft6, nested_static_with_default_dialect) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$id": "#foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver,
-                                "http://json-schema.org/draft-06/schema#")};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft6, vocabularies_shortcut) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-06/schema#",
-    "$id": "#foo"
-  })JSON");
-
-  const std::map<std::string, bool> vocabularies{sourcemeta::core::vocabularies(
-      document, sourcemeta::core::official_resolver)};
-
-  const auto anchors{sourcemeta::core::anchors(document, vocabularies)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft6, non_fragment_relative_id) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-06/schema#",
-    "$id": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft6, not_only_fragment) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-06/schema#",
-    "$id": "foo#bar"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
diff --git a/test/jsonschema/jsonschema_anchor_draft7_test.cc b/test/jsonschema/jsonschema_anchor_draft7_test.cc
deleted file mode 100644
index c44da4d33..000000000
--- a/test/jsonschema/jsonschema_anchor_draft7_test.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-#include <gtest/gtest.h>
-
-#include <sourcemeta/core/json.h>
-#include <sourcemeta/core/jsonschema.h>
-
-TEST(JSONSchema_anchor_draft7, boolean_schema) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json("true");
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver,
-                                "http://json-schema.org/draft-07/schema#")};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft7, top_level_no_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-07/schema#"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft7, top_level_static_anchor) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "$id": "#foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft7, nested_static_with_default_dialect) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$id": "#foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver,
-                                "http://json-schema.org/draft-07/schema#")};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft7, vocabularies_shortcut) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "$id": "#foo"
-  })JSON");
-
-  const std::map<std::string, bool> vocabularies{sourcemeta::core::vocabularies(
-      document, sourcemeta::core::official_resolver)};
-
-  const auto anchors{sourcemeta::core::anchors(document, vocabularies)};
-  EXPECT_EQ(anchors.size(), 1);
-  EXPECT_TRUE(anchors.contains("foo"));
-  EXPECT_EQ(anchors.at("foo"), sourcemeta::core::AnchorType::Static);
-}
-
-TEST(JSONSchema_anchor_draft7, non_fragment_relative_id) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "$id": "foo"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}
-
-TEST(JSONSchema_anchor_draft7, not_only_fragment) {
-  const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
-    "$schema": "http://json-schema.org/draft-07/schema#",
-    "$id": "foo#bar"
-  })JSON");
-
-  const auto anchors{
-      sourcemeta::core::anchors(document, sourcemeta::core::official_resolver)};
-  EXPECT_TRUE(anchors.empty());
-}