Skip to content

Commit 7f7c96f

Browse files
committed
Provide discriminator property on feature data
1 parent c01bba1 commit 7f7c96f

File tree

11 files changed

+112
-114
lines changed

11 files changed

+112
-114
lines changed

features/numeric-separators.yml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Generated from: numeric-separators.yml
22
# Do not edit this file by hand. Edit the source file instead!
33

4+
kind: feature
45
status:
56
baseline: high
67
baseline_low_date: 2020-07-28

features/numeric-seperators.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
redirect:
2-
reason: moved
3-
target: numeric-separators
1+
kind: moved
2+
redirect_target: numeric-separators

features/webcodecs.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
redirect:
2-
reason: split
3-
targets:
4-
- audio-codecs
5-
- video-codecs
1+
kind: split
2+
redirect_targets:
3+
- audio-codecs
4+
- video-codecs

index.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { unified } from 'unified';
1414

1515
import { BASELINE_LOW_TO_HIGH_DURATION, coreBrowserSet, parseRangedDateString } from 'compute-baseline';
1616
import { Compat } from 'compute-baseline/browser-compat-data';
17-
import { isOrdinaryFeatureData, isRedirectData } from './type-guards';
17+
import { isMoved, isSplit } from './type-guards';
1818

1919
// The longest name allowed, to allow for compact display.
2020
const nameMaxLength = 80;
@@ -188,7 +188,7 @@ for (const [key, data] of yamlEntries('features')) {
188188

189189
function assertValidReference(sourceID: string, targetID: string): void {
190190
if (targetID in features) {
191-
if (isRedirectData(features[targetID])) {
191+
if (isMoved(features[targetID]) || isSplit(features[targetID])) {
192192
throw new Error(`${sourceID} references a redirect "${targetID}" instead of an ordinary feature ID`);
193193
}
194194
return;
@@ -197,27 +197,24 @@ function assertValidReference(sourceID: string, targetID: string): void {
197197
}
198198

199199
for (const [id, feature] of Object.entries(features)) {
200-
if (isOrdinaryFeatureData(feature)) {
201-
for (const alternative of feature.discouraged?.alternatives ?? []) {
202-
assertValidReference(id, alternative);
203-
}
204-
}
205-
206-
if (isRedirectData(feature)) {
207-
const { reason } = feature.redirect;
208-
switch (reason) {
209-
case 'moved':
210-
assertValidReference(id, feature.redirect.target);
211-
break;
212-
case 'split':
213-
for (const target of feature.redirect.targets) {
214-
assertValidReference(id, target);
215-
}
216-
break;
217-
default:
218-
reason satisfies never;
219-
throw new Error(`Unhandled redirect reason ${reason}}`);
220-
}
200+
const { kind } = feature;
201+
switch (kind) {
202+
case "feature":
203+
for (const alternative of feature.discouraged?.alternatives ?? []) {
204+
assertValidReference(id, alternative);
205+
}
206+
break;
207+
case "moved":
208+
assertValidReference(id, feature.redirect_target);
209+
break;
210+
case "split":
211+
for (const target of feature.redirect_targets) {
212+
assertValidReference(id, target);
213+
}
214+
break;
215+
default:
216+
kind satisfies never;
217+
throw new Error(`Unhandled feature kind ${kind}}`);
221218
}
222219
}
223220

schemas/data.schema.json

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
{
5555
"$ref": "#/definitions/FeatureSplitData"
5656
}
57-
]
57+
],
58+
"$comment": "Use the `kind` property as a discriminator."
5859
}
5960
},
6061
"groups": {
@@ -112,6 +113,9 @@
112113
"description": "A feature data entry",
113114
"type": "object",
114115
"properties": {
116+
"kind": {
117+
"const": "feature"
118+
},
115119
"name": {
116120
"description": "Short name",
117121
"type": "string"
@@ -153,7 +157,14 @@
153157
"$ref": "#/definitions/Discouraged"
154158
}
155159
},
156-
"required": ["name", "description", "description_html", "spec", "status"],
160+
"required": [
161+
"kind",
162+
"name",
163+
"description",
164+
"description_html",
165+
"spec",
166+
"status"
167+
],
157168
"additionalProperties": false
158169
},
159170
"GroupData": {
@@ -191,44 +202,30 @@
191202
"description": "A feature has permanently moved to exactly one other ID",
192203
"type": "object",
193204
"properties": {
194-
"redirect": {
195-
"type": "object",
196-
"properties": {
197-
"reason": {
198-
"const": "moved"
199-
},
200-
"target": {
201-
"description": "The new ID for this feature",
202-
"type": "string"
203-
}
204-
},
205-
"required": ["reason", "target"],
206-
"additionalProperties": false
205+
"kind": {
206+
"const": "moved"
207+
},
208+
"redirect_target": {
209+
"description": "The new ID for this feature",
210+
"type": "string"
207211
}
208212
},
209-
"required": ["redirect"],
213+
"required": ["kind", "redirect_target"],
210214
"additionalProperties": false
211215
},
212216
"FeatureSplitData": {
213217
"description": "A feature has split into two or more other features",
214218
"type": "object",
215219
"properties": {
216-
"redirect": {
217-
"type": "object",
218-
"properties": {
219-
"reason": {
220-
"const": "split"
221-
},
222-
"targets": {
223-
"description": "The new IDs for this feature",
224-
"$ref": "#/definitions/Strings"
225-
}
226-
},
227-
"required": ["reason", "targets"],
228-
"additionalProperties": false
220+
"kind": {
221+
"const": "split"
222+
},
223+
"redirect_targets": {
224+
"description": "The new IDs for this feature",
225+
"$ref": "#/definitions/Strings"
229226
}
230227
},
231-
"required": ["redirect"],
228+
"required": ["kind", "redirect_targets"],
232229
"additionalProperties": false
233230
},
234231
"Status": {

scripts/build.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { basename } from "node:path";
77
import winston from "winston";
88
import yargs from "yargs";
99
import * as data from "../index.js";
10+
import { isOrdinaryFeatureData } from "../type-guards.js";
1011
import { validate } from "./validate.js";
1112

1213
const logger = winston.createLogger({
@@ -68,7 +69,7 @@ function buildPackage() {
6869

6970
function buildExtendedJSON() {
7071
for (const [id, featureData] of Object.entries(data.features)) {
71-
if ("redirect" in featureData) {
72+
if (!isOrdinaryFeatureData(featureData)) {
7273
continue;
7374
}
7475

scripts/dist.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@ import { isDeepStrictEqual } from "node:util";
1414
import winston from "winston";
1515
import YAML, { Document, Scalar, YAMLSeq } from "yaml";
1616
import yargs from "yargs";
17-
import { isRedirectData } from "../type-guards";
18-
import {
19-
FeatureMovedData,
20-
FeatureRedirectData,
21-
FeatureSplitData,
22-
} from "../types";
17+
import type { FeatureData, FeatureMovedData, FeatureSplitData } from "../types";
2318

2419
const compat = new Compat();
2520

@@ -189,7 +184,7 @@ function compareStatus(a: SupportStatus, b: SupportStatus) {
189184

190185
function toRedirectDist(
191186
id: string,
192-
source: FeatureMovedData | FeatureRedirectData,
187+
source: FeatureMovedData | FeatureSplitData,
193188
): YAML.Document {
194189
const dist = new Document({});
195190

@@ -199,17 +194,20 @@ function toRedirectDist(
199194
`Do not edit this file.`,
200195
];
201196

202-
switch (source.redirect.reason) {
197+
const { kind } = source;
198+
switch (kind) {
203199
case "moved":
204200
comment.push(
205-
`The data for this feature has moved to ${(source as FeatureMovedData).redirect.target}.yml`,
201+
`The data for this feature has moved to ${source.redirect_target}.yml`,
206202
);
207203
break;
208204
case "split":
209-
const targets = (source as FeatureSplitData).redirect.targets;
210205
comment.push(`The data for this feature has moved to:`);
211-
comment.push(...targets.map((to) => ` - ${to}.yml`));
206+
comment.push(...source.redirect_targets.map((dest) => ` - ${dest}.yml`));
212207
break;
208+
default:
209+
source satisfies never;
210+
throw new Error(`Unhandled feature kind ${kind}}`);
213211
}
214212

215213
dist.commentBefore = comment.map((line) => ` ${line}`).join("\n");
@@ -228,9 +226,10 @@ function toDist(sourcePath: string): YAML.Document {
228226
const source = YAML.parse(fs.readFileSync(sourcePath, { encoding: "utf-8" }));
229227
const { name: id } = path.parse(sourcePath);
230228

231-
if (isRedirectData(source)) {
229+
if ("redirect_target" in source || "redirect_targets" in source) {
232230
return toRedirectDist(id, source);
233231
}
232+
source as Partial<FeatureData>;
234233

235234
// Collect tagged compat features. A `compat_features` list in the source
236235
// takes precedence, but can be removed if it matches the tagged features.
@@ -340,7 +339,7 @@ function toDist(sourcePath: string): YAML.Document {
340339
}
341340

342341
// Assemble and return the dist YAML.
343-
const dist = new Document({});
342+
const dist = new Document({ kind: "feature" });
344343

345344
dist.commentBefore = [
346345
`Generated from: ${id}.yml`,

scripts/specs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
33
import webSpecs from 'web-specs' with { type: 'json' };
44

55
import { features } from '../index.js';
6-
import { isRedirectData } from "../type-guards.js";
6+
import { isOrdinaryFeatureData } from "../type-guards.js";
77

88
// Specs needs to be in "good standing". Nightly URLs are used if available,
99
// otherwise the snapshot/versioned URL is used. See browser-specs/web-specs
@@ -192,7 +192,7 @@ for (const [allowedUrl, message] of defaultAllowlist) {
192192
}
193193

194194
for (const [id, data] of Object.entries(features)) {
195-
if (isRedirectData(data)) {
195+
if (!isOrdinaryFeatureData(data)) {
196196
continue;
197197
}
198198

type-guards.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import type { FeatureData, FeatureRedirectData } from "./types";
1+
import type { FeatureData, FeatureMovedData, FeatureSplitData } from "./types";
22

33
export function isOrdinaryFeatureData(x: unknown): x is FeatureData {
4-
return typeof x === "object" && "name" in x;
4+
return typeof x === "object" && "kind" in x && x.kind === "feature";
55
}
66

7-
export function isRedirectData(x: unknown): x is FeatureRedirectData {
8-
return typeof x === "object" && "redirect" in x;
7+
export function isSplit(x: unknown): x is FeatureSplitData {
8+
return typeof x === "object" && "kind" in x && x.kind === "moved";
9+
}
10+
11+
export function isMoved(x: unknown): x is FeatureMovedData {
12+
return typeof x === "object" && "kind" in x && x.kind === "moved";
913
}

types.quicktype.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export interface FeatureData {
9090
* Group identifier(s)
9191
*/
9292
group?: string[] | string;
93+
kind: Kind;
9394
/**
9495
* Short name
9596
*/
@@ -107,7 +108,14 @@ export interface FeatureData {
107108
* that status
108109
*/
109110
status?: StatusHeadline;
110-
redirect?: Redirect;
111+
/**
112+
* The new ID for this feature
113+
*/
114+
redirect_target?: string;
115+
/**
116+
* The new IDs for this feature
117+
*/
118+
redirect_targets?: string[];
111119
}
112120

113121
/**
@@ -125,19 +133,7 @@ export interface Discouraged {
125133
alternatives?: string[];
126134
}
127135

128-
export interface Redirect {
129-
reason: Reason;
130-
/**
131-
* The new ID for this feature
132-
*/
133-
target?: string;
134-
/**
135-
* The new IDs for this feature
136-
*/
137-
targets?: string[];
138-
}
139-
140-
export type Reason = "moved" | "split";
136+
export type Kind = "feature" | "moved" | "split";
141137

142138
/**
143139
* Whether a feature is considered a "Baseline" web platform feature and when it achieved

0 commit comments

Comments
 (0)