@tony.ganchev/eslint-plugin-header 3.1.13 → 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.
- package/LICENSE.md +1 -1
- package/README.md +486 -183
- package/lib/comment-parser.js +3 -3
- package/lib/rules/header.js +256 -85
- package/lib/rules/header.schema.js +91 -13
- package/package.json +2 -2
- package/types/lib/comment-parser.d.ts +1 -1
- package/types/lib/comment-parser.d.ts.map +1 -1
- package/types/lib/rules/header.d.ts +32 -5
- package/types/lib/rules/header.d.ts.map +1 -1
- package/types/lib/rules/header.schema.d.ts.map +1 -1
- package/types/lib/rules/test-utils.d.ts.map +1 -1
package/lib/comment-parser.js
CHANGED
|
@@ -33,8 +33,8 @@ const assert = require("node:assert");
|
|
|
33
33
|
* This is a really simple and dumb parser, that looks just for a
|
|
34
34
|
* single kind of comment. It won't detect multiple block comments.
|
|
35
35
|
* @param {string} commentText comment text.
|
|
36
|
-
* @returns {['block' | 'line', string
|
|
37
|
-
*
|
|
36
|
+
* @returns {['block' | 'line', string[]]} comment type and comment content
|
|
37
|
+
* broken into lines.
|
|
38
38
|
*/
|
|
39
39
|
module.exports = function commentParser(commentText) {
|
|
40
40
|
assert.strictEqual(typeof commentText, "string");
|
|
@@ -46,7 +46,7 @@ module.exports = function commentParser(commentText) {
|
|
|
46
46
|
text.split(/\r?\n/).map((line) => line.substring(2))
|
|
47
47
|
];
|
|
48
48
|
} else if (text.startsWith("/*") && text.endsWith("*/")) {
|
|
49
|
-
return ["block", text.substring(2, text.length - 2)];
|
|
49
|
+
return ["block", text.substring(2, text.length - 2).split(/\r?\n/)];
|
|
50
50
|
} else {
|
|
51
51
|
throw new Error(
|
|
52
52
|
"Could not parse comment file: the file must contain either just line comments (//) or a single block " +
|
package/lib/rules/header.js
CHANGED
|
@@ -30,7 +30,7 @@ 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");
|
|
33
|
-
const {
|
|
33
|
+
const { lineEndingOptions, commentTypeOptions, schema } = require("./header.schema");
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Import type definitions.
|
|
@@ -46,13 +46,31 @@ const { commentTypeOptions, lineEndingOptions, schema } = require("./header.sche
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Local type definitions.
|
|
49
|
-
* @typedef {{ pattern: string, template?: string }} HeaderLinePattern
|
|
50
|
-
* @typedef {string | HeaderLinePattern} HeaderLine
|
|
51
|
-
* @typedef {
|
|
52
|
-
* @typedef {'unix' | 'windows'} LineEndingOption
|
|
49
|
+
* @typedef {{ pattern: string | RegExp, template?: string }} HeaderLinePattern
|
|
50
|
+
* @typedef {string | RegExp | HeaderLinePattern} HeaderLine
|
|
51
|
+
* @typedef {HeaderLine | HeaderLine[]} HeaderLines
|
|
52
|
+
* @typedef {'os' | 'unix' | 'windows'} LineEndingOption
|
|
53
53
|
* @typedef {{ lineEndings?: LineEndingOption }} HeaderSettings
|
|
54
54
|
* @typedef {'block' | 'line'} CommentType
|
|
55
|
-
* @typedef {
|
|
55
|
+
* @typedef {{
|
|
56
|
+
* file: string,
|
|
57
|
+
* encoding?: BufferEncoding
|
|
58
|
+
* }
|
|
59
|
+
* } FileBasedConfig
|
|
60
|
+
* @typedef {{
|
|
61
|
+
* commentType: CommentType,
|
|
62
|
+
* lines: HeaderLine[]
|
|
63
|
+
* }
|
|
64
|
+
* } InlineConfig
|
|
65
|
+
* @typedef {{ minimum?: number }} TrailingEmptyLines
|
|
66
|
+
* @typedef {{
|
|
67
|
+
* header: FileBasedConfig | InlineConfig,
|
|
68
|
+
* trailingEmptyLines?: TrailingEmptyLines
|
|
69
|
+
* }
|
|
70
|
+
* & HeaderSettings
|
|
71
|
+
* } HeaderOptions
|
|
72
|
+
* @typedef {[HeaderOptions] |
|
|
73
|
+
* [template: string] |
|
|
56
74
|
* [template: string, settings: HeaderSettings] |
|
|
57
75
|
* [type: CommentType, lines: HeaderLines] |
|
|
58
76
|
* [type: CommentType, lines: HeaderLines, settings: HeaderSettings] |
|
|
@@ -77,7 +95,8 @@ const { commentTypeOptions, lineEndingOptions, schema } = require("./header.sche
|
|
|
77
95
|
* otherwise.
|
|
78
96
|
*/
|
|
79
97
|
function isPattern(object) {
|
|
80
|
-
return typeof object === "object"
|
|
98
|
+
return typeof object === "object"
|
|
99
|
+
&& (Object.prototype.hasOwnProperty.call(object, "pattern"));
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
/**
|
|
@@ -291,39 +310,25 @@ function genEmptyLinesFixer(leadingComments, eol, missingEmptyLinesCount) {
|
|
|
291
310
|
};
|
|
292
311
|
}
|
|
293
312
|
|
|
294
|
-
/**
|
|
295
|
-
* Finds the option parameter within the list of rule config options.
|
|
296
|
-
* @param {AllHeaderOptions} options the config options passed to the rule.
|
|
297
|
-
* @returns {HeaderSettings | null} the settings parameter or `null` if no such
|
|
298
|
-
* is defined.
|
|
299
|
-
*/
|
|
300
|
-
function findSettings(options) {
|
|
301
|
-
const lastOption = options[options.length - 1];
|
|
302
|
-
if (typeof lastOption === "object" && !Array.isArray(lastOption) && lastOption !== null
|
|
303
|
-
&& !Object.prototype.hasOwnProperty.call(lastOption, "pattern")) {
|
|
304
|
-
return /** @type {HeaderSettings} */ (lastOption);
|
|
305
|
-
}
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
313
|
/**
|
|
310
314
|
* Returns the used line-termination characters per the rule's config if any or
|
|
311
315
|
* else based on the runtime environments.
|
|
312
|
-
* @param {
|
|
316
|
+
* @param {LineEndingOption} style line-ending styles.
|
|
313
317
|
* @returns {'\n' | '\r\n'} the correct line ending characters for the
|
|
314
|
-
*
|
|
318
|
+
* environment.
|
|
315
319
|
*/
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
+
function getEol(style) {
|
|
321
|
+
assert.strictEqual(Object.prototype.hasOwnProperty.call(lineEndingOptions, style), true,
|
|
322
|
+
"lineEnding style should have been populated in normalizeOptions().");
|
|
323
|
+
switch (style) {
|
|
324
|
+
case lineEndingOptions.unix:
|
|
320
325
|
return "\n";
|
|
321
|
-
|
|
322
|
-
if (settings.lineEndings === lineEndingOptions.windows) {
|
|
326
|
+
case lineEndingOptions.windows:
|
|
323
327
|
return "\r\n";
|
|
324
|
-
|
|
328
|
+
case lineEndingOptions.os:
|
|
329
|
+
default:
|
|
330
|
+
return /** @type {'\n' | '\r\n'} */ (os.EOL);
|
|
325
331
|
}
|
|
326
|
-
return /** @type {'\n' | '\r\n'} */ (os.EOL);
|
|
327
332
|
}
|
|
328
333
|
|
|
329
334
|
/**
|
|
@@ -337,6 +342,155 @@ function hasHeader(src) {
|
|
|
337
342
|
return srcWithoutShebang.startsWith("/*") || srcWithoutShebang.startsWith("//");
|
|
338
343
|
}
|
|
339
344
|
|
|
345
|
+
/**
|
|
346
|
+
* asserts on an expression and adds template texts to the failure message.
|
|
347
|
+
* Helper to write cleaner code.
|
|
348
|
+
* @param {boolean} condition assert condition.
|
|
349
|
+
* @param {string} message assert message on violation.
|
|
350
|
+
*/
|
|
351
|
+
function schemaAssert(condition, message) {
|
|
352
|
+
assert.strictEqual(condition, true, message + " - should have been handled by eslint schema validation.");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Ensures that if legacy options as defined in the original
|
|
357
|
+
* `eslint-plugin-header` are used, they'd be converted to the new object-based
|
|
358
|
+
* configuration. This is not a normalized internal representation of the
|
|
359
|
+
* options in that some settings are still union types and unspecified
|
|
360
|
+
* properties are not replaced by defaults. If the options follow the new
|
|
361
|
+
* format, a simple seep copy would be returned.
|
|
362
|
+
* @param {AllHeaderOptions} originalOptions the options as configured by the
|
|
363
|
+
* user.
|
|
364
|
+
* @returns {HeaderOptions} the transformed new-style options with no
|
|
365
|
+
* normalization.
|
|
366
|
+
*/
|
|
367
|
+
function transformLegacyOptions(originalOptions) {
|
|
368
|
+
schemaAssert(originalOptions?.length > 0,
|
|
369
|
+
"header options are required (at least one in addition to the severity)");
|
|
370
|
+
schemaAssert(originalOptions.length <= 4,
|
|
371
|
+
"header options should not be more than four (five including issue severity)");
|
|
372
|
+
if (originalOptions.length === 1 && typeof originalOptions[0] === "object") {
|
|
373
|
+
// The user chose to use the new-style config. We pass a copy of the
|
|
374
|
+
// incoming sole option to not mess with ESLint not relying on the old
|
|
375
|
+
// values of the option.
|
|
376
|
+
return structuredClone(originalOptions[0]);
|
|
377
|
+
}
|
|
378
|
+
// The user chose the legacy-style config. Transform them to new-style
|
|
379
|
+
// config.
|
|
380
|
+
schemaAssert(typeof originalOptions[0] === "string",
|
|
381
|
+
"first header option after severity should be either a filename or 'block' | 'line'");
|
|
382
|
+
/** @type {HeaderOptions} */
|
|
383
|
+
const transformedOptions = {};
|
|
384
|
+
// populate header
|
|
385
|
+
if (
|
|
386
|
+
originalOptions.length === 1
|
|
387
|
+
|| (
|
|
388
|
+
originalOptions.length === 2
|
|
389
|
+
&& typeof originalOptions[1] === "object"
|
|
390
|
+
&& !Array.isArray(originalOptions[1])
|
|
391
|
+
&& !isPattern(/** @type {HeaderLine} */(originalOptions[1]))
|
|
392
|
+
)) {
|
|
393
|
+
transformedOptions.header = { file: originalOptions[0], encoding: "utf8" };
|
|
394
|
+
} else {
|
|
395
|
+
schemaAssert(Object.prototype.hasOwnProperty.call(commentTypeOptions, originalOptions[0]),
|
|
396
|
+
"Only 'block' or 'line' is accepted as comment type");
|
|
397
|
+
schemaAssert(
|
|
398
|
+
typeof originalOptions[1] === "string"
|
|
399
|
+
|| Array.isArray(originalOptions[1])
|
|
400
|
+
|| isPattern(/** @type {HeaderLine} */(originalOptions[1])),
|
|
401
|
+
"second header option after severity should be a string, a pattern, or an array of the previous two");
|
|
402
|
+
transformedOptions.header = {
|
|
403
|
+
commentType: /** @type {CommentType} */ (originalOptions[0]),
|
|
404
|
+
lines: /** @type {HeaderLine[]} */ (
|
|
405
|
+
Array.isArray(originalOptions[1])
|
|
406
|
+
? originalOptions[1]
|
|
407
|
+
: [originalOptions[1]])
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
// configure required line settings
|
|
411
|
+
if (originalOptions.length >= 3) {
|
|
412
|
+
if (typeof originalOptions[2] === "number") {
|
|
413
|
+
transformedOptions.trailingEmptyLines = { minimum: originalOptions[2] };
|
|
414
|
+
if (originalOptions.length === 4) {
|
|
415
|
+
schemaAssert(typeof originalOptions[3] === "object",
|
|
416
|
+
"Fourth header option after severity should be either number of required trailing empty lines or " +
|
|
417
|
+
"a settings object");
|
|
418
|
+
Object.assign(transformedOptions, originalOptions[3]);
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
schemaAssert(typeof originalOptions[2] === "object",
|
|
422
|
+
"Third header option after severity should be either number of required trailing empty lines or a " +
|
|
423
|
+
"settings object");
|
|
424
|
+
Object.assign(transformedOptions, originalOptions[2]);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return transformedOptions;
|
|
428
|
+
}
|
|
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
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Transforms a set of new-style options adding defaults and standardizing on
|
|
449
|
+
* one of multiple config styles.
|
|
450
|
+
* @param {HeaderOptions} originalOptions new-style options to normalize.
|
|
451
|
+
* @returns {HeaderOptions} normalized options.
|
|
452
|
+
*/
|
|
453
|
+
function normalizeOptions(originalOptions) {
|
|
454
|
+
const options = structuredClone(originalOptions);
|
|
455
|
+
|
|
456
|
+
if (isFileBasedHeaderConfig(originalOptions.header)) {
|
|
457
|
+
const text = fs.readFileSync(originalOptions.header.file, originalOptions.header.encoding || "utf8");
|
|
458
|
+
const [commentType, lines] = commentParser(text);
|
|
459
|
+
options.header = { commentType, lines };
|
|
460
|
+
}
|
|
461
|
+
assertLineBasedHeaderConfig(options.header);
|
|
462
|
+
options.header.lines = options.header.lines.flatMap(
|
|
463
|
+
(line) => {
|
|
464
|
+
if (typeof line === "string") {
|
|
465
|
+
return /** @type {HeaderLine[]} */(line.split(/\r?\n/));
|
|
466
|
+
}
|
|
467
|
+
if (line instanceof RegExp) {
|
|
468
|
+
return [{ pattern: line }];
|
|
469
|
+
}
|
|
470
|
+
assert.ok(Object.prototype.hasOwnProperty.call(line, "pattern"));
|
|
471
|
+
const pattern = line.pattern instanceof RegExp ? line.pattern : new RegExp(line.pattern);
|
|
472
|
+
if (Object.prototype.hasOwnProperty.call(line, "template")) {
|
|
473
|
+
return [{
|
|
474
|
+
pattern,
|
|
475
|
+
template: line.template
|
|
476
|
+
}];
|
|
477
|
+
}
|
|
478
|
+
return [{ pattern }];
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
if (!options.lineEndings) {
|
|
482
|
+
options.lineEndings = "os";
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (!options.trailingEmptyLines) {
|
|
486
|
+
options.trailingEmptyLines = {};
|
|
487
|
+
}
|
|
488
|
+
if (typeof options.trailingEmptyLines.minimum !== "number") {
|
|
489
|
+
options.trailingEmptyLines.minimum = 1;
|
|
490
|
+
}
|
|
491
|
+
return options;
|
|
492
|
+
}
|
|
493
|
+
|
|
340
494
|
/**
|
|
341
495
|
* Calculates the source location of the violation that not enough empty lines
|
|
342
496
|
* follow the header.
|
|
@@ -376,7 +530,15 @@ const headerRule = {
|
|
|
376
530
|
},
|
|
377
531
|
fixable: "whitespace",
|
|
378
532
|
schema,
|
|
379
|
-
defaultOptions: [
|
|
533
|
+
defaultOptions: [
|
|
534
|
+
/** @type {AllHeaderOptions} */
|
|
535
|
+
{
|
|
536
|
+
lineEndings: lineEndingOptions.os,
|
|
537
|
+
trailingEmptyLines: {
|
|
538
|
+
minimum: 1
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
],
|
|
380
542
|
messages: {
|
|
381
543
|
headerLineMismatchAtPos: "header line does not match expected after this position; expected: {{expected}}",
|
|
382
544
|
headerLineTooLong: "header line longer than expected",
|
|
@@ -396,55 +558,41 @@ const headerRule = {
|
|
|
396
558
|
* @returns {NodeListener} the rule definition.
|
|
397
559
|
*/
|
|
398
560
|
create: function(context) {
|
|
399
|
-
let options = /** @type {AllHeaderOptions} */ (context.options);
|
|
400
|
-
const numNewlines = options.length > 2 && typeof options[2] === "number" ? options[2] : 1;
|
|
401
|
-
const eol = getEOL(options);
|
|
402
|
-
|
|
403
|
-
// If just one option then read comment from file
|
|
404
|
-
if (options.length === 1 || (options.length === 2 && findSettings(options))) {
|
|
405
|
-
const text = fs.readFileSync(context.options[0], "utf8");
|
|
406
|
-
options = commentParser(text);
|
|
407
|
-
}
|
|
408
561
|
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
562
|
+
const newStyleOptions = transformLegacyOptions(/** @type {AllHeaderOptions} */ (context.options));
|
|
563
|
+
const options = normalizeOptions(newStyleOptions);
|
|
564
|
+
|
|
565
|
+
assertLineBasedHeaderConfig(options.header);
|
|
566
|
+
const commentType = /** @type {CommentType} */ (options.header.commentType);
|
|
567
|
+
|
|
568
|
+
const eol = getEol(
|
|
569
|
+
/** @type {LineEndingOption} */ (options.lineEndings)
|
|
570
|
+
);
|
|
571
|
+
|
|
412
572
|
/** @type {string[]} */
|
|
413
573
|
let fixLines = [];
|
|
414
574
|
// If any of the lines are regular expressions, then we can't
|
|
415
575
|
// automatically fix them. We set this to true below once we
|
|
416
576
|
// ensure none of the lines are of type RegExp
|
|
417
|
-
let canFix =
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (isRegex) {
|
|
424
|
-
if (line.template) {
|
|
425
|
-
fixLines.push(line.template);
|
|
426
|
-
} else {
|
|
427
|
-
canFix = false;
|
|
428
|
-
}
|
|
429
|
-
return new RegExp(line.pattern);
|
|
577
|
+
let canFix = true;
|
|
578
|
+
const headerLines = options.header.lines.map(function(line) {
|
|
579
|
+
// Can only fix regex option if a template is also provided
|
|
580
|
+
if (isPattern(line)) {
|
|
581
|
+
if (Object.prototype.hasOwnProperty.call(line, "template")) {
|
|
582
|
+
fixLines.push(/** @type {string} */ (line.template));
|
|
430
583
|
} else {
|
|
431
|
-
|
|
432
|
-
|
|
584
|
+
canFix = false;
|
|
585
|
+
fixLines.push("");
|
|
433
586
|
}
|
|
434
|
-
|
|
435
|
-
} else {
|
|
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;
|
|
587
|
+
return line.pattern;
|
|
442
588
|
} else {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
fixLines = /** @type {string[]} */ (headerLines);
|
|
589
|
+
fixLines.push(/** @type {string} */ (line));
|
|
590
|
+
return line;
|
|
446
591
|
}
|
|
447
|
-
}
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
const numLines = /** @type {number} */ (options.trailingEmptyLines?.minimum);
|
|
448
596
|
|
|
449
597
|
return {
|
|
450
598
|
/**
|
|
@@ -469,7 +617,12 @@ const headerRule = {
|
|
|
469
617
|
}
|
|
470
618
|
},
|
|
471
619
|
messageId: "missingHeader",
|
|
472
|
-
fix: genPrependFixer(
|
|
620
|
+
fix: genPrependFixer(
|
|
621
|
+
commentType,
|
|
622
|
+
context,
|
|
623
|
+
fixLines,
|
|
624
|
+
eol,
|
|
625
|
+
numLines)
|
|
473
626
|
});
|
|
474
627
|
return;
|
|
475
628
|
}
|
|
@@ -495,7 +648,13 @@ const headerRule = {
|
|
|
495
648
|
commentType: commentType
|
|
496
649
|
},
|
|
497
650
|
fix: canFix
|
|
498
|
-
? genReplaceFixer(
|
|
651
|
+
? genReplaceFixer(
|
|
652
|
+
commentType,
|
|
653
|
+
context,
|
|
654
|
+
leadingComments,
|
|
655
|
+
fixLines,
|
|
656
|
+
eol,
|
|
657
|
+
numLines)
|
|
499
658
|
: null
|
|
500
659
|
});
|
|
501
660
|
return;
|
|
@@ -518,7 +677,13 @@ const headerRule = {
|
|
|
518
677
|
},
|
|
519
678
|
messageId: "incorrectHeader",
|
|
520
679
|
fix: canFix
|
|
521
|
-
? genReplaceFixer(
|
|
680
|
+
? genReplaceFixer(
|
|
681
|
+
commentType,
|
|
682
|
+
context,
|
|
683
|
+
leadingComments,
|
|
684
|
+
fixLines,
|
|
685
|
+
eol,
|
|
686
|
+
numLines)
|
|
522
687
|
: null
|
|
523
688
|
});
|
|
524
689
|
return;
|
|
@@ -544,7 +709,7 @@ const headerRule = {
|
|
|
544
709
|
leadingComments,
|
|
545
710
|
fixLines,
|
|
546
711
|
eol,
|
|
547
|
-
|
|
712
|
+
numLines)
|
|
548
713
|
: null
|
|
549
714
|
});
|
|
550
715
|
return;
|
|
@@ -577,7 +742,7 @@ const headerRule = {
|
|
|
577
742
|
leadingComments,
|
|
578
743
|
fixLines,
|
|
579
744
|
eol,
|
|
580
|
-
|
|
745
|
+
numLines)
|
|
581
746
|
});
|
|
582
747
|
return;
|
|
583
748
|
}
|
|
@@ -599,7 +764,7 @@ const headerRule = {
|
|
|
599
764
|
leadingComments,
|
|
600
765
|
fixLines,
|
|
601
766
|
eol,
|
|
602
|
-
|
|
767
|
+
numLines)
|
|
603
768
|
: null
|
|
604
769
|
});
|
|
605
770
|
return;
|
|
@@ -621,7 +786,7 @@ const headerRule = {
|
|
|
621
786
|
leadingComments,
|
|
622
787
|
fixLines,
|
|
623
788
|
eol,
|
|
624
|
-
|
|
789
|
+
numLines)
|
|
625
790
|
: null
|
|
626
791
|
});
|
|
627
792
|
return;
|
|
@@ -647,7 +812,7 @@ const headerRule = {
|
|
|
647
812
|
leadingComments,
|
|
648
813
|
fixLines,
|
|
649
814
|
eol,
|
|
650
|
-
|
|
815
|
+
numLines)
|
|
651
816
|
: null
|
|
652
817
|
});
|
|
653
818
|
return;
|
|
@@ -659,13 +824,13 @@ const headerRule = {
|
|
|
659
824
|
const commentRange = leadingComments[headerLines.length - 1].range;
|
|
660
825
|
assertDefined(commentRange);
|
|
661
826
|
const actualLeadingEmptyLines = leadingEmptyLines(sourceCode.text.substring(commentRange[1]));
|
|
662
|
-
const missingEmptyLines =
|
|
827
|
+
const missingEmptyLines = numLines - actualLeadingEmptyLines;
|
|
663
828
|
if (missingEmptyLines > 0) {
|
|
664
829
|
context.report({
|
|
665
830
|
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
666
831
|
messageId: "noNewlineAfterHeader",
|
|
667
832
|
data: {
|
|
668
|
-
expected:
|
|
833
|
+
expected: numLines,
|
|
669
834
|
actual: actualLeadingEmptyLines
|
|
670
835
|
},
|
|
671
836
|
fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
|
|
@@ -804,7 +969,13 @@ const headerRule = {
|
|
|
804
969
|
messageId: errorMessageId,
|
|
805
970
|
data: errorMessageData,
|
|
806
971
|
fix: canFix
|
|
807
|
-
? genReplaceFixer(
|
|
972
|
+
? genReplaceFixer(
|
|
973
|
+
commentType,
|
|
974
|
+
context,
|
|
975
|
+
leadingComments,
|
|
976
|
+
fixLines,
|
|
977
|
+
eol,
|
|
978
|
+
numLines)
|
|
808
979
|
: null
|
|
809
980
|
});
|
|
810
981
|
return;
|
|
@@ -812,13 +983,13 @@ const headerRule = {
|
|
|
812
983
|
|
|
813
984
|
const actualLeadingEmptyLines =
|
|
814
985
|
leadingEmptyLines(sourceCode.text.substring(firstLeadingCommentRange[1]));
|
|
815
|
-
const missingEmptyLines =
|
|
986
|
+
const missingEmptyLines = numLines - actualLeadingEmptyLines;
|
|
816
987
|
if (missingEmptyLines > 0) {
|
|
817
988
|
context.report({
|
|
818
989
|
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
819
990
|
messageId: "noNewlineAfterHeader",
|
|
820
991
|
data: {
|
|
821
|
-
expected:
|
|
992
|
+
expected: numLines,
|
|
822
993
|
actual: actualLeadingEmptyLines
|
|
823
994
|
},
|
|
824
995
|
fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
|
|
@@ -47,22 +47,31 @@ const schema = Object.freeze({
|
|
|
47
47
|
definitions: {
|
|
48
48
|
commentType: {
|
|
49
49
|
type: "string",
|
|
50
|
-
enum: [commentTypeOptions.block, commentTypeOptions.line]
|
|
50
|
+
enum: [commentTypeOptions.block, commentTypeOptions.line],
|
|
51
|
+
description: "Type of comment to expect as the header."
|
|
52
|
+
},
|
|
53
|
+
regExp: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
source: { type: "string" }
|
|
57
|
+
},
|
|
58
|
+
required: ["source"],
|
|
59
|
+
additionalProperties: true,
|
|
51
60
|
},
|
|
52
61
|
line: {
|
|
53
62
|
anyOf: [
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
},
|
|
63
|
+
{ type: "string" },
|
|
64
|
+
{ $ref: "#/definitions/regExp" },
|
|
57
65
|
{
|
|
58
66
|
type: "object",
|
|
59
67
|
properties: {
|
|
60
68
|
pattern: {
|
|
61
|
-
|
|
69
|
+
anyOf: [
|
|
70
|
+
{ type: "string" },
|
|
71
|
+
{ $ref: "#/definitions/regExp" },
|
|
72
|
+
]
|
|
62
73
|
},
|
|
63
|
-
template: {
|
|
64
|
-
type: "string"
|
|
65
|
-
}
|
|
74
|
+
template: { type: "string" }
|
|
66
75
|
},
|
|
67
76
|
required: ["pattern"],
|
|
68
77
|
additionalProperties: false
|
|
@@ -71,9 +80,7 @@ const schema = Object.freeze({
|
|
|
71
80
|
},
|
|
72
81
|
headerLines: {
|
|
73
82
|
anyOf: [
|
|
74
|
-
{
|
|
75
|
-
$ref: "#/definitions/line"
|
|
76
|
-
},
|
|
83
|
+
{ $ref: "#/definitions/line" },
|
|
77
84
|
{
|
|
78
85
|
type: "array",
|
|
79
86
|
items: {
|
|
@@ -86,18 +93,89 @@ const schema = Object.freeze({
|
|
|
86
93
|
type: "integer",
|
|
87
94
|
minimum: 0
|
|
88
95
|
},
|
|
96
|
+
lineEndings: {
|
|
97
|
+
type: "string",
|
|
98
|
+
enum: [lineEndingOptions.unix, lineEndingOptions.windows, lineEndingOptions.os],
|
|
99
|
+
description: "Line endings to use when aut-fixing the violations. Defaults to 'os' which means 'same as " +
|
|
100
|
+
"system'."
|
|
101
|
+
},
|
|
89
102
|
settings: {
|
|
90
103
|
type: "object",
|
|
91
104
|
properties: {
|
|
92
|
-
lineEndings: {
|
|
105
|
+
lineEndings: { $ref: "#/definitions/lineEndings" },
|
|
106
|
+
},
|
|
107
|
+
additionalProperties: false
|
|
108
|
+
},
|
|
109
|
+
fileBasedHeader: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
file: {
|
|
93
113
|
type: "string",
|
|
94
|
-
|
|
114
|
+
description: "Name of a file relative to the current directory (no back-tracking) that contains " +
|
|
115
|
+
"the header template. It should contain a single block comment or a contiguous number of " +
|
|
116
|
+
"line comments."
|
|
117
|
+
},
|
|
118
|
+
encoding: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Character encoding to use when parsing the file. Valid values are all encodings " +
|
|
121
|
+
"that can be passed to `fs.readFileSync()`. If not specified, 'utf8' would be used."
|
|
122
|
+
// NOTE: default value not supported by the ajv schema
|
|
123
|
+
// validator and there is no way to fix it through the
|
|
124
|
+
// plugin's `meta.defaultOptions`.
|
|
95
125
|
}
|
|
96
126
|
},
|
|
127
|
+
required: ["file"],
|
|
97
128
|
additionalProperties: false
|
|
98
129
|
},
|
|
130
|
+
inlineHeader: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
commentType: { $ref: "#/definitions/commentType" },
|
|
134
|
+
lines: {
|
|
135
|
+
type: "array",
|
|
136
|
+
items: { $ref: "#/definitions/line" },
|
|
137
|
+
description: "List of each line of the header - each being a string to match exactly or a " +
|
|
138
|
+
"combination of a regex pattern to match and an optional template string as the fix."
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
required: ["commentType", "lines"],
|
|
142
|
+
additionalProperties: false
|
|
143
|
+
},
|
|
144
|
+
trailingEmptyLines: {
|
|
145
|
+
type: "object",
|
|
146
|
+
properties: {
|
|
147
|
+
minimum: {
|
|
148
|
+
type: "number",
|
|
149
|
+
description: "Number of empty lines required after the header. Defaults to 1.",
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
additionalProperties: false,
|
|
153
|
+
description: "Configuration for how to validate and fix the set of empty lines after the header."
|
|
154
|
+
},
|
|
155
|
+
newOptions: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
header: {
|
|
159
|
+
anyOf: [
|
|
160
|
+
{ $ref: "#/definitions/fileBasedHeader" },
|
|
161
|
+
{ $ref: "#/definitions/inlineHeader" }
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
lineEndings: { $ref: "#/definitions/lineEndings" },
|
|
165
|
+
trailingEmptyLines: { $ref: "#/definitions/trailingEmptyLines" }
|
|
166
|
+
},
|
|
167
|
+
required: ["header"],
|
|
168
|
+
additionalProperties: false,
|
|
169
|
+
description: "Object-based extensible configuration format to use with the `header` rule."
|
|
170
|
+
},
|
|
99
171
|
options: {
|
|
100
172
|
anyOf: [
|
|
173
|
+
{
|
|
174
|
+
type: "array",
|
|
175
|
+
minItems: 1,
|
|
176
|
+
maxItems: 1,
|
|
177
|
+
items: { $ref: "#/definitions/newOptions" }
|
|
178
|
+
},
|
|
101
179
|
{
|
|
102
180
|
type: "array",
|
|
103
181
|
minItems: 1,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tony.ganchev/eslint-plugin-header",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
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
6
|
"types": "index.d.ts",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
],
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
|
55
|
-
"url": "https://github.com/tonyganchev/eslint-plugin-header.git"
|
|
55
|
+
"url": "git+https://github.com/tonyganchev/eslint-plugin-header.git"
|
|
56
56
|
},
|
|
57
57
|
"author": "Stuart Knightley",
|
|
58
58
|
"license": "MIT",
|