Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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];
}
Comment on lines +81 to +95
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens in a few places, so I consolidated it.


/**
* 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),
);
}
Comment on lines +101 to +105
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This too happens in a few places, now consolidated.


/**
* 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.`,
);
Comment on lines +117 to -100
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also happens in a couple of places, so I consolidated that too—it's at the end. I have no idea how to order things. 😬

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order is hard. End is fine.

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