Created
March 18, 2019 16:49
-
-
Save foolip/dcffd93594d71a4cda304061d4b96942 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const assert = require('assert'); | |
const compareVersions = require('compare-versions'); | |
const fs = require('fs'); | |
const path = require('path'); | |
const uaParser = require('ua-parser-js'); | |
function findEntry(bcd, path) { | |
let keys = path.split('.'); | |
let entry = bcd; | |
while (entry && keys.length) { | |
entry = entry[keys.shift()]; | |
} | |
return entry; | |
} | |
function isDirectory(fp) { | |
try { | |
return fs.statSync(fp).isDirectory(); | |
} catch (e) { | |
return false; | |
} | |
} | |
// https://github.com/mdn/browser-compat-data/issues/3617 | |
function save(bcd, bcdDir) { | |
function processObject(object, keypath) { | |
if (keypath.length && keypath[0] !== 'css') { | |
return; | |
} | |
for (const [key, value] of Object.entries(object)) { | |
const candidate = path.join(bcdDir, ...keypath, key); | |
if (isDirectory(candidate)) { | |
// If the path is a directory, recurse. | |
processObject(value, keypath.concat(key)); | |
} else { | |
// Otherwise, write data to file. | |
const filepath = `${candidate}.json`; | |
// Add wrapping objects with keys as in keypath. | |
let wrappedValue = value; | |
const keys = keypath.concat(key).reverse(); | |
for (const key of keys) { | |
const wrapper = {}; | |
wrapper[key] = wrappedValue; | |
wrappedValue = wrapper; | |
} | |
const json = JSON.stringify(wrappedValue, null, ' ') + '\n'; | |
fs.writeFileSync(filepath, json); | |
} | |
} | |
} | |
processObject(bcd, []); | |
} | |
function getBrowserAndVersion(userAgent) { | |
const ua = uaParser(userAgent); | |
let browser = ua.browser.name.toLowerCase(); | |
let version = String(ua.browser.major); | |
let os = ua.os.name.toLowerCase(); | |
if (browser === 'mobile safari') { | |
browser = 'safari_ios'; | |
} | |
if (os === 'android') { | |
browser += '_android'; | |
} | |
return [browser, version]; | |
} | |
// Get support map from BCD path to test result(null/true/false) for a single | |
// report. | |
function getSupportMap(report) { | |
// Transform `report` to map from test name (BCD path) to array of results. | |
const testMap = new Map; | |
for (const [url, results] of Object.entries(report.results)) { | |
if (url !== 'http://mdn-bcd-collector.appspot.com/css/properties/in-style.html') { | |
// work around https://github.com/foolip/mdn-bcd-collector/issues/3 | |
continue; | |
} | |
for (const test of results) { | |
const tests = testMap.get(test.name) || []; | |
tests.push({url, result: test.result}); | |
testMap.set(test.name, tests); | |
} | |
} | |
if (testMap.size === 0) { | |
throw new Error('No results!'); | |
} | |
// Transform `testMap` to map from test name (BCD path) to flattened support. | |
const supportMap = new Map; | |
for (const [name, results] of testMap.entries()) { | |
let supported = null; | |
for (const {url, result} of results) { | |
if (result === null) { | |
continue; | |
} | |
if (supported === null) { | |
supported = result; | |
continue; | |
} | |
if (supported !== result) { | |
// This will happen for [SecureContext] APIs. | |
//console.log(`Contradictory results for ${name}: ${JSON.stringify(results, null, ' ')}`); | |
supported = true; | |
break; | |
} | |
} | |
supportMap.set(name, supported); | |
} | |
return supportMap; | |
} | |
// Load all reports and build a map from BCD path to browser + version | |
// and test result (null/true/false) for that version. | |
function getSupportMatrix(bcd, reportFiles) { | |
const supportMatrix = new Map; | |
for (const reportFile of reportFiles) { | |
const report = JSON.parse(fs.readFileSync(reportFile)); | |
const [browser, version] = getBrowserAndVersion(report.userAgent); | |
if (!(browser in bcd.browsers)) { | |
console.warn(`Ignoring unknown browser: ${browser}`); | |
continue; | |
} | |
if (!(version in bcd.browsers[browser].releases)) { | |
console.warn(`Ignoring unknown browser version: ${browser} ${version}`); | |
continue; | |
} | |
const supportMap = getSupportMap(report); | |
// Merge `supportMap` into `supportMatrix`. | |
for (const [name, supported] of supportMap.entries()) { | |
let browserMap = supportMatrix.get(name); | |
if (!browserMap) { | |
browserMap = new Map; | |
supportMatrix.set(name, browserMap); | |
} | |
let versionMap = browserMap.get(browser); | |
if (!versionMap) { | |
versionMap = new Map; | |
browserMap.set(browser, versionMap); | |
} | |
versionMap.set(version, supported); | |
} | |
} | |
return supportMatrix; | |
} | |
function inferSupportStatements(versionMap) { | |
const versions = Array.from(versionMap.keys()); | |
// TODO: sort per browsers.json? | |
versions.sort(compareVersions); | |
const statements = []; | |
for (const [i, version] of versions.entries()) { | |
const supported = versionMap.get(version); | |
// TODO: supported null isn't handled properly | |
assert(typeof supported === 'boolean'); | |
if (i === 0) { | |
// TODO: exact version if it's the first version in browser.json | |
statements.push({version_added: supported}); | |
continue; | |
} | |
const lastStatement = statements[statements.length - 1]; | |
if (supported === true) { | |
if (!lastStatement.version_added) { | |
lastStatement.version_added = version; | |
} else if (lastStatement.version_removed) { | |
// added back again | |
statements.push({version_added: version}); | |
} else { | |
// leave `lastStatement.version_added` as is | |
} | |
} else if (supported === false) { | |
if (lastStatement.version_added) { | |
lastStatement.version_removed = version; | |
} | |
} | |
} | |
return statements; | |
} | |
function update(bcd, supportMatrix) { | |
for (const [path, browserMap] of supportMatrix.entries()) { | |
if (!path.startsWith('css.properties.')) { | |
continue; | |
} | |
const entry = findEntry(bcd, path); | |
if (!entry || !entry.__compat) { | |
continue; | |
} | |
for (const [browser, versionMap] of browserMap.entries()) { | |
const inferedStatments = inferSupportStatements(versionMap); | |
if (inferedStatments.length !== 1) { | |
// TODO: handle more complicated scenarios | |
continue; | |
} | |
let supportStatement = entry.__compat.support[browser]; | |
if (!supportStatement) { | |
//console.log(path); | |
continue; | |
} | |
if (!Array.isArray(supportStatement)) { | |
supportStatement = [supportStatement]; | |
} | |
const simpleStatement = supportStatement.find((statement) => { | |
return Object.keys(statement).length === 1; | |
}); | |
if (!simpleStatement) { | |
//console.log(path); | |
continue; | |
} | |
if (typeof simpleStatement.version_added !== 'string') { | |
simpleStatement.version_added = inferedStatments[0].version_added; | |
if (inferedStatments[0].version_removed) { | |
simpleStatement.version_removed = inferedStatments[0].version_removed | |
} | |
} | |
} | |
} | |
} | |
function main(reportFiles) { | |
const BCD_DIR = `../browser-compat-data`; | |
const bcd = require(BCD_DIR); | |
const supportMatrix = getSupportMatrix(bcd, reportFiles); | |
update(bcd, supportMatrix); | |
save(bcd, BCD_DIR); | |
} | |
main(process.argv.slice(2)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment