eslint 8.49.0 → 8.51.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.
@@ -12,7 +12,15 @@ const
12
12
  { isCommentToken } = require("@eslint-community/eslint-utils"),
13
13
  TokenStore = require("./token-store"),
14
14
  astUtils = require("../shared/ast-utils"),
15
- Traverser = require("../shared/traverser");
15
+ Traverser = require("../shared/traverser"),
16
+ globals = require("../../conf/globals"),
17
+ {
18
+ directivesPattern
19
+ } = require("../shared/directives"),
20
+
21
+ /* eslint-disable-next-line n/no-restricted-require -- Too messy to figure out right now. */
22
+ ConfigCommentParser = require("../linter/config-comment-parser"),
23
+ eslintScope = require("eslint-scope");
16
24
 
17
25
  //------------------------------------------------------------------------------
18
26
  // Type Definitions
@@ -24,6 +32,8 @@ const
24
32
  // Private
25
33
  //------------------------------------------------------------------------------
26
34
 
35
+ const commentParser = new ConfigCommentParser();
36
+
27
37
  /**
28
38
  * Validates that the given AST has the required information.
29
39
  * @param {ASTNode} ast The Program node of the AST to check.
@@ -49,6 +59,29 @@ function validate(ast) {
49
59
  }
50
60
  }
51
61
 
62
+ /**
63
+ * Retrieves globals for the given ecmaVersion.
64
+ * @param {number} ecmaVersion The version to retrieve globals for.
65
+ * @returns {Object} The globals for the given ecmaVersion.
66
+ */
67
+ function getGlobalsForEcmaVersion(ecmaVersion) {
68
+
69
+ switch (ecmaVersion) {
70
+ case 3:
71
+ return globals.es3;
72
+
73
+ case 5:
74
+ return globals.es5;
75
+
76
+ default:
77
+ if (ecmaVersion < 2015) {
78
+ return globals[`es${ecmaVersion + 2009}`];
79
+ }
80
+
81
+ return globals[`es${ecmaVersion}`];
82
+ }
83
+ }
84
+
52
85
  /**
53
86
  * Check to see if its a ES6 export declaration.
54
87
  * @param {ASTNode} astNode An AST node.
@@ -83,6 +116,36 @@ function sortedMerge(tokens, comments) {
83
116
  return result;
84
117
  }
85
118
 
119
+ /**
120
+ * Normalizes a value for a global in a config
121
+ * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in
122
+ * a global directive comment
123
+ * @returns {("readable"|"writeable"|"off")} The value normalized as a string
124
+ * @throws Error if global value is invalid
125
+ */
126
+ function normalizeConfigGlobal(configuredValue) {
127
+ switch (configuredValue) {
128
+ case "off":
129
+ return "off";
130
+
131
+ case true:
132
+ case "true":
133
+ case "writeable":
134
+ case "writable":
135
+ return "writable";
136
+
137
+ case null:
138
+ case false:
139
+ case "false":
140
+ case "readable":
141
+ case "readonly":
142
+ return "readonly";
143
+
144
+ default:
145
+ throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`);
146
+ }
147
+ }
148
+
86
149
  /**
87
150
  * Determines if two nodes or tokens overlap.
88
151
  * @param {ASTNode|Token} first The first node or token to check.
@@ -145,6 +208,116 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) {
145
208
  return false;
146
209
  }
147
210
 
211
+ //-----------------------------------------------------------------------------
212
+ // Directive Comments
213
+ //-----------------------------------------------------------------------------
214
+
215
+ /**
216
+ * Extract the directive and the justification from a given directive comment and trim them.
217
+ * @param {string} value The comment text to extract.
218
+ * @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
219
+ */
220
+ function extractDirectiveComment(value) {
221
+ const match = /\s-{2,}\s/u.exec(value);
222
+
223
+ if (!match) {
224
+ return { directivePart: value.trim(), justificationPart: "" };
225
+ }
226
+
227
+ const directive = value.slice(0, match.index).trim();
228
+ const justification = value.slice(match.index + match[0].length).trim();
229
+
230
+ return { directivePart: directive, justificationPart: justification };
231
+ }
232
+
233
+ /**
234
+ * Ensures that variables representing built-in properties of the Global Object,
235
+ * and any globals declared by special block comments, are present in the global
236
+ * scope.
237
+ * @param {Scope} globalScope The global scope.
238
+ * @param {Object|undefined} configGlobals The globals declared in configuration
239
+ * @param {Object|undefined} inlineGlobals The globals declared in the source code
240
+ * @returns {void}
241
+ */
242
+ function addDeclaredGlobals(globalScope, configGlobals = {}, inlineGlobals = {}) {
243
+
244
+ // Define configured global variables.
245
+ for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(inlineGlobals)])) {
246
+
247
+ /*
248
+ * `normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
249
+ * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
250
+ */
251
+ const configValue = configGlobals[id] === void 0 ? void 0 : normalizeConfigGlobal(configGlobals[id]);
252
+ const commentValue = inlineGlobals[id] && inlineGlobals[id].value;
253
+ const value = commentValue || configValue;
254
+ const sourceComments = inlineGlobals[id] && inlineGlobals[id].comments;
255
+
256
+ if (value === "off") {
257
+ continue;
258
+ }
259
+
260
+ let variable = globalScope.set.get(id);
261
+
262
+ if (!variable) {
263
+ variable = new eslintScope.Variable(id, globalScope);
264
+
265
+ globalScope.variables.push(variable);
266
+ globalScope.set.set(id, variable);
267
+ }
268
+
269
+ variable.eslintImplicitGlobalSetting = configValue;
270
+ variable.eslintExplicitGlobal = sourceComments !== void 0;
271
+ variable.eslintExplicitGlobalComments = sourceComments;
272
+ variable.writeable = (value === "writable");
273
+ }
274
+
275
+ /*
276
+ * "through" contains all references which definitions cannot be found.
277
+ * Since we augment the global scope using configuration, we need to update
278
+ * references and remove the ones that were added by configuration.
279
+ */
280
+ globalScope.through = globalScope.through.filter(reference => {
281
+ const name = reference.identifier.name;
282
+ const variable = globalScope.set.get(name);
283
+
284
+ if (variable) {
285
+
286
+ /*
287
+ * Links the variable and the reference.
288
+ * And this reference is removed from `Scope#through`.
289
+ */
290
+ reference.resolved = variable;
291
+ variable.references.push(reference);
292
+
293
+ return false;
294
+ }
295
+
296
+ return true;
297
+ });
298
+ }
299
+
300
+ /**
301
+ * Sets the given variable names as exported so they won't be triggered by
302
+ * the `no-unused-vars` rule.
303
+ * @param {eslint.Scope} globalScope The global scope to define exports in.
304
+ * @param {Record<string,string>} variables An object whose keys are the variable
305
+ * names to export.
306
+ * @returns {void}
307
+ */
308
+ function markExportedVariables(globalScope, variables) {
309
+
310
+ Object.keys(variables).forEach(name => {
311
+ const variable = globalScope.set.get(name);
312
+
313
+ if (variable) {
314
+ variable.eslintUsed = true;
315
+ variable.eslintExported = true;
316
+ }
317
+ });
318
+
319
+ }
320
+
148
321
  //------------------------------------------------------------------------------
149
322
  // Public Interface
150
323
  //------------------------------------------------------------------------------
@@ -187,7 +360,9 @@ class SourceCode extends TokenStore {
187
360
  * General purpose caching for the class.
188
361
  */
189
362
  this[caches] = new Map([
190
- ["scopes", new WeakMap()]
363
+ ["scopes", new WeakMap()],
364
+ ["vars", new Map()],
365
+ ["configNodes", void 0]
191
366
  ]);
192
367
 
193
368
  /**
@@ -266,7 +441,7 @@ class SourceCode extends TokenStore {
266
441
  // Cache for comments found using getComments().
267
442
  this._commentCache = new WeakMap();
268
443
 
269
- // don't allow modification of this object
444
+ // don't allow further modification of this object
270
445
  Object.freeze(this);
271
446
  Object.freeze(this.lines);
272
447
  }
@@ -724,6 +899,178 @@ class SourceCode extends TokenStore {
724
899
  }
725
900
 
726
901
 
902
+ /**
903
+ * Returns an array of all inline configuration nodes found in the
904
+ * source code.
905
+ * @returns {Array<Token>} An array of all inline configuration nodes.
906
+ */
907
+ getInlineConfigNodes() {
908
+
909
+ // check the cache first
910
+ let configNodes = this[caches].get("configNodes");
911
+
912
+ if (configNodes) {
913
+ return configNodes;
914
+ }
915
+
916
+ // calculate fresh config nodes
917
+ configNodes = this.ast.comments.filter(comment => {
918
+
919
+ // shebang comments are never directives
920
+ if (comment.type === "Shebang") {
921
+ return false;
922
+ }
923
+
924
+ const { directivePart } = extractDirectiveComment(comment.value);
925
+
926
+ const directiveMatch = directivesPattern.exec(directivePart);
927
+
928
+ if (!directiveMatch) {
929
+ return false;
930
+ }
931
+
932
+ // only certain comment types are supported as line comments
933
+ return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directiveMatch[1]);
934
+ });
935
+
936
+ this[caches].set("configNodes", configNodes);
937
+
938
+ return configNodes;
939
+ }
940
+
941
+ /**
942
+ * Applies language options sent in from the core.
943
+ * @param {Object} languageOptions The language options for this run.
944
+ * @returns {void}
945
+ */
946
+ applyLanguageOptions(languageOptions) {
947
+
948
+ /*
949
+ * Add configured globals and language globals
950
+ *
951
+ * Using Object.assign instead of object spread for performance reasons
952
+ * https://github.com/eslint/eslint/issues/16302
953
+ */
954
+ const configGlobals = Object.assign(
955
+ {},
956
+ getGlobalsForEcmaVersion(languageOptions.ecmaVersion),
957
+ languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0,
958
+ languageOptions.globals
959
+ );
960
+ const varsCache = this[caches].get("vars");
961
+
962
+ varsCache.set("configGlobals", configGlobals);
963
+ }
964
+
965
+ /**
966
+ * Applies configuration found inside of the source code. This method is only
967
+ * called when ESLint is running with inline configuration allowed.
968
+ * @returns {{problems:Array<Problem>,configs:{config:FlatConfigArray,node:ASTNode}}} Information
969
+ * that ESLint needs to further process the inline configuration.
970
+ */
971
+ applyInlineConfig() {
972
+
973
+ const problems = [];
974
+ const configs = [];
975
+ const exportedVariables = {};
976
+ const inlineGlobals = Object.create(null);
977
+
978
+ this.getInlineConfigNodes().forEach(comment => {
979
+
980
+ const { directivePart } = extractDirectiveComment(comment.value);
981
+ const match = directivesPattern.exec(directivePart);
982
+ const directiveText = match[1];
983
+ const directiveValue = directivePart.slice(match.index + directiveText.length);
984
+
985
+ switch (directiveText) {
986
+ case "exported":
987
+ Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
988
+ break;
989
+
990
+ case "globals":
991
+ case "global":
992
+ for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
993
+ let normalizedValue;
994
+
995
+ try {
996
+ normalizedValue = normalizeConfigGlobal(value);
997
+ } catch (err) {
998
+ problems.push({
999
+ ruleId: null,
1000
+ loc: comment.loc,
1001
+ message: err.message
1002
+ });
1003
+ continue;
1004
+ }
1005
+
1006
+ if (inlineGlobals[id]) {
1007
+ inlineGlobals[id].comments.push(comment);
1008
+ inlineGlobals[id].value = normalizedValue;
1009
+ } else {
1010
+ inlineGlobals[id] = {
1011
+ comments: [comment],
1012
+ value: normalizedValue
1013
+ };
1014
+ }
1015
+ }
1016
+ break;
1017
+
1018
+ case "eslint": {
1019
+ const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
1020
+
1021
+ if (parseResult.success) {
1022
+ configs.push({
1023
+ config: {
1024
+ rules: parseResult.config
1025
+ },
1026
+ node: comment
1027
+ });
1028
+ } else {
1029
+ problems.push(parseResult.error);
1030
+ }
1031
+
1032
+ break;
1033
+ }
1034
+
1035
+ // no default
1036
+ }
1037
+ });
1038
+
1039
+ // save all the new variables for later
1040
+ const varsCache = this[caches].get("vars");
1041
+
1042
+ varsCache.set("inlineGlobals", inlineGlobals);
1043
+ varsCache.set("exportedVariables", exportedVariables);
1044
+
1045
+ return {
1046
+ configs,
1047
+ problems
1048
+ };
1049
+ }
1050
+
1051
+ /**
1052
+ * Called by ESLint core to indicate that it has finished providing
1053
+ * information. We now add in all the missing variables and ensure that
1054
+ * state-changing methods cannot be called by rules.
1055
+ * @returns {void}
1056
+ */
1057
+ finalize() {
1058
+
1059
+ // Step 1: ensure that all of the necessary variables are up to date
1060
+ const varsCache = this[caches].get("vars");
1061
+ const globalScope = this.scopeManager.scopes[0];
1062
+ const configGlobals = varsCache.get("configGlobals");
1063
+ const inlineGlobals = varsCache.get("inlineGlobals");
1064
+ const exportedVariables = varsCache.get("exportedVariables");
1065
+
1066
+ addDeclaredGlobals(globalScope, configGlobals, inlineGlobals);
1067
+
1068
+ if (exportedVariables) {
1069
+ markExportedVariables(globalScope, exportedVariables);
1070
+ }
1071
+
1072
+ }
1073
+
727
1074
  }
728
1075
 
729
1076
  module.exports = SourceCode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.49.0",
3
+ "version": "8.51.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -63,7 +63,7 @@
63
63
  "@eslint-community/eslint-utils": "^4.2.0",
64
64
  "@eslint-community/regexpp": "^4.6.1",
65
65
  "@eslint/eslintrc": "^2.1.2",
66
- "@eslint/js": "8.49.0",
66
+ "@eslint/js": "8.51.0",
67
67
  "@humanwhocodes/config-array": "^0.11.11",
68
68
  "@humanwhocodes/module-importer": "^1.0.1",
69
69
  "@nodelib/fs.walk": "^1.2.8",