es-check 9.7.1 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/lib/browserslist.js +54 -6
- package/lib/check-runner/index.js +2 -1
- package/lib/check-runner/utils.js +41 -15
- package/lib/constants/es-features/browserSupport.js +2421 -0
- package/lib/constants/es-features/index.js +2 -0
- package/lib/constants/versions.js +18 -0
- package/lib/detectFeatures.js +25 -9
- package/lib/helpers/astDetector.js +38 -4
- package/lib/helpers/browserSupport.js +89 -0
- package/package.json +4 -3
|
@@ -12,6 +12,7 @@ const { ES16_FEATURES } = require("./16");
|
|
|
12
12
|
const { ES17_FEATURES } = require("./17");
|
|
13
13
|
const { ES_GLOBAL_MIN_VERSION, ES_GLOBALS_SOURCE } = require("./globals");
|
|
14
14
|
const { POLYFILL_PATTERNS, IMPORT_PATTERNS, FEATURE_TO_POLYFILL_MAP } = require("./polyfills");
|
|
15
|
+
const { FEATURE_BROWSER_SUPPORT } = require("./browserSupport");
|
|
15
16
|
|
|
16
17
|
const ES_FEATURES = Object.assign(
|
|
17
18
|
{},
|
|
@@ -36,4 +37,5 @@ module.exports = {
|
|
|
36
37
|
POLYFILL_PATTERNS,
|
|
37
38
|
IMPORT_PATTERNS,
|
|
38
39
|
FEATURE_TO_POLYFILL_MAP,
|
|
40
|
+
FEATURE_BROWSER_SUPPORT,
|
|
39
41
|
};
|
|
@@ -225,10 +225,28 @@ const VERSION_ORDER = [
|
|
|
225
225
|
|
|
226
226
|
const LATEST_PARSER_VERSION = 2026;
|
|
227
227
|
|
|
228
|
+
const BROWSERSLIST_TO_BCD = {
|
|
229
|
+
and_chr: "chrome_android",
|
|
230
|
+
and_ff: "firefox_android",
|
|
231
|
+
android: "webview_android",
|
|
232
|
+
chrome: "chrome",
|
|
233
|
+
deno: "deno",
|
|
234
|
+
edge: "edge",
|
|
235
|
+
firefox: "firefox",
|
|
236
|
+
ie: "ie",
|
|
237
|
+
ios_saf: "safari_ios",
|
|
238
|
+
node: "nodejs",
|
|
239
|
+
op_mob: "opera_android",
|
|
240
|
+
opera: "opera",
|
|
241
|
+
safari: "safari",
|
|
242
|
+
samsung: "samsunginternet_android",
|
|
243
|
+
};
|
|
244
|
+
|
|
228
245
|
module.exports = {
|
|
229
246
|
ECMA_VERSION_MAP,
|
|
230
247
|
ECMA_VERSION_TO_NUMBER,
|
|
231
248
|
BROWSER_TO_ES_VERSION,
|
|
249
|
+
BROWSERSLIST_TO_BCD,
|
|
232
250
|
JS_VERSIONS,
|
|
233
251
|
VERSION_ORDER,
|
|
234
252
|
LATEST_PARSER_VERSION,
|
package/lib/detectFeatures.js
CHANGED
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
const { ES_GLOBAL_MIN_VERSION } = require("./constants/es-features/globals");
|
|
8
8
|
const { POLYFILLABLE_FEATURES, CORE_JS_POLYFILLABLE } = require("./constants/polyfillableFeatures");
|
|
9
9
|
const { detectFeaturesFromAST } = require("./helpers/astDetector");
|
|
10
|
+
const { getUnsupportedBrowsers, formatFeatureWithBrowsers } = require("./helpers/browserSupport");
|
|
10
11
|
|
|
11
12
|
function getPolyfillableFeatures(library) {
|
|
12
13
|
if (!library) {
|
|
@@ -50,7 +51,7 @@ const detectPolyfills = (
|
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
const detectFeatures = (code, ecmaVersion, sourceType, ignoreList = new Set(), options = {}) => {
|
|
53
|
-
const { checkForPolyfills, ast, ignorePolyfillable } = options;
|
|
54
|
+
const { checkForPolyfills, ast, ignorePolyfillable, targetBrowsers } = options;
|
|
54
55
|
|
|
55
56
|
if (!ast) {
|
|
56
57
|
const error = new Error("AST is required for feature detection");
|
|
@@ -65,17 +66,26 @@ const detectFeatures = (code, ecmaVersion, sourceType, ignoreList = new Set(), o
|
|
|
65
66
|
|
|
66
67
|
const polyfillableSet = ignorePolyfillable ? getPolyfillableFeatures(ignorePolyfillable) : null;
|
|
67
68
|
|
|
69
|
+
const browserDetails = {};
|
|
70
|
+
|
|
68
71
|
function isUnsupportedFeature(name, minVersion) {
|
|
69
72
|
const wasFound = foundFeatures[name];
|
|
73
|
+
if (!wasFound) return false;
|
|
74
|
+
|
|
75
|
+
const unsupportedBrowsers = getUnsupportedBrowsers(name, targetBrowsers);
|
|
76
|
+
const lacksBrowserSupport = unsupportedBrowsers.length > 0;
|
|
70
77
|
const requiresHigherVersion = minVersion > ecmaVersion;
|
|
78
|
+
const shouldReport = requiresHigherVersion || lacksBrowserSupport;
|
|
79
|
+
if (!shouldReport) return false;
|
|
80
|
+
|
|
71
81
|
const isIgnored = ignoreList.has(name);
|
|
72
82
|
const isPolyfilled = checkForPolyfills && polyfills.has(name);
|
|
73
83
|
const isPolyfillableIgnored = polyfillableSet && polyfillableSet.has(name);
|
|
74
|
-
|
|
75
84
|
const isAllowed = isIgnored || isPolyfilled || isPolyfillableIgnored;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
if (isAllowed) return false;
|
|
86
|
+
|
|
87
|
+
if (lacksBrowserSupport) browserDetails[name] = unsupportedBrowsers;
|
|
88
|
+
return true;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
const unsupportedStandardFeatures = Object.entries(ES_FEATURES)
|
|
@@ -90,12 +100,17 @@ const detectFeatures = (code, ecmaVersion, sourceType, ignoreList = new Set(), o
|
|
|
90
100
|
const unsupportedFeatures = unsupportedStandardFeatures.concat(unsupportedGlobalFeatures);
|
|
91
101
|
|
|
92
102
|
if (unsupportedFeatures.length > 0) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
const hasBrowserDetails = Object.keys(browserDetails).length > 0;
|
|
104
|
+
const featureList = unsupportedFeatures
|
|
105
|
+
.map((name) => formatFeatureWithBrowsers(name, browserDetails[name]))
|
|
106
|
+
.join(", ");
|
|
107
|
+
const reason = hasBrowserDetails
|
|
108
|
+
? `These require a higher ES version than ${ecmaVersion} or are not supported by the target browsers.`
|
|
109
|
+
: `These require a higher ES version than ${ecmaVersion}.`;
|
|
110
|
+
const error = new Error(`Unsupported features detected: ${featureList}. ${reason}`);
|
|
97
111
|
error.type = "ES-Check";
|
|
98
112
|
error.features = unsupportedFeatures;
|
|
113
|
+
error.browserDetails = browserDetails;
|
|
99
114
|
error.ecmaVersion = ecmaVersion;
|
|
100
115
|
throw error;
|
|
101
116
|
}
|
|
@@ -103,6 +118,7 @@ const detectFeatures = (code, ecmaVersion, sourceType, ignoreList = new Set(), o
|
|
|
103
118
|
return {
|
|
104
119
|
foundFeatures,
|
|
105
120
|
unsupportedFeatures,
|
|
121
|
+
browserDetails,
|
|
106
122
|
};
|
|
107
123
|
};
|
|
108
124
|
|
|
@@ -1609,16 +1609,49 @@ function getTypeofComparisonGuardBranch(comparison) {
|
|
|
1609
1609
|
|
|
1610
1610
|
function getTypeofComparison(node) {
|
|
1611
1611
|
if (node.type !== "BinaryExpression") return null;
|
|
1612
|
-
if (!isTypeofComparisonOperator(node.operator)) return null;
|
|
1613
1612
|
|
|
1614
1613
|
const leftComparison = getTypeofComparisonSide(node.left, node.right);
|
|
1615
1614
|
if (leftComparison) {
|
|
1616
|
-
return
|
|
1615
|
+
return normalizeTypeofComparison(leftComparison, node.operator);
|
|
1617
1616
|
}
|
|
1618
1617
|
|
|
1619
1618
|
const rightComparison = getTypeofComparisonSide(node.right, node.left);
|
|
1620
|
-
if (
|
|
1621
|
-
|
|
1619
|
+
if (rightComparison) {
|
|
1620
|
+
return normalizeTypeofComparison(rightComparison, node.operator, true);
|
|
1621
|
+
}
|
|
1622
|
+
return getBooleanTypeofComparison(node);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function getBooleanTypeofComparison(node) {
|
|
1626
|
+
if (!isTypeofComparisonOperator(node.operator)) return null;
|
|
1627
|
+
|
|
1628
|
+
const isLeftBoolean = typeof node.left.value === "boolean";
|
|
1629
|
+
const valueNode = isLeftBoolean ? node.left : node.right;
|
|
1630
|
+
const expression = isLeftBoolean ? node.right : node.left;
|
|
1631
|
+
const isBoolean = valueNode.type === "Literal" && typeof valueNode.value === "boolean";
|
|
1632
|
+
if (!isBoolean) return null;
|
|
1633
|
+
|
|
1634
|
+
const comparison = getTypeofComparison(expression);
|
|
1635
|
+
if (!comparison) return null;
|
|
1636
|
+
const isEqual = node.operator === "===" || node.operator === "==";
|
|
1637
|
+
const preservesPolarity = isEqual === valueNode.value;
|
|
1638
|
+
const guardedBranch = getTypeofComparisonGuardBranch(comparison) === preservesPolarity;
|
|
1639
|
+
const operator = guardedBranch ? "!==" : "===";
|
|
1640
|
+
return { name: comparison.name, value: "undefined", operator };
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
function normalizeTypeofComparison(comparison, operator, isReversed = false) {
|
|
1644
|
+
if (isTypeofComparisonOperator(operator)) {
|
|
1645
|
+
return Object.assign({}, comparison, { operator });
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
const isRelational = operator === "<" || operator === ">";
|
|
1649
|
+
const isMinifiedGuard = isRelational && comparison.value === "u";
|
|
1650
|
+
if (!isMinifiedGuard) return null;
|
|
1651
|
+
|
|
1652
|
+
const isUndefined = (operator === ">") !== isReversed;
|
|
1653
|
+
const normalizedOperator = isUndefined ? "===" : "!==";
|
|
1654
|
+
return Object.assign({}, comparison, { value: "undefined", operator: normalizedOperator });
|
|
1622
1655
|
}
|
|
1623
1656
|
|
|
1624
1657
|
function isTypeofComparisonOperator(operator) {
|
|
@@ -1666,6 +1699,7 @@ function isFalseLiteral(node) {
|
|
|
1666
1699
|
}
|
|
1667
1700
|
|
|
1668
1701
|
function shouldSkipBranchGuard(node, branchValue) {
|
|
1702
|
+
if (node.type === "BinaryExpression") return true;
|
|
1669
1703
|
const isLogical = node.type === "LogicalExpression";
|
|
1670
1704
|
if (!isLogical) return false;
|
|
1671
1705
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const { FEATURE_BROWSER_SUPPORT } = require("../constants/es-features/browserSupport");
|
|
2
|
+
|
|
3
|
+
function compareVersions(a, b) {
|
|
4
|
+
const aParts = String(a).split(".").map(Number);
|
|
5
|
+
const bParts = String(b).split(".").map(Number);
|
|
6
|
+
const length = Math.max(aParts.length, bParts.length);
|
|
7
|
+
|
|
8
|
+
let index = 0;
|
|
9
|
+
while (index < length) {
|
|
10
|
+
const diff = (aParts[index] || 0) - (bParts[index] || 0);
|
|
11
|
+
if (diff !== 0) return diff;
|
|
12
|
+
index += 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isVersionSupported(targetVersion, versionAdded) {
|
|
19
|
+
const isSupportedWithoutVersion = versionAdded === true;
|
|
20
|
+
if (isSupportedWithoutVersion) return true;
|
|
21
|
+
|
|
22
|
+
const isNeverSupported = versionAdded === false;
|
|
23
|
+
if (isNeverSupported) return false;
|
|
24
|
+
|
|
25
|
+
return compareVersions(targetVersion, versionAdded) >= 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function keepLowestVersionPerBrowser(browsers) {
|
|
29
|
+
const lowestByName = new Map();
|
|
30
|
+
|
|
31
|
+
browsers.forEach((browser) => {
|
|
32
|
+
const current = lowestByName.get(browser.name);
|
|
33
|
+
const isLower = !current || compareVersions(browser.version, current.version) < 0;
|
|
34
|
+
if (isLower) lowestByName.set(browser.name, browser);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return Array.from(lowestByName.values());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getUnsupportedBrowsers(
|
|
41
|
+
featureName,
|
|
42
|
+
targetBrowsers,
|
|
43
|
+
browserSupport = FEATURE_BROWSER_SUPPORT,
|
|
44
|
+
) {
|
|
45
|
+
const support = browserSupport[featureName];
|
|
46
|
+
const hasNoTargets = !targetBrowsers || targetBrowsers.length === 0;
|
|
47
|
+
const hasNoData = !support || hasNoTargets;
|
|
48
|
+
if (hasNoData) return [];
|
|
49
|
+
|
|
50
|
+
const unsupported = targetBrowsers.filter((browser) => {
|
|
51
|
+
const versionAdded = support[browser.name];
|
|
52
|
+
const hasBrowserData = versionAdded !== undefined;
|
|
53
|
+
if (!hasBrowserData) return false;
|
|
54
|
+
return !isVersionSupported(browser.version, versionAdded);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return keepLowestVersionPerBrowser(unsupported).map((browser) => ({
|
|
58
|
+
name: browser.name,
|
|
59
|
+
version: browser.version,
|
|
60
|
+
requiredVersion: support[browser.name],
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatBrowserRequirement({ name, version, requiredVersion }) {
|
|
65
|
+
const isNeverSupported = requiredVersion === false;
|
|
66
|
+
if (isNeverSupported) return `${name} ${version} has no support`;
|
|
67
|
+
return `${name} ${version} requires ${requiredVersion}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatFeatureWithBrowsers(featureName, unsupportedBrowsers) {
|
|
71
|
+
const hasNoBrowsers = !unsupportedBrowsers || unsupportedBrowsers.length === 0;
|
|
72
|
+
if (hasNoBrowsers) return featureName;
|
|
73
|
+
|
|
74
|
+
const requirements = unsupportedBrowsers.map(formatBrowserRequirement).join(", ");
|
|
75
|
+
return `${featureName} (${requirements})`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function formatTargetBrowsers(targetBrowsers) {
|
|
79
|
+
return targetBrowsers.map((browser) => `${browser.name} ${browser.version}`).join(", ");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
compareVersions,
|
|
84
|
+
isVersionSupported,
|
|
85
|
+
getUnsupportedBrowsers,
|
|
86
|
+
formatBrowserRequirement,
|
|
87
|
+
formatFeatureWithBrowsers,
|
|
88
|
+
formatTargetBrowsers,
|
|
89
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "es-check",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.8.0",
|
|
4
4
|
"description": "Checks the ECMAScript version of .js glob against a specified version of ECMAScript with a shell command",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"check for es6",
|
|
@@ -75,9 +75,10 @@
|
|
|
75
75
|
"release:tag:dry": "node scripts/release-tag.mjs --dry-run",
|
|
76
76
|
"release:dry": "release-it --dry-run --no-git.requireUpstream",
|
|
77
77
|
"report:coverage": "mkdir -p coverage && node tests/scripts/test-runner.js unit --coverage --reporter=lcov --reporter-destination=coverage/lcov.info",
|
|
78
|
-
"scripts:generate": "pnpm run scripts:generate:requirements && pnpm run scripts:generate:polyfills",
|
|
78
|
+
"scripts:generate": "pnpm run scripts:generate:requirements && pnpm run scripts:generate:polyfills && pnpm run scripts:generate:browser-support",
|
|
79
79
|
"scripts:generate:requirements": "node scripts/generate/requirements/parser.mjs",
|
|
80
80
|
"scripts:generate:polyfills": "node scripts/generate/polyfills/core.mjs",
|
|
81
|
+
"scripts:generate:browser-support": "node scripts/generate/browser-support/index.mjs",
|
|
81
82
|
"test": "node tests/scripts/test-runner.js unit && npm run test:e2e",
|
|
82
83
|
"test:unit": "node tests/scripts/test-runner.js unit",
|
|
83
84
|
"test:unit:verbose": "node tests/scripts/test-runner.js unit --verbose",
|
|
@@ -100,7 +101,7 @@
|
|
|
100
101
|
"fast-glob": "^3.3.3"
|
|
101
102
|
},
|
|
102
103
|
"devDependencies": {
|
|
103
|
-
"@mdn/browser-compat-data": "^8.1.
|
|
104
|
+
"@mdn/browser-compat-data": "^8.1.2",
|
|
104
105
|
"codependence": "1.0.15",
|
|
105
106
|
"core-js-compat": "^3.50.0",
|
|
106
107
|
"oxfmt": "^0.67.0",
|