@tony.ganchev/eslint-plugin-header 3.1.12 → 3.1.13

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.
package/README.md CHANGED
@@ -45,6 +45,10 @@ It addresses the following issus:
45
45
  The plugin supports ESLint 7 / 8 / 9 / 10rc0 (check package.json for details).
46
46
  Both flat config and legacy, hierarchical config can be used.
47
47
 
48
+ The NPM package provides TypeScript type definitions and can be used with
49
+ TypeScript-based ESLint flat configuration without the need for `@ts-ignore`
50
+ statements.
51
+
48
52
  ## Usage
49
53
 
50
54
  This rule takes between 1 and 4 arguments after the rule validation severity.
@@ -67,6 +71,12 @@ The configuration can take any of the following forms:
67
71
  {<settings>}]` - define the header contents inline and an expected number of
68
72
  empty lines after the header and pass additional settings.
69
73
 
74
+ For TypesScript-based flat ESLint configuration, the following type is provided:
75
+
76
+ - `HeaderRuleConfig` defines the overall rule configuration for the `header`
77
+ rule and includes severity level and supports both the modern object-based
78
+ configuration and the legacy array-based configuration.
79
+
70
80
  ### File-based Configuration
71
81
 
72
82
  In this configuration mode, the first argument is a string pointing to a JS
@@ -446,7 +456,7 @@ The following guidelines apply:
446
456
  - **major versions** - new functionality that breaks compatibility.
447
457
  - **minor versions** - new features that do not break compatibility. For the
448
458
  most part we would aim to continue releasing new versions in the 3.x product
449
- line and have opt-in flags for changes in behavior of existign features.
459
+ line and have opt-in flags for changes in behavior of existing features.
450
460
  - **revisions** - bugfixes and minor non-feature improvements that do not break
451
461
  compatibility. Note that bug-fixes are allowed to break compatibility with
452
462
  previous version if the older version regressed previous expected behavior.
package/index.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2026-present Tony Ganchev and contributors
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the “Software”), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ export { HeaderOptions, HeaderRuleConfig } from "./types/lib/rules/header";
26
+
27
+ import plugin = require("./types");
28
+
29
+ export default plugin;
package/index.js CHANGED
@@ -24,11 +24,13 @@
24
24
 
25
25
  "use strict";
26
26
 
27
- module.exports = {
27
+ const { header } = require("./lib/rules/header");
28
+
29
+ /** @type {import('eslint').ESLint.Plugin} */
30
+ const pluginDefinition = {
28
31
  rules: {
29
- "header": require("./lib/rules/header")
30
- },
31
- rulesConfig: {
32
- "header": 0
32
+ header
33
33
  }
34
34
  };
35
+
36
+ module.exports = pluginDefinition;
@@ -24,7 +24,7 @@
24
24
 
25
25
  "use strict";
26
26
 
27
- const assert = require("assert");
27
+ const assert = require("node:assert");
28
28
 
29
29
  /**
30
30
  * Parses a line or block comment and returns the type of comment and an array
@@ -29,11 +29,11 @@ const assert = require("node:assert");
29
29
  const fs = require("node:fs");
30
30
  const path = require("node:path");
31
31
 
32
- const packageJsonContent = fs.readFileSync(path.resolve(__dirname, "../../package.json"));
32
+ const packageJsonContent = fs.readFileSync(path.resolve(__dirname, "../../package.json"), "utf8");
33
33
  const packageJson = JSON.parse(packageJsonContent);
34
34
  assert.equal(Object.prototype.hasOwnProperty.call(packageJson, "version"), true,
35
35
  "The plugin's package.json should be available two directories above the rule.");
36
- const pluginVersion = packageJsonContent.version;
36
+ const pluginVersion = packageJson.version;
37
37
 
38
38
  exports.description = "";
39
39
  exports.recommended = true;
@@ -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 { commentTypeOptions, lineEndingOptions, 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
@@ -47,26 +45,36 @@ const { commentTypeOptions, lineEndingOptions, schema } = require("./header.sche
47
45
  */
48
46
 
49
47
  /**
50
- * Local type defintions.
51
- * @typedef {string | { pattern: string, template?: string }} HeaderLine
48
+ * Local type definitions.
49
+ * @typedef {{ pattern: string, template?: string }} HeaderLinePattern
50
+ * @typedef {string | HeaderLinePattern} HeaderLine
52
51
  * @typedef {(HeaderLine | HeaderLine[])} HeaderLines
53
- * @typedef {{ lineEndings: ('unix' | 'windows') }} HeaderSettings
54
- * @typedef {
55
- * [string]
56
- * | [string, HeaderSettings]
57
- * | [('block' | 'line') | HeaderLines ]
58
- * | [('block' | 'line') | HeaderLines | HeaderSettings]
59
- * | [('block' | 'line') | HeaderLines | number ]
60
- * | [('block' | 'line') | HeaderLines | number | HeaderSettings]
61
- * } HeaderOptions
52
+ * @typedef {'unix' | 'windows'} LineEndingOption
53
+ * @typedef {{ lineEndings?: LineEndingOption }} HeaderSettings
54
+ * @typedef {'block' | 'line'} CommentType
55
+ * @typedef {[template: string] |
56
+ * [template: string, settings: HeaderSettings] |
57
+ * [type: CommentType, lines: HeaderLines] |
58
+ * [type: CommentType, lines: HeaderLines, settings: HeaderSettings] |
59
+ * [type: CommentType, lines: HeaderLines, minLines: number] |
60
+ * [
61
+ * type: CommentType,
62
+ * lines: HeaderLines,
63
+ * minLines: number,
64
+ * settings: HeaderSettings
65
+ * ]
66
+ * } AllHeaderOptions
67
+ * @typedef {import('eslint').Linter.RuleEntry<AllHeaderOptions>
68
+ * } HeaderRuleConfig
62
69
  */
63
70
 
64
71
  /**
65
72
  * Tests if the passed line configuration string or object is a pattern
66
73
  * definition.
67
74
  * @param {HeaderLine} object line configuration object or string
68
- * @returns {boolean} `true` if the line configuration is a pattern-defining
69
- * object or `false` otherwise.
75
+ * @returns {object is HeaderLinePattern} `true` if the line configuration is a
76
+ * pattern-defining object or `false`
77
+ * otherwise.
70
78
  */
71
79
  function isPattern(object) {
72
80
  return typeof object === "object" && Object.prototype.hasOwnProperty.call(object, "pattern");
@@ -75,13 +83,13 @@ function isPattern(object) {
75
83
  /**
76
84
  * Utility over a line config argument to match an expected string either
77
85
  * against a regex or for full match against a string.
78
- * @param {HeaderLine} actual the string to test.
79
- * @param {string} expected The string or regex to test again.
86
+ * @param {string} actual the string to test.
87
+ * @param {string | RegExp} expected The string or regex to test again.
80
88
  * @returns {boolean} `true` if the passed string matches the expected line
81
89
  * config or `false` otherwise.
82
90
  */
83
91
  function match(actual, expected) {
84
- if (expected.test) {
92
+ if (expected instanceof RegExp) {
85
93
  return expected.test(actual);
86
94
  } else {
87
95
  return expected === actual;
@@ -90,35 +98,62 @@ function match(actual, expected) {
90
98
 
91
99
  /**
92
100
  * Remove Unix she-bangs from the list of comments.
93
- * @param {Comment[]} comments the list of comment lines.
94
- * @returns {Comment[]} the list of comments with containing all incomming
101
+ * @param {(Comment | { type: "Shebang" })[]} comments the list of comment
102
+ * lines.
103
+ * @returns {Comment[]} the list of comments with containing all incoming
95
104
  * comments from `comments` with the shebang comments
96
105
  * omitted.
97
106
  */
98
107
  function excludeShebangs(comments) {
108
+ /** @type {Comment[]} */
99
109
  return comments.filter(function(comment) {
100
110
  return comment.type !== "Shebang";
101
111
  });
102
112
  }
103
113
 
114
+ /**
115
+ * TypeScript helper to confirm defined type.
116
+ * @template T
117
+ * @param {T | undefined} val the value to validate.
118
+ * @returns {asserts val is T} validates defined type
119
+ */
120
+ function assertDefined(val) {
121
+ assert.strict.notEqual(typeof val, "undefined");
122
+ }
123
+
124
+ /**
125
+ * TypeScript helper to confirm non-null type.
126
+ * @template T
127
+ * @param {T | null} val the value to validate.
128
+ * @returns {asserts val is T} validates non-null type
129
+ */
130
+ function assertNotNull(val) {
131
+ assert.strict.notEqual(val, null);
132
+ }
133
+
104
134
  /**
105
135
  * Returns either the first block comment or the first set of line comments that
106
136
  * are ONLY separated by a single newline. Note that this does not actually
107
137
  * check if they are at the start of the file since that is already checked by
108
138
  * `hasHeader()`.
109
139
  * @param {RuleContext} context ESLint execution environment.
110
- * @param {Program} node ESLint AST treee node being processed.
111
140
  * @returns {Comment[]} lines that constitute the leading comment.
112
141
  */
113
- function getLeadingComments(context, node) {
142
+ function getLeadingComments(context) {
114
143
  const sourceCode = contextSourceCode(context);
115
- const all = excludeShebangs(sourceCode.getAllComments(node.body.length ? node.body[0] : node));
144
+ const all = excludeShebangs(sourceCode.getAllComments());
145
+ assert.ok(all);
146
+ assert.ok(all.length);
116
147
  if (all[0].type.toLowerCase() === commentTypeOptions.block) {
117
148
  return [all[0]];
118
149
  }
119
150
  let i = 1;
120
151
  for (; i < all.length; ++i) {
121
- const txt = sourceCode.text.slice(all[i - 1].range[1], all[i].range[0]);
152
+ const previousRange = all[i - 1].range;
153
+ assertDefined(previousRange);
154
+ const currentRange = all[i].range;
155
+ assertDefined(currentRange);
156
+ const txt = sourceCode.text.slice(previousRange[1], currentRange[0]);
122
157
  if (!txt.match(/^(\r\n|\r|\n)$/)) {
123
158
  break;
124
159
  }
@@ -129,7 +164,7 @@ function getLeadingComments(context, node) {
129
164
  /**
130
165
  * Generate a comment including trailing spaces out of a number of comment body
131
166
  * lines.
132
- * @param {'block' | 'line'} commentType the type of comment to generate.
167
+ * @param {CommentType} commentType the type of comment to generate.
133
168
  * @param {string[]} textArray list of lines of the comment content.
134
169
  * @param {'\n' | '\r\n'} eol end-of-line characters.
135
170
  * @returns {string} resulting comment.
@@ -147,12 +182,21 @@ function genCommentBody(commentType, textArray, eol) {
147
182
  /**
148
183
  * Determines the start and end position in the source code of the leading
149
184
  * comment.
150
- * @param {string[]} comments list of comments.
185
+ * @param {Comment[]} comments list of comments.
151
186
  * @returns {[number, number]} resulting range.
152
187
  */
153
188
  function genCommentsRange(comments) {
154
- const start = comments[0].range[0];
155
- const end = comments.slice(-1)[0].range[1];
189
+ assert.ok(comments.length);
190
+ const firstComment = comments[0];
191
+ assertDefined(firstComment);
192
+ const firstCommentRange = firstComment.range;
193
+ assertDefined(firstCommentRange);
194
+ const start = firstCommentRange[0];
195
+ const lastComment = comments.slice(-1)[0];
196
+ assertDefined(lastComment);
197
+ const lastCommentRange = lastComment.range;
198
+ assertDefined(lastCommentRange);
199
+ const end = lastCommentRange[1];
156
200
  return [start, end];
157
201
  }
158
202
 
@@ -178,7 +222,7 @@ function leadingEmptyLines(src) {
178
222
 
179
223
  /**
180
224
  * Factory for fixer that adds a missing header.
181
- * @param {'block' | 'line'} commentType type of comment to use.
225
+ * @param {CommentType} commentType type of comment to use.
182
226
  * @param {RuleContext} context ESLint execution runtime.
183
227
  * @param {string[]} headerLines lines of the header comment.
184
228
  * @param {'\n' | '\r\n'} eol end-of-line characters
@@ -188,7 +232,7 @@ function leadingEmptyLines(src) {
188
232
  function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
189
233
  return function(fixer) {
190
234
  let insertPos = 0;
191
- let newHeader = genCommentBody(commentType, headerLines, eol, numNewlines);
235
+ let newHeader = genCommentBody(commentType, headerLines, eol);
192
236
  const sourceCode = contextSourceCode(context);
193
237
  if (sourceCode.text.startsWith("#!")) {
194
238
  const firstNewLinePos = sourceCode.text.indexOf("\n");
@@ -209,15 +253,13 @@ function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
209
253
 
210
254
  /**
211
255
  * Factory for fixer that replaces an incorrect header.
212
- * @param {'block' | 'line'} commentType type of comment to use.
256
+ * @param {CommentType} commentType type of comment to use.
213
257
  * @param {RuleContext} context ESLint execution context.
214
258
  * @param {Comment[]} leadingComments comment elements to replace.
215
259
  * @param {string[]} headerLines lines of the header comment.
216
260
  * @param {'\n' | '\r\n'} eol end-of-line characters
217
261
  * @param {number} numNewlines number of trailing lines after the comment.
218
- * @returns {
219
- * (fixer: RuleTextEditor) => RuleTextEdit | RuleTextEdit[] | null
220
- * } the fixer.
262
+ * @returns {(fixer: RuleFixer) => Fix | Fix[] | null} the fixer.
221
263
  */
222
264
  function genReplaceFixer(commentType, context, leadingComments, headerLines, eol, numNewlines) {
223
265
  return function(fixer) {
@@ -227,7 +269,7 @@ function genReplaceFixer(commentType, context, leadingComments, headerLines, eol
227
269
  const eols = eol.repeat(missingNewlines);
228
270
  return fixer.replaceTextRange(
229
271
  commentRange,
230
- genCommentBody(commentType, headerLines, eol, numNewlines) + eols
272
+ genCommentBody(commentType, headerLines, eol) + eols
231
273
  );
232
274
  };
233
275
  }
@@ -238,9 +280,7 @@ function genReplaceFixer(commentType, context, leadingComments, headerLines, eol
238
280
  * @param {'\n' | '\r\n'} eol end-of-line characters
239
281
  * @param {number} missingEmptyLinesCount number of trailing lines after the
240
282
  * comment.
241
- * @returns {
242
- * (fixer: RuleTextEditor) => RuleTextEdit | RuleTextEdit[] | null
243
- * } the fixer.
283
+ * @returns {(fixer: RuleFixer) => Fix | Fix[] | null} the fixer.
244
284
  */
245
285
  function genEmptyLinesFixer(leadingComments, eol, missingEmptyLinesCount) {
246
286
  return function(fixer) {
@@ -253,7 +293,7 @@ function genEmptyLinesFixer(leadingComments, eol, missingEmptyLinesCount) {
253
293
 
254
294
  /**
255
295
  * Finds the option parameter within the list of rule config options.
256
- * @param {HeaderOptions} options the config options passed to the rule.
296
+ * @param {AllHeaderOptions} options the config options passed to the rule.
257
297
  * @returns {HeaderSettings | null} the settings parameter or `null` if no such
258
298
  * is defined.
259
299
  */
@@ -261,7 +301,7 @@ function findSettings(options) {
261
301
  const lastOption = options[options.length - 1];
262
302
  if (typeof lastOption === "object" && !Array.isArray(lastOption) && lastOption !== null
263
303
  && !Object.prototype.hasOwnProperty.call(lastOption, "pattern")) {
264
- return lastOption;
304
+ return /** @type {HeaderSettings} */ (lastOption);
265
305
  }
266
306
  return null;
267
307
  }
@@ -269,7 +309,7 @@ function findSettings(options) {
269
309
  /**
270
310
  * Returns the used line-termination characters per the rule's config if any or
271
311
  * else based on the runtime environments.
272
- * @param {HeaderOptions} options rule configuration.
312
+ * @param {AllHeaderOptions} options rule configuration.
273
313
  * @returns {'\n' | '\r\n'} the correct line ending characters for the
274
314
  * environment.
275
315
  */
@@ -283,7 +323,7 @@ function getEOL(options) {
283
323
  return "\r\n";
284
324
  }
285
325
  }
286
- return os.EOL;
326
+ return /** @type {'\n' | '\r\n'} */ (os.EOL);
287
327
  }
288
328
 
289
329
  /**
@@ -311,7 +351,11 @@ function hasHeader(src) {
311
351
  * @returns {SourceLocation} the location (line and column) of the violation.
312
352
  */
313
353
  function missingEmptyLinesViolationLoc(leadingComments, actualEmptyLines) {
314
- const lastCommentLineLocEnd = leadingComments[leadingComments.length - 1].loc.end;
354
+ assert.ok(leadingComments);
355
+ const loc = leadingComments[leadingComments.length - 1].loc;
356
+ assertDefined(loc);
357
+ assertNotNull(loc);
358
+ const lastCommentLineLocEnd = loc.end;
315
359
  return {
316
360
  start: lastCommentLineLocEnd,
317
361
  end: {
@@ -321,7 +365,8 @@ function missingEmptyLinesViolationLoc(leadingComments, actualEmptyLines) {
321
365
  };
322
366
  }
323
367
 
324
- module.exports = {
368
+ /** @type {import('eslint').Rule.RuleModule} */
369
+ const headerRule = {
325
370
  meta: {
326
371
  type: "layout",
327
372
  docs: {
@@ -351,7 +396,7 @@ module.exports = {
351
396
  * @returns {NodeListener} the rule definition.
352
397
  */
353
398
  create: function(context) {
354
- let options = context.options;
399
+ let options = /** @type {AllHeaderOptions} */ (context.options);
355
400
  const numNewlines = options.length > 2 && typeof options[2] === "number" ? options[2] : 1;
356
401
  const eol = getEOL(options);
357
402
 
@@ -361,9 +406,10 @@ module.exports = {
361
406
  options = commentParser(text);
362
407
  }
363
408
 
364
- const commentType = options[0].toLowerCase();
365
- /** @type {(string | RegExp)[]} */
409
+ const commentType = /** @type {CommentType} */ (options[0].toLowerCase());
410
+ /** @type {(string | RegExp)[] | string[]} */
366
411
  let headerLines;
412
+ /** @type {string[]} */
367
413
  let fixLines = [];
368
414
  // If any of the lines are regular expressions, then we can't
369
415
  // automatically fix them. We set this to true below once we
@@ -374,32 +420,39 @@ module.exports = {
374
420
  headerLines = options[1].map(function(line) {
375
421
  const isRegex = isPattern(line);
376
422
  // Can only fix regex option if a template is also provided
377
- if (isRegex && !line.template) {
378
- canFix = false;
423
+ if (isRegex) {
424
+ if (line.template) {
425
+ fixLines.push(line.template);
426
+ } else {
427
+ canFix = false;
428
+ }
429
+ return new RegExp(line.pattern);
430
+ } else {
431
+ fixLines.push(line);
432
+ return line;
379
433
  }
380
- fixLines.push(line.template || line);
381
- return isRegex ? new RegExp(line.pattern) : line;
382
434
  });
383
- } else if (isPattern(options[1])) {
384
- const line = options[1];
385
- headerLines = [new RegExp(line.pattern)];
386
- fixLines.push(line.template || line);
387
- // Same as above for regex and template
388
- canFix = !!line.template;
389
435
  } else {
390
- canFix = true;
391
- headerLines = options[1].split(/\r?\n/);
392
- fixLines = headerLines;
436
+ const line = /** @type {HeaderLine} */ (options[1]);
437
+ if (isPattern(line)) {
438
+ headerLines = [new RegExp(line.pattern)];
439
+ fixLines.push(line.template || "");
440
+ // Same as above for regex and template
441
+ canFix = !!line.template;
442
+ } else {
443
+ canFix = true;
444
+ headerLines = line.split(/\r?\n/);
445
+ fixLines = /** @type {string[]} */ (headerLines);
446
+ }
393
447
  }
394
448
 
395
449
  return {
396
450
  /**
397
451
  * Hooks into the processing of the overall script node to do the
398
452
  * header validation.
399
- * @param {Program} node the whole script node
400
453
  * @returns {void}
401
454
  */
402
- Program: function(node) {
455
+ Program: function() {
403
456
  const sourceCode = contextSourceCode(context);
404
457
  if (!hasHeader(sourceCode.text)) {
405
458
  const hasShebang = sourceCode.text.startsWith("#!");
@@ -420,13 +473,22 @@ module.exports = {
420
473
  });
421
474
  return;
422
475
  }
423
- const leadingComments = getLeadingComments(context, node);
476
+ const leadingComments = getLeadingComments(context);
477
+ const firstLeadingCommentLoc = leadingComments[0].loc;
478
+ const firstLeadingCommentRange = leadingComments[0].range;
479
+ assertDefined(firstLeadingCommentRange);
480
+
481
+ const lastLeadingCommentLoc = leadingComments[leadingComments.length - 1].loc;
424
482
 
425
483
  if (leadingComments[0].type.toLowerCase() !== commentType) {
484
+ assertDefined(firstLeadingCommentLoc);
485
+ assertNotNull(firstLeadingCommentLoc);
486
+ assertDefined(lastLeadingCommentLoc);
487
+ assertNotNull(lastLeadingCommentLoc);
426
488
  context.report({
427
489
  loc: {
428
- start: leadingComments[0].loc.start,
429
- end: leadingComments[leadingComments.length - 1].loc.end
490
+ start: firstLeadingCommentLoc.start,
491
+ end: lastLeadingCommentLoc.end
430
492
  },
431
493
  messageId: "incorrectCommentType",
432
494
  data: {
@@ -445,10 +507,14 @@ module.exports = {
445
507
  !match(leadingCommentValues.join("\n"), headerLines[0])
446
508
  && !match(leadingCommentValues.join("\r\n"), headerLines[0])
447
509
  ) {
510
+ assertDefined(firstLeadingCommentLoc);
511
+ assertNotNull(firstLeadingCommentLoc);
512
+ assertDefined(lastLeadingCommentLoc);
513
+ assertNotNull(lastLeadingCommentLoc);
448
514
  context.report({
449
515
  loc: {
450
- start: leadingComments[0].loc.start,
451
- end: leadingComments[leadingComments.length - 1].loc.end
516
+ start: firstLeadingCommentLoc.start,
517
+ end: lastLeadingCommentLoc.end
452
518
  },
453
519
  messageId: "incorrectHeader",
454
520
  fix: canFix
@@ -460,9 +526,12 @@ module.exports = {
460
526
  } else {
461
527
  for (let i = 0; i < headerLines.length; i++) {
462
528
  if (leadingComments.length - 1 < i) {
529
+ assertDefined(lastLeadingCommentLoc);
530
+ assertNotNull(lastLeadingCommentLoc);
463
531
  context.report({
464
532
  loc: {
465
- start: leadingComments[leadingComments.length - 1].loc.end
533
+ start: lastLeadingCommentLoc.end,
534
+ end: lastLeadingCommentLoc.end
466
535
  },
467
536
  messageId: "headerTooShort",
468
537
  data: {
@@ -480,22 +549,27 @@ module.exports = {
480
549
  });
481
550
  return;
482
551
  }
483
- if (typeof headerLines[i] === "string") {
484
- const leadingCommentLength = leadingComments[i].value.length;
485
- const headerLineLength = headerLines[i].length;
552
+ const headerLine = headerLines[i];
553
+ const comment = leadingComments[i];
554
+ const commentLoc = comment.loc;
555
+ assertDefined(commentLoc);
556
+ assertNotNull(commentLoc);
557
+ if (typeof headerLine === "string") {
558
+ const leadingCommentLength = comment.value.length;
559
+ const headerLineLength = headerLine.length;
486
560
  for (let j = 0; j < Math.min(leadingCommentLength, headerLineLength); j++) {
487
- if (leadingComments[i].value[j] !== headerLines[i][j]) {
561
+ if (comment.value[j] !== headerLine[j]) {
488
562
  context.report({
489
563
  loc: {
490
564
  start: {
491
565
  column: "//".length + j,
492
- line: leadingComments[i].loc.start.line
566
+ line: commentLoc.start.line
493
567
  },
494
- end: leadingComments[i].loc.end
568
+ end: commentLoc.end
495
569
  },
496
570
  messageId: "headerLineMismatchAtPos",
497
571
  data: {
498
- expected: headerLines[i].substring(j)
572
+ expected: headerLine.substring(j)
499
573
  },
500
574
  fix: genReplaceFixer(
501
575
  commentType,
@@ -511,11 +585,12 @@ module.exports = {
511
585
  if (leadingCommentLength < headerLineLength) {
512
586
  context.report({
513
587
  loc: {
514
- start: leadingComments[i].loc.end,
588
+ start: commentLoc.end,
589
+ end: commentLoc.end,
515
590
  },
516
591
  messageId: "headerLineTooShort",
517
592
  data: {
518
- remainder: headerLines[i].substring(leadingCommentLength)
593
+ remainder: headerLine.substring(leadingCommentLength)
519
594
  },
520
595
  fix: canFix
521
596
  ? genReplaceFixer(
@@ -534,9 +609,9 @@ module.exports = {
534
609
  loc: {
535
610
  start: {
536
611
  column: "//".length + headerLineLength,
537
- line: leadingComments[i].loc.start.line
612
+ line: commentLoc.start.line
538
613
  },
539
- end: leadingComments[i].loc.end,
614
+ end: commentLoc.end,
540
615
  },
541
616
  messageId: "headerLineTooLong",
542
617
  fix: canFix
@@ -552,18 +627,18 @@ module.exports = {
552
627
  return;
553
628
  }
554
629
  } else {
555
- if (!match(leadingComments[i].value, headerLines[i])) {
630
+ if (!match(comment.value, headerLine)) {
556
631
  context.report({
557
632
  loc: {
558
633
  start: {
559
634
  column: "//".length,
560
- line: leadingComments[i].loc.start.line,
635
+ line: commentLoc.start.line,
561
636
  },
562
- end: leadingComments[i].loc.end,
637
+ end: commentLoc.end,
563
638
  },
564
639
  messageId: "incorrectHeaderLine",
565
640
  data: {
566
- pattern: headerLines[i]
641
+ pattern: headerLine
567
642
  },
568
643
  fix: canFix
569
644
  ? genReplaceFixer(
@@ -581,8 +656,9 @@ module.exports = {
581
656
  }
582
657
  }
583
658
 
584
- const actualLeadingEmptyLines =
585
- leadingEmptyLines(sourceCode.text.substring(leadingComments[headerLines.length - 1].range[1]));
659
+ const commentRange = leadingComments[headerLines.length - 1].range;
660
+ assertDefined(commentRange);
661
+ const actualLeadingEmptyLines = leadingEmptyLines(sourceCode.text.substring(commentRange[1]));
586
662
  const missingEmptyLines = numNewlines - actualLeadingEmptyLines;
587
663
  if (missingEmptyLines > 0) {
588
664
  context.report({
@@ -606,8 +682,8 @@ module.exports = {
606
682
 
607
683
  /** @type {null | string} */
608
684
  let errorMessageId = null;
609
- /** @type {null | Record<string, string | RegExp>} */
610
- let errorMessageData = null;
685
+ /** @type {undefined | Record<string, string | RegExp>} */
686
+ let errorMessageData;
611
687
  /** @type {null | SourceLocation} */
612
688
  let errorMessageLoc = null;
613
689
  for (let i = 0; i < headerLines.length; i++) {
@@ -618,7 +694,9 @@ module.exports = {
618
694
  if (leadingLine[j] !== headerLine[j]) {
619
695
  errorMessageId = "headerLineMismatchAtPos";
620
696
  const columnOffset = i === 0 ? "/*".length : 0;
621
- const line = leadingComments[0].loc.start.line + i;
697
+ assertDefined(firstLeadingCommentLoc);
698
+ assertNotNull(firstLeadingCommentLoc);
699
+ const line = firstLeadingCommentLoc.start.line + i;
622
700
  errorMessageLoc = {
623
701
  start: {
624
702
  column: columnOffset + j,
@@ -641,31 +719,35 @@ module.exports = {
641
719
  if (leadingLine.length < headerLine.length) {
642
720
  errorMessageId = "headerLineTooShort";
643
721
  const startColumn = (i === 0 ? "/*".length : 0) + leadingLine.length;
722
+ assertDefined(firstLeadingCommentLoc);
723
+ assertNotNull(firstLeadingCommentLoc);
644
724
  errorMessageLoc = {
645
725
  start: {
646
726
  column: startColumn,
647
- line: leadingComments[0].loc.start.line + i
727
+ line: firstLeadingCommentLoc.start.line + i
648
728
  },
649
729
  end: {
650
730
  column: startColumn + 1,
651
- line: leadingComments[0].loc.start.line + i
731
+ line: firstLeadingCommentLoc.start.line + i
652
732
  }
653
733
  };
654
734
  errorMessageData = {
655
- remainder: headerLines[i].substring(leadingLine.length)
735
+ remainder: headerLine.substring(leadingLine.length)
656
736
  };
657
737
  break;
658
738
  }
659
739
  if (leadingLine.length > headerLine.length) {
740
+ assertDefined(firstLeadingCommentLoc);
741
+ assertNotNull(firstLeadingCommentLoc);
660
742
  errorMessageId = "headerLineTooLong";
661
743
  errorMessageLoc = {
662
744
  start: {
663
745
  column: (i === 0 ? "/*".length : 0) + headerLine.length,
664
- line: leadingComments[0].loc.start.line + i
746
+ line: firstLeadingCommentLoc.start.line + i
665
747
  },
666
748
  end: {
667
749
  column: (i === 0 ? "/*".length : 0) + leadingLine.length,
668
- line: leadingComments[0].loc.start.line + i
750
+ line: firstLeadingCommentLoc.start.line + i
669
751
  }
670
752
  };
671
753
  break;
@@ -677,14 +759,16 @@ module.exports = {
677
759
  pattern: headerLine
678
760
  };
679
761
  const columnOffset = i === 0 ? "/*".length : 0;
762
+ assertDefined(firstLeadingCommentLoc);
763
+ assertNotNull(firstLeadingCommentLoc);
680
764
  errorMessageLoc = {
681
765
  start: {
682
766
  column: columnOffset + 0,
683
- line: leadingComments[0].loc.start.line + i
767
+ line: firstLeadingCommentLoc.start.line + i
684
768
  },
685
769
  end: {
686
770
  column: columnOffset + leadingLine.length,
687
- line: leadingComments[0].loc.start.line + i
771
+ line: firstLeadingCommentLoc.start.line + i
688
772
  }
689
773
  };
690
774
  break;
@@ -694,14 +778,18 @@ module.exports = {
694
778
 
695
779
  if (!errorMessageId && leadingLines.length > headerLines.length) {
696
780
  errorMessageId = "headerTooLong";
781
+ assertDefined(firstLeadingCommentLoc);
782
+ assertNotNull(firstLeadingCommentLoc);
783
+ assertDefined(lastLeadingCommentLoc);
784
+ assertNotNull(lastLeadingCommentLoc);
697
785
  errorMessageLoc = {
698
786
  start: {
699
787
  column: (headerLines.length === 0 ? "/*".length : 0) + 0,
700
- line: leadingComments[0].loc.start.line + headerLines.length
788
+ line: firstLeadingCommentLoc.start.line + headerLines.length
701
789
  },
702
790
  end: {
703
- column: leadingComments[leadingComments.length - 1].loc.end.column - "*/".length,
704
- line: leadingComments[leadingComments.length - 1].loc.end.line
791
+ column: lastLeadingCommentLoc.end.column - "*/".length,
792
+ line: lastLeadingCommentLoc.end.line
705
793
  }
706
794
  };
707
795
  }
@@ -710,6 +798,7 @@ module.exports = {
710
798
  if (canFix && headerLines.length > 1) {
711
799
  fixLines = [fixLines.join(eol)];
712
800
  }
801
+ assertNotNull(errorMessageLoc);
713
802
  context.report({
714
803
  loc: errorMessageLoc,
715
804
  messageId: errorMessageId,
@@ -722,7 +811,7 @@ module.exports = {
722
811
  }
723
812
 
724
813
  const actualLeadingEmptyLines =
725
- leadingEmptyLines(sourceCode.text.substring(leadingComments[0].range[1]));
814
+ leadingEmptyLines(sourceCode.text.substring(firstLeadingCommentRange[1]));
726
815
  const missingEmptyLines = numNewlines - actualLeadingEmptyLines;
727
816
  if (missingEmptyLines > 0) {
728
817
  context.report({
@@ -739,3 +828,5 @@ module.exports = {
739
828
  };
740
829
  }
741
830
  };
831
+
832
+ exports.header = headerRule;
@@ -41,6 +41,7 @@ const commentTypeOptions = Object.freeze({
41
41
  line: "line"
42
42
  });
43
43
 
44
+ /** @type {import('json-schema').JSONSchema4} */
44
45
  const schema = Object.freeze({
45
46
  $ref: "#/definitions/options",
46
47
  definitions: {
package/package.json CHANGED
@@ -1,33 +1,47 @@
1
1
  {
2
2
  "name": "@tony.ganchev/eslint-plugin-header",
3
- "version": "3.1.12",
3
+ "version": "3.1.13",
4
4
  "description": "ESLint plugin to ensure files begin with a given comment, usually a copyright or license notice.",
5
5
  "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "import": "./index.js",
11
+ "require": "./index.js"
12
+ }
13
+ },
6
14
  "files": [
7
- "/lib/**/*.js",
8
- "!/lib/rules/test-utils.js"
15
+ "lib/**/*.js",
16
+ "index.d.ts*",
17
+ "types",
18
+ "!lib/rules/test-utils.js"
9
19
  ],
10
20
  "scripts": {
21
+ "build": "npx tsc",
22
+ "e2e": "npm run build && npx mocha --timeout 60000 tests/e2e/*.js",
11
23
  "eslint": "npx eslint .",
12
24
  "lint": "npm run eslint && npm run markdownlint",
13
25
  "markdownlint": "npx markdownlint-cli *.md",
14
26
  "test": "npm run lint && npm run unit && npm run e2e",
15
- "unit": "npx nyc --reporter=html --reporter=text --reporter=text-summary --reporter=lcov --check-coverage=true --statements=100 --branches=100 --lines=100 --functions=100 mocha tests/lib/*.js tests/lib/**/*.js",
16
- "e2e": "npx mocha --timeout 60000 tests/e2e/*.js"
27
+ "unit": "npx nyc --reporter=html --reporter=text --reporter=text-summary --reporter=lcov --check-coverage=true --statements=100 --branches=100 --lines=100 --functions=100 mocha tests/lib/*.js tests/lib/**/*.js"
17
28
  },
18
29
  "devDependencies": {
19
30
  "@eslint/eslintrc": "^3.3.3",
20
31
  "@eslint/js": "^9.39.2",
21
32
  "@eslint/markdown": "^7.5.1",
22
- "@stylistic/eslint-plugin": "^5.7.0",
33
+ "@stylistic/eslint-plugin": "^5.7.1",
34
+ "@types/node": "^25.2.1",
23
35
  "eslint": "^9.39.2",
24
36
  "eslint-plugin-eslint-plugin": "^7.3.0",
25
- "eslint-plugin-jsdoc": "^62.1.0",
37
+ "eslint-plugin-jsdoc": "^62.5.2",
26
38
  "eslint-plugin-n": "^17.23.2",
27
39
  "markdownlint-cli": "^0.47.0",
28
- "mocha": "^12.0.0-beta-5",
40
+ "mocha": "^12.0.0-beta-6",
29
41
  "nyc": "^17.1.0",
30
- "testdouble": "^3.20.2"
42
+ "testdouble": "^3.20.2",
43
+ "typescript": "^5.9.3",
44
+ "typescript-eslint": "^8.54.0"
31
45
  },
32
46
  "peerDependencies": {
33
47
  "eslint": ">=7.7.0"
@@ -0,0 +1,4 @@
1
+ export = pluginDefinition;
2
+ /** @type {import('eslint').ESLint.Plugin} */
3
+ declare const pluginDefinition: import("eslint").ESLint.Plugin;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":";AA4BA,6CAA6C;AAC7C,gCADW,OAAO,QAAQ,EAAE,MAAM,CAAC,MAAM,CAKvC"}
@@ -0,0 +1,3 @@
1
+ declare function _exports(commentText: string): ["block" | "line", string | string[]];
2
+ export = _exports;
3
+ //# sourceMappingURL=comment-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../lib/comment-parser.js"],"names":[],"mappings":"AAsCiB,uCAJN,MAAM,GACJ,CAAC,OAAO,GAAG,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAmBjD"}
@@ -0,0 +1,10 @@
1
+ declare namespace _exports {
2
+ export { RuleContext, SourceCode };
3
+ }
4
+ declare namespace _exports {
5
+ function contextSourceCode(context: RuleContext): SourceCode;
6
+ }
7
+ export = _exports;
8
+ type RuleContext = import("eslint").Rule.RuleContext;
9
+ type SourceCode = import("eslint").SourceCode;
10
+ //# sourceMappingURL=eslint-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint-utils.d.ts","sourceRoot":"","sources":["../../../lib/rules/eslint-utils.js"],"names":[],"mappings":";;;;IAsCuB,oCAHR,WAAW,GACT,UAAU,CAItB;;;mBAbQ,OAAO,QAAQ,EAAE,IAAI,CAAC,WAAW;kBACjC,OAAO,QAAQ,EAAE,UAAU"}
@@ -0,0 +1,73 @@
1
+ export { headerRule as header };
2
+ /**
3
+ * Import type definitions.
4
+ */
5
+ export type Fix = import("eslint").Rule.Fix;
6
+ /**
7
+ * Import type definitions.
8
+ */
9
+ export type NodeListener = import("eslint").Rule.NodeListener;
10
+ /**
11
+ * Import type definitions.
12
+ */
13
+ export type ReportFixer = import("eslint").Rule.ReportFixer;
14
+ /**
15
+ * Import type definitions.
16
+ */
17
+ export type RuleFixer = import("eslint").Rule.RuleFixer;
18
+ /**
19
+ * Import type definitions.
20
+ */
21
+ export type RuleContext = import("eslint").Rule.RuleContext;
22
+ /**
23
+ * Import type definitions.
24
+ */
25
+ export type Comment = import("estree").Comment;
26
+ /**
27
+ * Import type definitions.
28
+ */
29
+ export type Program = import("estree").Program;
30
+ /**
31
+ * Import type definitions.
32
+ */
33
+ export type SourceLocation = import("estree").SourceLocation;
34
+ /**
35
+ * Local type definitions.
36
+ */
37
+ export type HeaderLinePattern = {
38
+ pattern: string;
39
+ template?: string;
40
+ };
41
+ /**
42
+ * Local type definitions.
43
+ */
44
+ export type HeaderLine = string | HeaderLinePattern;
45
+ /**
46
+ * Local type definitions.
47
+ */
48
+ export type HeaderLines = (HeaderLine | HeaderLine[]);
49
+ /**
50
+ * Local type definitions.
51
+ */
52
+ export type LineEndingOption = "unix" | "windows";
53
+ /**
54
+ * Local type definitions.
55
+ */
56
+ export type HeaderSettings = {
57
+ lineEndings?: LineEndingOption;
58
+ };
59
+ /**
60
+ * Local type definitions.
61
+ */
62
+ export type CommentType = "block" | "line";
63
+ /**
64
+ * Local type definitions.
65
+ */
66
+ export type AllHeaderOptions = [template: string] | [template: string, settings: HeaderSettings] | [type: CommentType, lines: HeaderLines] | [type: CommentType, lines: HeaderLines, settings: HeaderSettings] | [type: CommentType, lines: HeaderLines, minLines: number] | [type: CommentType, lines: HeaderLines, minLines: number, settings: HeaderSettings];
67
+ /**
68
+ * Local type definitions.
69
+ */
70
+ export type HeaderRuleConfig = import("eslint").Linter.RuleEntry<AllHeaderOptions>;
71
+ /** @type {import('eslint').Rule.RuleModule} */
72
+ declare const headerRule: import("eslint").Rule.RuleModule;
73
+ //# sourceMappingURL=header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../../lib/rules/header.js"],"names":[],"mappings":";;;;kBAoCa,OAAO,QAAQ,EAAE,IAAI,CAAC,GAAG;;;;2BACzB,OAAO,QAAQ,EAAE,IAAI,CAAC,YAAY;;;;0BAClC,OAAO,QAAQ,EAAE,IAAI,CAAC,WAAW;;;;wBACjC,OAAO,QAAQ,EAAE,IAAI,CAAC,SAAS;;;;0BAC/B,OAAO,QAAQ,EAAE,IAAI,CAAC,WAAW;;;;sBACjC,OAAO,QAAQ,EAAE,OAAO;;;;sBACxB,OAAO,QAAQ,EAAE,OAAO;;;;6BACxB,OAAO,QAAQ,EAAE,cAAc;;;;gCAK/B;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;;;;yBACtC,MAAM,GAAG,iBAAiB;;;;0BAC1B,CAAC,UAAU,GAAG,UAAU,EAAE,CAAC;;;;+BAC3B,MAAM,GAAG,SAAS;;;;6BAClB;IAAE,WAAW,CAAC,EAAE,gBAAgB,CAAA;CAAE;;;;0BAClC,OAAO,GAAG,MAAM;;;;+BAChB,CAAC,QAAQ,EAAE,MAAM,CAAC,GAC9B,CAAI,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,CAAC,GAC/C,CAAI,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,GAC1C,CAAI,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,CAAC,GACpE,CAAI,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAC,GAC5D,CACM,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,CAC1B;;;;+BAEQ,OAAO,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC;AA6ShE,+CAA+C;AAC/C,0BADW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU,CA8czC"}
@@ -0,0 +1,4 @@
1
+ export const description: "";
2
+ export const recommended: true;
3
+ export const url: string;
4
+ //# sourceMappingURL=header.docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.docs.d.ts","sourceRoot":"","sources":["../../../lib/rules/header.docs.js"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ export type lineEndingOptions = string;
2
+ /**
3
+ * @enum {string}
4
+ */
5
+ export const lineEndingOptions: Readonly<{
6
+ os: "os";
7
+ unix: "unix";
8
+ windows: "windows";
9
+ }>;
10
+ export type commentTypeOptions = string;
11
+ /**
12
+ * @enum {string}
13
+ */
14
+ export const commentTypeOptions: Readonly<{
15
+ block: "block";
16
+ line: "line";
17
+ }>;
18
+ /** @type {import('json-schema').JSONSchema4} */
19
+ export const schema: import("json-schema").JSONSchema4;
20
+ //# sourceMappingURL=header.schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.schema.d.ts","sourceRoot":"","sources":["../../../lib/rules/header.schema.js"],"names":[],"mappings":"gCA2BU,MAAM;AADhB;;GAEG;AACH;;;;GAIG;iCAGO,MAAM;AADhB;;GAEG;AACH;;;GAGG;AAEH,gDAAgD;AAChD,qBADW,OAAO,aAAa,EAAE,WAAW,CA0FzC"}
@@ -0,0 +1,10 @@
1
+ declare namespace _exports {
2
+ export { InvalidTestCase, TestCaseError };
3
+ }
4
+ declare namespace _exports {
5
+ function generateInvalidTestCaseNames(invalidTests: InvalidTestCase[]): InvalidTestCase[];
6
+ }
7
+ export = _exports;
8
+ type InvalidTestCase = import("eslint").RuleTester.InvalidTestCase;
9
+ type TestCaseError = import("eslint").RuleTester.TestCaseError;
10
+ //# sourceMappingURL=test-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../lib/rules/test-utils.js"],"names":[],"mappings":";;;;IA4EkC,oDAJnB,eAAe,EAAE,GACf,eAAe,EAAE,CA4B7B;;;uBAxEQ,OAAO,QAAQ,EAAE,UAAU,CAAC,eAAe;qBAC3C,OAAO,QAAQ,EAAE,UAAU,CAAC,aAAa"}