@tony.ganchev/eslint-plugin-header 3.2.0 → 3.2.1

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.
@@ -24,9 +24,9 @@
24
24
 
25
25
  "use strict";
26
26
 
27
- const assert = require("assert");
28
- const fs = require("fs");
29
- const os = require("os");
27
+ const assert = require("node:assert");
28
+ const fs = require("node:fs");
29
+ const os = require("node:os");
30
30
  const commentParser = require("../comment-parser");
31
31
  const { contextSourceCode } = require("./eslint-utils");
32
32
  const { description, recommended, url } = require("./header.docs");
@@ -38,8 +38,6 @@ const { lineEndingOptions, commentTypeOptions, schema } = require("./header.sche
38
38
  * @typedef {import('eslint').Rule.NodeListener} NodeListener
39
39
  * @typedef {import('eslint').Rule.ReportFixer} ReportFixer
40
40
  * @typedef {import('eslint').Rule.RuleFixer} RuleFixer
41
- * @typedef {import('eslint').Rule.RuleTextEdit} RuleTextEdit
42
- * @typedef {import('eslint').Rule.RuleTextEditor} RuleTextEditor
43
41
  * @typedef {import('eslint').Rule.RuleContext} RuleContext
44
42
  * @typedef {import('estree').Comment} Comment
45
43
  * @typedef {import('estree').Program} Program
@@ -50,61 +48,67 @@ const { lineEndingOptions, commentTypeOptions, schema } = require("./header.sche
50
48
  * Local type definitions.
51
49
  * @typedef {{ pattern: string | RegExp, template?: string }} HeaderLinePattern
52
50
  * @typedef {string | RegExp | HeaderLinePattern} HeaderLine
53
- * @typedef {(HeaderLine | HeaderLine[])} HeaderLines
54
- * @typedef {{ lineEndings?: ('unix' | 'windows' | 'os') }} HeaderSettings
55
- * @typedef {
56
- * [string]
57
- * | [string, HeaderSettings]
58
- * | [('block' | 'line'), HeaderLines ]
59
- * | [('block' | 'line'), HeaderLines, HeaderSettings]
60
- * | [('block' | 'line'), HeaderLines, number ]
61
- * | [('block' | 'line'), HeaderLines, number, HeaderSettings]
62
- * } LegacyHeaderOptions
63
- * @typedef {
64
- * {
51
+ * @typedef {HeaderLine | HeaderLine[]} HeaderLines
52
+ * @typedef {'os' | 'unix' | 'windows'} LineEndingOption
53
+ * @typedef {{ lineEndings?: LineEndingOption }} HeaderSettings
54
+ * @typedef {'block' | 'line'} CommentType
55
+ * @typedef {{
65
56
  * file: string,
66
- * encoding?: string
57
+ * encoding?: BufferEncoding
67
58
  * }
68
59
  * } FileBasedConfig
69
- * @typedef {
70
- * {
71
- * commentType: 'line' | 'block',
60
+ * @typedef {{
61
+ * commentType: CommentType,
72
62
  * lines: HeaderLine[]
73
63
  * }
74
- * } LinesBasedConfig
64
+ * } InlineConfig
75
65
  * @typedef {{ minimum?: number }} TrailingEmptyLines
76
- * @typedef {
77
- * {
78
- * header: FileBasedConfig | LinesBasedConfig,
66
+ * @typedef {{
67
+ * header: FileBasedConfig | InlineConfig,
79
68
  * trailingEmptyLines?: TrailingEmptyLines
80
69
  * }
81
70
  * & HeaderSettings
82
- * } NewHeaderOptions
83
- * @typedef {LegacyHeaderOptions | [NewHeaderOptions]} HeaderOptions
71
+ * } HeaderOptions
72
+ * @typedef {[HeaderOptions] |
73
+ * [template: string] |
74
+ * [template: string, settings: HeaderSettings] |
75
+ * [type: CommentType, lines: HeaderLines] |
76
+ * [type: CommentType, lines: HeaderLines, settings: HeaderSettings] |
77
+ * [type: CommentType, lines: HeaderLines, minLines: number] |
78
+ * [
79
+ * type: CommentType,
80
+ * lines: HeaderLines,
81
+ * minLines: number,
82
+ * settings: HeaderSettings
83
+ * ]
84
+ * } AllHeaderOptions
85
+ * @typedef {import('eslint').Linter.RuleEntry<AllHeaderOptions>
86
+ * } HeaderRuleConfig
84
87
  */
85
88
 
86
89
  /**
87
90
  * Tests if the passed line configuration string or object is a pattern
88
91
  * definition.
89
92
  * @param {HeaderLine} object line configuration object or string
90
- * @returns {boolean} `true` if the line configuration is a pattern-defining
91
- * object or `false` otherwise.
93
+ * @returns {object is HeaderLinePattern} `true` if the line configuration is a
94
+ * pattern-defining object or `false`
95
+ * otherwise.
92
96
  */
93
97
  function isPattern(object) {
94
98
  return typeof object === "object"
95
- && (object instanceof RegExp || Object.prototype.hasOwnProperty.call(object, "pattern"));
99
+ && (Object.prototype.hasOwnProperty.call(object, "pattern"));
96
100
  }
97
101
 
98
102
  /**
99
103
  * Utility over a line config argument to match an expected string either
100
104
  * against a regex or for full match against a string.
101
- * @param {HeaderLine} actual the string to test.
102
- * @param {string} expected The string or regex to test again.
105
+ * @param {string} actual the string to test.
106
+ * @param {string | RegExp} expected The string or regex to test again.
103
107
  * @returns {boolean} `true` if the passed string matches the expected line
104
108
  * config or `false` otherwise.
105
109
  */
106
110
  function match(actual, expected) {
107
- if (expected.test) {
111
+ if (expected instanceof RegExp) {
108
112
  return expected.test(actual);
109
113
  } else {
110
114
  return expected === actual;
@@ -113,35 +117,62 @@ function match(actual, expected) {
113
117
 
114
118
  /**
115
119
  * Remove Unix she-bangs from the list of comments.
116
- * @param {Comment[]} comments the list of comment lines.
120
+ * @param {(Comment | { type: "Shebang" })[]} comments the list of comment
121
+ * lines.
117
122
  * @returns {Comment[]} the list of comments with containing all incoming
118
123
  * comments from `comments` with the shebang comments
119
124
  * omitted.
120
125
  */
121
126
  function excludeShebangs(comments) {
127
+ /** @type {Comment[]} */
122
128
  return comments.filter(function(comment) {
123
129
  return comment.type !== "Shebang";
124
130
  });
125
131
  }
126
132
 
133
+ /**
134
+ * TypeScript helper to confirm defined type.
135
+ * @template T
136
+ * @param {T | undefined} val the value to validate.
137
+ * @returns {asserts val is T} validates defined type
138
+ */
139
+ function assertDefined(val) {
140
+ assert.strict.notEqual(typeof val, "undefined");
141
+ }
142
+
143
+ /**
144
+ * TypeScript helper to confirm non-null type.
145
+ * @template T
146
+ * @param {T | null} val the value to validate.
147
+ * @returns {asserts val is T} validates non-null type
148
+ */
149
+ function assertNotNull(val) {
150
+ assert.strict.notEqual(val, null);
151
+ }
152
+
127
153
  /**
128
154
  * Returns either the first block comment or the first set of line comments that
129
155
  * are ONLY separated by a single newline. Note that this does not actually
130
156
  * check if they are at the start of the file since that is already checked by
131
157
  * `hasHeader()`.
132
158
  * @param {RuleContext} context ESLint execution environment.
133
- * @param {Program} node ESLint AST tree node being processed.
134
159
  * @returns {Comment[]} lines that constitute the leading comment.
135
160
  */
136
- function getLeadingComments(context, node) {
161
+ function getLeadingComments(context) {
137
162
  const sourceCode = contextSourceCode(context);
138
- const all = excludeShebangs(sourceCode.getAllComments(node.body.length ? node.body[0] : node));
163
+ const all = excludeShebangs(sourceCode.getAllComments());
164
+ assert.ok(all);
165
+ assert.ok(all.length);
139
166
  if (all[0].type.toLowerCase() === commentTypeOptions.block) {
140
167
  return [all[0]];
141
168
  }
142
169
  let i = 1;
143
170
  for (; i < all.length; ++i) {
144
- const txt = sourceCode.text.slice(all[i - 1].range[1], all[i].range[0]);
171
+ const previousRange = all[i - 1].range;
172
+ assertDefined(previousRange);
173
+ const currentRange = all[i].range;
174
+ assertDefined(currentRange);
175
+ const txt = sourceCode.text.slice(previousRange[1], currentRange[0]);
145
176
  if (!txt.match(/^(\r\n|\r|\n)$/)) {
146
177
  break;
147
178
  }
@@ -152,7 +183,7 @@ function getLeadingComments(context, node) {
152
183
  /**
153
184
  * Generate a comment including trailing spaces out of a number of comment body
154
185
  * lines.
155
- * @param {'block' | 'line'} commentType the type of comment to generate.
186
+ * @param {CommentType} commentType the type of comment to generate.
156
187
  * @param {string[]} textArray list of lines of the comment content.
157
188
  * @param {'\n' | '\r\n'} eol end-of-line characters.
158
189
  * @returns {string} resulting comment.
@@ -170,12 +201,21 @@ function genCommentBody(commentType, textArray, eol) {
170
201
  /**
171
202
  * Determines the start and end position in the source code of the leading
172
203
  * comment.
173
- * @param {string[]} comments list of comments.
204
+ * @param {Comment[]} comments list of comments.
174
205
  * @returns {[number, number]} resulting range.
175
206
  */
176
207
  function genCommentsRange(comments) {
177
- const start = comments[0].range[0];
178
- const end = comments.slice(-1)[0].range[1];
208
+ assert.ok(comments.length);
209
+ const firstComment = comments[0];
210
+ assertDefined(firstComment);
211
+ const firstCommentRange = firstComment.range;
212
+ assertDefined(firstCommentRange);
213
+ const start = firstCommentRange[0];
214
+ const lastComment = comments.slice(-1)[0];
215
+ assertDefined(lastComment);
216
+ const lastCommentRange = lastComment.range;
217
+ assertDefined(lastCommentRange);
218
+ const end = lastCommentRange[1];
179
219
  return [start, end];
180
220
  }
181
221
 
@@ -201,7 +241,7 @@ function leadingEmptyLines(src) {
201
241
 
202
242
  /**
203
243
  * Factory for fixer that adds a missing header.
204
- * @param {'block' | 'line'} commentType type of comment to use.
244
+ * @param {CommentType} commentType type of comment to use.
205
245
  * @param {RuleContext} context ESLint execution runtime.
206
246
  * @param {string[]} headerLines lines of the header comment.
207
247
  * @param {'\n' | '\r\n'} eol end-of-line characters
@@ -232,15 +272,13 @@ function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
232
272
 
233
273
  /**
234
274
  * Factory for fixer that replaces an incorrect header.
235
- * @param {'block' | 'line'} commentType type of comment to use.
275
+ * @param {CommentType} commentType type of comment to use.
236
276
  * @param {RuleContext} context ESLint execution context.
237
277
  * @param {Comment[]} leadingComments comment elements to replace.
238
278
  * @param {string[]} headerLines lines of the header comment.
239
279
  * @param {'\n' | '\r\n'} eol end-of-line characters
240
280
  * @param {number} numNewlines number of trailing lines after the comment.
241
- * @returns {
242
- * (fixer: RuleTextEditor) => RuleTextEdit | RuleTextEdit[] | null
243
- * } the fixer.
281
+ * @returns {(fixer: RuleFixer) => Fix | Fix[] | null} the fixer.
244
282
  */
245
283
  function genReplaceFixer(commentType, context, leadingComments, headerLines, eol, numNewlines) {
246
284
  return function(fixer) {
@@ -250,7 +288,7 @@ function genReplaceFixer(commentType, context, leadingComments, headerLines, eol
250
288
  const eols = eol.repeat(missingNewlines);
251
289
  return fixer.replaceTextRange(
252
290
  commentRange,
253
- genCommentBody(commentType, headerLines, eol, numNewlines) + eols
291
+ genCommentBody(commentType, headerLines, eol) + eols
254
292
  );
255
293
  };
256
294
  }
@@ -261,9 +299,7 @@ function genReplaceFixer(commentType, context, leadingComments, headerLines, eol
261
299
  * @param {'\n' | '\r\n'} eol end-of-line characters
262
300
  * @param {number} missingEmptyLinesCount number of trailing lines after the
263
301
  * comment.
264
- * @returns {
265
- * (fixer: RuleTextEditor) => RuleTextEdit | RuleTextEdit[] | null
266
- * } the fixer.
302
+ * @returns {(fixer: RuleFixer) => Fix | Fix[] | null} the fixer.
267
303
  */
268
304
  function genEmptyLinesFixer(leadingComments, eol, missingEmptyLinesCount) {
269
305
  return function(fixer) {
@@ -277,7 +313,7 @@ function genEmptyLinesFixer(leadingComments, eol, missingEmptyLinesCount) {
277
313
  /**
278
314
  * Returns the used line-termination characters per the rule's config if any or
279
315
  * else based on the runtime environments.
280
- * @param {'os' | 'unix' | 'windows'} style line-ending styles.
316
+ * @param {LineEndingOption} style line-ending styles.
281
317
  * @returns {'\n' | '\r\n'} the correct line ending characters for the
282
318
  * environment.
283
319
  */
@@ -291,7 +327,7 @@ function getEol(style) {
291
327
  return "\r\n";
292
328
  case lineEndingOptions.os:
293
329
  default:
294
- return os.EOL;
330
+ return /** @type {'\n' | '\r\n'} */ (os.EOL);
295
331
  }
296
332
  }
297
333
 
@@ -323,8 +359,9 @@ function schemaAssert(condition, message) {
323
359
  * options in that some settings are still union types and unspecified
324
360
  * properties are not replaced by defaults. If the options follow the new
325
361
  * format, a simple seep copy would be returned.
326
- * @param {HeaderOptions} originalOptions the options as configured by the user.
327
- * @returns {NewHeaderOptions} the transformed new-style options with no
362
+ * @param {AllHeaderOptions} originalOptions the options as configured by the
363
+ * user.
364
+ * @returns {HeaderOptions} the transformed new-style options with no
328
365
  * normalization.
329
366
  */
330
367
  function transformLegacyOptions(originalOptions) {
@@ -342,7 +379,7 @@ function transformLegacyOptions(originalOptions) {
342
379
  // config.
343
380
  schemaAssert(typeof originalOptions[0] === "string",
344
381
  "first header option after severity should be either a filename or 'block' | 'line'");
345
- /** @type {NewHeaderOptions} */
382
+ /** @type {HeaderOptions} */
346
383
  const transformedOptions = {};
347
384
  // populate header
348
385
  if (
@@ -351,7 +388,7 @@ function transformLegacyOptions(originalOptions) {
351
388
  originalOptions.length === 2
352
389
  && typeof originalOptions[1] === "object"
353
390
  && !Array.isArray(originalOptions[1])
354
- && !isPattern(originalOptions[1])
391
+ && !isPattern(/** @type {HeaderLine} */(originalOptions[1]))
355
392
  )) {
356
393
  transformedOptions.header = { file: originalOptions[0], encoding: "utf8" };
357
394
  } else {
@@ -360,11 +397,14 @@ function transformLegacyOptions(originalOptions) {
360
397
  schemaAssert(
361
398
  typeof originalOptions[1] === "string"
362
399
  || Array.isArray(originalOptions[1])
363
- || isPattern(originalOptions[1]),
400
+ || isPattern(/** @type {HeaderLine} */(originalOptions[1])),
364
401
  "second header option after severity should be a string, a pattern, or an array of the previous two");
365
402
  transformedOptions.header = {
366
- commentType: originalOptions[0],
367
- lines: Array.isArray(originalOptions[1]) ? originalOptions[1] : [originalOptions[1]]
403
+ commentType: /** @type {CommentType} */ (originalOptions[0]),
404
+ lines: /** @type {HeaderLine[]} */ (
405
+ Array.isArray(originalOptions[1])
406
+ ? originalOptions[1]
407
+ : [originalOptions[1]])
368
408
  };
369
409
  }
370
410
  // configure required line settings
@@ -387,25 +427,42 @@ function transformLegacyOptions(originalOptions) {
387
427
  return transformedOptions;
388
428
  }
389
429
 
430
+ /**
431
+ * @param {FileBasedConfig | InlineConfig} config the header configuration.
432
+ * @returns {config is FileBasedConfig} true if `config` is `FileBasedConfig`.
433
+ */
434
+ function isFileBasedHeaderConfig(config) {
435
+ return Object.prototype.hasOwnProperty.call(config, "file");
436
+ }
437
+
438
+ /**
439
+ * @param {FileBasedConfig | InlineConfig} config the header configuration.
440
+ * @returns {asserts config is InlineConfig} asserts `config` is
441
+ * `LineBasedConfig`.
442
+ */
443
+ function assertLineBasedHeaderConfig(config) {
444
+ assert.ok(Object.prototype.hasOwnProperty.call(config, "lines"));
445
+ }
446
+
390
447
  /**
391
448
  * Transforms a set of new-style options adding defaults and standardizing on
392
449
  * one of multiple config styles.
393
- * @param {NewHeaderOptions} originalOptions new-style options to normalize.
394
- * @returns {NewHeaderOptions} normalized options.
450
+ * @param {HeaderOptions} originalOptions new-style options to normalize.
451
+ * @returns {HeaderOptions} normalized options.
395
452
  */
396
453
  function normalizeOptions(originalOptions) {
397
454
  const options = structuredClone(originalOptions);
398
455
 
399
- if (options.header.file) {
456
+ if (isFileBasedHeaderConfig(originalOptions.header)) {
400
457
  const text = fs.readFileSync(originalOptions.header.file, originalOptions.header.encoding || "utf8");
401
458
  const [commentType, lines] = commentParser(text);
402
459
  options.header = { commentType, lines };
403
460
  }
404
-
461
+ assertLineBasedHeaderConfig(options.header);
405
462
  options.header.lines = options.header.lines.flatMap(
406
463
  (line) => {
407
464
  if (typeof line === "string") {
408
- return line.split(/\r?\n/);
465
+ return /** @type {HeaderLine[]} */(line.split(/\r?\n/));
409
466
  }
410
467
  if (line instanceof RegExp) {
411
468
  return [{ pattern: line }];
@@ -448,7 +505,11 @@ function normalizeOptions(originalOptions) {
448
505
  * @returns {SourceLocation} the location (line and column) of the violation.
449
506
  */
450
507
  function missingEmptyLinesViolationLoc(leadingComments, actualEmptyLines) {
451
- const lastCommentLineLocEnd = leadingComments[leadingComments.length - 1].loc.end;
508
+ assert.ok(leadingComments);
509
+ const loc = leadingComments[leadingComments.length - 1].loc;
510
+ assertDefined(loc);
511
+ assertNotNull(loc);
512
+ const lastCommentLineLocEnd = loc.end;
452
513
  return {
453
514
  start: lastCommentLineLocEnd,
454
515
  end: {
@@ -458,7 +519,8 @@ function missingEmptyLinesViolationLoc(leadingComments, actualEmptyLines) {
458
519
  };
459
520
  }
460
521
 
461
- module.exports = {
522
+ /** @type {import('eslint').Rule.RuleModule} */
523
+ const headerRule = {
462
524
  meta: {
463
525
  type: "layout",
464
526
  docs: {
@@ -469,7 +531,7 @@ module.exports = {
469
531
  fixable: "whitespace",
470
532
  schema,
471
533
  defaultOptions: [
472
- /** @type {HeaderOptions} */
534
+ /** @type {AllHeaderOptions} */
473
535
  {
474
536
  lineEndings: lineEndingOptions.os,
475
537
  trailingEmptyLines: {
@@ -497,34 +559,48 @@ module.exports = {
497
559
  */
498
560
  create: function(context) {
499
561
 
500
- const newStyleOptions = transformLegacyOptions(context.options);
562
+ const newStyleOptions = transformLegacyOptions(/** @type {AllHeaderOptions} */ (context.options));
501
563
  const options = normalizeOptions(newStyleOptions);
502
564
 
503
- const eol = getEol(options.lineEndings);
565
+ assertLineBasedHeaderConfig(options.header);
566
+ const commentType = /** @type {CommentType} */ (options.header.commentType);
567
+
568
+ const eol = getEol(
569
+ /** @type {LineEndingOption} */ (options.lineEndings)
570
+ );
504
571
 
572
+ /** @type {string[]} */
505
573
  let fixLines = [];
506
574
  // If any of the lines are regular expressions, then we can't
507
575
  // automatically fix them. We set this to true below once we
508
576
  // ensure none of the lines are of type RegExp
509
577
  let canFix = true;
510
578
  const headerLines = options.header.lines.map(function(line) {
511
- const isRegex = isPattern(line);
512
579
  // Can only fix regex option if a template is also provided
513
- if (isRegex && !line.template) {
514
- canFix = false;
580
+ if (isPattern(line)) {
581
+ if (Object.prototype.hasOwnProperty.call(line, "template")) {
582
+ fixLines.push(/** @type {string} */ (line.template));
583
+ } else {
584
+ canFix = false;
585
+ fixLines.push("");
586
+ }
587
+ return line.pattern;
588
+ } else {
589
+ fixLines.push(/** @type {string} */ (line));
590
+ return line;
515
591
  }
516
- fixLines.push(line.template || line);
517
- return isRegex ? line.pattern : line;
518
592
  });
519
593
 
594
+
595
+ const numLines = /** @type {number} */ (options.trailingEmptyLines?.minimum);
596
+
520
597
  return {
521
598
  /**
522
599
  * Hooks into the processing of the overall script node to do the
523
600
  * header validation.
524
- * @param {Program} node the whole script node
525
601
  * @returns {void}
526
602
  */
527
- Program: function(node) {
603
+ Program: function() {
528
604
  const sourceCode = contextSourceCode(context);
529
605
  if (!hasHeader(sourceCode.text)) {
530
606
  const hasShebang = sourceCode.text.startsWith("#!");
@@ -542,60 +618,72 @@ module.exports = {
542
618
  },
543
619
  messageId: "missingHeader",
544
620
  fix: genPrependFixer(
545
- options.header.commentType,
621
+ commentType,
546
622
  context,
547
623
  fixLines,
548
624
  eol,
549
- options.trailingEmptyLines.minimum)
625
+ numLines)
550
626
  });
551
627
  return;
552
628
  }
553
-
554
- const leadingComments = getLeadingComments(context, node);
555
-
556
- if (leadingComments[0].type.toLowerCase() !== options.header.commentType) {
629
+ const leadingComments = getLeadingComments(context);
630
+ const firstLeadingCommentLoc = leadingComments[0].loc;
631
+ const firstLeadingCommentRange = leadingComments[0].range;
632
+ assertDefined(firstLeadingCommentRange);
633
+
634
+ const lastLeadingCommentLoc = leadingComments[leadingComments.length - 1].loc;
635
+
636
+ if (leadingComments[0].type.toLowerCase() !== commentType) {
637
+ assertDefined(firstLeadingCommentLoc);
638
+ assertNotNull(firstLeadingCommentLoc);
639
+ assertDefined(lastLeadingCommentLoc);
640
+ assertNotNull(lastLeadingCommentLoc);
557
641
  context.report({
558
642
  loc: {
559
- start: leadingComments[0].loc.start,
560
- end: leadingComments[leadingComments.length - 1].loc.end
643
+ start: firstLeadingCommentLoc.start,
644
+ end: lastLeadingCommentLoc.end
561
645
  },
562
646
  messageId: "incorrectCommentType",
563
647
  data: {
564
- commentType: options.header.commentType
648
+ commentType: commentType
565
649
  },
566
650
  fix: canFix
567
651
  ? genReplaceFixer(
568
- options.header.commentType,
652
+ commentType,
569
653
  context,
570
654
  leadingComments,
571
655
  fixLines,
572
656
  eol,
573
- options.trailingEmptyLines.minimum)
657
+ numLines)
574
658
  : null
575
659
  });
576
660
  return;
577
661
  }
578
- if (options.header.commentType === commentTypeOptions.line) {
662
+ if (commentType === commentTypeOptions.line) {
579
663
  if (headerLines.length === 1) {
580
664
  const leadingCommentValues = leadingComments.map((c) => c.value);
581
665
  if (
582
666
  !match(leadingCommentValues.join("\n"), headerLines[0])
583
667
  && !match(leadingCommentValues.join("\r\n"), headerLines[0])
584
668
  ) {
669
+ assertDefined(firstLeadingCommentLoc);
670
+ assertNotNull(firstLeadingCommentLoc);
671
+ assertDefined(lastLeadingCommentLoc);
672
+ assertNotNull(lastLeadingCommentLoc);
585
673
  context.report({
586
674
  loc: {
587
- start: leadingComments[0].loc.start,
588
- end: leadingComments[leadingComments.length - 1].loc.end
675
+ start: firstLeadingCommentLoc.start,
676
+ end: lastLeadingCommentLoc.end
589
677
  },
590
678
  messageId: "incorrectHeader",
591
679
  fix: canFix
592
680
  ? genReplaceFixer(
593
- options.header.commentType,
681
+ commentType,
594
682
  context,
595
683
  leadingComments,
596
684
  fixLines,
597
685
  eol,
598
- options.trailingEmptyLines.minimum)
686
+ numLines)
599
687
  : null
600
688
  });
601
689
  return;
@@ -603,9 +691,12 @@ module.exports = {
603
691
  } else {
604
692
  for (let i = 0; i < headerLines.length; i++) {
605
693
  if (leadingComments.length - 1 < i) {
694
+ assertDefined(lastLeadingCommentLoc);
695
+ assertNotNull(lastLeadingCommentLoc);
606
696
  context.report({
607
697
  loc: {
608
- start: leadingComments[leadingComments.length - 1].loc.end
698
+ start: lastLeadingCommentLoc.end,
699
+ end: lastLeadingCommentLoc.end
609
700
  },
610
701
  messageId: "headerTooShort",
611
702
  data: {
@@ -613,40 +704,45 @@ module.exports = {
613
704
  },
614
705
  fix: canFix
615
706
  ? genReplaceFixer(
616
- options.header.commentType,
707
+ commentType,
617
708
  context,
618
709
  leadingComments,
619
710
  fixLines,
620
711
  eol,
621
- options.trailingEmptyLines.minimum)
712
+ numLines)
622
713
  : null
623
714
  });
624
715
  return;
625
716
  }
626
- if (typeof headerLines[i] === "string") {
627
- const leadingCommentLength = leadingComments[i].value.length;
628
- const headerLineLength = headerLines[i].length;
717
+ const headerLine = headerLines[i];
718
+ const comment = leadingComments[i];
719
+ const commentLoc = comment.loc;
720
+ assertDefined(commentLoc);
721
+ assertNotNull(commentLoc);
722
+ if (typeof headerLine === "string") {
723
+ const leadingCommentLength = comment.value.length;
724
+ const headerLineLength = headerLine.length;
629
725
  for (let j = 0; j < Math.min(leadingCommentLength, headerLineLength); j++) {
630
- if (leadingComments[i].value[j] !== headerLines[i][j]) {
726
+ if (comment.value[j] !== headerLine[j]) {
631
727
  context.report({
632
728
  loc: {
633
729
  start: {
634
730
  column: "//".length + j,
635
- line: leadingComments[i].loc.start.line
731
+ line: commentLoc.start.line
636
732
  },
637
- end: leadingComments[i].loc.end
733
+ end: commentLoc.end
638
734
  },
639
735
  messageId: "headerLineMismatchAtPos",
640
736
  data: {
641
- expected: headerLines[i].substring(j)
737
+ expected: headerLine.substring(j)
642
738
  },
643
739
  fix: genReplaceFixer(
644
- options.header.commentType,
740
+ commentType,
645
741
  context,
646
742
  leadingComments,
647
743
  fixLines,
648
744
  eol,
649
- options.trailingEmptyLines.minimum)
745
+ numLines)
650
746
  });
651
747
  return;
652
748
  }
@@ -654,20 +750,21 @@ module.exports = {
654
750
  if (leadingCommentLength < headerLineLength) {
655
751
  context.report({
656
752
  loc: {
657
- start: leadingComments[i].loc.end,
753
+ start: commentLoc.end,
754
+ end: commentLoc.end,
658
755
  },
659
756
  messageId: "headerLineTooShort",
660
757
  data: {
661
- remainder: headerLines[i].substring(leadingCommentLength)
758
+ remainder: headerLine.substring(leadingCommentLength)
662
759
  },
663
760
  fix: canFix
664
761
  ? genReplaceFixer(
665
- options.header.commentType,
762
+ commentType,
666
763
  context,
667
764
  leadingComments,
668
765
  fixLines,
669
766
  eol,
670
- options.trailingEmptyLines.minimum)
767
+ numLines)
671
768
  : null
672
769
  });
673
770
  return;
@@ -677,45 +774,45 @@ module.exports = {
677
774
  loc: {
678
775
  start: {
679
776
  column: "//".length + headerLineLength,
680
- line: leadingComments[i].loc.start.line
777
+ line: commentLoc.start.line
681
778
  },
682
- end: leadingComments[i].loc.end,
779
+ end: commentLoc.end,
683
780
  },
684
781
  messageId: "headerLineTooLong",
685
782
  fix: canFix
686
783
  ? genReplaceFixer(
687
- options.header.commentType,
784
+ commentType,
688
785
  context,
689
786
  leadingComments,
690
787
  fixLines,
691
788
  eol,
692
- options.trailingEmptyLines.minimum)
789
+ numLines)
693
790
  : null
694
791
  });
695
792
  return;
696
793
  }
697
794
  } else {
698
- if (!match(leadingComments[i].value, headerLines[i])) {
795
+ if (!match(comment.value, headerLine)) {
699
796
  context.report({
700
797
  loc: {
701
798
  start: {
702
799
  column: "//".length,
703
- line: leadingComments[i].loc.start.line,
800
+ line: commentLoc.start.line,
704
801
  },
705
- end: leadingComments[i].loc.end,
802
+ end: commentLoc.end,
706
803
  },
707
804
  messageId: "incorrectHeaderLine",
708
805
  data: {
709
- pattern: headerLines[i]
806
+ pattern: headerLine
710
807
  },
711
808
  fix: canFix
712
809
  ? genReplaceFixer(
713
- options.header.commentType,
810
+ commentType,
714
811
  context,
715
812
  leadingComments,
716
813
  fixLines,
717
814
  eol,
718
- options.trailingEmptyLines.minimum)
815
+ numLines)
719
816
  : null
720
817
  });
721
818
  return;
@@ -724,15 +821,16 @@ module.exports = {
724
821
  }
725
822
  }
726
823
 
727
- const actualLeadingEmptyLines =
728
- leadingEmptyLines(sourceCode.text.substring(leadingComments[headerLines.length - 1].range[1]));
729
- const missingEmptyLines = options.trailingEmptyLines.minimum - actualLeadingEmptyLines;
824
+ const commentRange = leadingComments[headerLines.length - 1].range;
825
+ assertDefined(commentRange);
826
+ const actualLeadingEmptyLines = leadingEmptyLines(sourceCode.text.substring(commentRange[1]));
827
+ const missingEmptyLines = numLines - actualLeadingEmptyLines;
730
828
  if (missingEmptyLines > 0) {
731
829
  context.report({
732
830
  loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
733
831
  messageId: "noNewlineAfterHeader",
734
832
  data: {
735
- expected: options.trailingEmptyLines.minimum,
833
+ expected: numLines,
736
834
  actual: actualLeadingEmptyLines
737
835
  },
738
836
  fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
@@ -749,8 +847,8 @@ module.exports = {
749
847
 
750
848
  /** @type {null | string} */
751
849
  let errorMessageId = null;
752
- /** @type {null | Record<string, string | RegExp>} */
753
- let errorMessageData = null;
850
+ /** @type {undefined | Record<string, string | RegExp>} */
851
+ let errorMessageData;
754
852
  /** @type {null | SourceLocation} */
755
853
  let errorMessageLoc = null;
756
854
  for (let i = 0; i < headerLines.length; i++) {
@@ -761,7 +859,9 @@ module.exports = {
761
859
  if (leadingLine[j] !== headerLine[j]) {
762
860
  errorMessageId = "headerLineMismatchAtPos";
763
861
  const columnOffset = i === 0 ? "/*".length : 0;
764
- const line = leadingComments[0].loc.start.line + i;
862
+ assertDefined(firstLeadingCommentLoc);
863
+ assertNotNull(firstLeadingCommentLoc);
864
+ const line = firstLeadingCommentLoc.start.line + i;
765
865
  errorMessageLoc = {
766
866
  start: {
767
867
  column: columnOffset + j,
@@ -784,31 +884,35 @@ module.exports = {
784
884
  if (leadingLine.length < headerLine.length) {
785
885
  errorMessageId = "headerLineTooShort";
786
886
  const startColumn = (i === 0 ? "/*".length : 0) + leadingLine.length;
887
+ assertDefined(firstLeadingCommentLoc);
888
+ assertNotNull(firstLeadingCommentLoc);
787
889
  errorMessageLoc = {
788
890
  start: {
789
891
  column: startColumn,
790
- line: leadingComments[0].loc.start.line + i
892
+ line: firstLeadingCommentLoc.start.line + i
791
893
  },
792
894
  end: {
793
895
  column: startColumn + 1,
794
- line: leadingComments[0].loc.start.line + i
896
+ line: firstLeadingCommentLoc.start.line + i
795
897
  }
796
898
  };
797
899
  errorMessageData = {
798
- remainder: headerLines[i].substring(leadingLine.length)
900
+ remainder: headerLine.substring(leadingLine.length)
799
901
  };
800
902
  break;
801
903
  }
802
904
  if (leadingLine.length > headerLine.length) {
905
+ assertDefined(firstLeadingCommentLoc);
906
+ assertNotNull(firstLeadingCommentLoc);
803
907
  errorMessageId = "headerLineTooLong";
804
908
  errorMessageLoc = {
805
909
  start: {
806
910
  column: (i === 0 ? "/*".length : 0) + headerLine.length,
807
- line: leadingComments[0].loc.start.line + i
911
+ line: firstLeadingCommentLoc.start.line + i
808
912
  },
809
913
  end: {
810
914
  column: (i === 0 ? "/*".length : 0) + leadingLine.length,
811
- line: leadingComments[0].loc.start.line + i
915
+ line: firstLeadingCommentLoc.start.line + i
812
916
  }
813
917
  };
814
918
  break;
@@ -820,14 +924,16 @@ module.exports = {
820
924
  pattern: headerLine
821
925
  };
822
926
  const columnOffset = i === 0 ? "/*".length : 0;
927
+ assertDefined(firstLeadingCommentLoc);
928
+ assertNotNull(firstLeadingCommentLoc);
823
929
  errorMessageLoc = {
824
930
  start: {
825
931
  column: columnOffset + 0,
826
- line: leadingComments[0].loc.start.line + i
932
+ line: firstLeadingCommentLoc.start.line + i
827
933
  },
828
934
  end: {
829
935
  column: columnOffset + leadingLine.length,
830
- line: leadingComments[0].loc.start.line + i
936
+ line: firstLeadingCommentLoc.start.line + i
831
937
  }
832
938
  };
833
939
  break;
@@ -837,14 +943,18 @@ module.exports = {
837
943
 
838
944
  if (!errorMessageId && leadingLines.length > headerLines.length) {
839
945
  errorMessageId = "headerTooLong";
946
+ assertDefined(firstLeadingCommentLoc);
947
+ assertNotNull(firstLeadingCommentLoc);
948
+ assertDefined(lastLeadingCommentLoc);
949
+ assertNotNull(lastLeadingCommentLoc);
840
950
  errorMessageLoc = {
841
951
  start: {
842
952
  column: (headerLines.length === 0 ? "/*".length : 0) + 0,
843
- line: leadingComments[0].loc.start.line + headerLines.length
953
+ line: firstLeadingCommentLoc.start.line + headerLines.length
844
954
  },
845
955
  end: {
846
- column: leadingComments[leadingComments.length - 1].loc.end.column - "*/".length,
847
- line: leadingComments[leadingComments.length - 1].loc.end.line
956
+ column: lastLeadingCommentLoc.end.column - "*/".length,
957
+ line: lastLeadingCommentLoc.end.line
848
958
  }
849
959
  };
850
960
  }
@@ -853,32 +963,33 @@ module.exports = {
853
963
  if (canFix && headerLines.length > 1) {
854
964
  fixLines = [fixLines.join(eol)];
855
965
  }
966
+ assertNotNull(errorMessageLoc);
856
967
  context.report({
857
968
  loc: errorMessageLoc,
858
969
  messageId: errorMessageId,
859
970
  data: errorMessageData,
860
971
  fix: canFix
861
972
  ? genReplaceFixer(
862
- options.header.commentType,
973
+ commentType,
863
974
  context,
864
975
  leadingComments,
865
976
  fixLines,
866
977
  eol,
867
- options.trailingEmptyLines.minimum)
978
+ numLines)
868
979
  : null
869
980
  });
870
981
  return;
871
982
  }
872
983
 
873
984
  const actualLeadingEmptyLines =
874
- leadingEmptyLines(sourceCode.text.substring(leadingComments[0].range[1]));
875
- const missingEmptyLines = options.trailingEmptyLines.minimum - actualLeadingEmptyLines;
985
+ leadingEmptyLines(sourceCode.text.substring(firstLeadingCommentRange[1]));
986
+ const missingEmptyLines = numLines - actualLeadingEmptyLines;
876
987
  if (missingEmptyLines > 0) {
877
988
  context.report({
878
989
  loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
879
990
  messageId: "noNewlineAfterHeader",
880
991
  data: {
881
- expected: options.trailingEmptyLines.minimum,
992
+ expected: numLines,
882
993
  actual: actualLeadingEmptyLines
883
994
  },
884
995
  fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
@@ -889,3 +1000,4 @@ module.exports = {
889
1000
  }
890
1001
  };
891
1002
 
1003
+ exports.header = headerRule;