es-check 9.1.4 → 9.2.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 +82 -10
- package/constants.js +200 -53
- package/detectFeatures.js +13 -41
- package/index.js +58 -33
- package/package.json +13 -8
- package/polyfillDetector.js +27 -57
- package/utils.js +85 -3
package/README.md
CHANGED
|
@@ -24,10 +24,19 @@ Ensuring that JavaScript files can pass ES Check is important in a [modular and
|
|
|
24
24
|
|
|
25
25
|
**ES Check** version 9 is a major release update that can enforce more ES version specific features checks, implements initial browserslist integration, basic (naive) polyfill detection, and supports an allowlist. To enable ecmaVersion specific checks, pass the `--checkFeatures` flag. To enable browserslist integration, pass the `--checkBrowser` flag. To enable polyfill detection, pass the `--checkForPolyfills` flag. There is also more config file support. Besides this, there are other feature updates based on user feedback. This version should not break any existing scripts but, as significant changes/features have been added and it's know that es-check supports protecting against breaking errors going to production, a major version bump feels appropriate. Please report any issues!
|
|
26
26
|
|
|
27
|
+
|
|
28
|
+
### `--checkFeatures`
|
|
29
|
+
|
|
27
30
|
```sh
|
|
28
31
|
es-check es6 './dist/**/*.js' --checkFeatures
|
|
29
32
|
```
|
|
30
33
|
|
|
34
|
+
### `checkBrowser --browserslistQuery='<broswerslist query>'`
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
es-check checkBrowser ./dist/**/*.js --browserslistQuery="last 2 versions"
|
|
38
|
+
```
|
|
39
|
+
|
|
31
40
|
---
|
|
32
41
|
|
|
33
42
|
<p align="center">
|
|
@@ -82,22 +91,20 @@ ES Check checks syntax out of the box—to protect against breaking errors going
|
|
|
82
91
|
|
|
83
92
|
The images below demonstrate command line scripts and their corresponding logged results.
|
|
84
93
|
|
|
85
|
-
Pass
|
|
94
|
+
### Pass
|
|
95
|
+
|
|
86
96
|

|
|
87
97
|
|
|
88
|
-
Fail
|
|
98
|
+
### Fail
|
|
99
|
+
|
|
89
100
|

|
|
90
101
|
|
|
91
|
-
**ES Check** is run above with node commands. It can also be run within npm scripts, ci tools, or testing suites.
|
|
102
|
+
**ES Check** is run above with node commands. It can also be run within npm scripts, ci tools, or testing suites. It also provide minimal support for use in node apps.
|
|
92
103
|
|
|
93
104
|
---
|
|
94
105
|
|
|
95
106
|
## API
|
|
96
107
|
|
|
97
|
-
**ES Check** provides the necessities. It accepts its place as a JavaScript matcher/tester.
|
|
98
|
-
|
|
99
|
-
### General Information
|
|
100
|
-
|
|
101
108
|
```sh
|
|
102
109
|
|
|
103
110
|
# USAGE
|
|
@@ -145,6 +152,7 @@ Here's a comprehensive list of all available options:
|
|
|
145
152
|
| `--browserslistPath <path>` | Path to custom browserslist configuration (default: uses standard browserslist config resolution) |
|
|
146
153
|
| `--browserslistEnv <env>` | Browserslist environment to use (default: production) |
|
|
147
154
|
| `--config <path>` | Path to custom .escheckrc config file |
|
|
155
|
+
| `--batchSize <number>` | Number of files to process concurrently (0 for unlimited, default: 0) |
|
|
148
156
|
| `-h, --help` | Display help for command |
|
|
149
157
|
|
|
150
158
|
### Shell Completion
|
|
@@ -264,7 +272,7 @@ es-check checkBrowser ./dist/**/*.js
|
|
|
264
272
|
|
|
265
273
|
## Usage
|
|
266
274
|
|
|
267
|
-
ES Check is a shell command CLI. It is run in [shell tool](http://linuxcommand.org/lc3_learning_the_shell.php) like Terminal, ITerm, or Hyper. It takes in two arguments: an [ECMAScript version](https://www.w3schools.com/js/js_versions.asp) (`<ECMAScript version>`) and files (`[files]`) in [globs](http://searchsecurity.techtarget.com/definition/globbing).
|
|
275
|
+
ES Check is mainly a shell command CLI. It is run in [shell tool](http://linuxcommand.org/lc3_learning_the_shell.php) like Terminal, ITerm, or Hyper. It takes in two arguments: an [ECMAScript version](https://www.w3schools.com/js/js_versions.asp) (`<ECMAScript version>`) and files (`[files]`) in [globs](http://searchsecurity.techtarget.com/definition/globbing).
|
|
268
276
|
|
|
269
277
|
Here are some example of **es check** scripts that could be run:
|
|
270
278
|
|
|
@@ -276,6 +284,36 @@ es-check es6 ./js/*.js
|
|
|
276
284
|
es-check es6 ./js/*.js ./dist/*.js
|
|
277
285
|
```
|
|
278
286
|
|
|
287
|
+
### Using ES Check in Node
|
|
288
|
+
|
|
289
|
+
In addition to its CLI utility, ES Check can be used programmatically in Node.js applications:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
const { runChecks, loadConfig } = require('es-check');
|
|
293
|
+
|
|
294
|
+
async function checkMyFiles() {
|
|
295
|
+
const config = {
|
|
296
|
+
ecmaVersion: 'es5',
|
|
297
|
+
files: ['dist/**/*.js'],
|
|
298
|
+
module: false,
|
|
299
|
+
checkFeatures: true
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
await runChecks([config], logger);
|
|
304
|
+
console.log('All files passed ES5 check!');
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error('Some files failed the ES check');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function checkWithConfig() {
|
|
312
|
+
const configs = await loadConfig('./.escheckrc');
|
|
313
|
+
await runChecks(configs, logger);
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
279
317
|
---
|
|
280
318
|
|
|
281
319
|
## Configuration
|
|
@@ -485,6 +523,40 @@ es-check --checkBrowser --checkFeatures ./dist/**/*.js
|
|
|
485
523
|
|
|
486
524
|
---
|
|
487
525
|
|
|
526
|
+
## Performance Optimization
|
|
527
|
+
|
|
528
|
+
ES Check provides the `--batchSize` option to optimize performance for different scenarios:
|
|
529
|
+
|
|
530
|
+
```sh
|
|
531
|
+
# Process all files in parallel (default)
|
|
532
|
+
es-check es5 './dist/**/*.js' --batchSize 0
|
|
533
|
+
|
|
534
|
+
# Process 10 files at a time (memory-constrained environments)
|
|
535
|
+
es-check es5 './dist/**/*.js' --batchSize 10
|
|
536
|
+
|
|
537
|
+
# Process 50 files at a time (balanced approach)
|
|
538
|
+
es-check es5 './dist/**/*.js' --batchSize 50
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Performance Guidelines
|
|
542
|
+
|
|
543
|
+
| Scenario | Recommended `--batchSize` | Reason |
|
|
544
|
+
|----------|---------------------------|---------|
|
|
545
|
+
| Small codebases (< 100 files) | `0` (unlimited) | Maximum parallelism for fastest results |
|
|
546
|
+
| Medium codebases (100-500 files) | `0` or `50` | Balance between speed and memory |
|
|
547
|
+
| Large codebases (> 500 files) | `50-100` | Prevent memory spikes |
|
|
548
|
+
| CI/CD with limited memory | `10-20` | Conservative memory usage |
|
|
549
|
+
| Local development | `0` (default) | Utilize available hardware |
|
|
550
|
+
|
|
551
|
+
### Recent Performance Improvements
|
|
552
|
+
|
|
553
|
+
As of August 2025, ES Check has been optimized with:
|
|
554
|
+
- **Single-parse optimization**: Files are parsed once and the AST is reused
|
|
555
|
+
- **Async file processing**: Non-blocking I/O for better performance
|
|
556
|
+
- **Configurable batch processing**: Fine-tune based on your needs
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
488
560
|
## Checking node_modules Dependencies
|
|
489
561
|
|
|
490
562
|
To check node_modules dependencies for ES compatibility:
|
|
@@ -503,13 +575,13 @@ A simple example script is available in `examples/check-node-modules.js`.
|
|
|
503
575
|
|
|
504
576
|
## Acknowledgements
|
|
505
577
|
|
|
506
|
-
ES Check is a small utility using powerful tools that [Isaac Z. Schlueter](https://github.com/isaacs), [Marijn Haverbeke](https://github.com/marijnh), and [Matthias Etienne](https://github.com/mattallty) built. [ES Checker](https://github.com/ruanyf/es-checker) by [Ruan YiFeng](https://github.com/ruanyf) checks the JavaScript version supported within a [browser](http://ruanyf.github.io/es-checker/) at run time. ES Check offers similar feedback to ES Checker but at build time and is specific to the product that is using it. ES Check was started after reading
|
|
578
|
+
ES Check is a small utility using powerful tools that [Isaac Z. Schlueter](https://github.com/isaacs), [Marijn Haverbeke](https://github.com/marijnh), and [Matthias Etienne](https://github.com/mattallty) built. [ES Checker](https://github.com/ruanyf/es-checker) by [Ruan YiFeng](https://github.com/ruanyf) checks the JavaScript version supported within a [browser](http://ruanyf.github.io/es-checker/) at run time. ES Check offers similar feedback to ES Checker but at build time and is specific to the product that is using it. ES Check was started after reading [Philip Walton](https://github.com/philipwalton)'s post about [deploying es2015 code to production today](https://philipwalton.com/articles/deploying-es2015-code-in-production-today/).
|
|
507
579
|
|
|
508
580
|
---
|
|
509
581
|
|
|
510
582
|
## Contributing
|
|
511
583
|
|
|
512
|
-
ES Check has 7 dependencies: [acorn and acorn-walk](https://github.com/ternjs/acorn/), [fast-glob](https://github.com/mrmlnc/fast-glob), [supports-color](github.com/chalk/supports-color), [winston](https://github.com/winstonjs/winston), [browserslist](https://github.com/browserslist/browserslist), and [commander](https://github.com/tj/commander). To contribute, file an [issue](https://github.com/yowainwright/es-check/issues) or submit a pull request.
|
|
584
|
+
ES Check has 7 dependencies: [acorn and acorn-walk](https://github.com/ternjs/acorn/), [fast-glob](https://github.com/mrmlnc/fast-glob), [supports-color](https://github.com/chalk/supports-color), [winston](https://github.com/winstonjs/winston), [browserslist](https://github.com/browserslist/browserslist), and [commander](https://github.com/tj/commander). To contribute, file an [issue](https://github.com/yowainwright/es-check/issues) or submit a pull request.
|
|
513
585
|
|
|
514
586
|
To update es versions, check out these lines of code [here](https://github.com/yowainwright/es-check/blob/main/index.js#L92-L153) and [here (in acorn.js)](https://github.com/acornjs/acorn/blob/3221fa54f9dea30338228b97210c4f1fd332652d/acorn/src/acorn.d.ts#L586).
|
|
515
587
|
|
package/constants.js
CHANGED
|
@@ -502,6 +502,100 @@ const ES_FEATURES = {
|
|
|
502
502
|
property: 'with',
|
|
503
503
|
},
|
|
504
504
|
},
|
|
505
|
+
|
|
506
|
+
// ----------------------------------------------------------
|
|
507
|
+
// ES14 / ES2023 (continued)
|
|
508
|
+
// ----------------------------------------------------------
|
|
509
|
+
ArrayFindLast: {
|
|
510
|
+
minVersion: 14,
|
|
511
|
+
example: 'arr.findLast(x => x > 5)',
|
|
512
|
+
astInfo: {
|
|
513
|
+
nodeType: 'CallExpression',
|
|
514
|
+
property: 'findLast',
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
ArrayFindLastIndex: {
|
|
518
|
+
minVersion: 14,
|
|
519
|
+
example: 'arr.findLastIndex(x => x > 5)',
|
|
520
|
+
astInfo: {
|
|
521
|
+
nodeType: 'CallExpression',
|
|
522
|
+
property: 'findLastIndex',
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
// ----------------------------------------------------------
|
|
527
|
+
// ES15 / ES2024
|
|
528
|
+
// ----------------------------------------------------------
|
|
529
|
+
ObjectHasOwn: {
|
|
530
|
+
minVersion: 13,
|
|
531
|
+
example: 'Object.hasOwn(obj, "prop")',
|
|
532
|
+
astInfo: {
|
|
533
|
+
nodeType: 'CallExpression',
|
|
534
|
+
object: 'Object',
|
|
535
|
+
property: 'hasOwn',
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
StringIsWellFormed: {
|
|
539
|
+
minVersion: 15,
|
|
540
|
+
example: 'str.isWellFormed()',
|
|
541
|
+
astInfo: {
|
|
542
|
+
nodeType: 'CallExpression',
|
|
543
|
+
property: 'isWellFormed',
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
StringToWellFormed: {
|
|
547
|
+
minVersion: 15,
|
|
548
|
+
example: 'str.toWellFormed()',
|
|
549
|
+
astInfo: {
|
|
550
|
+
nodeType: 'CallExpression',
|
|
551
|
+
property: 'toWellFormed',
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
RegExpUnicodeSetFlag: {
|
|
555
|
+
minVersion: 15,
|
|
556
|
+
example: '/pattern/v',
|
|
557
|
+
astInfo: {
|
|
558
|
+
nodeType: 'RegExpLiteral',
|
|
559
|
+
flags: 'v',
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
|
|
563
|
+
// ----------------------------------------------------------
|
|
564
|
+
// ES16 / ES2025
|
|
565
|
+
// ----------------------------------------------------------
|
|
566
|
+
ArrayGroup: {
|
|
567
|
+
minVersion: 16,
|
|
568
|
+
example: 'arr.group(x => x.category)',
|
|
569
|
+
astInfo: {
|
|
570
|
+
nodeType: 'CallExpression',
|
|
571
|
+
property: 'group',
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
ArrayGroupToMap: {
|
|
575
|
+
minVersion: 16,
|
|
576
|
+
example: 'arr.groupToMap(x => x.category)',
|
|
577
|
+
astInfo: {
|
|
578
|
+
nodeType: 'CallExpression',
|
|
579
|
+
property: 'groupToMap',
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
PromiseTry: {
|
|
583
|
+
minVersion: 16,
|
|
584
|
+
example: 'Promise.try(() => syncOrAsyncFunction())',
|
|
585
|
+
astInfo: {
|
|
586
|
+
nodeType: 'CallExpression',
|
|
587
|
+
object: 'Promise',
|
|
588
|
+
property: 'try',
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
DuplicateNamedCaptureGroups: {
|
|
592
|
+
minVersion: 16,
|
|
593
|
+
example: '/(?<name>a)|(?<name>b)/',
|
|
594
|
+
astInfo: {
|
|
595
|
+
nodeType: 'RegExpLiteral',
|
|
596
|
+
duplicateNamedGroups: true,
|
|
597
|
+
},
|
|
598
|
+
},
|
|
505
599
|
};
|
|
506
600
|
|
|
507
601
|
const NODE_TYPES = {
|
|
@@ -512,6 +606,45 @@ const NODE_TYPES = {
|
|
|
512
606
|
NEW_EXPRESSION: 'NewExpression',
|
|
513
607
|
};
|
|
514
608
|
|
|
609
|
+
const POLYFILL_PATTERNS = [
|
|
610
|
+
// Array methods
|
|
611
|
+
{ pattern: /Array\.prototype\.toSorted/, feature: 'ArrayToSorted' },
|
|
612
|
+
{ pattern: /Array\.prototype\.findLast/, feature: 'ArrayFindLast' },
|
|
613
|
+
{ pattern: /Array\.prototype\.findLastIndex/, feature: 'ArrayFindLastIndex' },
|
|
614
|
+
{ pattern: /Array\.prototype\.at/, feature: 'ArrayAt' },
|
|
615
|
+
|
|
616
|
+
// String methods
|
|
617
|
+
{ pattern: /String\.prototype\.replaceAll/, feature: 'StringReplaceAll' },
|
|
618
|
+
{ pattern: /String\.prototype\.matchAll/, feature: 'StringMatchAll' },
|
|
619
|
+
{ pattern: /String\.prototype\.at/, feature: 'StringAt' },
|
|
620
|
+
|
|
621
|
+
// Object methods
|
|
622
|
+
{ pattern: /Object\.hasOwn/, feature: 'ObjectHasOwn' },
|
|
623
|
+
|
|
624
|
+
// Promise methods
|
|
625
|
+
{ pattern: /Promise\.any/, feature: 'PromiseAny' },
|
|
626
|
+
|
|
627
|
+
// RegExp methods
|
|
628
|
+
{ pattern: /RegExp\.prototype\.exec/, feature: 'RegExpExec' },
|
|
629
|
+
|
|
630
|
+
// Global methods
|
|
631
|
+
{ pattern: /globalThis/, feature: 'GlobalThis' },
|
|
632
|
+
];
|
|
633
|
+
|
|
634
|
+
const IMPORT_PATTERNS = [
|
|
635
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.array\.to-sorted['"]/, feature: 'ArrayToSorted' },
|
|
636
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.array\.find-last['"]/, feature: 'ArrayFindLast' },
|
|
637
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.array\.find-last-index['"]/, feature: 'ArrayFindLastIndex' },
|
|
638
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.array\.at['"]/, feature: 'ArrayAt' },
|
|
639
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.string\.replace-all['"]/, feature: 'StringReplaceAll' },
|
|
640
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.string\.match-all['"]/, feature: 'StringMatchAll' },
|
|
641
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.string\.at['"]/, feature: 'StringAt' },
|
|
642
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.object\.has-own['"]/, feature: 'ObjectHasOwn' },
|
|
643
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.promise\.any['"]/, feature: 'PromiseAny' },
|
|
644
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.regexp\.exec['"]/, feature: 'RegExpExec' },
|
|
645
|
+
{ pattern: /from\s+['"]core-js\/modules\/es\.global-this['"]/, feature: 'GlobalThis' },
|
|
646
|
+
];
|
|
647
|
+
|
|
515
648
|
const BROWSER_TO_ES_VERSION = {
|
|
516
649
|
ie: {
|
|
517
650
|
'11': 5
|
|
@@ -578,74 +711,88 @@ const JS_VERSIONS = [
|
|
|
578
711
|
'es3', 'es4', 'es5', 'es6', 'es2015',
|
|
579
712
|
'es7', 'es2016', 'es8', 'es2017', 'es9', 'es2018',
|
|
580
713
|
'es10', 'es2019', 'es11', 'es2020', 'es12', 'es2021',
|
|
581
|
-
'es13', 'es2022', 'es14', 'es2023', 'checkBrowser'
|
|
714
|
+
'es13', 'es2022', 'es14', 'es2023', 'es15', 'es2024', 'es16', 'es2025', 'checkBrowser'
|
|
582
715
|
];
|
|
583
716
|
|
|
584
717
|
/**
|
|
585
|
-
* Maps feature names from ES_FEATURES to their polyfill patterns
|
|
586
|
-
* This
|
|
718
|
+
* Maps feature names from ES_FEATURES to their polyfill patterns.
|
|
719
|
+
* This version uses standardized keys and robust regex for both manual and module polyfills.
|
|
587
720
|
*/
|
|
588
721
|
const FEATURE_TO_POLYFILL_MAP = {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
/
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
/
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
/
|
|
604
|
-
/from\s+['"]core-js\/modules\/es\.array\.at['"]/
|
|
605
|
-
],
|
|
722
|
+
// ES2015 (ES6)
|
|
723
|
+
'Array.from': [/\bArray\.from\s*=/, /['"]core-js\/modules\/es\.array\.from['"]/],
|
|
724
|
+
'Array.of': [/\bArray\.of\s*=/, /['"]core-js\/modules\/es\.array\.of['"]/],
|
|
725
|
+
'Array.prototype.find': [/\bArray\.prototype\.find\s*=/, /['"]core-js\/modules\/es\.array\.find['"]/],
|
|
726
|
+
'Array.prototype.findIndex': [/\bArray\.prototype\.findIndex\s*=/, /['"]core-js\/modules\/es\.array\.find-index['"]/],
|
|
727
|
+
'Object.assign': [/\bObject\.assign\s*=/, /['"]core-js\/modules\/es\.object\.assign['"]/, /object-assign/],
|
|
728
|
+
'Promise': [/\bPromise\s*=/, /['"]core-js\/modules\/es\.promise['"]/, /es6-promise/],
|
|
729
|
+
'String.prototype.startsWith': [/\bString\.prototype\.startsWith\s*=/, /['"]core-js\/modules\/es\.string\.starts-with['"]/],
|
|
730
|
+
'String.prototype.endsWith': [/\bString\.prototype\.endsWith\s*=/, /['"]core-js\/modules\/es\.string\.ends-with['"]/],
|
|
731
|
+
'String.prototype.includes': [/\bString\.prototype\.includes\s*=/, /['"]core-js\/modules\/es\.string\.includes['"]/],
|
|
732
|
+
'Symbol': [/\bSymbol\s*=/, /['"]core-js\/modules\/es\.symbol['"]/],
|
|
733
|
+
'Map': [/\bMap\s*=/, /['"]core-js\/modules\/es\.map['"]/],
|
|
734
|
+
'Set': [/\bSet\s*=/, /['"]core-js\/modules\/es\.set['"]/],
|
|
735
|
+
'WeakMap': [/\bWeakMap\s*=/, /['"]core-js\/modules\/es\.weak-map['"]/],
|
|
736
|
+
'WeakSet': [/\bWeakSet\s*=/, /['"]core-js\/modules\/es\.weak-set['"]/],
|
|
606
737
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
/String\.prototype\.replaceAll/,
|
|
610
|
-
/from\s+['"]core-js\/modules\/es\.string\.replace-all['"]/
|
|
611
|
-
],
|
|
612
|
-
'StringMatchAll': [
|
|
613
|
-
/String\.prototype\.matchAll/,
|
|
614
|
-
/from\s+['"]core-js\/modules\/es\.string\.match-all['"]/
|
|
615
|
-
],
|
|
616
|
-
'StringAt': [
|
|
617
|
-
/String\.prototype\.at/,
|
|
618
|
-
/from\s+['"]core-js\/modules\/es\.string\.at['"]/
|
|
619
|
-
],
|
|
738
|
+
// ES2016
|
|
739
|
+
'Array.prototype.includes': [/\bArray\.prototype\.includes\s*=/, /['"]core-js\/modules\/es\.array\.includes['"]/],
|
|
620
740
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
741
|
+
// ES2017
|
|
742
|
+
'Object.values': [/\bObject\.values\s*=/, /['"]core-js\/modules\/es\.object\.values['"]/],
|
|
743
|
+
'Object.entries': [/\bObject\.entries\s*=/, /['"]core-js\/modules\/es\.object\.entries['"]/],
|
|
744
|
+
'Object.getOwnPropertyDescriptors': [/\bObject\.getOwnPropertyDescriptors\s*=/, /['"]core-js\/modules\/es\.object\.get-own-property-descriptors['"]/],
|
|
745
|
+
'String.prototype.padStart': [/\bString\.prototype\.padStart\s*=/, /['"]core-js\/modules\/es\.string\.pad-start['"]/],
|
|
746
|
+
'String.prototype.padEnd': [/\bString\.prototype\.padEnd\s*=/, /['"]core-js\/modules\/es\.string\.pad-end['"]/],
|
|
626
747
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
/Promise\.any/,
|
|
630
|
-
/from\s+['"]core-js\/modules\/es\.promise\.any['"]/
|
|
631
|
-
],
|
|
748
|
+
// ES2018
|
|
749
|
+
'Promise.prototype.finally': [/\bPromise\.prototype\.finally\s*=/, /['"]core-js\/modules\/es\.promise\.finally['"]/],
|
|
632
750
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
751
|
+
// ES2019
|
|
752
|
+
'Array.prototype.flat': [/\bArray\.prototype\.flat\s*=/, /['"]core-js\/modules\/es\.array\.flat['"]/],
|
|
753
|
+
'Array.prototype.flatMap': [/\bArray\.prototype\.flatMap\s*=/, /['"]core-js\/modules\/es\.array\.flat-map['"]/],
|
|
754
|
+
'Object.fromEntries': [/\bObject\.fromEntries\s*=/, /['"]core-js\/modules\/es\.object\.from-entries['"]/],
|
|
755
|
+
'String.prototype.trimStart': [/\bString\.prototype\.trimStart\s*=/, /['"]core-js\/modules\/es\.string\.trim-start['"]/],
|
|
756
|
+
'String.prototype.trimEnd': [/\bString\.prototype\.trimEnd\s*=/, /['"]core-js\/modules\/es\.string\.trim-end['"]/],
|
|
638
757
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
/
|
|
642
|
-
/
|
|
643
|
-
|
|
758
|
+
// ES2020
|
|
759
|
+
'Promise.allSettled': [/\bPromise\.allSettled\s*=/, /['"]core-js\/modules\/es\.promise\.all-settled['"]/],
|
|
760
|
+
'String.prototype.matchAll': [/\bString\.prototype\.matchAll\s*=/, /['"]core-js\/modules\/es\.string\.match-all['"]/],
|
|
761
|
+
'globalThis': [/globalThis\s*=/, /['"]core-js\/modules\/es\.global-this['"]/],
|
|
762
|
+
'BigInt': [/\bBigInt\s*=/, /['"]core-js\/modules\/es\.bigint['"]/],
|
|
763
|
+
|
|
764
|
+
// ES2021
|
|
765
|
+
'Promise.any': [/\bPromise\.any\s*=/, /['"]core-js\/modules\/es\.promise\.any['"]/],
|
|
766
|
+
'String.prototype.replaceAll': [/\bString\.prototype\.replaceAll\s*=/, /['"]core-js\/modules\/es\.string\.replace-all['"]/],
|
|
767
|
+
|
|
768
|
+
// ES2022
|
|
769
|
+
'Array.prototype.at': [/\bArray\.prototype\.at\s*=/, /['"]core-js\/modules\/es\.array\.at['"]/],
|
|
770
|
+
'String.prototype.at': [/\bString\.prototype\.at\s*=/, /['"]core-js\/modules\/es\.string\.at['"]/],
|
|
771
|
+
|
|
772
|
+
// ES2023
|
|
773
|
+
'Array.prototype.findLast': [/\bArray\.prototype\.findLast\s*=/, /['"]core-js\/modules\/es\.array\.find-last['"]/],
|
|
774
|
+
'Array.prototype.findLastIndex': [/\bArray\.prototype\.findLastIndex\s*=/, /['"]core-js\/modules\/es\.array\.find-last-index['"]/],
|
|
775
|
+
'Array.prototype.toReversed': [/\bArray\.prototype\.toReversed\s*=/, /['"]core-js\/modules\/es\.array\.to-reversed['"]/],
|
|
776
|
+
'Array.prototype.toSorted': [/\bArray\.prototype\.toSorted\s*=/, /['"]core-js\/modules\/es\.array\.to-sorted['"]/],
|
|
777
|
+
'Array.prototype.toSpliced': [/\bArray\.prototype\.toSpliced\s*=/, /['"]core-js\/modules\/es\.array\.to-spliced['"]/],
|
|
778
|
+
'Array.prototype.with': [/\bArray\.prototype\.with\s*=/, /['"]core-js\/modules\/es\.array\.with['"]/],
|
|
779
|
+
|
|
780
|
+
// ES2024
|
|
781
|
+
'Object.hasOwn': [/\bObject\.hasOwn\s*=/, /['"]core-js\/modules\/es\.object\.has-own['"]/],
|
|
782
|
+
'String.prototype.isWellFormed': [/\bString\.prototype\.isWellFormed\s*=/, /['"]core-js\/modules\/es\.string\.is-well-formed['"]/],
|
|
783
|
+
'String.prototype.toWellFormed': [/\bString\.prototype\.toWellFormed\s*=/, /['"]core-js\/modules\/es\.string\.to-well-formed['"]/],
|
|
784
|
+
|
|
785
|
+
// ES2025
|
|
786
|
+
'Array.prototype.group': [/\bArray\.prototype\.group\s*=/, /['"]core-js\/modules\/es\.array\.group['"]/],
|
|
787
|
+
'Array.prototype.groupToMap': [/\bArray\.prototype\.groupToMap\s*=/, /['"]core-js\/modules\/es\.array\.group-to-map['"]/],
|
|
788
|
+
'Promise.try': [/\bPromise\.try\s*=/, /['"]core-js\/modules\/es\.promise\.try['"]/],
|
|
644
789
|
};
|
|
645
790
|
|
|
646
791
|
module.exports = {
|
|
647
792
|
ES_FEATURES,
|
|
648
793
|
NODE_TYPES,
|
|
794
|
+
POLYFILL_PATTERNS,
|
|
795
|
+
IMPORT_PATTERNS,
|
|
649
796
|
BROWSER_TO_ES_VERSION,
|
|
650
797
|
FEATURE_TO_POLYFILL_MAP,
|
|
651
798
|
JS_VERSIONS,
|
package/detectFeatures.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const acorn = require('acorn');
|
|
2
2
|
const walk = require('acorn-walk');
|
|
3
|
-
const { ES_FEATURES } = require('./constants');
|
|
3
|
+
const { ES_FEATURES, POLYFILL_PATTERNS, IMPORT_PATTERNS } = require('./constants');
|
|
4
4
|
const { checkMap } = require('./utils');
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -8,61 +8,33 @@ const { checkMap } = require('./utils');
|
|
|
8
8
|
* @param {string} code - The source code to check
|
|
9
9
|
* @param {Set} polyfills - Set to store detected polyfills
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
const detectPolyfills = (
|
|
12
|
+
code,
|
|
13
|
+
polyfills,
|
|
14
|
+
{
|
|
15
|
+
polyfillPatterns = POLYFILL_PATTERNS,
|
|
16
|
+
importPatterns = IMPORT_PATTERNS
|
|
17
|
+
} = {}) => {
|
|
12
18
|
if (code.includes('core-js') || code.includes('polyfill')) {
|
|
13
|
-
const polyfillPatterns = [
|
|
14
|
-
{ pattern: /Array\.prototype\.toSorted/, feature: 'ArrayToSorted' },
|
|
15
|
-
{ pattern: /Array\.prototype\.findLast/, feature: 'ArrayFindLast' },
|
|
16
|
-
{ pattern: /Array\.prototype\.findLastIndex/, feature: 'ArrayFindLastIndex' },
|
|
17
|
-
{ pattern: /Array\.prototype\.at/, feature: 'ArrayAt' },
|
|
18
|
-
{ pattern: /String\.prototype\.replaceAll/, feature: 'StringReplaceAll' },
|
|
19
|
-
{ pattern: /String\.prototype\.matchAll/, feature: 'StringMatchAll' },
|
|
20
|
-
{ pattern: /String\.prototype\.at/, feature: 'StringAt' },
|
|
21
|
-
{ pattern: /Object\.hasOwn/, feature: 'ObjectHasOwn' },
|
|
22
|
-
{ pattern: /Promise\.any/, feature: 'PromiseAny' },
|
|
23
|
-
{ pattern: /RegExp\.prototype\.exec/, feature: 'RegExpExec' },
|
|
24
|
-
{ pattern: /globalThis/, feature: 'GlobalThis' },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
19
|
for (const { pattern, feature } of polyfillPatterns) {
|
|
28
|
-
if (pattern.test(code))
|
|
29
|
-
polyfills.add(feature);
|
|
30
|
-
}
|
|
20
|
+
if (pattern.test(code)) polyfills.add(feature);
|
|
31
21
|
}
|
|
32
22
|
|
|
33
23
|
if (code.includes('import') && code.includes('core-js')) {
|
|
34
|
-
const importPatterns = [
|
|
35
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.array\.to-sorted['"]/, feature: 'ArrayToSorted' },
|
|
36
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.array\.find-last['"]/, feature: 'ArrayFindLast' },
|
|
37
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.array\.find-last-index['"]/, feature: 'ArrayFindLastIndex' },
|
|
38
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.array\.at['"]/, feature: 'ArrayAt' },
|
|
39
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.string\.replace-all['"]/, feature: 'StringReplaceAll' },
|
|
40
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.string\.match-all['"]/, feature: 'StringMatchAll' },
|
|
41
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.string\.at['"]/, feature: 'StringAt' },
|
|
42
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.object\.has-own['"]/, feature: 'ObjectHasOwn' },
|
|
43
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.promise\.any['"]/, feature: 'PromiseAny' },
|
|
44
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.regexp\.exec['"]/, feature: 'RegExpExec' },
|
|
45
|
-
{ pattern: /from\s+['"]core-js\/modules\/es\.global-this['"]/, feature: 'GlobalThis' },
|
|
46
|
-
];
|
|
47
|
-
|
|
48
24
|
for (const { pattern, feature } of importPatterns) {
|
|
49
|
-
if (pattern.test(code))
|
|
50
|
-
polyfills.add(feature);
|
|
51
|
-
}
|
|
25
|
+
if (pattern.test(code)) polyfills.add(feature);
|
|
52
26
|
}
|
|
53
27
|
}
|
|
54
28
|
}
|
|
55
29
|
}
|
|
56
30
|
|
|
57
31
|
const detectFeatures = (code, ecmaVersion, sourceType, ignoreList = new Set(), options = {}) => {
|
|
58
|
-
const { checkForPolyfills } = options;
|
|
32
|
+
const { checkForPolyfills, ast: providedAst } = options;
|
|
59
33
|
|
|
60
34
|
const polyfills = new Set();
|
|
61
|
-
if (checkForPolyfills)
|
|
62
|
-
detectPolyfills(code, polyfills);
|
|
63
|
-
}
|
|
35
|
+
if (checkForPolyfills) detectPolyfills(code, polyfills);
|
|
64
36
|
|
|
65
|
-
const ast = acorn.parse(code, {
|
|
37
|
+
const ast = providedAst || acorn.parse(code, {
|
|
66
38
|
ecmaVersion: 'latest',
|
|
67
39
|
sourceType,
|
|
68
40
|
});
|
package/index.js
CHANGED
|
@@ -11,7 +11,7 @@ let polyfillDetector = null;
|
|
|
11
11
|
const pkg = require('./package.json')
|
|
12
12
|
const { lilconfig } = require('lilconfig');
|
|
13
13
|
const { JS_VERSIONS } = require('./constants');
|
|
14
|
-
const { parseIgnoreList, createLogger, generateBashCompletion, generateZshCompletion } = require('./utils');
|
|
14
|
+
const { parseIgnoreList, createLogger, generateBashCompletion, generateZshCompletion, processBatchedFiles, readFileAsync, parseCode } = require('./utils');
|
|
15
15
|
|
|
16
16
|
program.configureOutput({
|
|
17
17
|
writeOut: (str) => process.stdout.write(str),
|
|
@@ -70,7 +70,7 @@ program
|
|
|
70
70
|
.version(pkg.version)
|
|
71
71
|
.argument(
|
|
72
72
|
'[ecmaVersion]',
|
|
73
|
-
'ecmaVersion to check files against. Can be: es3, es4, es5, es6/es2015, es7/es2016, es8/es2017, es9/es2018, es10/es2019, es11/es2020, es12/es2021, es13/es2022, es14/es2023, checkBrowser',
|
|
73
|
+
'ecmaVersion to check files against. Can be: es3, es4, es5, es6/es2015, es7/es2016, es8/es2017, es9/es2018, es10/es2019, es11/es2020, es12/es2021, es13/es2022, es14/es2023, es15/es2024, es16/es2025, checkBrowser',
|
|
74
74
|
)
|
|
75
75
|
.argument('[files...]', 'a glob of files to to test the EcmaScript version against')
|
|
76
76
|
.option('--module', 'use ES modules')
|
|
@@ -98,6 +98,7 @@ program
|
|
|
98
98
|
.option('--browserslistPath <path>', 'path to custom browserslist configuration')
|
|
99
99
|
.option('--browserslistEnv <env>', 'browserslist environment to use')
|
|
100
100
|
.option('--config <path>', 'path to custom .escheckrc config file')
|
|
101
|
+
.option('--batchSize <number>', 'number of files to process concurrently (0 for unlimited)', '0')
|
|
101
102
|
|
|
102
103
|
async function loadConfig(customConfigPath) {
|
|
103
104
|
const logger = createLogger();
|
|
@@ -178,6 +179,7 @@ program
|
|
|
178
179
|
browserslistQuery: options.browserslistQuery !== undefined ? options.browserslistQuery : baseConfig.browserslistQuery,
|
|
179
180
|
browserslistPath: options.browserslistPath !== undefined ? options.browserslistPath : baseConfig.browserslistPath,
|
|
180
181
|
browserslistEnv: options.browserslistEnv !== undefined ? options.browserslistEnv : baseConfig.browserslistEnv,
|
|
182
|
+
batchSize: options.batchSize !== undefined ? options.batchSize : baseConfig.batchSize,
|
|
181
183
|
};
|
|
182
184
|
|
|
183
185
|
if (ecmaVersionArg !== undefined) {
|
|
@@ -332,6 +334,14 @@ async function runChecks(configs, logger) {
|
|
|
332
334
|
case 'es2023':
|
|
333
335
|
ecmaVersion = '14'
|
|
334
336
|
break
|
|
337
|
+
case 'es15':
|
|
338
|
+
case 'es2024':
|
|
339
|
+
ecmaVersion = '15'
|
|
340
|
+
break
|
|
341
|
+
case 'es16':
|
|
342
|
+
case 'es2025':
|
|
343
|
+
ecmaVersion = '16'
|
|
344
|
+
break
|
|
335
345
|
default:
|
|
336
346
|
logger.error('Invalid ecmaScript version, please pass a valid version, use --help for help')
|
|
337
347
|
process.exit(1)
|
|
@@ -359,20 +369,22 @@ async function runChecks(configs, logger) {
|
|
|
359
369
|
}
|
|
360
370
|
}
|
|
361
371
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return
|
|
369
|
-
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
return globbedFiles;
|
|
372
|
+
let expandedPathsToIgnore = [];
|
|
373
|
+
if (pathsToIgnore.length > 0) {
|
|
374
|
+
expandedPathsToIgnore = pathsToIgnore.reduce((result, path) => {
|
|
375
|
+
if (path.includes('*')) {
|
|
376
|
+
return result.concat(glob.sync(path, globOpts));
|
|
377
|
+
}
|
|
378
|
+
return result.concat(path);
|
|
379
|
+
}, []);
|
|
373
380
|
}
|
|
374
381
|
|
|
375
|
-
|
|
382
|
+
let filteredFiles = allMatchedFiles;
|
|
383
|
+
if (expandedPathsToIgnore.length > 0) {
|
|
384
|
+
filteredFiles = allMatchedFiles.filter((filePath) => {
|
|
385
|
+
return !expandedPathsToIgnore.some((ignoreValue) => filePath.includes(ignoreValue));
|
|
386
|
+
});
|
|
387
|
+
}
|
|
376
388
|
|
|
377
389
|
const ignoreList = parseIgnoreList(config);
|
|
378
390
|
|
|
@@ -380,27 +392,27 @@ async function runChecks(configs, logger) {
|
|
|
380
392
|
logger.debug('ES-Check: ignoring features:', Array.from(ignoreList).join(', '));
|
|
381
393
|
}
|
|
382
394
|
|
|
383
|
-
|
|
384
|
-
|
|
395
|
+
const batchSize = parseInt(config.batchSize || '0', 10);
|
|
396
|
+
|
|
397
|
+
const processFile = async (file) => {
|
|
398
|
+
const { content: code, error: readError } = await readFileAsync(file, fs);
|
|
399
|
+
if (readError) {
|
|
400
|
+
return readError;
|
|
401
|
+
}
|
|
402
|
+
|
|
385
403
|
if (logger.isLevelEnabled('debug')) {
|
|
386
404
|
logger.debug(`ES-Check: checking ${file}`)
|
|
387
405
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
406
|
+
|
|
407
|
+
const { ast, error: parseError } = parseCode(code, acornOpts, acorn, file);
|
|
408
|
+
if (parseError) {
|
|
391
409
|
if (logger.isLevelEnabled('debug')) {
|
|
392
|
-
logger.debug(`ES-Check: failed to parse file: ${file} \n - error: ${err}`)
|
|
410
|
+
logger.debug(`ES-Check: failed to parse file: ${file} \n - error: ${parseError.err}`)
|
|
393
411
|
}
|
|
394
|
-
|
|
395
|
-
err,
|
|
396
|
-
stack: err.stack,
|
|
397
|
-
file,
|
|
398
|
-
}
|
|
399
|
-
errArray.push(errorObj);
|
|
400
|
-
return;
|
|
412
|
+
return parseError;
|
|
401
413
|
}
|
|
402
414
|
|
|
403
|
-
if (!checkFeatures) return;
|
|
415
|
+
if (!checkFeatures) return null;
|
|
404
416
|
const parseSourceType = acornOpts.sourceType || 'script';
|
|
405
417
|
const esVersion = parseInt(ecmaVersion, 10);
|
|
406
418
|
|
|
@@ -408,7 +420,8 @@ async function runChecks(configs, logger) {
|
|
|
408
420
|
code,
|
|
409
421
|
esVersion,
|
|
410
422
|
parseSourceType,
|
|
411
|
-
ignoreList
|
|
423
|
+
ignoreList,
|
|
424
|
+
{ ast, checkForPolyfills }
|
|
412
425
|
);
|
|
413
426
|
|
|
414
427
|
if (logger.isLevelEnabled('debug')) {
|
|
@@ -434,13 +447,18 @@ async function runChecks(configs, logger) {
|
|
|
434
447
|
const isSupported = filteredUnsupportedFeatures.length === 0;
|
|
435
448
|
if (!isSupported) {
|
|
436
449
|
const error = new Error(`Unsupported features used: ${filteredUnsupportedFeatures.join(', ')} but your target is ES${ecmaVersion}.`);
|
|
437
|
-
|
|
450
|
+
return {
|
|
438
451
|
err: error,
|
|
439
452
|
file,
|
|
440
453
|
stack: error.stack
|
|
441
|
-
}
|
|
454
|
+
};
|
|
442
455
|
}
|
|
443
|
-
|
|
456
|
+
return null;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const results = await processBatchedFiles(filteredFiles, processFile, batchSize);
|
|
460
|
+
const errors = results.filter(result => result !== null);
|
|
461
|
+
errArray.push(...errors);
|
|
444
462
|
|
|
445
463
|
if (errArray.length > 0) {
|
|
446
464
|
logger.error(`ES-Check: there were ${errArray.length} ES version matching errors.`)
|
|
@@ -467,4 +485,11 @@ async function runChecks(configs, logger) {
|
|
|
467
485
|
}
|
|
468
486
|
}
|
|
469
487
|
|
|
470
|
-
|
|
488
|
+
if (require.main === module) {
|
|
489
|
+
program.parse()
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
module.exports = {
|
|
493
|
+
runChecks,
|
|
494
|
+
loadConfig
|
|
495
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "es-check",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "Checks the ECMAScript version of .js glob against a specified version of ECMAScript with a shell command",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"commit": "git-cz",
|
|
20
20
|
"commit-msg": "commitlint --edit $1",
|
|
21
|
+
"dev": "pnpm site:dev",
|
|
21
22
|
"lint": "eslint index.js --fix",
|
|
22
23
|
"lint:ci": "eslint index.js utils.js detectFeatures.js constants.js browserslist.js polyfillDetector.js",
|
|
23
24
|
"pre-commit": "pnpm lint && pnpm test",
|
|
@@ -28,7 +29,11 @@
|
|
|
28
29
|
"setup": "pnpm install --reporter=silent",
|
|
29
30
|
"test": "nyc mocha test.js utils.test.js browserslist.test.js polyfillDetector.test.js detectFeatures.test.js --timeout 10s",
|
|
30
31
|
"update": "codependence --update",
|
|
31
|
-
"benchmark": "./benchmarks/run-benchmarks.sh"
|
|
32
|
+
"benchmark": "./benchmarks/run-benchmarks.sh",
|
|
33
|
+
"site:dev": "pnpm --filter es-check-docs run dev",
|
|
34
|
+
"site:build": "pnpm --filter es-check-docs run build",
|
|
35
|
+
"site:preview": "pnpm --filter es-check-docs run preview",
|
|
36
|
+
"site:install": "pnpm --filter es-check-docs install"
|
|
32
37
|
},
|
|
33
38
|
"repository": {
|
|
34
39
|
"type": "git",
|
|
@@ -52,19 +57,19 @@
|
|
|
52
57
|
"codependence": "^0.3.1",
|
|
53
58
|
"commitizen": "4.3.1",
|
|
54
59
|
"conventional-changelog-cli": "^5.0.0",
|
|
55
|
-
"eslint": "9.
|
|
56
|
-
"eslint-config-prettier": "10.1.
|
|
60
|
+
"eslint": "9.32.0",
|
|
61
|
+
"eslint-config-prettier": "10.1.8",
|
|
57
62
|
"eslint-plugin-es5": "^1.5.0",
|
|
58
63
|
"husky": "9.1.7",
|
|
59
64
|
"is-ci": "^3.0.1",
|
|
60
|
-
"mocha": "11.
|
|
65
|
+
"mocha": "11.7.1",
|
|
61
66
|
"nyc": "^17.1.0",
|
|
62
67
|
"path-exists-cli": "^2.0.0",
|
|
63
|
-
"prettier": "3.
|
|
64
|
-
"release-it": "19.0.
|
|
68
|
+
"prettier": "3.6.2",
|
|
69
|
+
"release-it": "19.0.4"
|
|
65
70
|
},
|
|
66
71
|
"dependencies": {
|
|
67
|
-
"acorn": "8.
|
|
72
|
+
"acorn": "8.15.0",
|
|
68
73
|
"acorn-walk": "^8.3.4",
|
|
69
74
|
"browserslist": "^4.23.3",
|
|
70
75
|
"commander": "14.0.0",
|
package/polyfillDetector.js
CHANGED
|
@@ -1,77 +1,47 @@
|
|
|
1
1
|
const { FEATURE_TO_POLYFILL_MAP } = require('./constants');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Detects polyfills in the code and returns a set of polyfilled feature names
|
|
5
|
-
*
|
|
6
|
-
* @param {
|
|
7
|
-
* @
|
|
4
|
+
* Detects polyfills in the code and returns a set of polyfilled feature names.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} code - The source code to check.
|
|
7
|
+
* @param {object} logger - A required logger instance.
|
|
8
|
+
* @param {object} [featureMap=FEATURE_TO_POLYFILL_MAP] - The map of features.
|
|
9
|
+
* @returns {Set<string>} - A set of polyfilled feature names.
|
|
8
10
|
*/
|
|
9
|
-
function detectPolyfills(code, logger) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (code.includes('core-js/modules/es.array.to-sorted')) {
|
|
18
|
-
polyfills.add('ArrayToSorted');
|
|
19
|
-
}
|
|
20
|
-
if (code.includes('core-js/modules/es.object.has-own')) {
|
|
21
|
-
polyfills.add('ObjectHasOwn');
|
|
22
|
-
}
|
|
23
|
-
if (code.includes('core-js/modules/es.string.replace-all')) {
|
|
24
|
-
polyfills.add('StringReplaceAll');
|
|
11
|
+
function detectPolyfills(code, logger, featureMap = FEATURE_TO_POLYFILL_MAP) {
|
|
12
|
+
const polyfills = new Set();
|
|
13
|
+
if (!code || !featureMap) return polyfills;
|
|
14
|
+
|
|
15
|
+
// Since logger is required, we can use it without checking for its existence.
|
|
16
|
+
// The optional chaining (?.) is still good practice for properties like isLevelEnabled.
|
|
17
|
+
if (logger?.isLevelEnabled?.('debug')) {
|
|
18
|
+
// We can log at the beginning if needed, or at the end.
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
const polyfillFeatures = Object.entries(featureMap);
|
|
22
|
+
polyfillFeatures.forEach(([feature, patterns]) => {
|
|
23
|
+
const isPolyfilled = patterns.some(pattern => pattern.test(code));
|
|
24
|
+
if (isPolyfilled) polyfills.add(feature);
|
|
25
|
+
});
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
27
|
+
if (logger?.isLevelEnabled?.('debug')) {
|
|
28
|
+
const hasPolyfills = polyfills.size > 0;
|
|
29
|
+
if (hasPolyfills) logger.debug(`ES-Check: Detected polyfills: ${Array.from(polyfills).join(', ')}`);
|
|
30
|
+
else logger.debug('ES-Check: No polyfills detected.');
|
|
38
31
|
}
|
|
39
|
-
}
|
|
40
32
|
|
|
41
|
-
if (polyfills.size === 0 && (code.includes('polyfill') || code.includes('Array.prototype') || code.includes('Object.') || code.includes('String.prototype'))) {
|
|
42
|
-
} else if (polyfills.size > 0) {
|
|
43
33
|
return polyfills;
|
|
44
|
-
} else if (!code.includes('core-js') && !code.includes('polyfill') && !code.includes('Array.prototype')) {
|
|
45
|
-
return polyfills;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
for (const [featureName, patterns] of Object.entries(FEATURE_TO_POLYFILL_MAP)) {
|
|
49
|
-
for (const pattern of patterns) {
|
|
50
|
-
if (pattern.test(code)) {
|
|
51
|
-
polyfills.add(featureName);
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (logger?.isLevelEnabled?.('debug') && polyfills.size > 0) {
|
|
58
|
-
logger.debug(`ES-Check: Detected polyfills: ${Array.from(polyfills).join(', ')}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return polyfills;
|
|
62
34
|
}
|
|
63
35
|
|
|
64
36
|
/**
|
|
65
|
-
* Filters unsupported features by removing those that have been polyfilled
|
|
37
|
+
* Filters unsupported features by removing those that have been polyfilled.
|
|
66
38
|
* @param {Array<string>} unsupportedFeatures - List of unsupported features
|
|
67
39
|
* @param {Set<string>} polyfills - Set of polyfilled feature names
|
|
68
40
|
* @returns {Array<string>} - Filtered list of unsupported features
|
|
69
41
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
42
|
+
const filterPolyfilled = (unsupportedFeatures, polyfills) => {
|
|
43
|
+
const hasPolyfills = polyfills && polyfills.size > 0;
|
|
44
|
+
if (!hasPolyfills) return unsupportedFeatures;
|
|
75
45
|
return unsupportedFeatures.filter(feature => !polyfills.has(feature));
|
|
76
46
|
}
|
|
77
47
|
|
package/utils.js
CHANGED
|
@@ -113,6 +113,12 @@ const checkMap = {
|
|
|
113
113
|
node.callee.property.type === 'Identifier' &&
|
|
114
114
|
node.callee.property.name === astInfo.property
|
|
115
115
|
);
|
|
116
|
+
} else if (astInfo.property) {
|
|
117
|
+
// Check for method calls with any object when only property is specified
|
|
118
|
+
return (
|
|
119
|
+
node.callee.property.type === 'Identifier' &&
|
|
120
|
+
node.callee.property.name === astInfo.property
|
|
121
|
+
);
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
return false;
|
|
@@ -180,7 +186,7 @@ function createLogger(options = {}) {
|
|
|
180
186
|
level,
|
|
181
187
|
stderrLevels: ['error', 'warn'],
|
|
182
188
|
format: winston.format.combine(
|
|
183
|
-
...(supportsColor.stdout
|
|
189
|
+
...(supportsColor.stdout && !noColor ? [winston.format.colorize()] : []),
|
|
184
190
|
winston.format.simple(),
|
|
185
191
|
),
|
|
186
192
|
})
|
|
@@ -217,7 +223,7 @@ _${cmdName.replace(/-/g, '_')}_completion() {
|
|
|
217
223
|
opts="${optsStr}"
|
|
218
224
|
|
|
219
225
|
# ES versions
|
|
220
|
-
es_versions="es3 es5 es6 es2015 es7 es2016 es8 es2017 es9 es2018 es10 es2019 es11 es2020 es12 es2021 es13 es2022 es14 es2023"
|
|
226
|
+
es_versions="es3 es5 es6 es2015 es7 es2016 es8 es2017 es9 es2018 es10 es2019 es11 es2020 es12 es2021 es13 es2022 es14 es2023 es15 es2024 es16 es2025"
|
|
221
227
|
|
|
222
228
|
# Handle special cases based on previous argument
|
|
223
229
|
case "\$prev" in
|
|
@@ -288,6 +294,10 @@ _${cmdName.replace(/-/g, '_')}() {
|
|
|
288
294
|
"es2022:ECMAScript 2022"
|
|
289
295
|
"es14:ECMAScript 2023"
|
|
290
296
|
"es2023:ECMAScript 2023"
|
|
297
|
+
"es15:ECMAScript 2024"
|
|
298
|
+
"es2024:ECMAScript 2024"
|
|
299
|
+
"es16:ECMAScript 2025"
|
|
300
|
+
"es2025:ECMAScript 2025"
|
|
291
301
|
)
|
|
292
302
|
|
|
293
303
|
# Commands
|
|
@@ -328,6 +338,75 @@ _${cmdName.replace(/-/g, '_')}
|
|
|
328
338
|
`;
|
|
329
339
|
}
|
|
330
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Process files in batches for better performance
|
|
343
|
+
* @param {string[]} files - Array of file paths to process
|
|
344
|
+
* @param {Function} processor - Async function to process each file
|
|
345
|
+
* @param {number} batchSize - Number of files to process concurrently (0 for unlimited)
|
|
346
|
+
* @returns {Promise<Array>} Array of results from processing all files
|
|
347
|
+
*/
|
|
348
|
+
async function processBatchedFiles(files, processor, batchSize = 0) {
|
|
349
|
+
if (batchSize <= 0) {
|
|
350
|
+
return Promise.all(files.map(processor));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const results = [];
|
|
354
|
+
|
|
355
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
356
|
+
const batch = files.slice(i, i + batchSize);
|
|
357
|
+
const batchResults = await Promise.all(batch.map(processor));
|
|
358
|
+
results.push(...batchResults);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return results;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Read file asynchronously with error handling
|
|
366
|
+
* @param {string} file - File path to read
|
|
367
|
+
* @param {Object} fs - File system module
|
|
368
|
+
* @returns {Promise<{content: string, error: null} | {content: null, error: Object}>}
|
|
369
|
+
*/
|
|
370
|
+
async function readFileAsync(file, fs) {
|
|
371
|
+
try {
|
|
372
|
+
const content = await fs.promises.readFile(file, 'utf8');
|
|
373
|
+
return { content, error: null };
|
|
374
|
+
} catch (err) {
|
|
375
|
+
return {
|
|
376
|
+
content: null,
|
|
377
|
+
error: {
|
|
378
|
+
err,
|
|
379
|
+
file,
|
|
380
|
+
stack: err.stack
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Parse code with acorn and handle errors
|
|
388
|
+
* @param {string} code - Code to parse
|
|
389
|
+
* @param {Object} acornOpts - Acorn parsing options
|
|
390
|
+
* @param {Object} acorn - Acorn module
|
|
391
|
+
* @param {string} file - File path for error reporting
|
|
392
|
+
* @returns {{ast: Object, error: null} | {ast: null, error: Object}}
|
|
393
|
+
*/
|
|
394
|
+
function parseCode(code, acornOpts, acorn, file) {
|
|
395
|
+
try {
|
|
396
|
+
const ast = acorn.parse(code, acornOpts);
|
|
397
|
+
return { ast, error: null };
|
|
398
|
+
} catch (err) {
|
|
399
|
+
return {
|
|
400
|
+
ast: null,
|
|
401
|
+
error: {
|
|
402
|
+
err,
|
|
403
|
+
stack: err.stack,
|
|
404
|
+
file
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
331
410
|
module.exports = {
|
|
332
411
|
parseIgnoreList,
|
|
333
412
|
checkVarKindMatch,
|
|
@@ -337,5 +416,8 @@ module.exports = {
|
|
|
337
416
|
checkMap,
|
|
338
417
|
createLogger,
|
|
339
418
|
generateBashCompletion,
|
|
340
|
-
generateZshCompletion
|
|
419
|
+
generateZshCompletion,
|
|
420
|
+
processBatchedFiles,
|
|
421
|
+
readFileAsync,
|
|
422
|
+
parseCode
|
|
341
423
|
};
|