es-check 9.7.2 → 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 CHANGED
@@ -140,13 +140,13 @@ Here's a comprehensive list of all available options:
140
140
  | `--quiet` | Quiet mode: only displays warn and error messages (default: false) |
141
141
  | `--looseGlobMatching` | Doesn't fail if no files are found in some globs/files (default: false) |
142
142
  | `--silent` | Silent mode: does not output anything, giving no indication of success or failure other than the exit code (default: false) |
143
- | `--checkFeatures` | Check for actual ES version specific features (default: false) |
143
+ | `--checkFeatures` | Check ES features; with `--checkBrowser`, also check support in target browsers (default: false) |
144
144
  | `--checkForPolyfills` | Consider polyfills when checking features (only works with --checkFeatures) (default: false) |
145
145
  | `--ignorePolyfillable [lib]` | Ignore polyfillable features; optionally specify library (e.g., `core-js`) to limit scope |
146
146
  | `--ignore <features>` | Comma-separated list of features to ignore, e.g., "ErrorCause,TopLevelAwait" |
147
147
  | `--ignoreFile <path>` | Path to JSON file containing features to ignore |
148
148
  | `--allowList <features>` | Comma-separated list of features to allow even in lower ES versions, e.g., "const,let" |
149
- | `--checkBrowser` | Use browserslist configuration to determine ES version (default: false) |
149
+ | `--checkBrowser` | Select an ES target from Browserslist; add `--checkFeatures` for per-browser feature checks (default: false) |
150
150
  | `--browserslistQuery <query>` | Custom browserslist query (e.g., "last 2 versions") |
151
151
  | `--browserslistPath <path>` | Path to custom browserslist configuration (default: uses standard browserslist config resolution) |
152
152
  | `--browserslistEnv <env>` | Browserslist environment to use (default: production) |
@@ -229,6 +229,16 @@ es-check --checkBrowser --browserslistQuery="last 2 versions" ./dist/**/*.js
229
229
  es-check --checkBrowser --browserslistQuery=">0.5%, not dead" --checkFeatures ./dist/**/*.js
230
230
  ```
231
231
 
232
+ When `--checkBrowser` is combined with `--checkFeatures`, every detected feature is also checked
233
+ against the individual browser versions resolved from browserslist using
234
+ [MDN browser-compat-data](https://github.com/mdn/browser-compat-data). For example
235
+ `--browserslistQuery="ios_saf 15.0"` maps to ES2022, but `Object.hasOwn` and class static blocks are still
236
+ reported because iOS Safari added them in 15.4 and 16.4. Failures name the browser that lacks support:
237
+
238
+ ```
239
+ Unsupported features detected: ObjectHasOwn (safari_ios 15.0 requires 15.4). These require a higher ES version than 13 or are not supported by the target browsers.
240
+ ```
241
+
232
242
  **Using browserlist just like an es version**
233
243
 
234
244
  ```sh
@@ -332,12 +342,12 @@ Here's an example of what an `.escheckrc` file will look like:
332
342
  | `not` | Array | Files or glob patterns to exclude |
333
343
  | `allowHashBang` | Boolean | Whether to allow hash bang in files |
334
344
  | `looseGlobMatching` | Boolean | Whether to ignore missing files in globs |
335
- | `checkFeatures` | Boolean | Whether to check for ES version specific features |
345
+ | `checkFeatures` | Boolean | Check ES features and, with `checkBrowser`, support in target browsers |
336
346
  | `checkForPolyfills` | Boolean | Whether to consider polyfills when checking features |
337
347
  | `ignorePolyfillable` | Boolean/String | Ignore polyfillable features; set to library name (e.g., `"core-js"`) to limit scope |
338
348
  | `ignore` | Array | Features to ignore when checking |
339
349
  | `allowList` | Array | Features to allow even in lower ES versions |
340
- | `checkBrowser` | Boolean | Whether to use browserslist configuration to determine ES version |
350
+ | `checkBrowser` | Boolean | Select an ES target from Browserslist; use with `checkFeatures` for browser checks |
341
351
  | `browserslistQuery` | String | Custom browserslist query to use |
342
352
  | `browserslistPath` | String | Path to custom browserslist configuration |
343
353
  | `browserslistEnv` | String | Browserslist environment to use |
@@ -513,6 +523,8 @@ es-check --checkBrowser --browserslistEnv="production" ./dist/**/*.js
513
523
 
514
524
  **Combining with feature checking:**
515
525
 
526
+ Add `--checkFeatures` to check detected features against individual target browser versions using MDN compatibility data, as well as the selected ES version:
527
+
516
528
  ```sh
517
529
  es-check --checkBrowser --checkFeatures ./dist/**/*.js
518
530
  ```
@@ -1,5 +1,16 @@
1
1
  const browserslist = require("browserslist");
2
- const { BROWSER_TO_ES_VERSION } = require("./constants/versions");
2
+ const { BROWSER_TO_ES_VERSION, BROWSERSLIST_TO_BCD } = require("./constants/versions");
3
+
4
+ const UNVERSIONED_BROWSER_VERSIONS = new Set(["all", "TP"]);
5
+
6
+ function resolveBrowsers(options = {}) {
7
+ const { browserslistPath, browserslistEnv, browserslistQuery } = options;
8
+
9
+ return browserslist(browserslistQuery ?? null, {
10
+ config: browserslistPath,
11
+ env: browserslistEnv,
12
+ });
13
+ }
3
14
 
4
15
  function getESVersionForBrowser(browser, version) {
5
16
  const defaultVersion = 5;
@@ -42,13 +53,10 @@ function getESVersionForBrowser(browser, version) {
42
53
  }
43
54
 
44
55
  function getESVersionFromBrowserslist(options = {}) {
45
- const { browserslistPath, browserslistEnv, browserslistQuery } = options;
56
+ const { browserslistPath, browserslistEnv } = options;
46
57
 
47
58
  try {
48
- const browsers = browserslist(browserslistQuery ?? null, {
49
- config: browserslistPath,
50
- env: browserslistEnv,
51
- });
59
+ const browsers = resolveBrowsers(options);
52
60
 
53
61
  const hasNoBrowsers = !browsers || browsers.length === 0;
54
62
  if (hasNoBrowsers) {
@@ -99,7 +107,47 @@ function getESVersionFromBrowserslist(options = {}) {
99
107
  }
100
108
  }
101
109
 
110
+ function parseBrowserVersion(version) {
111
+ const hasVersion = typeof version === "string" && version.length > 0;
112
+ if (!hasVersion) return null;
113
+
114
+ const lowerBound = version.split("-")[0];
115
+ const isUnversioned = UNVERSIONED_BROWSER_VERSIONS.has(lowerBound);
116
+ if (isUnversioned) return null;
117
+
118
+ return lowerBound;
119
+ }
120
+
121
+ function toTargetBrowser(entry) {
122
+ const [browserslistName, version] = entry.split(" ");
123
+ const name = BROWSERSLIST_TO_BCD[browserslistName];
124
+ const parsedVersion = parseBrowserVersion(version);
125
+
126
+ const isUnknownBrowser = !name;
127
+ const isUnknownVersion = parsedVersion === null;
128
+ const isUnknownTarget = isUnknownBrowser || isUnknownVersion;
129
+ if (isUnknownTarget) return null;
130
+
131
+ return { name, version: parsedVersion, browserslistName };
132
+ }
133
+
134
+ function getTargetBrowsers(options = {}) {
135
+ let browsers;
136
+
137
+ try {
138
+ browsers = resolveBrowsers(options);
139
+ } catch {
140
+ return [];
141
+ }
142
+
143
+ const hasNoBrowsers = !browsers || browsers.length === 0;
144
+ if (hasNoBrowsers) return [];
145
+
146
+ return browsers.map(toTargetBrowser).filter(Boolean);
147
+ }
148
+
102
149
  module.exports = {
103
150
  getESVersionFromBrowserslist,
104
151
  getESVersionForBrowser,
152
+ getTargetBrowsers,
105
153
  };
@@ -78,7 +78,7 @@ function processConfig(config, context) {
78
78
  return { hasErrors: true, shouldContinue: true };
79
79
  }
80
80
 
81
- const { ecmaVersion } = versionResult;
81
+ const { ecmaVersion, targetBrowsers } = versionResult;
82
82
  const useLatestForParsing = config.checkFeatures;
83
83
  const targetEsVersion = parseInt(ecmaVersion, 10);
84
84
 
@@ -130,6 +130,7 @@ function processConfig(config, context) {
130
130
  logger,
131
131
  isDebug,
132
132
  ecmaVersion,
133
+ targetBrowsers,
133
134
  });
134
135
 
135
136
  const results = processBatchedFiles(filteredFiles, processFile, batchSize);
@@ -199,18 +199,20 @@ function determineEcmaVersion(config, options) {
199
199
  const isBrowserslistCheck = Boolean(expectedEcmaVersion === "checkBrowser" || checkBrowser);
200
200
 
201
201
  if (isBrowserslistCheck) {
202
- const browserslistQuery = config.browserslistQuery;
202
+ const browserslistOptions = {
203
+ browserslistQuery: config.browserslistQuery,
204
+ browserslistPath: config.browserslistPath,
205
+ browserslistEnv: config.browserslistEnv,
206
+ };
203
207
  let browserslistError = null;
204
208
  let ecmaVersion = null;
209
+ let targetBrowsers = [];
205
210
 
206
211
  try {
207
- const { getESVersionFromBrowserslist } = require("../browserslist");
208
- const esVersionFromBrowserslist = getESVersionFromBrowserslist({
209
- browserslistQuery,
210
- browserslistPath: config.browserslistPath,
211
- browserslistEnv: config.browserslistEnv,
212
- });
212
+ const { getESVersionFromBrowserslist, getTargetBrowsers } = require("../browserslist");
213
+ const esVersionFromBrowserslist = getESVersionFromBrowserslist(browserslistOptions);
213
214
  ecmaVersion = esVersionFromBrowserslist.toString();
215
+ targetBrowsers = getTargetBrowsers(browserslistOptions);
214
216
  } catch (err) {
215
217
  browserslistError = err;
216
218
  }
@@ -220,7 +222,10 @@ function determineEcmaVersion(config, options) {
220
222
  const shouldDebug = hasNoError && isDebug;
221
223
 
222
224
  if (shouldDebug) {
223
- logger.debug(`ES-Check: Using ES${ecmaVersion} based on browserslist configuration`);
225
+ const { formatTargetBrowsers } = require("../helpers/browserSupport");
226
+ logger.debug(
227
+ `ES-Check: Using ES${ecmaVersion} based on browserslist configuration (target browsers: ${formatTargetBrowsers(targetBrowsers)})`,
228
+ );
224
229
  }
225
230
 
226
231
  if (hasError) {
@@ -229,10 +234,10 @@ function determineEcmaVersion(config, options) {
229
234
  isNodeAPI,
230
235
  allErrors,
231
236
  });
232
- return { ecmaVersion: null, hasError: true };
237
+ return { ecmaVersion: null, targetBrowsers: [], hasError: true };
233
238
  }
234
239
 
235
- return { ecmaVersion, hasError: false };
240
+ return { ecmaVersion, targetBrowsers, hasError: false };
236
241
  }
237
242
 
238
243
  const mappedVersion = ECMA_VERSION_MAP[expectedEcmaVersion];
@@ -248,11 +253,11 @@ function determineEcmaVersion(config, options) {
248
253
 
249
254
  if (isInvalidVersion) {
250
255
  handleInvalidVersion({ logger, isNodeAPI, allErrors });
251
- return { ecmaVersion: null, hasError: true };
256
+ return { ecmaVersion: null, targetBrowsers: [], hasError: true };
252
257
  }
253
258
 
254
259
  const ecmaVersion = String(ECMA_VERSION_TO_NUMBER[mappedVersion]);
255
- return { ecmaVersion, hasError: false };
260
+ return { ecmaVersion, targetBrowsers: [], hasError: false };
256
261
  }
257
262
 
258
263
  function filterIgnoredFiles(files, pathsToIgnore, globOpts) {
@@ -283,7 +288,17 @@ function filterIgnoredFiles(files, pathsToIgnore, globOpts) {
283
288
  return files.filter(shouldKeepFile);
284
289
  }
285
290
 
286
- function processFullAST(code, acornOpts, file, config, ignoreList, ecmaVersion, isDebug, logger) {
291
+ function processFullAST(
292
+ code,
293
+ acornOpts,
294
+ file,
295
+ config,
296
+ ignoreList,
297
+ ecmaVersion,
298
+ isDebug,
299
+ logger,
300
+ targetBrowsers,
301
+ ) {
287
302
  const needsFullAST = config.checkFeatures;
288
303
  const parserOptions = needsFullAST
289
304
  ? acornOpts
@@ -317,6 +332,7 @@ function processFullAST(code, acornOpts, file, config, ignoreList, ecmaVersion,
317
332
  ast,
318
333
  checkForPolyfills: config.checkForPolyfills,
319
334
  ignorePolyfillable: config.ignorePolyfillable,
335
+ targetBrowsers,
320
336
  });
321
337
  foundFeatures = result.foundFeatures;
322
338
  unsupportedFeatures = result.unsupportedFeatures;
@@ -398,7 +414,7 @@ function processFullAST(code, acornOpts, file, config, ignoreList, ecmaVersion,
398
414
  }
399
415
 
400
416
  function createFileProcessor(config, options) {
401
- const { acornOpts, ignoreList, logger, isDebug, ecmaVersion } = options;
417
+ const { acornOpts, ignoreList, logger, isDebug, ecmaVersion, targetBrowsers } = options;
402
418
 
403
419
  return (file) => {
404
420
  const useCache = config.cache !== false;
@@ -411,7 +427,17 @@ function createFileProcessor(config, options) {
411
427
  logger.debug(`ES-Check: checking ${file}`);
412
428
  }
413
429
 
414
- return processFullAST(code, acornOpts, file, config, ignoreList, ecmaVersion, isDebug, logger);
430
+ return processFullAST(
431
+ code,
432
+ acornOpts,
433
+ file,
434
+ config,
435
+ ignoreList,
436
+ ecmaVersion,
437
+ isDebug,
438
+ logger,
439
+ targetBrowsers,
440
+ );
415
441
  };
416
442
  }
417
443