Skip to content

Commit

Permalink
compute-baseline: Add per-release support checks to Features (#1339)
Browse files Browse the repository at this point in the history
Co-authored-by: Philip Jägenstedt <[email protected]>
  • Loading branch information
ddbeck and foolip authored Jul 11, 2024
1 parent c64c81a commit ca0769d
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 54 deletions.
12 changes: 12 additions & 0 deletions packages/compute-baseline/src/baseline/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ export function support(feature: Feature, browsers: Browser[]): Support {
const support: Support = new Map();
for (const b of browsers) {
const releases = feature.supportedBy({ only: [b] });
// TODO:
// let lastInitial: Release | undefined;
// let lastInitialBoundary: "" | "≤" | undefined;

// const reverseChronological = b.releases.slice().reverse();
// let previousRelease: string | undefined;
// for (let index = b.releases.length - 1; index >= 0; index--) {
// const release = reverseChronological[index] as Release;
// const current = feature.flatSupportedIn(release);

// // Check if current has changed, etc.
// }

const unqualifiedReleases = [];
const qualifiedReleases = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";

import { feature } from "./feature.js";
import { browser } from "./index.js";
import { browser, Compat } from "./index.js";

describe("features", function () {
describe("feature()", function () {
Expand Down Expand Up @@ -69,5 +69,81 @@ describe("features", function () {
assert(f.tags.length === 0);
});
});

describe("flatSupportedIn()", function () {
it("returns true for unqualified support", function () {
const cr = browser("chrome");
assert.equal(feature("api.Attr").supportedIn(cr.version("100")), true);
});

it("returns false for qualified support", function () {
const cr = browser("chrome");
assert.equal(
feature("css.properties.line-clamp").supportedIn(cr.version("100")),
false,
); // { version_added: "…", "prefix": "-webkit-" }
});

it("returns false for wholly unsupported", function () {
const fx = browser("firefox");
assert.equal(
feature("api.Accelerometer").supportedIn(fx.current()),
false,
); // { version_added: false }
});

it("returns null for unknown support", function () {
const edge = browser("edge");
const f = feature("svg.elements.animate"); // { version_added: "≤79" }

assert.equal(f.supportedIn(edge.version("12")), null);
assert.equal(f.supportedIn(edge.version("79")), true);
assert.equal(f.supportedIn(edge.version("80")), true);
});
});

describe("supportedIn()", function () {
it("returns support for features supported with and without qualification", function () {
const compat = new Compat();
const cr = browser("chrome");

// { version_added: "…" }
const bgColor = feature(
"css.properties.background-color",
).supportedInDetails(cr.version("100"));
assert.equal(bgColor.length, 1);
assert.equal(bgColor[0]?.supported, true);

// { version_added: "…", prefix: "-webkit-" }
const lineClamp = feature(
"css.properties.line-clamp",
).supportedInDetails(cr.version("100"));
assert.equal(lineClamp.length, 1);
assert.equal(lineClamp[0]?.supported, true);
assert.equal(lineClamp[0]?.qualifications?.prefix, "-webkit-");
});

it("returns mixed results for (un)prefixed features", function () {
const fx = browser("firefox");
const actual = feature(
"css.types.image.gradient.repeating-linear-gradient",
).supportedInDetails(fx.version("100"));
assert.equal(actual.length, 3); // unprefixed, -moz-, and -webkit-
assert(actual.some((s) => s.supported && "qualifications" in s));
assert(actual.some((s) => s.supported && !("qualifications" in s)));
});

it("returns unknown support before version ranges", function () {
const edge = browser("edge");
const f = feature("svg.elements.animate");
const unknown = f.supportedInDetails(edge.version("12"));
assert.equal(unknown.length, 1);
assert.equal(unknown[0]?.supported, null);

const known = f.supportedInDetails(edge.version("79"));
assert.equal(known.length, 1);
assert.equal(known[0]?.supported, true);
});
});
});
});
97 changes: 82 additions & 15 deletions packages/compute-baseline/src/browser-compat-data/feature.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Identifier } from "@mdn/browser-compat-data";

import { Identifier, SimpleSupportStatement } from "@mdn/browser-compat-data";
import { Browser } from "./browser.js";
import { Compat, defaultCompat } from "./compat.js";
import { Release } from "./release.js";
import {
Qualifications,
RealSupportStatement,
statement,
Supported,
SupportStatement,
UnknownSupport,
Unsupported,
} from "./supportStatements.js";
import { isFeatureData } from "./typeUtils.js";

Expand Down Expand Up @@ -72,33 +75,84 @@ export class Feature {
return this.data.__compat?.status?.standard_track ?? false;
}

_supportedBy(
browser: Browser,
): { release: Release; qualifications?: Qualifications }[] {
/**
* Get this feature's support statement data, for a given browser.
*/
rawSupportStatements(browser: Browser): SimpleSupportStatement[] {
const support = this.data?.__compat?.support;
if (support === undefined) {
throw Error("This feature contains no __compat object.");
throw new Error("This feature contains no __compat object.");
}

const statementOrStatements = support[browser.id];

if (statementOrStatements === undefined) {
throw Error(`${this} contains no support data for ${browser.name}`);
throw new Error(`${this} contains no support data for ${browser.name}`);
}

const rawStatements = Array.isArray(statementOrStatements)
return Array.isArray(statementOrStatements)
? statementOrStatements
: [statementOrStatements];
}

/**
* Get this feature's `SupportStatement` or `RealSupportStatement` objects,
* for a given browser.
*/
supportStatements(browser: Browser): SupportStatement[] {
return this.rawSupportStatements(browser).map((raw) =>
statement(raw, browser, this),
);
}

/**
* Find out whether this feature's support data says that a given browser
* release is supported (with or without qualifications), unsupported, or
* unknown.
*/
supportedInDetails(
release: Release,
): (Supported | Unsupported | UnknownSupport)[] {
const result = [];
for (const raw of rawStatements) {
const s = statement(raw, browser, this);
for (const s of this.supportStatements(release.browser)) {
this.assertRealSupportStatement(s, release.browser);

if (!(s instanceof RealSupportStatement)) {
throw Error(
`${this.id} contains non-real values for ${browser.name}. Cannot expand support.`,
);
result.push(s.supportedInDetails(release));
}
return result;
}

/**
* Find out whether this feature's support data says that a given browser
* release is supported (`true`), unsupported (`false`), or unknown (`null`).
* Note that this ignores qualifications such as partial implementations,
* prefixes, alternative names, and flags.
*/
supportedIn(release: Release): boolean | null {
let unknown = false;
for (const s of this.supportStatements(release.browser)) {
this.assertRealSupportStatement(s, release.browser);

const supported = s.supportedInDetails(release);
if (supported.supported && !supported.qualifications) {
return true;
}

if (supported.supported === null) {
unknown = true;
}
}
if (unknown) {
return null;
}
return false;
}

_supportedBy(
browser: Browser,
): { release: Release; qualifications?: Qualifications }[] {
const result = [];
for (const s of this.supportStatements(browser)) {
this.assertRealSupportStatement(s, browser);

result.push(...s.supportedBy());
}
Expand All @@ -121,4 +175,17 @@ export class Feature {
}
return result;
}

/**
* Throws when a support statement contains non-real values.
*/
assertRealSupportStatement(
statement: SupportStatement,
browser: Browser,
): asserts statement is RealSupportStatement {
if (!(statement instanceof RealSupportStatement))
throw new Error(
`${this.id} contains non-real values for ${browser.name}. Cannot expand support.`,
);
}
}
Loading

0 comments on commit ca0769d

Please sign in to comment.