es-check 9.5.2 → 9.5.4

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.
@@ -56,7 +56,7 @@ function getESVersionFromBrowserslist(options = {}) {
56
56
 
57
57
  try {
58
58
  const browsers = browserslist(browserslistQuery ?? null, {
59
- path: browserslistPath,
59
+ config: browserslistPath,
60
60
  env: browserslistEnv,
61
61
  });
62
62
 
@@ -88,20 +88,23 @@ function getESVersionFromBrowserslist(options = {}) {
88
88
  return 5;
89
89
  }
90
90
 
91
- if (browserslistPath && browserslistPath.includes("mixed")) {
91
+ const knownBrowsers = browsers.filter((browser) => {
92
+ const [browserName] = browser.split(" ");
93
+ return BROWSER_TO_ES_VERSION[browserName];
94
+ });
95
+
96
+ if (knownBrowsers.length === 0) {
92
97
  return 5;
93
98
  }
94
99
 
95
- const esVersions = browsers.map((browser) => {
100
+ const esVersions = knownBrowsers.map((browser) => {
96
101
  const [browserName, version] = browser.split(" ");
97
102
  return getESVersionForBrowser(browserName, version);
98
103
  });
99
104
 
100
- const maxESVersion = esVersions.reduce((max, version) => {
101
- return version > max ? version : max;
102
- }, 5);
105
+ const minESVersion = Math.min(...esVersions);
103
106
 
104
- return maxESVersion;
107
+ return minESVersion;
105
108
  } catch {
106
109
  return 5;
107
110
  }
@@ -24,7 +24,7 @@ function determineLogLevel(logger) {
24
24
  };
25
25
  }
26
26
 
27
- async function processConfig(config, context) {
27
+ function processConfig(config, context) {
28
28
  const { logger, isDebug, isWarn, isNodeAPI, allErrors } = context;
29
29
 
30
30
  const ignoreFilePath = config.ignoreFile || config["ignore-file"];
@@ -80,7 +80,11 @@ async function processConfig(config, context) {
80
80
  }
81
81
 
82
82
  const { ecmaVersion } = versionResult;
83
- const acornOpts = { ecmaVersion: parseInt(ecmaVersion, 10), silent: true };
83
+ const useLatestForParsing = config.checkFeatures;
84
+ const parserEcmaVersion = useLatestForParsing
85
+ ? 2025
86
+ : parseInt(ecmaVersion, 10);
87
+ const acornOpts = { ecmaVersion: parserEcmaVersion, silent: true };
84
88
 
85
89
  if (isDebug) {
86
90
  logger.debug(`ES-Check: Going to check files using version ${ecmaVersion}`);
@@ -134,23 +138,13 @@ async function processConfig(config, context) {
134
138
  ecmaVersion,
135
139
  });
136
140
 
137
- const results = await processBatchedFiles(
138
- filteredFiles,
139
- processFile,
140
- batchSize,
141
- );
141
+ const results = processBatchedFiles(filteredFiles, processFile, batchSize);
142
142
  const errors = results.filter((result) => result !== null);
143
143
 
144
- return await processConfigResult(
145
- errors,
146
- logger,
147
- isNodeAPI,
148
- allErrors,
149
- ecmaVersion,
150
- );
144
+ return processConfigResult(errors, logger, isNodeAPI, allErrors, ecmaVersion);
151
145
  }
152
146
 
153
- async function runChecks(configs, loggerOrOptions) {
147
+ function runChecks(configs, loggerOrOptions) {
154
148
  const { isNodeAPI, logger } = determineInvocationType(loggerOrOptions);
155
149
 
156
150
  const logLevels = determineLogLevel(logger);
@@ -162,7 +156,7 @@ async function runChecks(configs, loggerOrOptions) {
162
156
 
163
157
  const results = [];
164
158
  for (const config of configs) {
165
- const result = await processConfig(config, context);
159
+ const result = processConfig(config, context);
166
160
  results.push(result);
167
161
 
168
162
  const shouldBreak = result.hasErrors && !result.shouldContinue;
@@ -6,7 +6,7 @@ const {
6
6
  polyfillDetector: polyfillDetectorModule,
7
7
  parseIgnoreList,
8
8
  processBatchedFiles,
9
- readFileAsync,
9
+ readFile,
10
10
  parseCode,
11
11
  } = require("../helpers");
12
12
  const {
@@ -108,12 +108,14 @@ function findFiles(patterns, options) {
108
108
  return { files: [], hasError: true };
109
109
  }
110
110
 
111
+ let hasPatternWithNoFiles = false;
111
112
  const allMatchedFiles = patterns.flatMap((pattern) => {
112
113
  const globbedFiles = glob.sync(pattern, globOpts);
113
114
  const noFilesFound = globbedFiles.length === 0;
114
115
  const shouldErrorOnNoFiles = noFilesFound && !looseGlobMatching;
115
116
 
116
117
  if (shouldErrorOnNoFiles) {
118
+ hasPatternWithNoFiles = true;
117
119
  handleMissingFiles(pattern, options);
118
120
  }
119
121
 
@@ -149,13 +151,7 @@ function findFiles(patterns, options) {
149
151
  );
150
152
  }
151
153
 
152
- const hasError = patterns.some((pattern) => {
153
- const globbedFiles = glob.sync(pattern, globOpts);
154
- const noFilesFound = globbedFiles.length === 0;
155
- return noFilesFound && !looseGlobMatching;
156
- });
157
-
158
- return { files: allMatchedFiles, hasError };
154
+ return { files: allMatchedFiles, hasError: hasPatternWithNoFiles };
159
155
  }
160
156
 
161
157
  function handleBrowserslistError(browserslistError, options) {
@@ -323,13 +319,26 @@ function processFullAST(
323
319
  const parseSourceType = acornOpts.sourceType || "script";
324
320
  const esVersion = parseInt(ecmaVersion, 10);
325
321
 
326
- const { foundFeatures, unsupportedFeatures } = detectFeatures(
327
- code,
328
- esVersion,
329
- parseSourceType,
330
- ignoreList,
331
- { ast, checkForPolyfills: config.checkForPolyfills },
332
- );
322
+ let foundFeatures;
323
+ let unsupportedFeatures;
324
+
325
+ try {
326
+ const result = detectFeatures(
327
+ code,
328
+ esVersion,
329
+ parseSourceType,
330
+ ignoreList,
331
+ { ast, checkForPolyfills: config.checkForPolyfills },
332
+ );
333
+ foundFeatures = result.foundFeatures;
334
+ unsupportedFeatures = result.unsupportedFeatures;
335
+ } catch (err) {
336
+ const isESCheckFeatureError = err.type === "ES-Check" && err.features;
337
+ if (isESCheckFeatureError) {
338
+ return { err, file, stack: err.stack };
339
+ }
340
+ throw err;
341
+ }
333
342
 
334
343
  if (isDebug) {
335
344
  const stringifiedFeatures = JSON.stringify(foundFeatures, null, 2);
@@ -388,13 +397,9 @@ function processFullAST(
388
397
  function createFileProcessor(config, options) {
389
398
  const { acornOpts, ignoreList, logger, isDebug, ecmaVersion } = options;
390
399
 
391
- return async (file) => {
400
+ return (file) => {
392
401
  const useCache = config.cache !== false;
393
- const { content: code, error: readError } = await readFileAsync(
394
- file,
395
- fs,
396
- useCache,
397
- );
402
+ const { content: code, error: readError } = readFile(file, fs, useCache);
398
403
 
399
404
  const hasReadError = readError !== null;
400
405
  if (hasReadError) return readError;
@@ -416,7 +421,7 @@ function createFileProcessor(config, options) {
416
421
  };
417
422
  }
418
423
 
419
- async function logErrors(errors, logger) {
424
+ function logErrors(errors, logger) {
420
425
  const hasLogger = logger !== null && logger !== undefined;
421
426
  if (!hasLogger) return;
422
427
 
@@ -429,7 +434,7 @@ async function logErrors(errors, logger) {
429
434
  for (const error of errors) {
430
435
  const hasLocation = error.line !== null && error.line !== undefined;
431
436
  const mapped = hasLocation
432
- ? await mapErrorPosition(error.file, error.line, error.column)
437
+ ? mapErrorPosition(error.file, error.line, error.column)
433
438
  : { file: error.file, line: error.line, column: error.column };
434
439
 
435
440
  const locationInfo = hasLocation
@@ -448,7 +453,7 @@ async function logErrors(errors, logger) {
448
453
  }
449
454
  }
450
455
 
451
- async function processConfigResult(
456
+ function processConfigResult(
452
457
  errors,
453
458
  logger,
454
459
  isNodeAPI,
@@ -458,7 +463,7 @@ async function processConfigResult(
458
463
  const hasFileErrors = errors.length > 0;
459
464
 
460
465
  if (hasFileErrors) {
461
- await logErrors(errors, logger);
466
+ logErrors(errors, logger);
462
467
  allErrors.push(...errors);
463
468
 
464
469
  const isExiting = !isNodeAPI;
@@ -99,8 +99,8 @@ const BROWSER_TO_ES_VERSION = {
99
99
  chrome: {
100
100
  51: 6,
101
101
  55: 7,
102
- 60: 8,
103
- 70: 9,
102
+ 58: 8,
103
+ 60: 9,
104
104
  75: 10,
105
105
  80: 11,
106
106
  85: 12,
@@ -1,3 +1,18 @@
1
+ function hasOptionsCause(node) {
2
+ const args = node.arguments;
3
+ if (!args || args.length < 2) return false;
4
+
5
+ const optionsArg = args[1];
6
+ if (optionsArg?.type !== "ObjectExpression") return false;
7
+
8
+ return optionsArg.properties?.some(
9
+ (prop) =>
10
+ prop.type === "Property" &&
11
+ prop.key?.type === "Identifier" &&
12
+ prop.key?.name === "cause",
13
+ );
14
+ }
15
+
1
16
  function checkMap(node, astInfo) {
2
17
  const hasKindMismatch = astInfo.kind && node.kind !== astInfo.kind;
3
18
  const hasOperatorMismatch =
@@ -15,15 +30,28 @@ function checkMap(node, astInfo) {
15
30
  node.callee?.object?.name,
16
31
  );
17
32
 
33
+ const needsObjectAndProperty = astInfo.object && astInfo.property;
34
+ const hasObjectAndPropertyMismatch =
35
+ needsObjectAndProperty && !(hasCalleeObject && hasPropertyMatch);
36
+
18
37
  const hasObjectPropertyMismatch =
19
- astInfo.object && hasCalleeObject && hasPropertyMismatch;
38
+ !needsObjectAndProperty &&
39
+ astInfo.object &&
40
+ hasCalleeObject &&
41
+ hasPropertyMismatch;
20
42
  const hasObjectButNoMatch =
21
- astInfo.object && !hasCalleeObject && !hasCalleeIdentifier;
43
+ !needsObjectAndProperty &&
44
+ astInfo.object &&
45
+ !hasCalleeObject &&
46
+ !hasCalleeIdentifier;
22
47
  const hasPropertyWithoutMatch =
23
48
  astInfo.property && !astInfo.object && !hasPropertyMatch;
24
49
  const hasPropertyWithExclusion =
25
50
  astInfo.property && !astInfo.object && hasPropertyMatch && shouldExclude;
26
51
 
52
+ const hasOptionsCauseMismatch =
53
+ astInfo.hasOptionsCause && !hasOptionsCause(node);
54
+
27
55
  const hasMismatch =
28
56
  hasKindMismatch ||
29
57
  hasOperatorMismatch ||
@@ -31,7 +59,9 @@ function checkMap(node, astInfo) {
31
59
  hasObjectPropertyMismatch ||
32
60
  hasObjectButNoMatch ||
33
61
  hasPropertyWithoutMatch ||
34
- hasPropertyWithExclusion;
62
+ hasPropertyWithExclusion ||
63
+ hasObjectAndPropertyMismatch ||
64
+ hasOptionsCauseMismatch;
35
65
 
36
66
  if (hasMismatch) return false;
37
67
 
@@ -1,6 +1,13 @@
1
1
  const { ES_FEATURES } = require("../constants");
2
2
  const { checkMap } = require("./ast");
3
3
 
4
+ const FUNCTION_TYPES = new Set([
5
+ "FunctionDeclaration",
6
+ "FunctionExpression",
7
+ "ArrowFunctionExpression",
8
+ "MethodDefinition",
9
+ ]);
10
+
4
11
  const CHILD_KEYS = [
5
12
  "body",
6
13
  "declarations",
@@ -64,7 +71,7 @@ function buildFeatureIndex(features) {
64
71
 
65
72
  const featuresByNodeType = buildFeatureIndex(ES_FEATURES);
66
73
 
67
- function matchesFeature(node, astInfo) {
74
+ function matchesFeature(node, astInfo, context = {}) {
68
75
  if (astInfo.childType) {
69
76
  return node.elements?.some((el) => el?.type === astInfo.childType) || false;
70
77
  }
@@ -93,6 +100,14 @@ function matchesFeature(node, astInfo) {
93
100
  return node.superClass !== null;
94
101
  }
95
102
 
103
+ if (astInfo.topLevel && node.type === "AwaitExpression") {
104
+ return context.isTopLevel === true;
105
+ }
106
+
107
+ if (astInfo.leftIsPrivate && node.type === "BinaryExpression") {
108
+ return node.left?.type === "PrivateIdentifier";
109
+ }
110
+
96
111
  if (node.type === "CallExpression" || node.type === "NewExpression") {
97
112
  return checkMap(node, astInfo);
98
113
  }
@@ -107,17 +122,21 @@ function detectFeaturesFromAST(ast) {
107
122
  }
108
123
 
109
124
  const remaining = new Set(Object.keys(ES_FEATURES));
110
- const stack = [ast];
125
+ const stack = [{ node: ast, functionDepth: 0 }];
111
126
 
112
127
  while (stack.length > 0 && remaining.size > 0) {
113
- const node = stack.pop();
128
+ const { node, functionDepth } = stack.pop();
114
129
  if (!node || typeof node !== "object") continue;
115
130
 
131
+ const isFunction = FUNCTION_TYPES.has(node.type);
132
+ const newFunctionDepth = isFunction ? functionDepth + 1 : functionDepth;
133
+
116
134
  const candidates = featuresByNodeType[node.type];
117
135
  if (candidates) {
136
+ const context = { isTopLevel: functionDepth === 0 };
118
137
  for (const { name, astInfo } of candidates) {
119
138
  if (!remaining.has(name)) continue;
120
- if (matchesFeature(node, astInfo)) {
139
+ if (matchesFeature(node, astInfo, context)) {
121
140
  foundFeatures[name] = true;
122
141
  remaining.delete(name);
123
142
  }
@@ -129,10 +148,11 @@ function detectFeaturesFromAST(ast) {
129
148
  if (!child) continue;
130
149
  if (Array.isArray(child)) {
131
150
  for (let i = child.length - 1; i >= 0; i--) {
132
- if (child[i]) stack.push(child[i]);
151
+ if (child[i])
152
+ stack.push({ node: child[i], functionDepth: newFunctionDepth });
133
153
  }
134
154
  } else if (child.type) {
135
- stack.push(child);
155
+ stack.push({ node: child, functionDepth: newFunctionDepth });
136
156
  }
137
157
  }
138
158
  }
@@ -2,26 +2,25 @@ const fileCache = new Map();
2
2
  let cacheHits = 0;
3
3
  let cacheMisses = 0;
4
4
 
5
- async function processBatchedFiles(files, processor, batchSize = 0) {
5
+ function processBatchedFiles(files, processor, batchSize = 0) {
6
6
  if (batchSize <= 0) {
7
- return await Promise.all(files.map(processor));
7
+ return files.map(processor);
8
8
  }
9
9
 
10
10
  const results = [];
11
11
  for (let i = 0; i < files.length; i += batchSize) {
12
12
  const batch = files.slice(i, i + batchSize);
13
- const batchResults = await Promise.all(batch.map(processor));
13
+ const batchResults = batch.map(processor);
14
14
  results.push(...batchResults);
15
15
  }
16
16
 
17
17
  return results;
18
18
  }
19
19
 
20
- async function readFileAsync(file, fs, useCache = false) {
20
+ function readFile(file, fs, useCache = false) {
21
21
  if (useCache && fileCache.has(file)) {
22
22
  cacheHits++;
23
- const cached = fileCache.get(file);
24
- return cached;
23
+ return fileCache.get(file);
25
24
  }
26
25
 
27
26
  if (useCache) {
@@ -29,7 +28,7 @@ async function readFileAsync(file, fs, useCache = false) {
29
28
  }
30
29
 
31
30
  try {
32
- const content = await fs.promises.readFile(file, "utf8");
31
+ const content = fs.readFileSync(file, "utf8");
33
32
  const result = { content, error: null };
34
33
 
35
34
  if (useCache) {
@@ -75,7 +74,7 @@ function getFileCacheStats() {
75
74
 
76
75
  module.exports = {
77
76
  processBatchedFiles,
78
- readFileAsync,
77
+ readFile,
79
78
  clearFileCache,
80
79
  getFileCacheStats,
81
80
  };
@@ -239,28 +239,27 @@ class SourceMapConsumer {
239
239
  }
240
240
  }
241
241
 
242
- async function loadSourceMap(file) {
242
+ function loadSourceMap(file) {
243
243
  const mapFile = `${file}.map`;
244
244
 
245
245
  try {
246
246
  const mapExists = fs.existsSync(mapFile);
247
247
  if (!mapExists) return null;
248
248
 
249
- const mapContent = await fs.promises.readFile(mapFile, "utf8");
249
+ const mapContent = fs.readFileSync(mapFile, "utf8");
250
250
  const sourceMapData = JSON.parse(mapContent);
251
251
 
252
252
  const isValidVersion = sourceMapData.version === 3;
253
253
  if (!isValidVersion) return null;
254
254
 
255
- const consumer = new SourceMapConsumer(sourceMapData);
256
- return consumer;
255
+ return new SourceMapConsumer(sourceMapData);
257
256
  } catch {
258
257
  return null;
259
258
  }
260
259
  }
261
260
 
262
- async function mapErrorPosition(file, line, column) {
263
- const consumer = await loadSourceMap(file);
261
+ function mapErrorPosition(file, line, column) {
262
+ const consumer = loadSourceMap(file);
264
263
  const hasNoConsumer = !consumer;
265
264
 
266
265
  if (hasNoConsumer) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "es-check",
3
- "version": "9.5.2",
3
+ "version": "9.5.4",
4
4
  "description": "Checks the ECMAScript version of .js glob against a specified version of ECMAScript with a shell command",
5
5
  "main": "lib/index.js",
6
6
  "license": "MIT",
@@ -52,11 +52,11 @@
52
52
  "homepage": "https://github.com/yowainwright/es-check#readme",
53
53
  "website": "https://jeffry.in/es-check",
54
54
  "devDependencies": {
55
- "codependence": "^0.3.1",
55
+ "codependence": "1.0.0-1",
56
56
  "oxlint": "^1.29.0",
57
- "pastoralist": "1.8.2",
58
- "prettier": "3.6.2",
59
- "release-it": "19.0.6"
57
+ "pastoralist": "1.10.0-1",
58
+ "prettier": "3.7.4",
59
+ "release-it": "19.2.3"
60
60
  },
61
61
  "dependencies": {
62
62
  "acorn": "8.15.0",
@@ -109,22 +109,16 @@
109
109
  "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
110
110
  }
111
111
  },
112
- "packageManager": "pnpm@10.12.1",
112
+ "packageManager": "pnpm@10.27.0",
113
113
  "pastoralist": {
114
114
  "appendix": {
115
115
  "vite@6.4.1": {
116
116
  "dependents": {
117
- "es-check": "vite (transitive dependency)"
117
+ "es-check": "vite (transitive dependency)",
118
+ "es-check-docs": "vite@^7.3.0"
118
119
  },
119
120
  "ledger": {
120
- "addedDate": "2025-11-21T20:27:19.148Z",
121
- "reason": "Security fix: vite allows server.fs.deny bypass via backslash on Windows (medium)",
122
- "securityChecked": true,
123
- "securityCheckDate": "2025-11-21T20:27:19.148Z",
124
- "securityProvider": "github",
125
- "cve": "CVE-2025-62522",
126
- "severity": "medium",
127
- "url": "https://github.com/yowainwright/es-check/security/dependabot/39"
121
+ "addedDate": "2026-01-12T05:15:47.989Z"
128
122
  }
129
123
  },
130
124
  "tmp@0.2.4": {
@@ -132,38 +126,38 @@
132
126
  "es-check": "tmp (transitive dependency)"
133
127
  },
134
128
  "ledger": {
135
- "addedDate": "2025-11-21T20:27:19.148Z"
129
+ "addedDate": "2026-01-12T05:15:47.989Z"
136
130
  }
137
131
  },
138
- "astro@5.15.9": {
132
+ "js-yaml@4.1.1": {
139
133
  "dependents": {
140
- "es-check": "astro (transitive dependency)",
141
- "es-check-docs": "astro@^5.15.9"
134
+ "es-check": "js-yaml (transitive dependency)"
142
135
  },
143
136
  "ledger": {
144
- "addedDate": "2025-11-21T20:27:19.148Z",
145
- "reason": "Security fix: Astro Cloudflare adapter has Stored Cross Site Scripting vulnerability in /_image endpoint (medium)",
146
- "securityChecked": true,
147
- "securityCheckDate": "2025-11-21T20:27:19.148Z",
148
- "securityProvider": "github",
149
- "cve": "CVE-2025-65019",
150
- "severity": "medium",
151
- "url": "https://github.com/yowainwright/es-check/security/dependabot/51"
137
+ "addedDate": "2026-01-12T05:15:47.989Z"
152
138
  }
153
139
  },
154
- "js-yaml@4.1.1": {
140
+ "mdast-util-to-hast@13.2.1": {
155
141
  "dependents": {
156
- "es-check": "js-yaml (transitive dependency)"
142
+ "es-check": "mdast-util-to-hast (transitive dependency)"
143
+ },
144
+ "ledger": {
145
+ "addedDate": "2026-01-12T05:15:47.989Z"
146
+ }
147
+ },
148
+ "undici@6.23.0": {
149
+ "dependents": {
150
+ "es-check": "undici (transitive dependency)"
157
151
  },
158
152
  "ledger": {
159
- "addedDate": "2025-11-21T20:27:19.148Z",
160
- "reason": "Security fix: js-yaml has prototype pollution in merge (<<) (medium)",
153
+ "addedDate": "2026-01-17T00:15:11.438Z",
154
+ "reason": "Security fix: Undici has an unbounded decompression chain in HTTP responses on Node.js Fetch API via Content-Encoding leads to resource exhaustion (low)",
161
155
  "securityChecked": true,
162
- "securityCheckDate": "2025-11-21T20:27:19.148Z",
156
+ "securityCheckDate": "2026-01-17T00:15:11.438Z",
163
157
  "securityProvider": "github",
164
- "cve": "CVE-2025-64718",
165
- "severity": "medium",
166
- "url": "https://github.com/yowainwright/es-check/security/dependabot/46"
158
+ "cve": "CVE-2026-22036",
159
+ "severity": "low",
160
+ "url": "https://github.com/yowainwright/es-check/security/dependabot/57"
167
161
  }
168
162
  }
169
163
  },
@@ -184,8 +178,9 @@
184
178
  "overrides": {
185
179
  "vite": "6.4.1",
186
180
  "tmp": "0.2.4",
187
- "astro": "5.15.9",
188
- "js-yaml": "4.1.1"
181
+ "js-yaml": "4.1.1",
182
+ "mdast-util-to-hast": "13.2.1",
183
+ "undici": "6.23.0"
189
184
  }
190
185
  }
191
186
  }