blob: 0d205569c25cd4a7b2f1722457cd20c24d1cf822 [file] [log] [blame] [edit]
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */
import chalk from 'chalk-template';
import { compareVersions } from 'compare-versions';
import { Logger, createStatementGroupKey } from '../utils.js';
import {
BrowserName,
SimpleSupportStatement,
SupportStatement,
} from '../../types/types.js';
import compareStatements from '../../scripts/lib/compare-statements.js';
/**
* Groups statements by group key.
* @param data The support statements to group.
* @returns the statement groups
*/
const groupByStatementKey = (data: SimpleSupportStatement[]) => {
const groups = new Map<string, SimpleSupportStatement[]>();
for (const support of data) {
const key = createStatementGroupKey(support);
const group = groups.get(key);
if (group) {
group.push(support);
} else {
groups.set(key, [support]);
}
}
return groups;
};
/**
* Formats a support statement as a simplified JSON-like version range.
* @param support The statement to format
* @returns The formatted range
*/
const formatRange = (support: SimpleSupportStatement): string => {
const result: string[] = [];
if (support.version_added) {
result.push(`added: ${support.version_added}`);
}
if (support.version_removed) {
result.push(`removed: ${support.version_removed}`);
}
return `{ ${result.join(', ')} }`;
};
/**
* Process data and check to make sure there aren't support statements whose version ranges overlap.
* @param data The data to test
* @param browser The name of the browser
* @param options The check options
* @param options.logger The logger to output errors to
* @param options.fix Whether the statements should be fixed (if possible)
* @returns the data (with fixes, if specified)
*/
export const checkOverlap = (
data: SupportStatement,
browser: BrowserName,
{ logger, fix = false }: { logger?: Logger; fix?: boolean },
): SupportStatement => {
if (!Array.isArray(data)) {
// If there's only one statement, skip since this is a linter for multiple statements
return data;
}
const filteredData = data.filter((support) => !support.flags);
const groups = groupByStatementKey(filteredData);
for (const [groupKey, groupData] of groups.entries()) {
const statements = groupData.slice().sort(compareStatements).reverse();
for (let i = 0; i < statements.length - 1; i++) {
const current = statements.at(i) as SimpleSupportStatement;
const next = statements.at(i + 1) as SimpleSupportStatement;
if (!statementsOverlap(current, next)) {
continue;
}
let fixed = false;
if (fix) {
if (
typeof current.version_removed === 'undefined' &&
next.version_added !== 'preview'
) {
current.version_removed = next.version_added;
fixed = true;
}
}
if (!fixed && logger) {
logger.error(
chalk`{bold ${browser}} statements overlap for {bold ${groupKey}}: ` +
`[${formatRange(next)}, ${formatRange(current)}]`,
);
}
}
}
return data;
};
/**
* Checks if the support statements overlap in terms of their version ranges.
* @param current the current statement.
* @param next the chronologically following statement.
* @returns Whether the support statements overlap.
*/
const statementsOverlap = (
current: SimpleSupportStatement,
next: SimpleSupportStatement,
): boolean => {
if (typeof current.version_removed === 'string') {
// If previous has no removed version, we always have an overlap.
if (next.version_added === 'preview') {
// Feature got re-introduced.
return false;
}
if (
typeof next.version_added === 'string' &&
compareVersions(current.version_removed, next.version_added) <= 0
) {
// No overlap.
return false;
}
} else if (
next.version_added === 'preview' &&
current.partial_implementation === true
) {
// Stable has partial support.
// Preview has full support.
return false;
}
return true;
};