blob: a6a23cba1d3787d9e6143b3dbac88ed7d5c02449 [file]
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */
import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { styleText } from 'node:util';
import esMain from 'es-main';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import dataFolders from '../scripts/lib/data-folders.js';
import fixBrowserOrder from './fixer/browser-order.js';
import fixCommonErrors from './fixer/common-errors.js';
import fixFeatureOrder from './fixer/feature-order.js';
import fixPropertyOrder from './fixer/property-order.js';
import fixStatementOrder from './fixer/statement-order.js';
import fixDescriptions from './fixer/descriptions.js';
import fixFlags from './fixer/flags.js';
import fixLinks from './fixer/links.js';
import fixMDNURLs from './fixer/mdn-urls.js';
import fixStatus from './fixer/status.js';
import fixMirror from './fixer/mirror.js';
import fixOverlap from './fixer/overlap.js';
import fixStandardTrackExceptions from './fixer/standard-track-exceptions.js';
import { IS_WINDOWS } from './utils.js';
/** @import {Stats} from 'node:fs' */
/** @import {LintOptions} from './types.js' */
const dirname = fileURLToPath(new URL('.', import.meta.url));
/** @type {Readonly<Record<string, function(string, string): Promise<string> | string>>} */
const FIXES = Object.freeze({
descriptions: fixDescriptions,
common_errors: fixCommonErrors,
flags: fixFlags,
links: fixLinks,
mdn_urls: fixMDNURLs,
status: fixStatus,
mirror: fixMirror,
overlap: fixOverlap,
browser_order: fixBrowserOrder,
feature_order: fixFeatureOrder,
property_order: fixPropertyOrder,
statement_order: fixStatementOrder,
standard_track_exceptions: fixStandardTrackExceptions,
});
/**
* Recursively load one or more files and/or directories passed as arguments and perform automatic fixes.
* @param {LintOptions} options The lint options
* @param {...string} files The files to load and perform fix upon
* @returns {Promise<void>}
*/
const load = async (options, ...files) => {
const fixes = Object.entries(FIXES)
.filter(([key]) => !options.only || options.only.includes(key))
.map(([, fix]) => fix);
for (let file of files) {
if (file.indexOf(dirname) !== 0) {
file = path.resolve(dirname, '..', file);
}
/** @type {Stats} */
let fsStats;
try {
fsStats = await stat(file);
} catch {
console.warn(
styleText('yellow', `File ${styleText('bold', file)} doesn't exist!`),
);
continue;
}
if (fsStats.isFile()) {
if (path.extname(file) === '.json' && !file.endsWith('.schema.json')) {
let initial = (await readFile(file, 'utf-8')).trim();
let expected = initial;
for (const fix of fixes) {
expected = await fix(file, expected);
}
if (IS_WINDOWS) {
// prevent false positives from git.core.autocrlf on Windows
initial = initial.replace(/\r/g, '');
expected = expected.replace(/\r/g, '');
}
if (initial !== expected) {
await writeFile(file, expected + '\n', 'utf-8');
}
}
} else {
const subFiles = (await readdir(file)).map((subfile) =>
path.join(file, subfile),
);
// Sort so files come before directories (e.g., meta.json before meta/).
// This ensures parent features are fixed before their sub-features.
subFiles.sort((a, b) => {
const aIsJson = a.endsWith('.json');
const bIsJson = b.endsWith('.json');
if (aIsJson !== bIsJson) {
return aIsJson ? -1 : 1;
}
return a.localeCompare(b);
});
await load(options, ...subFiles);
}
}
};
/**
* Fix any errors in specified file(s) and/or folder(s), or all of BCD
* @param {string[]} files The file(s) and/or folder(s) to fix. Leave undefined for everything.
* @param {LintOptions} options Lint options
* @returns {Promise<void>}
*/
const main = async (files, options) => {
await load(options, ...files);
};
if (esMain(import.meta)) {
const argv = yargs(hideBin(process.argv))
.command('$0 [files..]', false)
.positional('files', {
array: true,
description: 'The files to fix (leave blank to test everything)',
type: 'string',
})
.option('only', {
array: true,
description: 'The checks to run',
choices: Object.keys(FIXES),
})
.parseSync();
const { files = dataFolders, only } = argv;
await main(files, { only });
}
export default load;