blob: cb6eebc09aee374381fab3aff2d52dad66b92c6d [file] [edit]
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { compare } from 'compare-versions';
import esMain from 'es-main';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import {
BrowserName,
SimpleSupportStatement,
CompatStatement,
} from '../types/types.js';
import bcd from '../index.js';
import { IS_WINDOWS } from '../test/utils.js';
const dirname = fileURLToPath(new URL('.', import.meta.url));
/**
* Get the earliest version number from an array of versions
* @param {string[]} args The version numbers to check
* @returns {string} The earliest of the version numbers
*/
const getEarliestVersion = (...args: string[]): string => {
const versions = args
.filter((version) => typeof version === 'string' && version !== 'preview')
.map((version) => version.replace('≤', ''));
let earliestVersion;
for (const version of versions) {
if (
!earliestVersion ||
earliestVersion === 'preview' ||
(version !== 'preview' && compare(earliestVersion, version, '>'))
) {
earliestVersion = version;
}
}
return earliestVersion;
};
/**
* Removes redundant flags from the compatibility data
* @param {string} key The object key (make sure it's '__compat')
* @param {CompatStatement} value The compatibility statement to test
* @param {BrowserName?} limitBrowser If flags should only be removed from a specific browser
* @returns {CompatStatement} The compatibility statement with all of the flags removed
*/
export const removeRedundantFlags = (
key: string,
value: CompatStatement,
limitBrowser: BrowserName | null,
): CompatStatement => {
if (key === '__compat') {
for (const [browser, rawSupportData] of Object.entries(value.support)) {
if (limitBrowser && browser != limitBrowser) {
continue;
}
const supportData = Array.isArray(rawSupportData)
? rawSupportData
: [rawSupportData];
const result: SimpleSupportStatement[] = [];
const simpleStatement = supportData.find((statement) => {
const ignoreKeys = new Set([
'version_removed',
'notes',
'partial_implementation',
]);
const keys = Object.keys(statement).filter(
(key) => !ignoreKeys.has(key),
);
return keys.length === 1;
});
for (let i = 0; i < supportData.length; i++) {
let addData = true;
if (supportData[i].flags) {
const versionToCheck = getEarliestVersion(
(supportData[i].version_removed as string) ||
((simpleStatement && simpleStatement.version_added) as string),
(simpleStatement && simpleStatement.version_added) as string,
);
if (typeof versionToCheck === 'string') {
const releaseDate =
bcd.browsers[browser as BrowserName]?.releases[versionToCheck]
?.release_date;
if (
releaseDate &&
(!(simpleStatement && simpleStatement.version_removed) ||
compare(
(supportData[i].version_added as string).replace('≤', ''),
(simpleStatement.version_removed as string).replace('≤', ''),
'<',
))
) {
addData = false;
}
}
}
if (addData) {
result.push(supportData[i]);
}
}
if (result.length == 1) {
value.support[browser] = result[0];
} else if (result.length == 0) {
value.support[browser] = { version_added: false };
} else {
value.support[browser] = result;
}
}
}
return value;
};
/**
* Removes redundant flags from the compatibility data of a specified file
* @param {string} filename The filename containing compatibility info
* @param {BrowserName?} limitBrowser If flags should only be removed from a specific browser
*/
export const fixRedundantFlags = (
filename: string,
limitBrowser: BrowserName | null,
): void => {
let actual = fs.readFileSync(filename, 'utf-8').trim();
let expected = JSON.stringify(
JSON.parse(actual, (k, v) => removeRedundantFlags(k, v, limitBrowser)),
null,
2,
);
if (IS_WINDOWS) {
// prevent false positives from git.core.autocrlf on Windows
actual = actual.replace(/\r/g, '');
expected = expected.replace(/\r/g, '');
}
if (actual !== expected) {
fs.writeFileSync(filename, expected + '\n', 'utf-8');
}
};
/**
* Removes redundant flags from the compatibility data of specified files/folders
* @param {string[]} files_or_folders The files and/or folders to run removal on
* @param {BrowserName?} browser If flags should only be removed from a specific
*/
const main = (
files_or_folders: string[],
browser: BrowserName | null,
): void => {
for (let file of files_or_folders) {
if (file.indexOf(dirname) !== 0) {
file = path.resolve(dirname, '..', file);
}
if (!fs.existsSync(file)) {
continue; // Ignore non-existent files
}
if (fs.statSync(file).isFile()) {
if (path.extname(file) === '.json') {
fixRedundantFlags(file, browser);
}
continue;
}
const subFiles = fs
.readdirSync(file)
.map((subfile) => path.join(file, subfile));
main(subFiles, browser);
}
};
if (esMain(import.meta)) {
const { argv } = yargs(hideBin(process.argv)).command(
'$0 [file]',
'Remove data for redundant flags',
(yargs) => {
yargs
.positional('file', {
describe: 'The file(s) and/or folder(s) to test',
type: 'string',
array: true,
default: [
'api',
'css',
'html',
'http',
'svg',
'javascript',
'mathml',
'test',
'webassembly',
'webdriver',
'webextensions',
],
})
.option('browser', {
alias: 'b',
describe: 'The browser to test for',
type: 'string',
default: null,
});
},
);
main((argv as any).file, (argv as any).browser);
}