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

Add linter for incorrect feature statuses #15889

Merged
merged 37 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4773c1e
Add linter for incorrect experimental statuses
saschanaz Oct 3, 2020
85d8a09
?. is now a thing
saschanaz May 20, 2021
ac496e6
Prepare for more status checking
queengooborg Apr 19, 2022
9d5da15
Test standard_track when spec_url present
queengooborg Apr 19, 2022
2a2792c
Simplify code
queengooborg Apr 19, 2022
1931acc
Merge branch 'main' into linter/test-status
queengooborg Apr 22, 2022
e2e1263
Format
queengooborg Apr 24, 2022
ecd47a0
Merge branch 'main' into linter/test-status
queengooborg Apr 24, 2022
d1f0a5c
Update JSDoc
queengooborg Apr 24, 2022
779cbf4
Switch fixStatus() to only focus on standard track
queengooborg Apr 24, 2022
c90c4e1
Update import
queengooborg Apr 24, 2022
2758498
Fix JSDoc
queengooborg Apr 24, 2022
eec7617
Merge branch 'main' into linter/test-status
queengooborg May 3, 2022
e80130b
Merge branch 'main' into linter/test-status
queengooborg May 7, 2022
98387d1
Merge branch 'main' into linter/test-status
queengooborg May 8, 2022
cf4db7e
Add copyright and use strict
queengooborg May 8, 2022
c9518da
Merge branch 'main' into linter/test-status
queengooborg May 13, 2022
c855fd0
Merge branch 'main' into linter/test-status
queengooborg May 13, 2022
8e80db8
Add test for experimental status
queengooborg May 13, 2022
dc32858
Fix import
queengooborg May 13, 2022
9053c98
Fix imports
queengooborg May 13, 2022
d615c3e
Rename file
queengooborg May 14, 2022
2cfc567
Update fix script to include migration's code
queengooborg May 14, 2022
e536fc7
Merge branch 'main' into linter/test-status
queengooborg May 18, 2022
f5a6f87
Migrate to ESM
queengooborg May 18, 2022
bcc81fd
Migrate to ESM (continued)
queengooborg May 18, 2022
d0f7d1c
Merge branch 'main' into linter/test-status
queengooborg May 19, 2022
e37ee57
Merge branch 'main' into linter/test-status
queengooborg May 19, 2022
62076a7
Merge branch 'main' into linter/test-status
queengooborg May 23, 2022
e8cdb2f
Remove duplicate statements
queengooborg May 23, 2022
77caaec
Fix bad merge
queengooborg May 23, 2022
45ef5ad
Fix variable names
queengooborg May 23, 2022
5ee8cf0
Merge branch 'main' into linter/test-status
queengooborg Jun 1, 2022
6b07dd3
Merge branch 'main' into linter/test-status
queengooborg Jun 13, 2022
797a8e3
Fix the status fix script
queengooborg Jun 13, 2022
2cffd84
Fix lint script
queengooborg Jun 13, 2022
d2336a3
Run fix script
queengooborg Jun 13, 2022
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
135 changes: 135 additions & 0 deletions scripts/fix/feature-status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

'use strict';

const fs = require('fs');
const path = require('path');
const { platform } = require('os');

const { IS_WINDOWS } = require('../../test/utils.js');

/**
* Return a new feature object whose status properties have been adjusted according to a few predefined rules.
*
* @param {string} key The key in the object
* @param {*} value The value of the key
*
* @returns {*} The new value
*/
const fixStatus = (key, value) => {
const compat = value?.__compat;
if (compat) {
if (compat.spec_url && compat.status.standard_track === false) {
compat.status.standard_track = true;
}
if (compat.status.experimental) {
const browserSupport = new Set();

for (const [browser, support] of Object.entries(compat.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) {
browserSupport.add(browser);
}
}

// 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;
engineSupport.add(engine);
}

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

if (engineCount > 1) {
compat.status.experimental = false;
}
}
}

return value;
};

/**
* @param {string} filename
*/
const fixStatusFromFile = (filename) => {
let actual = fs.readFileSync(filename, 'utf-8').trim();
let expected = JSON.stringify(JSON.parse(actual, fixStatus), 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');
}
};

if (require.main === module) {
/**
* @param {string[]} files
*/
function load(...files) {
for (let file of files) {
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') {
fixStatusFromFile(file);
}

continue;
}

const subFiles = fs.readdirSync(file).map((subfile) => {
return path.join(file, subfile);
});

load(...subFiles);
}
}

if (process.argv[2]) {
load(process.argv[2]);
} else {
load(
'api',
'css',
'html',
'http',
'svg',
'javascript',
'mathml',
'test',
'webdriver',
'webextensions',
);
}
}

module.exports = fixStatusFromFile;
2 changes: 2 additions & 0 deletions scripts/fix/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const path = require('path');
const fixBrowserOrder = require('./browser-order');
const fixFeatureOrder = require('./feature-order');
const fixLinks = require('./links');
const fixFeatureStatus = require('./feature-status');

/**
* Recursively load one or more files and/or directories passed as arguments and perform automatic fixes.
Expand All @@ -31,6 +32,7 @@ function load(...files) {
fixBrowserOrder(file);
fixFeatureOrder(file);
fixLinks(file);
fixFeatureStatus(file);
}

continue;
Expand Down
4 changes: 4 additions & 0 deletions test/lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
testPrefix,
testRealValues,
testSchema,
testStatus,
testStyle,
testVersions,
} = require('./linter/index.js');
Expand Down Expand Up @@ -68,6 +69,7 @@ function load(...files) {
hasRealValueErrors = false,
hasPrefixErrors = false,
hasDescriptionsErrors = false,
hasStatusErrors = false,
hasNotesErrors = false;
const relativeFilePath = path.relative(process.cwd(), file);

Expand Down Expand Up @@ -109,6 +111,7 @@ function load(...files) {
hasRealValueErrors = testRealValues(file);
hasPrefixErrors = testPrefix(file);
hasDescriptionsErrors = testDescriptions(file);
hasStatusErrors = testStatus(file);
hasNotesErrors = testNotes(file);
}
} catch (e) {
Expand All @@ -128,6 +131,7 @@ function load(...files) {
hasRealValueErrors,
hasPrefixErrors,
hasDescriptionsErrors,
hasStatusErrors,
hasNotesErrors,
].some((x) => !!x);

Expand Down
2 changes: 2 additions & 0 deletions test/linter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const testNotes = require('./test-notes.js');
const testPrefix = require('./test-prefix.js');
const testRealValues = require('./test-real-values.js');
const testSchema = require('./test-schema.js');
const testStatus = require('./test-status.js');
const testStyle = require('./test-style.js');
const testVersions = require('./test-versions.js');

Expand All @@ -25,6 +26,7 @@ module.exports = {
testPrefix,
testRealValues,
testSchema,
testStatus,
testStyle,
testVersions,
};
106 changes: 106 additions & 0 deletions test/linter/test-status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

'use strict';

const chalk = require('chalk');
const { Logger } = require('../utils.js');

const { browsers } = require('../../index.js');

/**
* @typedef {import('../../types').Identifier} Identifier
*/

/**
* @param {Identifier} data
* @param {Logger} logger
* @param {string} path
*/
function checkStatus(data, logger, path = '') {
const compat = data.__compat;
if (compat?.status) {
if (compat.spec_url && compat.status.standard_track === false) {
logger.error(
chalk`{red → {bold ${path}} is marked as {bold non-standard}, but has a {bold spec_url}}`,
);
}
if (compat.status.experimental && compat.status.deprecated) {
logger.error(
chalk`{red → Unexpected simultaneous experimental and deprecated status in {bold ${path}}}`,
);
}
if (compat.status.experimental) {
// Check if experimental should be false (code copied from migration 007)

const browserSupport = new Set();

for (const [browser, support] of Object.entries(compat.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) {
browserSupport.add(browser);
}
}

// 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;
engineSupport.add(engine);
}

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

if (engineCount > 1) {
logger.error(
chalk`{red → Experimental should be set to {bold false} for {bold ${path}} as the feature is supported in multiple browser engines.}`,
);
}
}
}

// Check children
for (const member in data) {
queengooborg marked this conversation as resolved.
Show resolved Hide resolved
if (member === '__compat') {
continue;
}
checkStatus(
data[member],
logger,
path && path.length > 0 ? `${path}.${member}` : member,
);
}
}

/**
* @param {string} filename
* @returns {boolean} If the file contains errors
*/
function testStatus(filename) {
/** @type {Identifier} */
const data = require(filename);

const logger = new Logger('Feature Status');

checkStatus(data, logger);

logger.emit();
return logger.hasErrors();
}

module.exports = testStatus;
queengooborg marked this conversation as resolved.
Show resolved Hide resolved