Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically assign status.[experimental, standard_track] during build #21860

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
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
9 changes: 0 additions & 9 deletions api/AbortSignal.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
},
Expand Down Expand Up @@ -90,7 +89,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
}
Expand Down Expand Up @@ -135,7 +133,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
},
Expand Down Expand Up @@ -184,7 +181,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
}
Expand Down Expand Up @@ -231,7 +227,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
}
Expand Down Expand Up @@ -282,7 +277,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
}
Expand Down Expand Up @@ -332,7 +326,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
}
Expand Down Expand Up @@ -382,7 +375,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
}
Expand Down Expand Up @@ -441,7 +433,6 @@
},
"status": {
"experimental": false,
"standard_track": true,
"deprecated": false
}
}
Expand Down
15 changes: 5 additions & 10 deletions lint/fixer/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import fs from 'node:fs';

import { Identifier } from '../../types/types.js';
import { checkExperimental } from '../linter/test-status.js';
import { IS_WINDOWS } from '../utils.js';
import walk from '../../utils/walk.js';

Expand All @@ -14,18 +13,14 @@ import walk from '../../utils/walk.js';
* @returns The updated value
*/
export const fixStatusValue = (value: Identifier): Identifier => {

const compat = value?.__compat;
if (compat?.status) {
if (compat.status.experimental && compat.status.deprecated) {
compat.status.experimental = false;
if (compat.status.experimental) {
delete (value.__compat.status as any).experimental;
}

if (compat.spec_url && compat.status.standard_track === false) {
compat.status.standard_track = true;
}

if (!checkExperimental(compat)) {
compat.status.experimental = false;
if (compat.status.standard_track) {
delete (value.__compat.status as any).standard_track;
}

if (compat.status.deprecated) {
Expand Down
72 changes: 5 additions & 67 deletions lint/linter/test-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,7 @@
import chalk from 'chalk-template';

import { Linter, Logger, LinterData } from '../utils.js';
import { BrowserName, CompatStatement } from '../../types/types.js';
import bcd from '../../index.js';
const { browsers } = bcd;

/**
* Check if experimental should be true or false
* @param data The data to check
* @returns The expected experimental status
*/
export const checkExperimental = (data: CompatStatement): boolean => {
if (data.status?.experimental) {
// Check if experimental should be false (code copied from migration 007)

const browserSupport = new Set<BrowserName>();

for (const [browser, support] of Object.entries(data.support)) {
// Consider only the first part of an array statement.
const statement = Array.isArray(support) ? support[0] : support;
// Ignore anything behind flag, prefix or alternative name
if (statement.flags || statement.prefix || statement.alternative_name) {
continue;
}
if (statement.version_added && !statement.version_removed) {
if (statement.version_added !== 'preview') {
browserSupport.add(browser as BrowserName);
}
}
}

// Now check which of Blink, Gecko and WebKit support it.

const engineSupport = new Set();

for (const browser of browserSupport) {
const currentRelease = Object.values(browsers[browser].releases).find(
(r) => r.status === 'current',
);
const engine = currentRelease?.engine;
if (engine) {
engineSupport.add(engine);
}
}

let engineCount = 0;
for (const engine of ['Blink', 'Gecko', 'WebKit']) {
if (engineSupport.has(engine)) {
engineCount++;
}
}

if (engineCount > 1) {
return false;
}
}

return true;
};
import { CompatStatement } from '../../types/types.js';

/**
* Check the status blocks of the compat date
Expand All @@ -83,22 +27,16 @@ const checkStatus = (
);
}

if (status.experimental && status.deprecated) {
if ('experimental' in status) {
logger.error(
chalk`{red Unexpected simultaneous {bold experimental} and {bold deprecated} status}`,
chalk`{red The {bold experimental} property is automatically set}`,
{ fixable: true },
);
}

if (data.spec_url && status.standard_track === false) {
logger.error(
chalk`{red Marked as {bold non-standard}, but has a {bold spec_url}}`,
);
}

if (!checkExperimental(data)) {
if ('standard_track' in status) {
logger.error(
chalk`{red {bold Experimental} should be set to {bold false} as the feature is {bold supported} in {bold multiple browser} engines.}`,
chalk`{red The {bold standard_track} property is automatically set}`,
{ fixable: true },
);
}
Expand Down
13 changes: 13 additions & 0 deletions scripts/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { WalkOutput } from '../../utils/walk.js';
import bcd from '../../index.js';

import mirrorSupport from './mirror.js';
import getStatus from './status.js';

const dirname = new URL('.', import.meta.url);
const rootdir = new URL('../../', dirname);
Expand Down Expand Up @@ -119,6 +120,17 @@ export const addVersionLast = (feature: WalkOutput): void => {
}
};

/**
* Add status block
* @param feature The BCD to transform
*/
export const addStatus = (feature: WalkOutput): void => {
const status = getStatus(feature.path, feature.compat);
if (status) {
(feature.data as any).__compat.status = status;
}
};

/**
* Applies transforms to the given data.
* @param data - The data to apply transforms to.
Expand All @@ -129,6 +141,7 @@ export const applyTransforms = (data): void => {
for (const feature of walker) {
applyMirroring(feature);
addVersionLast(feature);
addStatus(feature);
}
};

Expand Down
138 changes: 138 additions & 0 deletions scripts/build/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

import bcd from '../../index.js';
import {
CompatStatement,
StatusBlock,
BrowserName,
SimpleSupportStatement,
} from '../../types/types.js';
import { InternalSupportStatement } from '../../types/index.js';

const { browsers } = bcd;

const now = new Date();

const twoYearsAgo = new Date();
twoYearsAgo.setFullYear(twoYearsAgo.getFullYear() - 2);

/**
* Check if experimental should be true or false
* @param data The data to check
* @returns The expected experimental status
*/
export const checkExperimental = (data: CompatStatement): boolean => {
if (data.status?.experimental) {
// Check if experimental should be false (code copied from migration 007)

const browserSupport = new Set<BrowserName>();

for (const [browser, support] of Object.entries(
data.support as InternalSupportStatement,
)) {
if (support === 'mirror') {
// We don't need to check mirrored statements when calculating experimental status
continue;
}

for (const statement of Array.isArray(support) ? support : [support]) {
if (!statement.version_added) {
// Ignore statements without support
continue;
}

if (statement.flags || statement.prefix || statement.alternative_name) {
// Ignore anything behind flag, prefix or alternative name
continue;
}

if (statement.version_added === 'preview') {
// Ignore preview browsers
continue;
}

if (statement.version_removed) {
// Ignore browsers that removed support
continue;
}

if (typeof statement.version_added === 'string') {
const release =
browsers[browser].releases[
statement.version_added.replace('≤', '')
];

if (release.status !== 'current') {
// Experimental status is only set based upon support in stable browser releases
continue;
}

if (new Date(release.date) <= twoYearsAgo) {
// If any browser supported the feature for over two years, experimental should be false
return false;
}
}

// We'll need to check support by engine if the above conditions don't apply
browserSupport.add(browser as BrowserName);
}
}

// Now check which of Blink, Gecko and WebKit support it.
const engineSupport = new Set();

for (const browser of browserSupport) {
const currentRelease = Object.values(browsers[browser].releases).find(
(r) => r.status === 'current',
);
const engine = currentRelease?.engine;
if (engine) {
engineSupport.add(engine);
}
}

let engineCount = 0;
for (const engine of ['Blink', 'Gecko', 'WebKit']) {
if (engineSupport.has(engine)) {
engineCount++;
}
}

if (engineCount > 1) {
return false;
}
}

// If none of the conditions matched, the feature is experimental
return true;
};

/**
* Automatically set the status blocks for features
* @param data The data to update
* @returns The updated data
*/
const getStatus = (
ident: string,
data: CompatStatement,
): StatusBlock | null => {
if (ident.startsWith('webextensions')) {
// Web extensions data doesn't have a status block
return null;
}
const status = {
deprecated: data.status?.deprecated || false,
experimental: checkExperimental(data),
standard_track: !!data.spec_url,
};

if (status.deprecated) {
// Deprecated takes priority over experimental
status.experimental = false;
}

return status;
};

export default getStatus;
Loading