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

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,12 +24,12 @@
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
- const { description, recommended, url } = require("./header.docs");
32
+ const { description, recommended } = require("./header.docs");
33
33
  const { lineEndingOptions, commentTypeOptions, schema } = require("./header.schema");
34
34
 
35
35
  /**
@@ -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,18 +519,18 @@ 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: {
465
527
  description,
466
- recommended,
467
- url
528
+ recommended
468
529
  },
469
530
  fixable: "whitespace",
470
531
  schema,
471
532
  defaultOptions: [
472
- /** @type {HeaderOptions} */
533
+ /** @type {AllHeaderOptions} */
473
534
  {
474
535
  lineEndings: lineEndingOptions.os,
475
536
  trailingEmptyLines: {
@@ -497,34 +558,48 @@ module.exports = {
497
558
  */
498
559
  create: function(context) {
499
560
 
500
- const newStyleOptions = transformLegacyOptions(context.options);
561
+ const newStyleOptions = transformLegacyOptions(/** @type {AllHeaderOptions} */ (context.options));
501
562
  const options = normalizeOptions(newStyleOptions);
502
563
 
503
- const eol = getEol(options.lineEndings);
564
+ assertLineBasedHeaderConfig(options.header);
565
+ const commentType = /** @type {CommentType} */ (options.header.commentType);
566
+
567
+ const eol = getEol(
568
+ /** @type {LineEndingOption} */ (options.lineEndings)
569
+ );
504
570
 
571
+ /** @type {string[]} */
505
572
  let fixLines = [];
506
573
  // If any of the lines are regular expressions, then we can't
507
574
  // automatically fix them. We set this to true below once we
508
575
  // ensure none of the lines are of type RegExp
509
576
  let canFix = true;
510
577
  const headerLines = options.header.lines.map(function(line) {
511
- const isRegex = isPattern(line);
512
578
  // Can only fix regex option if a template is also provided
513
- if (isRegex && !line.template) {
514
- canFix = false;
579
+ if (isPattern(line)) {
580
+ if (Object.prototype.hasOwnProperty.call(line, "template")) {
581
+ fixLines.push(/** @type {string} */ (line.template));
582
+ } else {
583
+ canFix = false;
584
+ fixLines.push("");
585
+ }
586
+ return line.pattern;
587
+ } else {
588
+ fixLines.push(/** @type {string} */ (line));
589
+ return line;
515
590
  }
516
- fixLines.push(line.template || line);
517
- return isRegex ? line.pattern : line;
518
591
  });
519
592
 
593
+
594
+ const numLines = /** @type {number} */ (options.trailingEmptyLines?.minimum);
595
+
520
596
  return {
521
597
  /**
522
598
  * Hooks into the processing of the overall script node to do the
523
599
  * header validation.
524
- * @param {Program} node the whole script node
525
600
  * @returns {void}
526
601
  */
527
- Program: function(node) {
602
+ Program: function() {
528
603
  const sourceCode = contextSourceCode(context);
529
604
  if (!hasHeader(sourceCode.text)) {
530
605
  const hasShebang = sourceCode.text.startsWith("#!");
@@ -542,60 +617,72 @@ module.exports = {
542
617
  },
543
618
  messageId: "missingHeader",
544
619
  fix: genPrependFixer(
545
- options.header.commentType,
620
+ commentType,
546
621
  context,
547
622
  fixLines,
548
623
  eol,
549
- options.trailingEmptyLines.minimum)
624
+ numLines)
550
625
  });
551
626
  return;
552
627
  }
553
-
554
- const leadingComments = getLeadingComments(context, node);
555
-
556
- if (leadingComments[0].type.toLowerCase() !== options.header.commentType) {
628
+ const leadingComments = getLeadingComments(context);
629
+ const firstLeadingCommentLoc = leadingComments[0].loc;
630
+ const firstLeadingCommentRange = leadingComments[0].range;
631
+ assertDefined(firstLeadingCommentRange);
632
+
633
+ const lastLeadingCommentLoc = leadingComments[leadingComments.length - 1].loc;
634
+
635
+ if (leadingComments[0].type.toLowerCase() !== commentType) {
636
+ assertDefined(firstLeadingCommentLoc);
637
+ assertNotNull(firstLeadingCommentLoc);
638
+ assertDefined(lastLeadingCommentLoc);
639
+ assertNotNull(lastLeadingCommentLoc);
557
640
  context.report({
558
641
  loc: {
559
- start: leadingComments[0].loc.start,
560
- end: leadingComments[leadingComments.length - 1].loc.end
642
+ start: firstLeadingCommentLoc.start,
643
+ end: lastLeadingCommentLoc.end
561
644
  },
562
645
  messageId: "incorrectCommentType",
563
646
  data: {
564
- commentType: options.header.commentType
647
+ commentType: commentType
565
648
  },
566
649
  fix: canFix
567
650
  ? genReplaceFixer(
568
- options.header.commentType,
651
+ commentType,
569
652
  context,
570
653
  leadingComments,
571
654
  fixLines,
572
655
  eol,
573
- options.trailingEmptyLines.minimum)
656
+ numLines)
574
657
  : null
575
658
  });
576
659
  return;
577
660
  }
578
- if (options.header.commentType === commentTypeOptions.line) {
661
+ if (commentType === commentTypeOptions.line) {
579
662
  if (headerLines.length === 1) {
580
663
  const leadingCommentValues = leadingComments.map((c) => c.value);
581
664
  if (
582
665
  !match(leadingCommentValues.join("\n"), headerLines[0])
583
666
  && !match(leadingCommentValues.join("\r\n"), headerLines[0])
584
667
  ) {
668
+ assertDefined(firstLeadingCommentLoc);
669
+ assertNotNull(firstLeadingCommentLoc);
670
+ assertDefined(lastLeadingCommentLoc);
671
+ assertNotNull(lastLeadingCommentLoc);
585
672
  context.report({
586
673
  loc: {
587
- start: leadingComments[0].loc.start,
588
- end: leadingComments[leadingComments.length - 1].loc.end
674
+ start: firstLeadingCommentLoc.start,
675
+ end: lastLeadingCommentLoc.end
589
676
  },
590
677
  messageId: "incorrectHeader",
591
678
  fix: canFix
592
679
  ? genReplaceFixer(
593
- options.header.commentType,
680
+ commentType,
594
681
  context,
595
682
  leadingComments,
596
683
  fixLines,
597
684
  eol,
598
- options.trailingEmptyLines.minimum)
685
+ numLines)
599
686
  : null
600
687
  });
601
688
  return;
@@ -603,9 +690,12 @@ module.exports = {
603
690
  } else {
604
691
  for (let i = 0; i < headerLines.length; i++) {
605
692
  if (leadingComments.length - 1 < i) {
693
+ assertDefined(lastLeadingCommentLoc);
694
+ assertNotNull(lastLeadingCommentLoc);
606
695
  context.report({
607
696
  loc: {
608
- start: leadingComments[leadingComments.length - 1].loc.end
697
+ start: lastLeadingCommentLoc.end,
698
+ end: lastLeadingCommentLoc.end
609
699
  },
610
700
  messageId: "headerTooShort",
611
701
  data: {
@@ -613,40 +703,45 @@ module.exports = {
613
703
  },
614
704
  fix: canFix
615
705
  ? genReplaceFixer(
616
- options.header.commentType,
706
+ commentType,
617
707
  context,
618
708
  leadingComments,
619
709
  fixLines,
620
710
  eol,
621
- options.trailingEmptyLines.minimum)
711
+ numLines)
622
712
  : null
623
713
  });
624
714
  return;
625
715
  }
626
- if (typeof headerLines[i] === "string") {
627
- const leadingCommentLength = leadingComments[i].value.length;
628
- const headerLineLength = headerLines[i].length;
716
+ const headerLine = headerLines[i];
717
+ const comment = leadingComments[i];
718
+ const commentLoc = comment.loc;
719
+ assertDefined(commentLoc);
720
+ assertNotNull(commentLoc);
721
+ if (typeof headerLine === "string") {
722
+ const leadingCommentLength = comment.value.length;
723
+ const headerLineLength = headerLine.length;
629
724
  for (let j = 0; j < Math.min(leadingCommentLength, headerLineLength); j++) {
630
- if (leadingComments[i].value[j] !== headerLines[i][j]) {
725
+ if (comment.value[j] !== headerLine[j]) {
631
726
  context.report({
632
727
  loc: {
633
728
  start: {
634
729
  column: "//".length + j,
635
- line: leadingComments[i].loc.start.line
730
+ line: commentLoc.start.line
636
731
  },
637
- end: leadingComments[i].loc.end
732
+ end: commentLoc.end
638
733
  },
639
734
  messageId: "headerLineMismatchAtPos",
640
735
  data: {
641
- expected: headerLines[i].substring(j)
736
+ expected: headerLine.substring(j)
642
737
  },
643
738
  fix: genReplaceFixer(
644
- options.header.commentType,
739
+ commentType,
645
740
  context,
646
741
  leadingComments,
647
742
  fixLines,
648
743
  eol,
649
- options.trailingEmptyLines.minimum)
744
+ numLines)
650
745
  });
651
746
  return;
652
747
  }
@@ -654,20 +749,21 @@ module.exports = {
654
749
  if (leadingCommentLength < headerLineLength) {
655
750
  context.report({
656
751
  loc: {
657
- start: leadingComments[i].loc.end,
752
+ start: commentLoc.end,
753
+ end: commentLoc.end,
658
754
  },
659
755
  messageId: "headerLineTooShort",
660
756
  data: {
661
- remainder: headerLines[i].substring(leadingCommentLength)
757
+ remainder: headerLine.substring(leadingCommentLength)
662
758
  },
663
759
  fix: canFix
664
760
  ? genReplaceFixer(
665
- options.header.commentType,
761
+ commentType,
666
762
  context,
667
763
  leadingComments,
668
764
  fixLines,
669
765
  eol,
670
- options.trailingEmptyLines.minimum)
766
+ numLines)
671
767
  : null
672
768
  });
673
769
  return;
@@ -677,45 +773,45 @@ module.exports = {
677
773
  loc: {
678
774
  start: {
679
775
  column: "//".length + headerLineLength,
680
- line: leadingComments[i].loc.start.line
776
+ line: commentLoc.start.line
681
777
  },
682
- end: leadingComments[i].loc.end,
778
+ end: commentLoc.end,
683
779
  },
684
780
  messageId: "headerLineTooLong",
685
781
  fix: canFix
686
782
  ? genReplaceFixer(
687
- options.header.commentType,
783
+ commentType,
688
784
  context,
689
785
  leadingComments,
690
786
  fixLines,
691
787
  eol,
692
- options.trailingEmptyLines.minimum)
788
+ numLines)
693
789
  : null
694
790
  });
695
791
  return;
696
792
  }
697
793
  } else {
698
- if (!match(leadingComments[i].value, headerLines[i])) {
794
+ if (!match(comment.value, headerLine)) {
699
795
  context.report({
700
796
  loc: {
701
797
  start: {
702
798
  column: "//".length,
703
- line: leadingComments[i].loc.start.line,
799
+ line: commentLoc.start.line,
704
800
  },
705
- end: leadingComments[i].loc.end,
801
+ end: commentLoc.end,
706
802
  },
707
803
  messageId: "incorrectHeaderLine",
708
804
  data: {
709
- pattern: headerLines[i]
805
+ pattern: headerLine.toString()
710
806
  },
711
807
  fix: canFix
712
808
  ? genReplaceFixer(
713
- options.header.commentType,
809
+ commentType,
714
810
  context,
715
811
  leadingComments,
716
812
  fixLines,
717
813
  eol,
718
- options.trailingEmptyLines.minimum)
814
+ numLines)
719
815
  : null
720
816
  });
721
817
  return;
@@ -724,15 +820,16 @@ module.exports = {
724
820
  }
725
821
  }
726
822
 
727
- const actualLeadingEmptyLines =
728
- leadingEmptyLines(sourceCode.text.substring(leadingComments[headerLines.length - 1].range[1]));
729
- const missingEmptyLines = options.trailingEmptyLines.minimum - actualLeadingEmptyLines;
823
+ const commentRange = leadingComments[headerLines.length - 1].range;
824
+ assertDefined(commentRange);
825
+ const actualLeadingEmptyLines = leadingEmptyLines(sourceCode.text.substring(commentRange[1]));
826
+ const missingEmptyLines = numLines - actualLeadingEmptyLines;
730
827
  if (missingEmptyLines > 0) {
731
828
  context.report({
732
829
  loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
733
830
  messageId: "noNewlineAfterHeader",
734
831
  data: {
735
- expected: options.trailingEmptyLines.minimum,
832
+ expected: numLines,
736
833
  actual: actualLeadingEmptyLines
737
834
  },
738
835
  fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
@@ -749,8 +846,8 @@ module.exports = {
749
846
 
750
847
  /** @type {null | string} */
751
848
  let errorMessageId = null;
752
- /** @type {null | Record<string, string | RegExp>} */
753
- let errorMessageData = null;
849
+ /** @type {undefined | Record<string, string>} */
850
+ let errorMessageData;
754
851
  /** @type {null | SourceLocation} */
755
852
  let errorMessageLoc = null;
756
853
  for (let i = 0; i < headerLines.length; i++) {
@@ -761,7 +858,9 @@ module.exports = {
761
858
  if (leadingLine[j] !== headerLine[j]) {
762
859
  errorMessageId = "headerLineMismatchAtPos";
763
860
  const columnOffset = i === 0 ? "/*".length : 0;
764
- const line = leadingComments[0].loc.start.line + i;
861
+ assertDefined(firstLeadingCommentLoc);
862
+ assertNotNull(firstLeadingCommentLoc);
863
+ const line = firstLeadingCommentLoc.start.line + i;
765
864
  errorMessageLoc = {
766
865
  start: {
767
866
  column: columnOffset + j,
@@ -784,31 +883,35 @@ module.exports = {
784
883
  if (leadingLine.length < headerLine.length) {
785
884
  errorMessageId = "headerLineTooShort";
786
885
  const startColumn = (i === 0 ? "/*".length : 0) + leadingLine.length;
886
+ assertDefined(firstLeadingCommentLoc);
887
+ assertNotNull(firstLeadingCommentLoc);
787
888
  errorMessageLoc = {
788
889
  start: {
789
890
  column: startColumn,
790
- line: leadingComments[0].loc.start.line + i
891
+ line: firstLeadingCommentLoc.start.line + i
791
892
  },
792
893
  end: {
793
894
  column: startColumn + 1,
794
- line: leadingComments[0].loc.start.line + i
895
+ line: firstLeadingCommentLoc.start.line + i
795
896
  }
796
897
  };
797
898
  errorMessageData = {
798
- remainder: headerLines[i].substring(leadingLine.length)
899
+ remainder: headerLine.substring(leadingLine.length)
799
900
  };
800
901
  break;
801
902
  }
802
903
  if (leadingLine.length > headerLine.length) {
904
+ assertDefined(firstLeadingCommentLoc);
905
+ assertNotNull(firstLeadingCommentLoc);
803
906
  errorMessageId = "headerLineTooLong";
804
907
  errorMessageLoc = {
805
908
  start: {
806
909
  column: (i === 0 ? "/*".length : 0) + headerLine.length,
807
- line: leadingComments[0].loc.start.line + i
910
+ line: firstLeadingCommentLoc.start.line + i
808
911
  },
809
912
  end: {
810
913
  column: (i === 0 ? "/*".length : 0) + leadingLine.length,
811
- line: leadingComments[0].loc.start.line + i
914
+ line: firstLeadingCommentLoc.start.line + i
812
915
  }
813
916
  };
814
917
  break;
@@ -817,17 +920,19 @@ module.exports = {
817
920
  if (!match(leadingLine, headerLine)) {
818
921
  errorMessageId = "incorrectHeaderLine";
819
922
  errorMessageData = {
820
- pattern: headerLine
923
+ pattern: headerLine.toString()
821
924
  };
822
925
  const columnOffset = i === 0 ? "/*".length : 0;
926
+ assertDefined(firstLeadingCommentLoc);
927
+ assertNotNull(firstLeadingCommentLoc);
823
928
  errorMessageLoc = {
824
929
  start: {
825
930
  column: columnOffset + 0,
826
- line: leadingComments[0].loc.start.line + i
931
+ line: firstLeadingCommentLoc.start.line + i
827
932
  },
828
933
  end: {
829
934
  column: columnOffset + leadingLine.length,
830
- line: leadingComments[0].loc.start.line + i
935
+ line: firstLeadingCommentLoc.start.line + i
831
936
  }
832
937
  };
833
938
  break;
@@ -837,14 +942,18 @@ module.exports = {
837
942
 
838
943
  if (!errorMessageId && leadingLines.length > headerLines.length) {
839
944
  errorMessageId = "headerTooLong";
945
+ assertDefined(firstLeadingCommentLoc);
946
+ assertNotNull(firstLeadingCommentLoc);
947
+ assertDefined(lastLeadingCommentLoc);
948
+ assertNotNull(lastLeadingCommentLoc);
840
949
  errorMessageLoc = {
841
950
  start: {
842
951
  column: (headerLines.length === 0 ? "/*".length : 0) + 0,
843
- line: leadingComments[0].loc.start.line + headerLines.length
952
+ line: firstLeadingCommentLoc.start.line + headerLines.length
844
953
  },
845
954
  end: {
846
- column: leadingComments[leadingComments.length - 1].loc.end.column - "*/".length,
847
- line: leadingComments[leadingComments.length - 1].loc.end.line
955
+ column: lastLeadingCommentLoc.end.column - "*/".length,
956
+ line: lastLeadingCommentLoc.end.line
848
957
  }
849
958
  };
850
959
  }
@@ -853,32 +962,33 @@ module.exports = {
853
962
  if (canFix && headerLines.length > 1) {
854
963
  fixLines = [fixLines.join(eol)];
855
964
  }
965
+ assertNotNull(errorMessageLoc);
856
966
  context.report({
857
967
  loc: errorMessageLoc,
858
968
  messageId: errorMessageId,
859
969
  data: errorMessageData,
860
970
  fix: canFix
861
971
  ? genReplaceFixer(
862
- options.header.commentType,
972
+ commentType,
863
973
  context,
864
974
  leadingComments,
865
975
  fixLines,
866
976
  eol,
867
- options.trailingEmptyLines.minimum)
977
+ numLines)
868
978
  : null
869
979
  });
870
980
  return;
871
981
  }
872
982
 
873
983
  const actualLeadingEmptyLines =
874
- leadingEmptyLines(sourceCode.text.substring(leadingComments[0].range[1]));
875
- const missingEmptyLines = options.trailingEmptyLines.minimum - actualLeadingEmptyLines;
984
+ leadingEmptyLines(sourceCode.text.substring(firstLeadingCommentRange[1]));
985
+ const missingEmptyLines = numLines - actualLeadingEmptyLines;
876
986
  if (missingEmptyLines > 0) {
877
987
  context.report({
878
988
  loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
879
989
  messageId: "noNewlineAfterHeader",
880
990
  data: {
881
- expected: options.trailingEmptyLines.minimum,
991
+ expected: numLines,
882
992
  actual: actualLeadingEmptyLines
883
993
  },
884
994
  fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
@@ -889,3 +999,4 @@ module.exports = {
889
999
  }
890
1000
  };
891
1001
 
1002
+ exports.header = headerRule;