Turn unsupported-apis into a rule factory

This way we can mark specific platforms as compatible
This commit is contained in:
Oliver Blanthorn 2026-07-11 15:02:07 +02:00
parent 8d18f541bc
commit c127fb6bea
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
6 changed files with 126 additions and 64 deletions

View file

@ -1,3 +1,5 @@
const browserTargets = require("./browser-targets.json")
/*
👋 Hi! This file was autogenerated by tslint-to-eslint-config.
https://github.com/typescript-eslint/tslint-to-eslint-config
@ -34,7 +36,14 @@ module.exports = {
"sonarjs"
],
"rules": {
"unsupported-apis": "warn",
"unsupported-apis-chrome": [
"warn",
{ "minimumVersion": browserTargets.chrome.minimumVersion }
],
"unsupported-apis-firefox": [
"warn",
{ "minimumVersion": browserTargets.firefox.minimumVersion }
],
"sonarjs/cognitive-complexity": "off", //"error",
"sonarjs/no-duplicate-string": "off",
"sonarjs/no-unused-collection": "off", //"error", // There seems to be a bug with this rule - exported collections are assumed unused

View file

@ -0,0 +1,90 @@
const bcd = require("@mdn/browser-compat-data")
const api = bcd.webextensions.api
function propertyNameOrValue(n) {
return n.property.type == "Literal" ? n.property.value : n.property.name
}
function isVersionNewer(version, minimumVersion) {
const versionParts = String(version).split(".").map(Number)
const minimumParts = String(minimumVersion).split(".").map(Number)
if (versionParts.some(Number.isNaN) || minimumParts.some(Number.isNaN))
return false
const length = Math.max(versionParts.length, minimumParts.length)
for (let i = 0; i < length; i++) {
const difference = (versionParts[i] || 0) - (minimumParts[i] || 0)
if (difference !== 0) return difference > 0
}
return false
}
function detectBrowserUsage(context, node, browser, minimumVersion) {
let localApi = api
const fullName = []
while (
node.type == "MemberExpression" &&
propertyNameOrValue(node) in localApi
) {
const n = node
node = node.parent
const name = propertyNameOrValue(n)
fullName.push(name)
localApi = localApi[name]
if (!localApi.__compat) {
continue
}
const support = localApi.__compat.support
if (support[browser].version_added === false) {
context.report({
node: n,
messageId: "unsupportedApis",
data: {
name: browser,
api: fullName.join("."),
},
})
} else {
const version = support[browser].version_added
if (
minimumVersion !== undefined &&
isVersionNewer(version, minimumVersion)
) {
context.report({
node: n,
messageId: "apiTooRecent",
data: {
api: fullName.join("."),
name: browser,
version: minimumVersion,
},
})
}
}
}
}
module.exports = browser => ({
meta: {
schema: [
{
type: "object",
properties: { minimumVersion: { type: "string" } },
additionalProperties: false,
},
],
messages: {
unsupportedApis: "{{ api }} unsupported on '{{ name }}'",
apiTooRecent:
"{{ api }} is not supported on {{ name }} {{ version }}",
},
},
create(context) {
const { minimumVersion } = context.options[0] || {}
const detect = node =>
detectBrowserUsage(context, node, browser, minimumVersion)
return {
'MemberExpression[object.name="browser"]': detect,
'MemberExpression[object.name="browserBg"]': detect,
}
},
})

View file

@ -0,0 +1,24 @@
# adding a new target
make a file here like
```js
// unsupported-apis-mosaic.js
module.exports = require("./lib/unsupported-apis")("mosaic")
```
add a line to `../browser-targets.json`
```json
"mosaic": { "minimumVersion": "1.0.0" }
```
add a few lines to `../.eslintrc.js`
```js
"unsupported-apis-mosaic": [
"warn",
{ "minimumVersion": browserTargets.mosaic.minimumVersion }
],
```

View file

@ -0,0 +1 @@
module.exports = require("./lib/unsupported-apis")("chrome")

View file

@ -0,0 +1 @@
module.exports = require("./lib/unsupported-apis")("firefox")

View file

@ -1,63 +0,0 @@
const bcd = require('@mdn/browser-compat-data');
const api = bcd.webextensions.api;
const supported_browsers = ["firefox", "chrome"];
const minimalSupportedFirefoxVersion = 114;
function propertyNameOrValue(n) {
return (n.property.type == "Literal" ? n.property.value : n.property.name)
}
function detectBrowserUsage(context, node) {
let localApi = api;
let fullName = [];
while (node.type == "MemberExpression" && propertyNameOrValue(node) in localApi) {
const n = node;
node = node.parent;
let name = propertyNameOrValue(n);
fullName.push(name);
localApi = localApi[name];
if (!localApi.__compat) {
continue;
}
let support = localApi.__compat.support;
for (let browser of supported_browsers) {
if (support[browser].version_added === false) {
context.report({
node: n,
messageId: "unsupportedApis",
data: {
name: browser,
api: fullName.join("."),
}
});
} else {
const version = Number(support[browser].version_added);
if (!isNaN(version) && version > minimalSupportedFirefoxVersion) {
context.report({
node: n,
messageId: "apiTooRecent",
data: {
api: fullName.join("."),
version: minimalSupportedFirefoxVersion
}
});
}
}
}
}
}
module.exports = {
meta: {
messages: {
unsupportedApis: "{{ api }} unsupported on '{{ name }}'",
apiTooRecent: "{{ api }} is not supported on firefox {{ version }}",
}
},
create(context) {
return {
'MemberExpression[object.name="browser"]': (n) => detectBrowserUsage(context, n),
'MemberExpression[object.name="browserBg"]': (n) => detectBrowserUsage(context, n),
};
}
};