es-check 9.2.0 → 9.3.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
@@ -153,6 +153,7 @@ Here's a comprehensive list of all available options:
153
153
  | `--browserslistEnv <env>` | Browserslist environment to use (default: production) |
154
154
  | `--config <path>` | Path to custom .escheckrc config file |
155
155
  | `--batchSize <number>` | Number of files to process concurrently (0 for unlimited, default: 0) |
156
+ | `--noCache` | Disable file caching (cache is enabled by default) |
156
157
  | `-h, --help` | Display help for command |
157
158
 
158
159
  ### Shell Completion
@@ -203,7 +204,7 @@ es-check es6 './dist/**/*.js' --module
203
204
  **Checking files with hash bang:**
204
205
 
205
206
  ```sh
206
- es-check es6 './bin/*.js' --allowHashBang
207
+ es-check es6 './tests/*.js' --allowHashBang
207
208
  ```
208
209
 
209
210
  **Skipping specific files or directories:**
@@ -284,7 +285,7 @@ es-check es6 ./js/*.js
284
285
  es-check es6 ./js/*.js ./dist/*.js
285
286
  ```
286
287
 
287
- ### Using ES Check in Node
288
+ ### Using ES Check in Node (Programmatic API)
288
289
 
289
290
  In addition to its CLI utility, ES Check can be used programmatically in Node.js applications:
290
291
 
@@ -292,25 +293,30 @@ In addition to its CLI utility, ES Check can be used programmatically in Node.js
292
293
  const { runChecks, loadConfig } = require('es-check');
293
294
 
294
295
  async function checkMyFiles() {
295
- const config = {
296
+ const configs = [{
296
297
  ecmaVersion: 'es5',
297
298
  files: ['dist/**/*.js'],
298
299
  module: false,
299
300
  checkFeatures: true
300
- };
301
+ }];
301
302
 
302
- try {
303
- await runChecks([config], logger);
303
+ const result = await runChecks(configs);
304
+
305
+ if (result.success) {
304
306
  console.log('All files passed ES5 check!');
305
- } catch (error) {
306
- console.error('Some files failed the ES check');
307
- process.exit(1);
307
+ // Output: All files passed ES5 check!
308
+ } else {
309
+ console.error(`ES Check failed with ${result.errors.length} errors`);
310
+ result.errors.forEach(error => {
311
+ console.error(`- ${error.file}: ${error.err.message}`);
312
+ });
313
+ // Example output:
314
+ // ES Check failed with 2 errors
315
+ // - dist/app.js: Unsupported features used: const, arrow-functions but your target is ES5.
316
+ // - dist/utils.js: Unsupported features used: template-literals but your target is ES5.
308
317
  }
309
- }
310
-
311
- async function checkWithConfig() {
312
- const configs = await loadConfig('./.escheckrc');
313
- await runChecks(configs, logger);
318
+
319
+ return result;
314
320
  }
315
321
  ```
316
322
 
@@ -525,7 +531,26 @@ es-check --checkBrowser --checkFeatures ./dist/**/*.js
525
531
 
526
532
  ## Performance Optimization
527
533
 
528
- ES Check provides the `--batchSize` option to optimize performance for different scenarios:
534
+ ES Check includes several performance optimizations:
535
+
536
+ ### File Caching
537
+ File caching is **enabled by default** for faster re-checking:
538
+
539
+ ```sh
540
+ # Cache is enabled by default
541
+ es-check es5 './dist/**/*.js'
542
+
543
+ # Disable cache if needed
544
+ es-check es5 './dist/**/*.js' --noCache
545
+ ```
546
+
547
+ We observed ~28% performance improvement in our [benchmark tests](./benchmarks/README.md). Your results may vary based on file sizes and system configuration. Try the benchmarks yourself:
548
+ ```sh
549
+ node benchmarks/compare-tools.js 3 ./benchmarks/test-files
550
+ ```
551
+
552
+ ### Batch Processing
553
+ The `--batchSize` option optimizes memory usage:
529
554
 
530
555
  ```sh
531
556
  # Process all files in parallel (default)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module';
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const esCheck = require('./index.js');
6
+
7
+ export const { runChecks, loadConfig, createLogger } = esCheck;
8
+ export default esCheck;
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, processBatchedFiles, readFileAsync, parseCode } = require('./utils');
14
+ const { parseIgnoreList, createLogger, generateBashCompletion, generateZshCompletion, processBatchedFiles, readFileAsync, clearFileCache, getFileCacheStats, parseCode, determineInvocationType, determineLogLevel, handleESVersionError } = require('./utils');
15
15
 
16
16
  program.configureOutput({
17
17
  writeOut: (str) => process.stdout.write(str),
@@ -99,6 +99,7 @@ program
99
99
  .option('--browserslistEnv <env>', 'browserslist environment to use')
100
100
  .option('--config <path>', 'path to custom .escheckrc config file')
101
101
  .option('--batchSize <number>', 'number of files to process concurrently (0 for unlimited)', '0')
102
+ .option('--noCache', 'disable file caching (caching is enabled by default)', false)
102
103
 
103
104
  async function loadConfig(customConfigPath) {
104
105
  const logger = createLogger();
@@ -180,6 +181,7 @@ program
180
181
  browserslistPath: options.browserslistPath !== undefined ? options.browserslistPath : baseConfig.browserslistPath,
181
182
  browserslistEnv: options.browserslistEnv !== undefined ? options.browserslistEnv : baseConfig.browserslistEnv,
182
183
  batchSize: options.batchSize !== undefined ? options.batchSize : baseConfig.batchSize,
184
+ cache: options.noCache ? false : (baseConfig.cache !== undefined ? baseConfig.cache : true),
183
185
  };
184
186
 
185
187
  if (ecmaVersionArg !== undefined) {
@@ -203,8 +205,17 @@ program
203
205
  return runChecks(configs, logger);
204
206
  })
205
207
 
206
- async function runChecks(configs, logger) {
208
+ async function runChecks(configs, loggerOrOptions) {
209
+ const { isNodeAPI, logger } = determineInvocationType(loggerOrOptions);
210
+
211
+ const logLevels = determineLogLevel(logger);
212
+ const isDebug = logLevels?.isDebug || false;
213
+ const isWarn = logLevels?.isWarn || false;
214
+ const isInfo = logLevels?.isInfo || false;
215
+ const isError = logLevels?.isError || false;
216
+
207
217
  let hasErrors = false;
218
+ const allErrors = [];
208
219
 
209
220
  for (const config of configs) {
210
221
  const expectedEcmaVersion = config.ecmaVersion;
@@ -227,47 +238,88 @@ async function runChecks(configs, logger) {
227
238
  const checkBrowser = config.checkBrowser;
228
239
  const ignoreFilePath = config.ignoreFile || config['ignore-file'];
229
240
 
230
- if (ignoreFilePath && !fs.existsSync(ignoreFilePath) && logger.isLevelEnabled('warn')) {
241
+ const ignoreFileExists = ignoreFilePath && fs.existsSync(ignoreFilePath);
242
+ const shouldWarnAboutIgnoreFile = ignoreFilePath && !ignoreFileExists && isWarn;
243
+ if (shouldWarnAboutIgnoreFile) {
231
244
  logger.warn(`Warning: Ignore file '${ignoreFilePath}' does not exist or is not accessible`);
232
245
  }
233
246
 
234
- if (!expectedEcmaVersion && !config.checkBrowser) {
235
- logger.error('No ecmaScript version or checkBrowser option specified in configuration');
236
- process.exit(1);
247
+ const hasEcmaVersion = Boolean(expectedEcmaVersion);
248
+ const hasBrowserCheck = Boolean(config.checkBrowser);
249
+ const missingVersionSpecification = !hasEcmaVersion && !hasBrowserCheck;
250
+
251
+ if (missingVersionSpecification) {
252
+ if (logger) logger.error('No ecmaScript version or checkBrowser option specified in configuration');
253
+ if (!isNodeAPI) {
254
+ process.exit(1);
255
+ } else {
256
+ allErrors.push({ err: new Error('No ecmaScript version or checkBrowser option specified in configuration'), file: 'config' });
257
+ hasErrors = true;
258
+ continue;
259
+ }
237
260
  }
238
261
 
239
- if (looseGlobMatching && logger.isLevelEnabled('debug')) {
262
+ if (looseGlobMatching && isDebug) {
240
263
  logger.debug('ES-Check: loose-glob-matching is set');
241
264
  }
242
265
 
243
266
  const globOpts = { nodir: true }
244
267
  let allMatchedFiles = [];
245
- if (patternsToGlob.length === 0 && !looseGlobMatching) {
246
- logger.error('ES-Check: No file patterns specified to check.');
247
- process.exit(1);
268
+
269
+ const hasFilePatterns = patternsToGlob.length > 0;
270
+ const shouldEnforceFilePatterns = !hasFilePatterns && !looseGlobMatching;
271
+
272
+ if (shouldEnforceFilePatterns) {
273
+ if (logger) logger.error('ES-Check: No file patterns specified to check.');
274
+ if (!isNodeAPI) {
275
+ process.exit(1);
276
+ } else {
277
+ allErrors.push({ err: new Error('No file patterns specified to check'), file: 'config' });
278
+ hasErrors = true;
279
+ continue;
280
+ }
248
281
  }
249
282
 
250
283
  patternsToGlob.forEach((pattern) => {
251
284
  const globbedFiles = glob.sync(pattern, globOpts);
252
- if (globbedFiles.length === 0 && !looseGlobMatching) {
253
- logger.error(`ES-Check: Did not find any files to check for pattern: ${pattern}.`);
254
- process.exit(1);
285
+ const noFilesFound = globbedFiles.length === 0;
286
+ const shouldErrorOnNoFiles = noFilesFound && !looseGlobMatching;
287
+
288
+ if (shouldErrorOnNoFiles) {
289
+ if (logger) logger.error(`ES-Check: Did not find any files to check for pattern: ${pattern}.`);
290
+ if (!isNodeAPI) {
291
+ process.exit(1);
292
+ } else {
293
+ allErrors.push({ err: new Error(`Did not find any files to check for pattern: ${pattern}`), file: 'glob' });
294
+ hasErrors = true;
295
+ }
255
296
  }
256
297
  allMatchedFiles = allMatchedFiles.concat(globbedFiles);
257
298
  });
258
299
 
259
- if (allMatchedFiles.length === 0) {
260
- if (patternsToGlob.length > 0) {
261
- logger.error(`ES-Check: Did not find any files to check across all patterns: ${patternsToGlob.join(', ')}.`);
262
- process.exit(1);
263
- } else if (looseGlobMatching) {
264
- logger.warn('ES-Check: No file patterns specified or no files found (running in loose mode).');
300
+ const noMatchedFiles = allMatchedFiles.length === 0;
301
+ const shouldErrorOnNoMatchedFiles = noMatchedFiles && hasFilePatterns && !looseGlobMatching;
302
+ const shouldWarnOnNoMatchedFiles = noMatchedFiles && looseGlobMatching;
303
+
304
+ if (noMatchedFiles) {
305
+ if (shouldErrorOnNoMatchedFiles) {
306
+ if (logger) logger.error(`ES-Check: Did not find any files to check across all patterns: ${patternsToGlob.join(', ')}.`);
307
+ if (!isNodeAPI) {
308
+ process.exit(1);
309
+ } else {
310
+ allErrors.push({ err: new Error(`Did not find any files to check across all patterns: ${patternsToGlob.join(', ')}`), file: 'glob' });
311
+ hasErrors = true;
312
+ continue;
313
+ }
314
+ } else if (shouldWarnOnNoMatchedFiles) {
315
+ if (logger) logger.warn('ES-Check: No file patterns specified or no files found (running in loose mode).');
265
316
  }
266
317
  }
267
318
 
268
319
  let ecmaVersion
269
320
 
270
321
  const isBrowserslistCheck = Boolean(expectedEcmaVersion === 'checkBrowser' || checkBrowser !== undefined);
322
+
271
323
  if (isBrowserslistCheck) {
272
324
  const browserslistQuery = config.browserslistQuery;
273
325
  try {
@@ -280,12 +332,18 @@ async function runChecks(configs, logger) {
280
332
 
281
333
  ecmaVersion = esVersionFromBrowserslist.toString();
282
334
 
283
- if (logger.isLevelEnabled('debug')) {
335
+ if (isDebug) {
284
336
  logger.debug(`ES-Check: Using ES${ecmaVersion} based on browserslist configuration`);
285
337
  }
286
338
  } catch (err) {
287
- logger.error(`Error determining ES version from browserslist: ${err.message}`);
288
- process.exit(1);
339
+ if (logger) logger.error(`Error determining ES version from browserslist: ${err.message}`);
340
+ if (!isNodeAPI) {
341
+ process.exit(1);
342
+ } else {
343
+ allErrors.push({ err: new Error(`Error determining ES version from browserslist: ${err.message}`), file: 'browserslist' });
344
+ hasErrors = true;
345
+ continue;
346
+ }
289
347
  }
290
348
  } else {
291
349
  switch (expectedEcmaVersion) {
@@ -293,8 +351,14 @@ async function runChecks(configs, logger) {
293
351
  ecmaVersion = '3'
294
352
  break
295
353
  case 'es4':
296
- logger.error('ES4 is not supported.')
297
- process.exit(1)
354
+ if (logger) logger.error('ES4 is not supported.')
355
+ if (!isNodeAPI) {
356
+ process.exit(1)
357
+ } else {
358
+ allErrors.push({ err: new Error('ES4 is not supported'), file: 'config' });
359
+ hasErrors = true;
360
+ continue;
361
+ }
298
362
  case 'es5':
299
363
  ecmaVersion = '5'
300
364
  break
@@ -343,28 +407,34 @@ async function runChecks(configs, logger) {
343
407
  ecmaVersion = '16'
344
408
  break
345
409
  default:
346
- logger.error('Invalid ecmaScript version, please pass a valid version, use --help for help')
347
- process.exit(1)
410
+ if (logger) logger.error('Invalid ecmaScript version, please pass a valid version, use --help for help')
411
+ if (!isNodeAPI) {
412
+ process.exit(1)
413
+ } else {
414
+ allErrors.push({ err: new Error('Invalid ecmaScript version'), file: 'config' });
415
+ hasErrors = true;
416
+ continue;
417
+ }
348
418
  }
349
419
  }
350
420
 
351
421
  const errArray = []
352
422
  const acornOpts = { ecmaVersion: parseInt(ecmaVersion, 10), silent: true }
353
423
 
354
- if (logger.isLevelEnabled('debug')) {
424
+ if (isDebug) {
355
425
  logger.debug(`ES-Check: Going to check files using version ${ecmaVersion}`)
356
426
  }
357
427
 
358
428
  if (esmodule) {
359
429
  acornOpts.sourceType = 'module'
360
- if (logger.isLevelEnabled('debug')) {
430
+ if (isDebug) {
361
431
  logger.debug('ES-Check: esmodule is set')
362
432
  }
363
433
  }
364
434
 
365
435
  if (allowHashBang) {
366
436
  acornOpts.allowHashBang = true
367
- if (logger.isLevelEnabled('debug')) {
437
+ if (isDebug) {
368
438
  logger.debug('ES-Check: allowHashBang is set')
369
439
  }
370
440
  }
@@ -388,25 +458,29 @@ async function runChecks(configs, logger) {
388
458
 
389
459
  const ignoreList = parseIgnoreList(config);
390
460
 
391
- if (ignoreList.size > 0 && logger.isLevelEnabled('debug')) {
461
+ if (ignoreList.size > 0 && isDebug) {
392
462
  logger.debug('ES-Check: ignoring features:', Array.from(ignoreList).join(', '));
393
463
  }
394
464
 
395
465
  const batchSize = parseInt(config.batchSize || '0', 10);
396
466
 
397
467
  const processFile = async (file) => {
398
- const { content: code, error: readError } = await readFileAsync(file, fs);
468
+ const useCache = config.cache !== false;
469
+ const { content: code, error: readError } = await readFileAsync(file, fs, useCache);
399
470
  if (readError) {
400
471
  return readError;
401
472
  }
402
473
 
403
- if (logger.isLevelEnabled('debug')) {
474
+ if (isDebug) {
404
475
  logger.debug(`ES-Check: checking ${file}`)
405
476
  }
406
477
 
407
- const { ast, error: parseError } = parseCode(code, acornOpts, acorn, file);
478
+ const needsFullAST = checkFeatures;
479
+ const parserOptions = needsFullAST ? acornOpts : { ...acornOpts, locations: false, ranges: false, onComment: null };
480
+
481
+ const { ast, error: parseError } = parseCode(code, parserOptions, acorn, file);
408
482
  if (parseError) {
409
- if (logger.isLevelEnabled('debug')) {
483
+ if (isDebug) {
410
484
  logger.debug(`ES-Check: failed to parse file: ${file} \n - error: ${parseError.err}`)
411
485
  }
412
486
  return parseError;
@@ -424,7 +498,7 @@ async function runChecks(configs, logger) {
424
498
  { ast, checkForPolyfills }
425
499
  );
426
500
 
427
- if (logger.isLevelEnabled('debug')) {
501
+ if (isDebug) {
428
502
  const stringifiedFeatures = JSON.stringify(foundFeatures, null, 2);
429
503
  logger.debug(`Features found in ${file}: ${stringifiedFeatures}`);
430
504
  }
@@ -435,11 +509,11 @@ async function runChecks(configs, logger) {
435
509
  polyfillDetector = require('./polyfillDetector');
436
510
  }
437
511
 
438
- const polyfills = polyfillDetector.detectPolyfills(code, logger);
512
+ const polyfills = polyfillDetector.detectPolyfills(code, logger || { debug: () => {}, isLevelEnabled: () => false });
439
513
 
440
514
  filteredUnsupportedFeatures = polyfillDetector.filterPolyfilled(unsupportedFeatures, polyfills);
441
515
 
442
- if (logger.isLevelEnabled('debug') && filteredUnsupportedFeatures.length !== unsupportedFeatures.length) {
516
+ if (isDebug && filteredUnsupportedFeatures.length !== unsupportedFeatures.length) {
443
517
  logger.debug(`ES-Check: Polyfills reduced unsupported features from ${unsupportedFeatures.length} to ${filteredUnsupportedFeatures.length}`);
444
518
  }
445
519
  }
@@ -461,9 +535,11 @@ async function runChecks(configs, logger) {
461
535
  errArray.push(...errors);
462
536
 
463
537
  if (errArray.length > 0) {
464
- logger.error(`ES-Check: there were ${errArray.length} ES version matching errors.`)
465
- errArray.forEach((o) => {
466
- logger.info(`
538
+ allErrors.push(...errArray);
539
+ if (logger) {
540
+ logger.error(`ES-Check: there were ${errArray.length} ES version matching errors.`)
541
+ errArray.forEach((o) => {
542
+ logger.info(`
467
543
  ES-Check Error:
468
544
  ----
469
545
  · erroring file: ${o.file}
@@ -472,16 +548,29 @@ async function runChecks(configs, logger) {
472
548
  ----\n
473
549
  ${o.stack}
474
550
  `)
475
- })
551
+ })
552
+ }
476
553
  hasErrors = true;
477
- process.exit(1)
478
- return;
554
+
555
+ if (!isNodeAPI) {
556
+ process.exit(1)
557
+ return;
558
+ }
559
+ } else {
560
+ if (logger) logger.info(`ES-Check: there were no ES version matching errors! 🎉`)
479
561
  }
480
- logger.info(`ES-Check: there were no ES version matching errors! 🎉`)
481
562
  }
482
563
 
483
564
  if (hasErrors) {
484
- process.exit(1);
565
+ if (!isNodeAPI) {
566
+ process.exit(1);
567
+ } else {
568
+ return { success: false, errors: allErrors };
569
+ }
570
+ }
571
+
572
+ if (isNodeAPI) {
573
+ return { success: true, errors: [] };
485
574
  }
486
575
  }
487
576
 
@@ -491,5 +580,7 @@ if (require.main === module) {
491
580
 
492
581
  module.exports = {
493
582
  runChecks,
494
- loadConfig
583
+ loadConfig,
584
+ // Export commonly used utilities
585
+ createLogger
495
586
  }
package/package.json CHANGED
@@ -1,16 +1,23 @@
1
1
  {
2
2
  "name": "es-check",
3
- "version": "9.2.0",
3
+ "version": "9.3.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",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./esm-wrapper.mjs",
10
+ "require": "./index.js"
11
+ }
12
+ },
7
13
  "files": [
8
14
  "index.js",
9
15
  "utils.js",
10
16
  "detectFeatures.js",
11
17
  "constants.js",
12
18
  "browserslist.js",
13
- "polyfillDetector.js"
19
+ "polyfillDetector.js",
20
+ "esm-wrapper.mjs"
14
21
  ],
15
22
  "bin": {
16
23
  "es-check": "index.js"
@@ -27,7 +34,10 @@
27
34
  "release": "release-it --no-git.requireUpstream",
28
35
  "report:coverage": "nyc report --reporter=lcov > coverage.lcov && codecov",
29
36
  "setup": "pnpm install --reporter=silent",
30
- "test": "nyc mocha test.js utils.test.js browserslist.test.js polyfillDetector.test.js detectFeatures.test.js --timeout 10s",
37
+ "test": "nyc mocha test.js utils.test.js browserslist.test.js polyfillDetector.test.js detectFeatures.test.js --timeout 10s && npm run test:e2e",
38
+ "test:e2e": "npm run test:e2e:cli && npm run test:e2e:esm",
39
+ "test:e2e:cli": "node e2e/test-cli.js",
40
+ "test:e2e:esm": "node e2e/test-esm-import.mjs",
31
41
  "update": "codependence --update",
32
42
  "benchmark": "./benchmarks/run-benchmarks.sh",
33
43
  "site:dev": "pnpm --filter es-check-docs run dev",
@@ -57,7 +67,7 @@
57
67
  "codependence": "^0.3.1",
58
68
  "commitizen": "4.3.1",
59
69
  "conventional-changelog-cli": "^5.0.0",
60
- "eslint": "9.32.0",
70
+ "eslint": "9.33.0",
61
71
  "eslint-config-prettier": "10.1.8",
62
72
  "eslint-plugin-es5": "^1.5.0",
63
73
  "husky": "9.1.7",
package/utils.js CHANGED
@@ -361,18 +361,35 @@ async function processBatchedFiles(files, processor, batchSize = 0) {
361
361
  return results;
362
362
  }
363
363
 
364
+ const SimpleCache = require('./cache');
365
+ const fileCache = new SimpleCache(500, 60000);
366
+
364
367
  /**
365
368
  * Read file asynchronously with error handling
366
369
  * @param {string} file - File path to read
367
370
  * @param {Object} fs - File system module
371
+ * @param {boolean} useCache - Whether to use file cache (default: false)
368
372
  * @returns {Promise<{content: string, error: null} | {content: null, error: Object}>}
369
373
  */
370
- async function readFileAsync(file, fs) {
374
+ async function readFileAsync(file, fs, useCache = false) {
375
+ if (useCache) {
376
+ const cached = fileCache.get(file);
377
+ if (cached !== undefined) {
378
+ return cached;
379
+ }
380
+ }
381
+
371
382
  try {
372
383
  const content = await fs.promises.readFile(file, 'utf8');
373
- return { content, error: null };
384
+ const result = { content, error: null };
385
+
386
+ if (useCache) {
387
+ fileCache.set(file, result);
388
+ }
389
+
390
+ return result;
374
391
  } catch (err) {
375
- return {
392
+ const result = {
376
393
  content: null,
377
394
  error: {
378
395
  err,
@@ -380,9 +397,29 @@ async function readFileAsync(file, fs) {
380
397
  stack: err.stack
381
398
  }
382
399
  };
400
+
401
+ if (useCache) {
402
+ fileCache.set(file, result);
403
+ }
404
+
405
+ return result;
383
406
  }
384
407
  }
385
408
 
409
+ /**
410
+ * Clear the file cache
411
+ */
412
+ function clearFileCache() {
413
+ fileCache.clear();
414
+ }
415
+
416
+ /**
417
+ * Get file cache statistics
418
+ */
419
+ function getFileCacheStats() {
420
+ return fileCache.getStats();
421
+ }
422
+
386
423
  /**
387
424
  * Parse code with acorn and handle errors
388
425
  * @param {string} code - Code to parse
@@ -407,6 +444,77 @@ function parseCode(code, acornOpts, acorn, file) {
407
444
  }
408
445
  }
409
446
 
447
+ /**
448
+ * Determine how runChecks is being invoked (CLI vs Node API)
449
+ * @param {Object|null} loggerOrOptions - Logger or options object passed to runChecks
450
+ * @returns {{isNodeAPI: boolean, logger: Object|null}}
451
+ */
452
+ function determineInvocationType(loggerOrOptions) {
453
+ let isNodeAPI = false;
454
+ let logger = null;
455
+
456
+ if (!loggerOrOptions) {
457
+ isNodeAPI = true;
458
+ logger = null;
459
+ } else if (typeof loggerOrOptions === 'object' && !loggerOrOptions.info && !loggerOrOptions.error) {
460
+ isNodeAPI = true;
461
+ logger = loggerOrOptions.logger || null;
462
+ } else {
463
+ isNodeAPI = false;
464
+ logger = loggerOrOptions;
465
+ }
466
+
467
+ return { isNodeAPI, logger };
468
+ }
469
+
470
+ /**
471
+ * Determine log levels based on logger capabilities
472
+ * @param {Object|null} logger - Logger object
473
+ * @returns {Object|null} Object with log level flags or null if no logger
474
+ */
475
+ function determineLogLevel(logger) {
476
+ if (!logger || !logger.isLevelEnabled) {
477
+ return null;
478
+ }
479
+
480
+ return {
481
+ isDebug: logger.isLevelEnabled('debug'),
482
+ isWarn: logger.isLevelEnabled('warn'),
483
+ isInfo: logger.isLevelEnabled('info'),
484
+ isError: logger.isLevelEnabled('error')
485
+ };
486
+ }
487
+
488
+ /**
489
+ * Handle ES version errors uniformly for different error types
490
+ * @param {Object} options - Options object
491
+ * @param {string} options.errorType - Type of error ('browserslist', 'es3', 'default')
492
+ * @param {string} options.errorMessage - Error message to display
493
+ * @param {Object|null} options.logger - Logger object
494
+ * @param {boolean} options.isNodeAPI - Whether running as Node API
495
+ * @param {Array} options.allErrors - Array to collect errors
496
+ * @param {string} [options.file='config'] - File reference for error
497
+ * @returns {{shouldContinue: boolean, hasErrors: boolean}}
498
+ */
499
+ function handleESVersionError(options) {
500
+ const { errorType, errorMessage, logger, isNodeAPI, allErrors, file = 'config' } = options;
501
+
502
+ if (logger) {
503
+ logger.error(errorMessage);
504
+ }
505
+
506
+ if (!isNodeAPI) {
507
+ process.exit(1);
508
+ return { shouldContinue: false, hasErrors: true };
509
+ } else {
510
+ allErrors.push({
511
+ err: new Error(errorMessage),
512
+ file
513
+ });
514
+ return { shouldContinue: true, hasErrors: true };
515
+ }
516
+ }
517
+
410
518
  module.exports = {
411
519
  parseIgnoreList,
412
520
  checkVarKindMatch,
@@ -419,5 +527,10 @@ module.exports = {
419
527
  generateZshCompletion,
420
528
  processBatchedFiles,
421
529
  readFileAsync,
422
- parseCode
530
+ clearFileCache,
531
+ getFileCacheStats,
532
+ parseCode,
533
+ determineInvocationType,
534
+ determineLogLevel,
535
+ handleESVersionError
423
536
  };