Skip to content
Draft
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"lint:fix": "node lint/fix.js",
"fix": "npm run format:fix && npm run lint:fix",
"stats": "node scripts/statistics.js",
"flag-inventory": "node scripts/flag-inventory.js",
"build": "node scripts/build/index.js",
"gentypes": "node scripts/generate-types.js",
"release": "node scripts/release/index.js",
Expand Down
66 changes: 66 additions & 0 deletions scripts/flag-inventory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

import esMain from 'es-main';

import walk from '../utils/walk.js';

/** @import {InternalDataType} from '../types/index.js' */

/**
* Compare map entries by key using locale-independent alphabetical order.
* @template T
* @param {[string, T]} a First entry
* @param {[string, T]} b Second entry
* @returns {number} Sort order
*/
const compareKeys = ([a], [b]) => (a < b ? -1 : a > b ? 1 : 0);

/**
* List explicit flag occurrences by type, browser, and name, alphabetically.
* Each flags entry counts once, regardless of value or support version.
* @param {InternalDataType} [data] Source compatibility data
* @returns {string} The indented flag inventory
*/
const flagInventory = (data) => {
/** @type {Map<string, Map<string, Map<string, number>>>} */
const inventory = new Map();

for (const { compat } of walk(undefined, data)) {
for (const [browser, support] of Object.entries(compat.support)) {
if (support === 'mirror') {
continue;
}
for (const statement of Array.isArray(support) ? support : [support]) {
for (const { type, name } of statement.flags ?? []) {
const browsers = inventory.get(type) ?? new Map();
inventory.set(type, browsers);
const flags = browsers.get(browser) ?? new Map();
browsers.set(browser, flags);
flags.set(name, (flags.get(name) ?? 0) + 1);
}
}
}
}

const lines = [];
for (const [type, browsers] of [...inventory].sort(compareKeys)) {
lines.push(type);
for (const [browser, flags] of [...browsers].sort(compareKeys)) {
lines.push(` ${browser}`);
for (const [name, count] of [...flags].sort(compareKeys)) {
lines.push(` ${name}: ${count}`);
}
}
}
return lines.join('\n');
};

if (esMain(import.meta)) {
const output = flagInventory();
if (output) {
console.log(output);
}
}

export default flagInventory;
89 changes: 89 additions & 0 deletions scripts/flag-inventory.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import flagInventory from './flag-inventory.js';

describe('flagInventory', () => {
it('counts all explicit occurrences and sorts each grouping', () => {
// The recursive InternalIdentifier index signature cannot type this fixture.
// eslint-disable-next-line jsdoc/reject-any-type
/** @type {*} */
const data = {
api: {
Example: {
__compat: {
support: {
firefox: [
{
version_added: '2',
flags: [
{ type: 'runtime_flag', name: 'z-flag' },
{ type: 'preference', name: 'z-pref' },
{
type: 'preference',
name: 'a-pref',
value_to_set: 'true',
},
],
},
{
version_added: '1',
version_removed: '2',
flags: [
{
type: 'preference',
name: 'a-pref',
value_to_set: 'false',
},
],
},
],
firefox_android: 'mirror',
chrome: {
version_added: '1',
flags: [{ type: 'preference', name: 'a-pref' }],
},
safari: { version_added: false },
},
},
nested: {
__compat: {
support: {
firefox: {
version_added: '1',
flags: [
{ type: 'preference', name: 'a-pref' },
{ type: 'runtime_flag', name: 'a-pref' },
],
},
},
},
},
},
},
};

assert.equal(
flagInventory(data),
[
'preference',
' chrome',
' a-pref: 1',
' firefox',
' a-pref: 3',
' z-pref: 1',
'runtime_flag',
' firefox',
' a-pref: 1',
' z-flag: 1',
].join('\n'),
);
});

it('returns empty output when there are no flags', () => {
assert.equal(flagInventory({}), '');
});
});