From e473578173de920d533b172ec1fdae7980c973d8 Mon Sep 17 00:00:00 2001 From: "Daniel D. Beck" Date: Mon, 11 May 2026 14:08:20 +0200 Subject: [PATCH] Allow overlapping compat keys --- assertions.test.ts | 57 +++++++++- assertions.ts | 21 ++++ docs/guidelines.md | 32 ++++++ features/_overlap_allowlist.yml | 54 +++++++++ features/audio.yml | 63 ++++++++++- features/audio.yml.dist | 194 ++++++++++++++++++++++++++++++++ index.ts | 32 ++++-- scripts/dist.ts | 4 + 8 files changed, 443 insertions(+), 14 deletions(-) create mode 100644 features/_overlap_allowlist.yml diff --git a/assertions.test.ts b/assertions.test.ts index 2ff81193779..4b3e5a42616 100644 --- a/assertions.test.ts +++ b/assertions.test.ts @@ -1,5 +1,8 @@ import assert from "node:assert/strict"; -import { assertValidFeatureReference } from "./assertions"; +import { + assertAllowedOverlap, + assertValidFeatureReference, +} from "./assertions"; describe("assertValidReference()", function () { it("throws if target ID is a move", function () { @@ -34,3 +37,55 @@ describe("assertValidReference()", function () { }); }); }); + +describe("assertAllowedOverlap()", function () { + it("does not throw when a key does not overlap", function () { + assert.doesNotThrow(() => { + const keysToIDs = new Map([["api.HTMLMediaElement", ["audio"]]]); + assertAllowedOverlap( + `api.HTMLMediaElement`, + `audio`, + keysToIDs, + new Map(), + ); + }); + }); + + it("does not throw when a key is allowlisted with another feature", function () { + assert.doesNotThrow(() => { + const keysToIDs = new Map([["api.HTMLMediaElement", ["audio", "video"]]]); + assertAllowedOverlap( + "api.HTMLMediaElement", + "audio", + keysToIDs, + new Map([["api.HTMLMediaElement", ["audio", "video"]]]), + ); + }); + }); + + it("throws when a key is not allowlisted and overlaps", function () { + assert.throws(() => { + const keysToIDs = new Map([["api.HTMLMediaElement", ["audio", "video"]]]); + assertAllowedOverlap( + "api.HTMLMediaElement", + "audio", + keysToIDs, + new Map(), + ); + }); + }); + + it("throws when a key is allowlisted but overlaps with an unnamed feature", function () { + assert.throws(() => { + const keysToIDs = new Map([ + ["api.HTMLMediaElement", ["audio", "media-super-feature"]], + ]); + assertAllowedOverlap( + "api.HTMLMediaElement", + "video", + keysToIDs, + new Map([["api.HTMLMediaElement", ["audio", "media-super-feature"]]]), + ); + }); + }); +}); diff --git a/assertions.ts b/assertions.ts index 7069705efe0..07bd3b1f4f4 100644 --- a/assertions.ts +++ b/assertions.ts @@ -58,4 +58,25 @@ export function assertRequiredRemovalDateSet( ); } +// compat keys to overlapping feature ID list +export type OverlapAllowlist = Map; + +export function assertAllowedOverlap( + compatKey: string, + featureID: string, + compatKeysToIDs: Map, + allowlist: OverlapAllowlist, +) { + const featuresWithThisCompatKey = compatKeysToIDs.get(compatKey) ?? []; + if (featuresWithThisCompatKey.length === 1) { + return; + } + const overlappers = allowlist.get(compatKey) ?? []; + if (!overlappers.includes(featureID)) { + throw new Error( + `BCD key ${compatKey} overlaps with one or more other features (${featuresWithThisCompatKey.join(", ")}) but the feature is not allowlisted (${overlappers.join(", ")})`, + ); + } +} + // TODO: assertValidSnapshotReference diff --git a/docs/guidelines.md b/docs/guidelines.md index fae24d70be0..6290494beec 100644 --- a/docs/guidelines.md +++ b/docs/guidelines.md @@ -423,3 +423,35 @@ When you set a `discouraged` block in a feature file, do: - Set one or more (optional) `alternatives` feature IDs that are whole or partial substitutes for the discouraged feature. An alternative doesn't have to be a narrow drop-in replacement for the discouraged feature but it must handle some use case of the discouraged feature. Guide developers to the most relevant features that would help them stop using the discouraged feature. + +## Overlapping `compat_features` keys + +Typically, an `@mdn/browser-compat-data` (BCD) key is assigned to one and only one feature. +Rarely, two or more features have equal claim to a key and their sets of `compat_features` keys must overlap. +You can add a single key to two or more `compat_features` lists when independent features share some interface and one of these situations applies: + +- No feature has any claim to being first and the shared interface does not represent a useful feature on its own. + This sometimes happens when two independent features share a common interface. + For example, the `HTMLMediaElement` interface is required for both the `