Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple BCD queries #6004

Closed
wants to merge 6 commits into from
Closed
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
2 changes: 1 addition & 1 deletion build/bcd-urls.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function normalizeBCDURLs(doc, options) {

function getPathFromAbsoluteURL(absURL) {
// Because the compat data is mutated out of @mdn/browser-compat-data,
// through our `packageBCD` function, it's very possible that
// through our `queryBCD` function, it's very possible that
// the `doc[i].type === 'browser_compatibility` has already been
// processed.
if (!absURL.includes("://")) {
Expand Down
279 changes: 147 additions & 132 deletions build/document-extractor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const cheerio = require("cheerio");
const { packageBCD } = require("./resolve-bcd");
const {
queryBCD,
BCD_BROWSER_RELEASES,
BCD_BROWSERS,
} = require("./resolve-bcd");
const specs = require("browser-specs");
const web = require("../kumascript/src/api/web.js");

Expand Down Expand Up @@ -344,68 +348,96 @@ function _addSingleSpecialSection($) {
return proseSections;
}
const query = dataQuery.replace(/^bcd:/, "");
const { browsers, data } = packageBCD(query);
const data = _getBCD(query);

if (specialSectionType === "browser_compatibility") {
if (data === undefined) {
return [
{
type: specialSectionType,
value: {
title,
id,
isH3,
data: null,
query,
browsers: null,
},
const browsers = BCD_BROWSERS;

return [
{
type: "browser_compatibility",
value: {
title,
id,
isH3,
data,
query,
browsers,
},
];
}
return _buildSpecialBCDSection();
},
];
} else if (specialSectionType === "specifications") {
if (data === undefined && specURLsString === "") {
return [
{
type: specialSectionType,
value: {
title,
id,
isH3,
query,
specifications: [],
},
const specifications = _getSpecifications(specURLsString, data);

return [
{
type: "specifications",
value: {
title,
id,
isH3,
specifications,
query,
},
];
}
return _buildSpecialSpecSection();
},
];
}

throw new Error(`Unrecognized special section type '${specialSectionType}'`);

function _buildSpecialBCDSection() {
// First extract a map of all release data, keyed by (normalized) browser
// name and the versions.
// You'll have a map that looks like this:
//
// 'chrome_android': {
// '28': {
// release_date: '2012-06-01',
// release_notes: '...',
// ...
//
// The reason we extract this to a locally scoped map, is so we can
// use it to augment the `__compat` blocks for the latest version
// when (if known) it was added.
const browserReleaseData = new Map();
for (const [name, browser] of Object.entries(browsers)) {
const releaseData = new Map();
for (const [version, data] of Object.entries(browser.releases || [])) {
if (data) {
releaseData.set(version, data);
}
}
browserReleaseData.set(name, releaseData);
/**
* @param {string} queryString
* @returns {object}
*/
function _getBCD(queryString) {
const data = _getBCDData(queryString);

_processBCDData(data);

return data;
}

/**
* @param {string} queryString
* @returns {object}
*/
function _getBCDData(queryString) {
const queries = queryString.split(",");

const data = queries.reduce((data, query) => {
const dataForQuery = queryBCD(query);

const breadcrumbs = query.split(".");
const name = breadcrumbs[breadcrumbs.length - 1];

return {
...data,
[name]: dataForQuery,
};
}, {});

const keys = Object.keys(data);

switch (keys.length) {
case 0:
return null;

case 1:
return data[keys[0]];

default:
return data;
}
}

/**
*
* @param {object|undefined} data
* @returns {object|undefined}
*/
function _processBCDData(data) {
if (!data) {
return data;
}

for (const [key, compat] of Object.entries(data)) {
Expand Down Expand Up @@ -434,11 +466,10 @@ function _addSingleSpecialSection($) {
infoEntry.version_added.startsWith("≤")
? infoEntry.version_added.slice(1)
: infoEntry.version_added;
if (browserReleaseData.has(browser)) {
if (browserReleaseData.get(browser).has(added)) {
infoEntry.release_date = browserReleaseData
.get(browser)
.get(added).release_date;
if (BCD_BROWSER_RELEASES.has(browser)) {
if (BCD_BROWSER_RELEASES.get(browser).has(added)) {
infoEntry.release_date =
BCD_BROWSER_RELEASES.get(browser).get(added).release_date;
}
}
}
Expand All @@ -448,20 +479,6 @@ function _addSingleSpecialSection($) {
}
}
}

return [
{
type: "browser_compatibility",
value: {
title,
id,
isH3,
data,
query,
browsers,
},
},
];
}

/**
Expand Down Expand Up @@ -517,75 +534,73 @@ function _addSingleSpecialSection($) {

return version.split(".").map(Number);
}
}

function _buildSpecialSpecSection() {
// Collect spec URLs from a BCD feature, a 'spec-urls' value, or both;
// For a BCD feature, it can either be a string or an array of strings.
let specURLs = [];

if (data) {
// If 'data' is non-null, that means we have data for a BCD feature
// that we can extract spec URLs from.
for (const [key, compat] of Object.entries(data)) {
if (key === "__compat" && compat.spec_url) {
if (Array.isArray(compat.spec_url)) {
specURLs = compat.spec_url;
} else {
specURLs.push(compat.spec_url);
}
/**
* @param {string} specURLsString
* @param {object} data
* @returns
*/
function _getSpecifications(specURLsString, data) {
if (data === undefined && specURLsString === "") {
return [];
}

// Collect spec URLs from a BCD feature, a 'spec-urls' value, or both;
// For a BCD feature, it can either be a string or an array of strings.
let specURLs = [];

if (data) {
// If 'data' is non-null, that means we have data for a BCD feature
// that we can extract spec URLs from.
for (const [key, compat] of Object.entries(data)) {
if (key === "__compat" && compat.spec_url) {
if (Array.isArray(compat.spec_url)) {
specURLs = compat.spec_url;
} else {
specURLs.push(compat.spec_url);
}
}
}
}
Comment on lines +553 to +565
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (data) {
// If 'data' is non-null, that means we have data for a BCD feature
// that we can extract spec URLs from.
for (const [key, compat] of Object.entries(data)) {
if (key === "__compat" && compat.spec_url) {
if (Array.isArray(compat.spec_url)) {
specURLs = compat.spec_url;
} else {
specURLs.push(compat.spec_url);
}
}
}
}
if (data) {
// If 'data' is non-null, that means we have data for one or more BCD
// features that we can extract spec URLs from.
//
// If we’re processing data for just one feature, then the 'data'
// variable will have a __compat key. So we get the one spec_url value
// from that, and move on.
//
// (The value may contain data for subfeatures too — each subfeature
// with its own __compat key that may contain a spec_url — but in that
// case, for the purposes of the Specifications section, we don’t want
// to recurse through all the subfeatures to get those spec_url values;
// instead we only want the spec_url from the top-level __compat key.
if (data.__compat) {
const compat = data.__compat;
if (compat.spec_url) {
if (Array.isArray(compat.spec_url)) {
specURLs = compat.spec_url;
} else {
specURLs.push(compat.spec_url);
}
}
} else {
for (const block of Object.values(data)) {
// If we reach here, we’re processing data for two or more features
// and the 'data' variable will contain multiple blocks (objects) —
// one for each feature — each with its own __compat key
const compat = block.__compat;
if (compat.spec_url) {
if (Array.isArray(compat.spec_url)) {
specURLs = compat.spec_url;
} else {
specURLs.push(compat.spec_url);
}
}
}
}
}

Copy link
Member

Choose a reason for hiding this comment

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

Without this change, the code doesn’t correctly output the Specifications section when the browser-compat value contains multiple BCD queries.


if (specURLsString !== "") {
// If specURLsString is non-empty, then it has the string contents of
// the document’s 'spec-urls' frontmatter key: one or more URLs.
specURLs.push(...specURLsString.split(",").map((url) => url.trim()));
}
if (specURLsString !== "") {
// If specURLsString is non-empty, then it has the string contents of
// the document’s 'spec-urls' frontmatter key: one or more URLs.
specURLs.push(...specURLsString.split(",").map((url) => url.trim()));
}

// Use BCD specURLs to look up more specification data
// from the browser-specs package
const specifications = specURLs
.map((specURL) => {
const spec = specs.find(
(spec) =>
specURL.startsWith(spec.url) ||
specURL.startsWith(spec.nightly.url) ||
specURL.startsWith(spec.series.nightlyUrl)
// Use BCD specURLs to look up more specification data
// from the browser-specs package
const specifications = specURLs
.map((specURL) => {
const spec = specs.find(
(spec) =>
specURL.startsWith(spec.url) ||
specURL.startsWith(spec.nightly.url) ||
specURL.startsWith(spec.series.nightlyUrl)
);
const specificationsData = {
bcdSpecificationURL: specURL,
title: "Unknown specification",
};
if (spec) {
specificationsData.title = spec.title;
} else {
const specList = web.getJSONData("SpecData");
const titleFromSpecData = Object.keys(specList).find(
(key) => specList[key]["url"] === specURL.split("#")[0]
);
const specificationsData = {
bcdSpecificationURL: specURL,
title: "Unknown specification",
};
if (spec) {
specificationsData.title = spec.title;
} else {
const specList = web.getJSONData("SpecData");
const titleFromSpecData = Object.keys(specList).find(
(key) => specList[key]["url"] === specURL.split("#")[0]
);
if (titleFromSpecData) {
specificationsData.title = titleFromSpecData;
}
if (titleFromSpecData) {
specificationsData.title = titleFromSpecData;
}
}

return specificationsData;
})
.filter(Boolean);
return specificationsData;
})
.filter(Boolean);

return [
{
type: "specifications",
value: {
title,
id,
isH3,
specifications,
query,
},
},
];
}
return specifications;
}

function _addSectionProse($) {
Expand Down
4 changes: 2 additions & 2 deletions build/flaws/bad-bcd-queries.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { packageBCD } = require("../resolve-bcd");
const { queryBCD } = require("../resolve-bcd");

// Bad BCD queries are when the `<div class="bc-data">` tags have an
// ID (or even lack the `id` attribute) that don't match anything in the
Expand All @@ -14,7 +14,7 @@ function getBadBCDQueriesFlaws(doc, $) {
return "BCD table without an ID";
}
const query = dataQuery.replace(/^bcd:/, "");
return !packageBCD(query).data && `No BCD data for query: ${query}`;
return !queryBCD(query) && `No BCD data for query: ${query}`;
})
.get()
.filter((explanation) => !!explanation)
Expand Down
Loading