forked from mdn/browser-compat-data
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Copy ddbeck/bcd-utils into tree From https://github.com/ddbeck/bcd-utils/tree/4f2499c81cd3c683b034f53e3176e9289460379b/src * Apply this repo's prettier config * Expand tests to cover any *.test.js file * Make BCD require wrapper work * Provide more descriptive text on one of the tests * Make test of visit vs walk comprehensive * Fix typo * Make iterSupport return more lifelike data for non-existent browsers * Add missing `new` keyword * Switch to strict assertion mode https://nodejs.org/api/assert.html#assert_strict_assertion_mode * Add support for optional `data` for all utils * Remove wrapper around BCD * Rework signature for `visit()` * From walk, don't yield undefined values * Unbreak tests * Document in-repo the experimental status of utilities
- Loading branch information
Showing
14 changed files
with
404 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,5 @@ schemas | |
CODE_OF_CONDUCT.md | ||
CONTRIBUTING.md | ||
GOVERNANCE.md | ||
utils | ||
scripts |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Experimental utilities | ||
|
||
These are experimental utilities for working with compat data, which may inform future work on the project's API. The modules in this directory are NOT included in the released `@mdn/browser-compat-data` package. They are NOT covered by the [_Semantic versioning policy_](../README.md#Semantic-versioning-policy); backwards compatibility is not assured. With those limitations in mind, you're free to experiment with these utilities and [provide feedback](https://github.com/mdn/browser-compat-data/issues/new/choose). |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
const iterSupport = require('./iter-support'); | ||
const query = require('./query'); | ||
const { walk } = require('./walk'); | ||
const visit = require('./visit'); | ||
|
||
module.exports = { | ||
iterSupport, | ||
query, | ||
walk, | ||
visit, | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
function iterSupport(compat, browser) { | ||
if (browser in compat.support) { | ||
const data = compat.support[browser]; | ||
return Array.isArray(data) ? data : [data]; | ||
} | ||
|
||
return [{ version_added: null }]; | ||
} | ||
|
||
module.exports = iterSupport; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
const assert = require('assert').strict; | ||
|
||
const iterSupport = require('./iter-support'); | ||
|
||
describe('iterSupport()', function () { | ||
it('returns a `"version_added": null` support statement for non-existent browsers', function () { | ||
assert.deepEqual(iterSupport({ support: { firefox: [] } }, 'chrome'), [ | ||
{ version_added: null }, | ||
]); | ||
}); | ||
|
||
it('returns a single support statement as an array', function () { | ||
assert.deepEqual( | ||
iterSupport({ support: { firefox: { version_added: true } } }, 'firefox'), | ||
[{ version_added: true }], | ||
); | ||
}); | ||
|
||
it('returns an array of support statements as an array', function () { | ||
const compatObj = { | ||
support: { firefox: [{ version_added: true }, { version_added: '1' }] }, | ||
}; | ||
const support = [{ version_added: true }, { version_added: '1' }]; | ||
|
||
assert.deepEqual(iterSupport(compatObj, 'firefox'), support); | ||
}); | ||
}); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
const bcd = require('..'); | ||
|
||
/** | ||
* Get a subtree of compat data. | ||
* | ||
* @param {string} path Dotted path to a given feature (e.g., `css.properties.background`) | ||
* @param {*} [data=bcd] A tree to query. All of BCD, by default. | ||
* @returns {*} A BCD subtree | ||
* @throws {ReferenceError} For invalid identifiers | ||
*/ | ||
function query(path, data = bcd) { | ||
const pathElements = path.split('.'); | ||
let lookup = data; | ||
while (pathElements.length) { | ||
const next = pathElements.shift(); | ||
lookup = lookup[next]; | ||
if (lookup === undefined) { | ||
throw new ReferenceError( | ||
`${path} is not a valid tree identifier (failed at '${next}')`, | ||
); | ||
} | ||
} | ||
return lookup; | ||
} | ||
|
||
module.exports = query; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
const assert = require('assert').strict; | ||
|
||
const query = require('./query'); | ||
|
||
describe('query()', function () { | ||
describe('should throw on non-existent features', function () { | ||
assert.throws(() => query('nonExistentNameSpace'), ReferenceError); | ||
assert.throws(() => query('api.NonExistentFeature'), ReferenceError); | ||
assert.throws( | ||
() => query('api.NonExistentFeature.subFeature'), | ||
ReferenceError, | ||
); | ||
}); | ||
|
||
it('should return the expected point in the tree (namespace)', function () { | ||
const obj = query('css'); | ||
|
||
assert.ok(!('__compat' in obj)); | ||
assert.ok('properties' in obj); | ||
assert.ok('at-rules' in obj); | ||
}); | ||
|
||
it('should return the expected point in the tree (feature)', function () { | ||
const obj = query('api.HTMLAnchorElement.href'); | ||
|
||
assert.ok('support' in obj.__compat); | ||
assert.ok('status' in obj.__compat); | ||
assert.equal( | ||
'https://developer.mozilla.org/docs/Web/API/HTMLAnchorElement/href', | ||
obj.__compat.mdn_url, | ||
); | ||
}); | ||
|
||
it('should return the expected point in the tree (feature with children)', function () { | ||
const obj = query('api.HTMLAnchorElement'); | ||
|
||
assert.ok('__compat' in obj); | ||
assert.ok('charset' in obj); | ||
assert.ok('href' in obj); | ||
}); | ||
}); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
const bcd = require('..'); | ||
const query = require('./query'); | ||
const { descendantKeys, joinPath, isFeature } = require('./walkingUtils'); | ||
|
||
const BREAK = Symbol('break'); | ||
const CONTINUE = Symbol('continue'); | ||
|
||
function visit(visitor, options = {}) { | ||
const { entryPoint, data } = options; | ||
const test = options.test !== undefined ? options.test : () => true; | ||
|
||
const tree = entryPoint === undefined ? bcd : query(entryPoint, data); | ||
|
||
let outcome; | ||
if (isFeature(tree) && test(entryPoint, tree.__compat)) { | ||
outcome = visitor(entryPoint, tree.__compat); | ||
} | ||
if (outcome === BREAK) { | ||
return outcome; | ||
} | ||
if (outcome !== CONTINUE) { | ||
for (const key of descendantKeys(tree)) { | ||
const suboutcome = visit(visitor, { | ||
entryPoint: joinPath(entryPoint, key), | ||
test, | ||
data, | ||
}); | ||
if (suboutcome === BREAK) { | ||
return suboutcome; | ||
} | ||
} | ||
} | ||
return outcome; | ||
} | ||
|
||
visit.BREAK = BREAK; | ||
visit.CONTINUE = CONTINUE; | ||
|
||
module.exports = visit; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
const assert = require('assert').strict; | ||
|
||
const visit = require('./visit'); | ||
const { walk } = require('./walk'); | ||
|
||
describe('visit()', function () { | ||
it('runs the function on all features if no other entry point is specified', function () { | ||
const walker = walk(); | ||
visit(visitorPath => { | ||
assert.equal(visitorPath, walker.next().value.path); | ||
}); | ||
}); | ||
|
||
it('skips features not selected by testFn', function () { | ||
const hits = new Set(); | ||
const misses = new Set(); | ||
visit( | ||
path => { | ||
hits.add(path); | ||
}, | ||
{ | ||
entryPoint: 'css', | ||
test(path) { | ||
if (path.includes('at-rules')) { | ||
return true; | ||
} | ||
misses.add(path); | ||
return false; | ||
}, | ||
}, | ||
); | ||
|
||
for (const miss in misses) { | ||
assert.ok(!hits.has(miss)); | ||
} | ||
}); | ||
|
||
it('visitorFn can break iteration', function () { | ||
visit(path => { | ||
if (path.startsWith('css')) { | ||
return visit.BREAK; | ||
} | ||
if (path.startsWith('html')) { | ||
assert.fail( | ||
`visitorFn should never be invoked after the css tree. Reached ${path}`, | ||
); | ||
} | ||
}); | ||
}); | ||
|
||
it('visitorFn can skip traversal of children', function () { | ||
visit(path => { | ||
if (path === 'css.at-rules.counter-style') { | ||
return visit.CONTINUE; | ||
} | ||
if (path.startsWith('css.at-rules-counter-style.')) { | ||
assert.fail( | ||
`visitorFn should never reach a child of counter-style. Reached ${path}`, | ||
); | ||
} | ||
}); | ||
}); | ||
}); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
const bcd = require('..'); | ||
const { isBrowser, descendantKeys, joinPath } = require('./walkingUtils'); | ||
const query = require('./query'); | ||
|
||
function* lowLevelWalk(data = bcd, path, depth = Infinity) { | ||
if (path !== undefined) { | ||
const next = { | ||
path, | ||
data, | ||
}; | ||
|
||
if (isBrowser(data)) { | ||
next.browser = data; | ||
} else if (data.__compat !== undefined) { | ||
next.compat = data.__compat; | ||
} | ||
yield next; | ||
} | ||
|
||
if (depth > 0) { | ||
for (const key of descendantKeys(data)) { | ||
yield* lowLevelWalk(data[key], joinPath(path, key), depth - 1); | ||
} | ||
} | ||
} | ||
|
||
function* walk(entryPoints, data = bcd) { | ||
const walkers = []; | ||
|
||
if (entryPoints === undefined) { | ||
walkers.push(lowLevelWalk(data)); | ||
} else { | ||
entryPoints = Array.isArray(entryPoints) ? entryPoints : [entryPoints]; | ||
walkers.push( | ||
...entryPoints.map(entryPoint => | ||
lowLevelWalk(query(entryPoint, data), entryPoint), | ||
), | ||
); | ||
} | ||
|
||
for (const walker of walkers) { | ||
for (const step of walker) { | ||
if (step.compat) { | ||
yield step; | ||
} | ||
} | ||
} | ||
} | ||
|
||
module.exports = { walk, lowLevelWalk }; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
const assert = require('assert').strict; | ||
|
||
const bcd = require('..'); | ||
const { walk } = require('./index'); | ||
const { lowLevelWalk } = require('./walk'); | ||
|
||
describe('lowLevelWalk()', function () { | ||
it('visits every top-level tree', function () { | ||
const expectedPaths = [ | ||
'api', | ||
'browsers', | ||
'css', | ||
'html', | ||
'http', | ||
'javascript', | ||
'mathml', | ||
'svg', | ||
'webdriver', | ||
'webextensions', | ||
'xpath', | ||
'xslt', | ||
]; | ||
|
||
const steps = Array.from(lowLevelWalk(undefined, undefined, 1)); | ||
const paths = steps.map(step => step.path); | ||
assert.equal(steps.length, expectedPaths.length); | ||
assert.deepEqual(paths, expectedPaths); | ||
}); | ||
it('visits every point in the tree', function () { | ||
const paths = Array.from(lowLevelWalk()).map(step => step.path); | ||
assert.ok(paths.length > 13000); | ||
}); | ||
}); | ||
|
||
describe('walk()', function () { | ||
it('should visit deeply nested features', function () { | ||
let results = Array.from(walk('html')).map(feature => feature.path); | ||
assert.ok(results.includes('html.elements.a.href.href_top')); | ||
}); | ||
|
||
it('should walk a single tree', function () { | ||
let results = Array.from(walk('api.Notification')); | ||
assert.equal(results.length, 27); | ||
assert.equal(results[0].path, 'api.Notification'); | ||
assert.equal(results[1].path, 'api.Notification.Notification'); | ||
}); | ||
|
||
it('should walk multiple trees', function () { | ||
let results = Array.from( | ||
walk(['api.Notification', 'css.properties.color']), | ||
); | ||
assert.equal(results.length, 28); | ||
assert.equal(results[0].path, 'api.Notification'); | ||
assert.equal(results[results.length - 1].path, 'css.properties.color'); | ||
}); | ||
|
||
it('should yield every feature by default', function () { | ||
const featureCountFromString = JSON.stringify(bcd, undefined, 2) | ||
.split('\n') | ||
.filter(line => line.includes('__compat')).length; | ||
const featureCountFromWalk = Array.from(walk()).length; | ||
|
||
assert.equal(featureCountFromString, featureCountFromWalk); | ||
}); | ||
}); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
function joinPath() { | ||
return Array.from(arguments).filter(Boolean).join('.'); | ||
} | ||
|
||
function isFeature(obj) { | ||
return '__compat' in obj; | ||
} | ||
function isBrowser(obj) { | ||
return 'name' in obj && 'releases' in obj; | ||
} | ||
|
||
function descendantKeys(data) { | ||
if (isFeature(data)) { | ||
return Object.keys(data).filter(key => key !== '__compat'); | ||
} | ||
|
||
if (isBrowser(data)) { | ||
// Browsers never have independently meaningful descendants | ||
return []; | ||
} | ||
|
||
return Object.keys(data); | ||
} | ||
|
||
module.exports = { | ||
joinPath, | ||
isFeature, | ||
isBrowser, | ||
descendantKeys, | ||
}; |
Oops, something went wrong.