eslint 9.4.0 → 9.6.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.
Files changed (42) hide show
  1. package/README.md +3 -3
  2. package/conf/ecma-version.js +1 -1
  3. package/conf/globals.js +6 -1
  4. package/lib/api.js +1 -1
  5. package/lib/cli.js +23 -2
  6. package/lib/config/default-config.js +5 -0
  7. package/lib/config/flat-config-array.js +71 -8
  8. package/lib/config/flat-config-schema.js +46 -62
  9. package/lib/eslint/eslint-helpers.js +32 -17
  10. package/lib/eslint/eslint.js +30 -12
  11. package/lib/eslint/legacy-eslint.js +14 -0
  12. package/lib/languages/js/index.js +247 -0
  13. package/lib/{source-code → languages/js/source-code}/source-code.js +46 -22
  14. package/lib/languages/js/validate-language-options.js +181 -0
  15. package/lib/linter/apply-disable-directives.js +8 -3
  16. package/lib/linter/config-comment-parser.js +3 -16
  17. package/lib/linter/linter.js +291 -249
  18. package/lib/linter/report-translator.js +14 -7
  19. package/lib/linter/vfile.js +104 -0
  20. package/lib/options.js +13 -1
  21. package/lib/rule-tester/rule-tester.js +5 -2
  22. package/lib/rules/no-sparse-arrays.js +26 -3
  23. package/lib/rules/no-unused-vars.js +33 -31
  24. package/lib/shared/flags.js +26 -0
  25. package/lib/shared/logging.js +10 -1
  26. package/lib/shared/types.js +1 -1
  27. package/messages/all-matched-files-ignored.js +21 -0
  28. package/package.json +13 -18
  29. /package/lib/{source-code → languages/js/source-code}/index.js +0 -0
  30. /package/lib/{source-code → languages/js/source-code}/token-store/backward-token-comment-cursor.js +0 -0
  31. /package/lib/{source-code → languages/js/source-code}/token-store/backward-token-cursor.js +0 -0
  32. /package/lib/{source-code → languages/js/source-code}/token-store/cursor.js +0 -0
  33. /package/lib/{source-code → languages/js/source-code}/token-store/cursors.js +0 -0
  34. /package/lib/{source-code → languages/js/source-code}/token-store/decorative-cursor.js +0 -0
  35. /package/lib/{source-code → languages/js/source-code}/token-store/filter-cursor.js +0 -0
  36. /package/lib/{source-code → languages/js/source-code}/token-store/forward-token-comment-cursor.js +0 -0
  37. /package/lib/{source-code → languages/js/source-code}/token-store/forward-token-cursor.js +0 -0
  38. /package/lib/{source-code → languages/js/source-code}/token-store/index.js +0 -0
  39. /package/lib/{source-code → languages/js/source-code}/token-store/limit-cursor.js +0 -0
  40. /package/lib/{source-code → languages/js/source-code}/token-store/padded-token-cursor.js +0 -0
  41. /package/lib/{source-code → languages/js/source-code}/token-store/skip-cursor.js +0 -0
  42. /package/lib/{source-code → languages/js/source-code}/token-store/utils.js +0 -0
@@ -0,0 +1,247 @@
1
+ /**
2
+ * @fileoverview JavaScript Language Object
3
+ * @author Nicholas C. Zakas
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //-----------------------------------------------------------------------------
9
+ // Requirements
10
+ //-----------------------------------------------------------------------------
11
+
12
+ const { SourceCode } = require("./source-code");
13
+ const createDebug = require("debug");
14
+ const astUtils = require("../../shared/ast-utils");
15
+ const eslintScope = require("eslint-scope");
16
+ const evk = require("eslint-visitor-keys");
17
+ const { validateLanguageOptions } = require("./validate-language-options");
18
+
19
+ //-----------------------------------------------------------------------------
20
+ // Type Definitions
21
+ //-----------------------------------------------------------------------------
22
+
23
+ /** @typedef {import("../../linter/vfile").VFile} VFile */
24
+
25
+ //-----------------------------------------------------------------------------
26
+ // Helpers
27
+ //-----------------------------------------------------------------------------
28
+
29
+ const debug = createDebug("eslint:languages:js");
30
+ const DEFAULT_ECMA_VERSION = 5;
31
+
32
+ /**
33
+ * Analyze scope of the given AST.
34
+ * @param {ASTNode} ast The `Program` node to analyze.
35
+ * @param {LanguageOptions} languageOptions The parser options.
36
+ * @param {Record<string, string[]>} visitorKeys The visitor keys.
37
+ * @returns {ScopeManager} The analysis result.
38
+ */
39
+ function analyzeScope(ast, languageOptions, visitorKeys) {
40
+ const parserOptions = languageOptions.parserOptions;
41
+ const ecmaFeatures = parserOptions.ecmaFeatures || {};
42
+ const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
43
+
44
+ return eslintScope.analyze(ast, {
45
+ ignoreEval: true,
46
+ nodejsScope: ecmaFeatures.globalReturn,
47
+ impliedStrict: ecmaFeatures.impliedStrict,
48
+ ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
49
+ sourceType: languageOptions.sourceType || "script",
50
+ childVisitorKeys: visitorKeys || evk.KEYS,
51
+ fallback: evk.getKeys
52
+ });
53
+ }
54
+
55
+ //-----------------------------------------------------------------------------
56
+ // Exports
57
+ //-----------------------------------------------------------------------------
58
+
59
+ module.exports = {
60
+
61
+ fileType: "text",
62
+ lineStart: 1,
63
+ columnStart: 0,
64
+ nodeTypeKey: "type",
65
+ visitorKeys: evk.KEYS,
66
+
67
+ validateLanguageOptions,
68
+
69
+ /**
70
+ * Determines if a given node matches a given selector class.
71
+ * @param {string} className The class name to check.
72
+ * @param {ASTNode} node The node to check.
73
+ * @param {Array<ASTNode>} ancestry The ancestry of the node.
74
+ * @returns {boolean} True if there's a match, false if not.
75
+ * @throws {Error} When an unknown class name is passed.
76
+ */
77
+ matchesSelectorClass(className, node, ancestry) {
78
+
79
+ /*
80
+ * Copyright (c) 2013, Joel Feenstra
81
+ * All rights reserved.
82
+ *
83
+ * Redistribution and use in source and binary forms, with or without
84
+ * modification, are permitted provided that the following conditions are met:
85
+ * * Redistributions of source code must retain the above copyright
86
+ * notice, this list of conditions and the following disclaimer.
87
+ * * Redistributions in binary form must reproduce the above copyright
88
+ * notice, this list of conditions and the following disclaimer in the
89
+ * documentation and/or other materials provided with the distribution.
90
+ * * Neither the name of the ESQuery nor the names of its contributors may
91
+ * be used to endorse or promote products derived from this software without
92
+ * specific prior written permission.
93
+ *
94
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
95
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
96
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
97
+ * DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY
98
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
99
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
100
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
101
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
102
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
103
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
104
+ */
105
+
106
+ switch (className.toLowerCase()) {
107
+
108
+ case "statement":
109
+ if (node.type.slice(-9) === "Statement") {
110
+ return true;
111
+ }
112
+
113
+ // fallthrough: interface Declaration <: Statement { }
114
+
115
+ case "declaration":
116
+ return node.type.slice(-11) === "Declaration";
117
+
118
+ case "pattern":
119
+ if (node.type.slice(-7) === "Pattern") {
120
+ return true;
121
+ }
122
+
123
+ // fallthrough: interface Expression <: Node, Pattern { }
124
+
125
+ case "expression":
126
+ return node.type.slice(-10) === "Expression" ||
127
+ node.type.slice(-7) === "Literal" ||
128
+ (
129
+ node.type === "Identifier" &&
130
+ (ancestry.length === 0 || ancestry[0].type !== "MetaProperty")
131
+ ) ||
132
+ node.type === "MetaProperty";
133
+
134
+ case "function":
135
+ return node.type === "FunctionDeclaration" ||
136
+ node.type === "FunctionExpression" ||
137
+ node.type === "ArrowFunctionExpression";
138
+
139
+ default:
140
+ throw new Error(`Unknown class name: ${className}`);
141
+ }
142
+ },
143
+
144
+ /**
145
+ * Parses the given file into an AST.
146
+ * @param {VFile} file The virtual file to parse.
147
+ * @param {Object} options Additional options passed from ESLint.
148
+ * @param {LanguageOptions} options.languageOptions The language options.
149
+ * @returns {Object} The result of parsing.
150
+ */
151
+ parse(file, { languageOptions }) {
152
+
153
+ // Note: BOM already removed
154
+ const { body: text, path: filePath } = file;
155
+ const textToParse = text.replace(astUtils.shebangPattern, (match, captured) => `//${captured}`);
156
+ const { ecmaVersion, sourceType, parser } = languageOptions;
157
+ const parserOptions = Object.assign(
158
+ { ecmaVersion, sourceType },
159
+ languageOptions.parserOptions,
160
+ {
161
+ loc: true,
162
+ range: true,
163
+ raw: true,
164
+ tokens: true,
165
+ comment: true,
166
+ eslintVisitorKeys: true,
167
+ eslintScopeManager: true,
168
+ filePath
169
+ }
170
+ );
171
+
172
+ /*
173
+ * Check for parsing errors first. If there's a parsing error, nothing
174
+ * else can happen. However, a parsing error does not throw an error
175
+ * from this method - it's just considered a fatal error message, a
176
+ * problem that ESLint identified just like any other.
177
+ */
178
+ try {
179
+ debug("Parsing:", filePath);
180
+ const parseResult = (typeof parser.parseForESLint === "function")
181
+ ? parser.parseForESLint(textToParse, parserOptions)
182
+ : { ast: parser.parse(textToParse, parserOptions) };
183
+
184
+ debug("Parsing successful:", filePath);
185
+
186
+ const {
187
+ ast,
188
+ services: parserServices = {},
189
+ visitorKeys = evk.KEYS,
190
+ scopeManager
191
+ } = parseResult;
192
+
193
+ return {
194
+ ok: true,
195
+ ast,
196
+ parserServices,
197
+ visitorKeys,
198
+ scopeManager
199
+ };
200
+ } catch (ex) {
201
+
202
+ // If the message includes a leading line number, strip it:
203
+ const message = `Parsing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
204
+
205
+ debug("%s\n%s", message, ex.stack);
206
+
207
+ return {
208
+ ok: false,
209
+ errors: [{
210
+ message,
211
+ line: ex.lineNumber,
212
+ column: ex.column
213
+ }]
214
+ };
215
+ }
216
+
217
+ },
218
+
219
+ /**
220
+ * Creates a new `SourceCode` object from the given information.
221
+ * @param {VFile} file The virtual file to create a `SourceCode` object from.
222
+ * @param {Object} parseResult The result returned from `parse()`.
223
+ * @param {Object} options Additional options passed from ESLint.
224
+ * @param {LanguageOptions} options.languageOptions The language options.
225
+ * @returns {SourceCode} The new `SourceCode` object.
226
+ */
227
+ createSourceCode(file, parseResult, { languageOptions }) {
228
+
229
+ const { body: text, path: filePath, bom: hasBOM } = file;
230
+ const { ast, parserServices, visitorKeys } = parseResult;
231
+
232
+ debug("Scope analysis:", filePath);
233
+ const scopeManager = parseResult.scopeManager || analyzeScope(ast, languageOptions, visitorKeys);
234
+
235
+ debug("Scope analysis successful:", filePath);
236
+
237
+ return new SourceCode({
238
+ text,
239
+ ast,
240
+ hasBOM,
241
+ parserServices,
242
+ scopeManager,
243
+ visitorKeys
244
+ });
245
+ }
246
+
247
+ };
@@ -11,18 +11,16 @@
11
11
  const
12
12
  { isCommentToken } = require("@eslint-community/eslint-utils"),
13
13
  TokenStore = require("./token-store"),
14
- astUtils = require("../shared/ast-utils"),
15
- Traverser = require("../shared/traverser"),
16
- globals = require("../../conf/globals"),
14
+ astUtils = require("../../../shared/ast-utils"),
15
+ Traverser = require("../../../shared/traverser"),
16
+ globals = require("../../../../conf/globals"),
17
17
  {
18
18
  directivesPattern
19
- } = require("../shared/directives"),
19
+ } = require("../../../shared/directives"),
20
20
 
21
- /* eslint-disable n/no-restricted-require -- Should eventually be moved into SourceCode. */
22
- CodePathAnalyzer = require("../linter/code-path-analysis/code-path-analyzer"),
23
- createEmitter = require("../linter/safe-emitter"),
24
- ConfigCommentParser = require("../linter/config-comment-parser"),
25
- /* eslint-enable n/no-restricted-require -- Should eventually be moved into SourceCode. */
21
+ CodePathAnalyzer = require("../../../linter/code-path-analysis/code-path-analyzer"),
22
+ createEmitter = require("../../../linter/safe-emitter"),
23
+ ConfigCommentParser = require("../../../linter/config-comment-parser"),
26
24
 
27
25
  eslintScope = require("eslint-scope");
28
26
 
@@ -441,24 +439,28 @@ class SourceCode extends TokenStore {
441
439
  #steps;
442
440
 
443
441
  /**
442
+ * Creates a new instance.
444
443
  * @param {string|Object} textOrConfig The source code text or config object.
445
444
  * @param {string} textOrConfig.text The source code text.
446
445
  * @param {ASTNode} textOrConfig.ast The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped.
446
+ * @param {boolean} textOrConfig.hasBOM Indicates if the text has a Unicode BOM.
447
447
  * @param {Object|null} textOrConfig.parserServices The parser services.
448
448
  * @param {ScopeManager|null} textOrConfig.scopeManager The scope of this source code.
449
449
  * @param {Object|null} textOrConfig.visitorKeys The visitor keys to traverse AST.
450
450
  * @param {ASTNode} [astIfNoConfig] The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped.
451
451
  */
452
452
  constructor(textOrConfig, astIfNoConfig) {
453
- let text, ast, parserServices, scopeManager, visitorKeys;
453
+ let text, hasBOM, ast, parserServices, scopeManager, visitorKeys;
454
454
 
455
- // Process overloading.
455
+ // Process overloading of arguments
456
456
  if (typeof textOrConfig === "string") {
457
457
  text = textOrConfig;
458
458
  ast = astIfNoConfig;
459
+ hasBOM = false;
459
460
  } else if (typeof textOrConfig === "object" && textOrConfig !== null) {
460
461
  text = textOrConfig.text;
461
462
  ast = textOrConfig.ast;
463
+ hasBOM = textOrConfig.hasBOM;
462
464
  parserServices = textOrConfig.parserServices;
463
465
  scopeManager = textOrConfig.scopeManager;
464
466
  visitorKeys = textOrConfig.visitorKeys;
@@ -476,18 +478,39 @@ class SourceCode extends TokenStore {
476
478
  ["configNodes", void 0]
477
479
  ]);
478
480
 
481
+ /**
482
+ * Indicates if the AST is ESTree compatible.
483
+ * @type {boolean}
484
+ */
485
+ this.isESTree = ast.type === "Program";
486
+
487
+ /*
488
+ * Backwards compatibility for BOM handling.
489
+ *
490
+ * The `hasBOM` property has been available on the `SourceCode` object
491
+ * for a long time and is used to indicate if the source contains a BOM.
492
+ * The linter strips the BOM and just passes the `hasBOM` property to the
493
+ * `SourceCode` constructor to make it easier for languages to not deal with
494
+ * the BOM.
495
+ *
496
+ * However, the text passed in to the `SourceCode` constructor might still
497
+ * have a BOM if the constructor is called outside of the linter, so we still
498
+ * need to check for the BOM in the text.
499
+ */
500
+ const textHasBOM = text.charCodeAt(0) === 0xFEFF;
501
+
479
502
  /**
480
503
  * The flag to indicate that the source code has Unicode BOM.
481
504
  * @type {boolean}
482
505
  */
483
- this.hasBOM = (text.charCodeAt(0) === 0xFEFF);
506
+ this.hasBOM = textHasBOM || !!hasBOM;
484
507
 
485
508
  /**
486
509
  * The original text source code.
487
510
  * BOM was stripped from this text.
488
511
  * @type {string}
489
512
  */
490
- this.text = (this.hasBOM ? text.slice(1) : text);
513
+ this.text = (textHasBOM ? text.slice(1) : text);
491
514
 
492
515
  /**
493
516
  * The parsed AST for the source code.
@@ -1076,7 +1099,7 @@ class SourceCode extends TokenStore {
1076
1099
  /**
1077
1100
  * Applies configuration found inside of the source code. This method is only
1078
1101
  * called when ESLint is running with inline configuration allowed.
1079
- * @returns {{problems:Array<Problem>,configs:{config:FlatConfigArray,node:ASTNode}}} Information
1102
+ * @returns {{problems:Array<Problem>,configs:{config:FlatConfigArray,loc:Location}}} Information
1080
1103
  * that ESLint needs to further process the inline configuration.
1081
1104
  */
1082
1105
  applyInlineConfig() {
@@ -1124,17 +1147,21 @@ class SourceCode extends TokenStore {
1124
1147
  break;
1125
1148
 
1126
1149
  case "eslint": {
1127
- const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
1150
+ const parseResult = commentParser.parseJsonConfig(directiveValue);
1128
1151
 
1129
1152
  if (parseResult.success) {
1130
1153
  configs.push({
1131
1154
  config: {
1132
1155
  rules: parseResult.config
1133
1156
  },
1134
- node: comment
1157
+ loc: comment.loc
1135
1158
  });
1136
1159
  } else {
1137
- problems.push(parseResult.error);
1160
+ problems.push({
1161
+ ruleId: null,
1162
+ loc: comment.loc,
1163
+ message: parseResult.error.message
1164
+ });
1138
1165
  }
1139
1166
 
1140
1167
  break;
@@ -1164,12 +1191,11 @@ class SourceCode extends TokenStore {
1164
1191
  */
1165
1192
  finalize() {
1166
1193
 
1167
- // Step 1: ensure that all of the necessary variables are up to date
1168
1194
  const varsCache = this[caches].get("vars");
1169
- const globalScope = this.scopeManager.scopes[0];
1170
1195
  const configGlobals = varsCache.get("configGlobals");
1171
1196
  const inlineGlobals = varsCache.get("inlineGlobals");
1172
1197
  const exportedVariables = varsCache.get("exportedVariables");
1198
+ const globalScope = this.scopeManager.scopes[0];
1173
1199
 
1174
1200
  addDeclaredGlobals(globalScope, configGlobals, inlineGlobals);
1175
1201
 
@@ -1227,9 +1253,7 @@ class SourceCode extends TokenStore {
1227
1253
  * Program node at the top level. This is not a perfect heuristic, but it
1228
1254
  * is good enough for now.
1229
1255
  */
1230
- const isESTree = this.ast.type === "Program";
1231
-
1232
- if (isESTree) {
1256
+ if (this.isESTree) {
1233
1257
  analyzer = new CodePathAnalyzer(analyzer);
1234
1258
 
1235
1259
  CODE_PATH_EVENTS.forEach(eventName => {
@@ -0,0 +1,181 @@
1
+ /**
2
+ * @fileoverview The schema to validate language options
3
+ * @author Nicholas C. Zakas
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //-----------------------------------------------------------------------------
9
+ // Data
10
+ //-----------------------------------------------------------------------------
11
+
12
+ const globalVariablesValues = new Set([
13
+ true, "true", "writable", "writeable",
14
+ false, "false", "readonly", "readable", null,
15
+ "off"
16
+ ]);
17
+
18
+ //------------------------------------------------------------------------------
19
+ // Helpers
20
+ //------------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Check if a value is a non-null object.
24
+ * @param {any} value The value to check.
25
+ * @returns {boolean} `true` if the value is a non-null object.
26
+ */
27
+ function isNonNullObject(value) {
28
+ return typeof value === "object" && value !== null;
29
+ }
30
+
31
+ /**
32
+ * Check if a value is a non-null non-array object.
33
+ * @param {any} value The value to check.
34
+ * @returns {boolean} `true` if the value is a non-null non-array object.
35
+ */
36
+ function isNonArrayObject(value) {
37
+ return isNonNullObject(value) && !Array.isArray(value);
38
+ }
39
+
40
+ /**
41
+ * Check if a value is undefined.
42
+ * @param {any} value The value to check.
43
+ * @returns {boolean} `true` if the value is undefined.
44
+ */
45
+ function isUndefined(value) {
46
+ return typeof value === "undefined";
47
+ }
48
+
49
+ //-----------------------------------------------------------------------------
50
+ // Schemas
51
+ //-----------------------------------------------------------------------------
52
+
53
+ /**
54
+ * Validates the ecmaVersion property.
55
+ * @param {string|number} ecmaVersion The value to check.
56
+ * @returns {void}
57
+ * @throws {TypeError} If the value is invalid.
58
+ */
59
+ function validateEcmaVersion(ecmaVersion) {
60
+
61
+ if (isUndefined(ecmaVersion)) {
62
+ throw new TypeError("Key \"ecmaVersion\": Expected an \"ecmaVersion\" property.");
63
+ }
64
+
65
+ if (typeof ecmaVersion !== "number" && ecmaVersion !== "latest") {
66
+ throw new TypeError("Key \"ecmaVersion\": Expected a number or \"latest\".");
67
+ }
68
+
69
+ }
70
+
71
+ /**
72
+ * Validates the sourceType property.
73
+ * @param {string} sourceType The value to check.
74
+ * @returns {void}
75
+ * @throws {TypeError} If the value is invalid.
76
+ */
77
+ function validateSourceType(sourceType) {
78
+
79
+ if (typeof sourceType !== "string" || !/^(?:script|module|commonjs)$/u.test(sourceType)) {
80
+ throw new TypeError("Key \"sourceType\": Expected \"script\", \"module\", or \"commonjs\".");
81
+ }
82
+
83
+ }
84
+
85
+ /**
86
+ * Validates the globals property.
87
+ * @param {Object} globals The value to check.
88
+ * @returns {void}
89
+ * @throws {TypeError} If the value is invalid.
90
+ */
91
+ function validateGlobals(globals) {
92
+
93
+ if (!isNonArrayObject(globals)) {
94
+ throw new TypeError("Key \"globals\": Expected an object.");
95
+ }
96
+
97
+ for (const key of Object.keys(globals)) {
98
+
99
+ // avoid hairy edge case
100
+ if (key === "__proto__") {
101
+ continue;
102
+ }
103
+
104
+ if (key !== key.trim()) {
105
+ throw new TypeError(`Key "globals": Global "${key}" has leading or trailing whitespace.`);
106
+ }
107
+
108
+ if (!globalVariablesValues.has(globals[key])) {
109
+ throw new TypeError(`Key "globals": Key "${key}": Expected "readonly", "writable", or "off".`);
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Validates the parser property.
116
+ * @param {Object} parser The value to check.
117
+ * @returns {void}
118
+ * @throws {TypeError} If the value is invalid.
119
+ */
120
+ function validateParser(parser) {
121
+
122
+ if (!parser || typeof parser !== "object" ||
123
+ (typeof parser.parse !== "function" && typeof parser.parseForESLint !== "function")
124
+ ) {
125
+ throw new TypeError("Key \"parser\": Expected object with parse() or parseForESLint() method.");
126
+ }
127
+
128
+ }
129
+
130
+ /**
131
+ * Validates the language options.
132
+ * @param {Object} languageOptions The language options to validate.
133
+ * @returns {void}
134
+ * @throws {TypeError} If the language options are invalid.
135
+ */
136
+ function validateLanguageOptions(languageOptions) {
137
+
138
+ if (!isNonArrayObject(languageOptions)) {
139
+ throw new TypeError("Expected an object.");
140
+ }
141
+
142
+ const {
143
+ ecmaVersion,
144
+ sourceType,
145
+ globals,
146
+ parser,
147
+ parserOptions,
148
+ ...otherOptions
149
+ } = languageOptions;
150
+
151
+ if ("ecmaVersion" in languageOptions) {
152
+ validateEcmaVersion(ecmaVersion);
153
+ }
154
+
155
+ if ("sourceType" in languageOptions) {
156
+ validateSourceType(sourceType);
157
+ }
158
+
159
+ if ("globals" in languageOptions) {
160
+ validateGlobals(globals);
161
+ }
162
+
163
+ if ("parser" in languageOptions) {
164
+ validateParser(parser);
165
+ }
166
+
167
+ if ("parserOptions" in languageOptions) {
168
+ if (!isNonArrayObject(parserOptions)) {
169
+ throw new TypeError("Key \"parserOptions\": Expected an object.");
170
+ }
171
+ }
172
+
173
+ const otherOptionKeys = Object.keys(otherOptions);
174
+
175
+ if (otherOptionKeys.length > 0) {
176
+ throw new TypeError(`Unexpected key "${otherOptionKeys[0]}" found.`);
177
+ }
178
+
179
+ }
180
+
181
+ module.exports = { validateLanguageOptions };
@@ -369,6 +369,8 @@ function applyDirectives(options) {
369
369
 
370
370
  const processed = processUnusedDirectives(unusedDisableDirectivesToReport)
371
371
  .concat(processUnusedDirectives(unusedEnableDirectivesToReport));
372
+ const columnOffset = options.language.columnStart === 1 ? 0 : 1;
373
+ const lineOffset = options.language.lineStart === 1 ? 0 : 1;
372
374
 
373
375
  const unusedDirectives = processed
374
376
  .map(({ description, fix, unprocessedDirective }) => {
@@ -388,8 +390,8 @@ function applyDirectives(options) {
388
390
  return {
389
391
  ruleId: null,
390
392
  message,
391
- line: type === "disable-next-line" ? parentDirective.node.loc.start.line : line,
392
- column: type === "disable-next-line" ? parentDirective.node.loc.start.column + 1 : column,
393
+ line: type === "disable-next-line" ? parentDirective.node.loc.start.line + lineOffset : line,
394
+ column: type === "disable-next-line" ? parentDirective.node.loc.start.column + columnOffset : column,
393
395
  severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
394
396
  nodeType: null,
395
397
  ...options.disableFixes ? {} : { fix }
@@ -403,6 +405,7 @@ function applyDirectives(options) {
403
405
  * Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list
404
406
  * of reported problems, adds the suppression information to the problems.
405
407
  * @param {Object} options Information about directives and problems
408
+ * @param {Language} options.language The language being linted.
406
409
  * @param {{
407
410
  * type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
408
411
  * ruleId: (string|null),
@@ -421,7 +424,7 @@ function applyDirectives(options) {
421
424
  * @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]}
422
425
  * An object with a list of reported problems, the suppressed of which contain the suppression information.
423
426
  */
424
- module.exports = ({ directives, disableFixes, problems, configuredRules, ruleFilter, reportUnusedDisableDirectives = "off" }) => {
427
+ module.exports = ({ language, directives, disableFixes, problems, configuredRules, ruleFilter, reportUnusedDisableDirectives = "off" }) => {
425
428
  const blockDirectives = directives
426
429
  .filter(directive => directive.type === "disable" || directive.type === "enable")
427
430
  .map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
@@ -470,6 +473,7 @@ module.exports = ({ directives, disableFixes, problems, configuredRules, ruleFil
470
473
  }
471
474
 
472
475
  const blockDirectivesResult = applyDirectives({
476
+ language,
473
477
  problems,
474
478
  directives: blockDirectives,
475
479
  disableFixes,
@@ -477,6 +481,7 @@ module.exports = ({ directives, disableFixes, problems, configuredRules, ruleFil
477
481
  rulesToIgnore
478
482
  });
479
483
  const lineDirectivesResult = applyDirectives({
484
+ language,
480
485
  problems: blockDirectivesResult.problems,
481
486
  directives: lineDirectives,
482
487
  disableFixes,
@@ -22,12 +22,6 @@ const levn = require("levn"),
22
22
 
23
23
  const debug = require("debug")("eslint:config-comment-parser");
24
24
 
25
- //------------------------------------------------------------------------------
26
- // Typedefs
27
- //------------------------------------------------------------------------------
28
-
29
- /** @typedef {import("../shared/types").LintMessage} LintMessage */
30
-
31
25
  //------------------------------------------------------------------------------
32
26
  // Public Interface
33
27
  //------------------------------------------------------------------------------
@@ -69,10 +63,9 @@ module.exports = class ConfigCommentParser {
69
63
  /**
70
64
  * Parses a JSON-like config.
71
65
  * @param {string} string The string to parse.
72
- * @param {Object} location Start line and column of comments for potential error message.
73
- * @returns {({success: true, config: Object}|{success: false, error: LintMessage})} Result map object
66
+ * @returns {({success: true, config: Object}|{success: false, error: {message: string}})} Result map object
74
67
  */
75
- parseJsonConfig(string, location) {
68
+ parseJsonConfig(string) {
76
69
  debug("Parsing JSON config");
77
70
 
78
71
  // Parses a JSON-like comment by the same way as parsing CLI option.
@@ -115,13 +108,7 @@ module.exports = class ConfigCommentParser {
115
108
  return {
116
109
  success: false,
117
110
  error: {
118
- ruleId: null,
119
- fatal: true,
120
- severity: 2,
121
- message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`,
122
- line: location.start.line,
123
- column: location.start.column + 1,
124
- nodeType: null
111
+ message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`
125
112
  }
126
113
  };
127
114