blob: c363bcdc94fe0965ccff08d9f429093001fa1cd3 [file] [edit]
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */
import esMain from 'es-main';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { BrowserName, Identifier } from '../types/types.js';
import bcd from '../index.js';
/**
* Traverse all of the features within a specified object and find all features that have one of the specified values
* @param {Identifier} obj The compat data to traverse through
* @param {BrowserName[]} browsers The browsers to test for
* @param {string[]} values The values to test for
* @param {number} depth The depth to traverse
* @param {string} tag The tag to filter results with
* @param {string} identifier The identifier of the current object
* @yields {string} The feature identifier
*/
function* iterateFeatures(
obj,
browsers: BrowserName[],
values: string[],
depth: number,
tag: string,
identifier: string,
): IterableIterator<string> {
depth--;
if (depth >= 0) {
for (const i in obj) {
if (!!obj[i] && typeof obj[i] == 'object' && i !== '__compat') {
if (obj[i].__compat) {
if (tag) {
const tags = obj[i].__compat.tags;
if (tags && tags.includes(tag)) {
yield `${identifier}${i}`;
}
} else {
const comp = obj[i].__compat.support;
for (const browser of browsers) {
let browserData = comp[browser];
if (!browserData) {
if (values.length == 0 || values.includes('null')) {
yield `${identifier}${i}`;
}
continue;
}
if (!Array.isArray(browserData)) {
browserData = [browserData];
}
for (const range in browserData) {
if (browserData[range] === 'mirror') {
if (values.includes('mirror')) {
yield `${identifier}${i}`;
}
} else if (values.includes('nonmirror')) {
// If checking for non-mirrored data and it's not mirrored
yield `${identifier}${i}`;
} else if (browserData[range] === undefined) {
if (values.length == 0 || values.includes('null')) {
yield `${identifier}${i}`;
}
} else if (
values.length == 0 ||
values.includes(String(browserData[range].version_added)) ||
values.includes(String(browserData[range].version_removed))
) {
let f = `${identifier}${i}`;
if (browserData[range].prefix) {
f += ` (${browserData[range].prefix} prefix)`;
}
if (browserData[range].alternative_name) {
f += ` (as ${browserData[range].alternative_name})`;
}
yield f;
}
}
}
}
}
yield* iterateFeatures(
obj[i],
browsers,
values,
depth,
tag,
identifier + i + '.',
);
}
}
}
}
/**
* Traverse all of the features within a specified object and find all features that have one of the specified values
* @param {Identifier} obj The compat data to traverse through
* @param {string[]} browsers The browsers to traverse for
* @param {string[]} values The version values to traverse for
* @param {number} depth The depth to traverse
* @param {string} tag The tag to filter results with
* @param {string} identifier The identifier of the current object
* @returns {string[]} An array of the features
*/
const traverseFeatures = (
obj,
browsers: BrowserName[],
values: string[],
depth: number,
tag: string,
identifier: string,
): string[] => {
const features = Array.from(
iterateFeatures(obj, browsers, values, depth, tag, identifier),
);
return features.filter((item, pos) => features.indexOf(item) == pos);
};
/**
* Traverse the features within BCD
* @param {string[]} folders The folders to traverse
* @param {BrowserName[]} browsers The browsers to traverse for
* @param {string[]} values The version values to traverse for
* @param {number} depth The depth to traverse
* @param {string} tag The tag to filter results with
* @returns {string[]} The list of features
*/
const main = (
folders = [
'api',
'css',
'html',
'http',
'svg',
'javascript',
'mathml',
'webassembly',
'webdriver',
],
browsers: BrowserName[] = Object.keys(bcd.browsers) as BrowserName[],
values = ['null', 'true'],
depth = 100,
tag = '',
): string[] => {
const features: string[] = [];
for (const folder in folders) {
features.push(
...traverseFeatures(
bcd[folders[folder]],
browsers,
values,
depth,
tag,
folders[folder] + '.',
),
);
}
return features;
};
if (esMain(import.meta)) {
const { argv }: { argv } = yargs(hideBin(process.argv)).command(
'$0 [folder...]',
'Print feature names in the folder (and optionally filter features to specific browser or version values)',
(yargs) => {
yargs
.positional('folder', {
describe: 'The folder(s) to traverse',
type: 'string',
array: true,
default: Object.keys(bcd).filter((k) => k !== 'browsers'),
})
.option('browser', {
alias: 'b',
describe: 'Filter by a browser. May repeat.',
type: 'array',
nargs: 1,
default: Object.keys(bcd.browsers),
})
.option('filter', {
alias: 'f',
describe:
'Filter by version value. May repeat. Set to "mirror" for mirrored entries, and "nonmirror" for non-mirrored entries.',
type: 'array',
string: true,
nargs: 1,
default: [],
})
.option('tag', {
alias: 't',
describe: 'Filter by tag value.',
type: 'string',
nargs: 1,
default: '',
})
.option('non-real', {
alias: 'n',
describe:
'Filter to features with non-real values. Alias for "-f true -f null"',
type: 'boolean',
nargs: 0,
})
.option('depth', {
alias: 'd',
describe:
'Depth of features to traverse (ex. "2" will capture "api.CSSStyleSheet.insertRule" but not "api.CSSStyleSheet.insertRule.optional_index")',
type: 'number',
nargs: 1,
default: 10,
})
.example(
'npm run traverse -- --browser=safari -n',
'Find all features containing non-real Safari entries',
)
.example(
'npm run traverse -- -b webview_android -f true',
'Find all features marked as true for WebView',
)
.example(
'npm run traverse -- -b firefox -f 10',
'Find all features marked as supported since Firefox 10',
)
.example(
'npm run traverse -- -b samsunginternet_android -f mirror',
'Find all features in Samsung Internet that mirror data from Chrome Android',
)
.example(
'npm run traverse -- -t web-features:idle-detection',
'Find all features tagged with web-features:idle-detection.',
);
},
);
const filter = [...argv.filter, ...(argv.nonReal ? ['true', 'null'] : [])];
const features = main(
argv.folder,
argv.browser,
filter,
argv.depth,
argv.tag,
);
console.log(features.join('\n'));
console.log(features.length);
}
export default main;