@tony.ganchev/eslint-plugin-header 3.1.10 → 3.1.12
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 +10 -4
- package/index.js +1 -1
- package/lib/comment-parser.js +1 -1
- package/lib/rules/eslint-utils.js +42 -0
- package/lib/rules/header.js +300 -63
- package/package.json +14 -12
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2015-present Stuart Knightley, Tony Ganchev and contributors
|
|
3
|
+
Copyright (c) 2015-present Stuart Knightley, Tony Ganchev, and contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
6
|
this software and associated documentation files (the “Software”), to deal in
|
package/README.md
CHANGED
|
@@ -10,17 +10,18 @@ defined in the rule settings.
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
12
12
|
1. [Scope and Acknowledgements](#scope-and-acknowledgements)
|
|
13
|
-
2. [
|
|
13
|
+
2. [Compatibility](#compatibility)
|
|
14
|
+
3. [Usage](#usage)
|
|
14
15
|
1. [File-based Configuration](#file-based-configuration)
|
|
15
16
|
2. [Inline Configuration](#inline-configuration)
|
|
16
17
|
1. [Header Contents Configuration](#header-contents-configuration)
|
|
17
18
|
2. [Trailing Empty Lines Configuration](#trailing-empty-lines-configuration)
|
|
18
19
|
3. [Line Endings](#line-endings)
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
4. [Examples](#examples)
|
|
21
|
+
5. [Versioning](#versioning)
|
|
21
22
|
1. [What is a Feature?](#what-is-a-feature)
|
|
22
23
|
2. [What is Backward-compatibility?](#what-is-backward-compatibility)
|
|
23
|
-
|
|
24
|
+
6. [License](#license)
|
|
24
25
|
|
|
25
26
|
## Scope and Acknowledgements
|
|
26
27
|
|
|
@@ -39,6 +40,11 @@ It addresses the following issus:
|
|
|
39
40
|
forward. This would come at the expense of plugin compatibility and the
|
|
40
41
|
portability of fixes to the upstream repository.
|
|
41
42
|
|
|
43
|
+
## Compatibility
|
|
44
|
+
|
|
45
|
+
The plugin supports ESLint 7 / 8 / 9 / 10rc0 (check package.json for details).
|
|
46
|
+
Both flat config and legacy, hierarchical config can be used.
|
|
47
|
+
|
|
42
48
|
## Usage
|
|
43
49
|
|
|
44
50
|
This rule takes between 1 and 4 arguments after the rule validation severity.
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* MIT License
|
|
3
3
|
*
|
|
4
|
-
* Copyright (c) 2015-present Stuart Knightley, Tony Ganchev and contributors
|
|
4
|
+
* Copyright (c) 2015-present Stuart Knightley, Tony Ganchev, and contributors
|
|
5
5
|
*
|
|
6
6
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
* of this software and associated documentation files (the “Software”), to deal
|
package/lib/comment-parser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* MIT License
|
|
3
3
|
*
|
|
4
|
-
* Copyright (c) 2015-present Stuart Knightley, Tony Ganchev and contributors
|
|
4
|
+
* Copyright (c) 2015-present Stuart Knightley, Tony Ganchev, and contributors
|
|
5
5
|
*
|
|
6
6
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
* of this software and associated documentation files (the “Software”), to deal
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025-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
|
+
"use strict";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {import('eslint').Rule.RuleContext} RuleContext
|
|
29
|
+
* @typedef {import('eslint').SourceCode} SourceCode
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
/**
|
|
34
|
+
* Provides compatibility wrapper for ESLint 7 through 10 for getting the
|
|
35
|
+
* full source code object from the execution context.
|
|
36
|
+
* @param {RuleContext} context ESLint execution context.
|
|
37
|
+
* @returns {SourceCode} the source-code object.
|
|
38
|
+
*/
|
|
39
|
+
contextSourceCode: function(context) {
|
|
40
|
+
return context.sourceCode || context.getSourceCode();
|
|
41
|
+
}
|
|
42
|
+
};
|
package/lib/rules/header.js
CHANGED
|
@@ -24,10 +24,16 @@
|
|
|
24
24
|
|
|
25
25
|
"use strict";
|
|
26
26
|
|
|
27
|
+
const assert = require("assert");
|
|
28
|
+
const fs = require("fs");
|
|
29
|
+
const os = require("os");
|
|
30
|
+
const commentParser = require("../comment-parser");
|
|
31
|
+
const { contextSourceCode } = require("./eslint-utils");
|
|
32
|
+
const { description, recommended, url } = require("./header.docs");
|
|
33
|
+
const { commentTypeOptions, lineEndingOptions, schema } = require("./header.schema");
|
|
34
|
+
|
|
27
35
|
/**
|
|
28
36
|
* Import type definitions.
|
|
29
|
-
* @typedef {import('estree').Comment} Comment
|
|
30
|
-
* @typedef {import('estree').Program} Program
|
|
31
37
|
* @typedef {import('eslint').Rule.Fix} Fix
|
|
32
38
|
* @typedef {import('eslint').Rule.NodeListener} NodeListener
|
|
33
39
|
* @typedef {import('eslint').Rule.ReportFixer} ReportFixer
|
|
@@ -35,6 +41,9 @@
|
|
|
35
41
|
* @typedef {import('eslint').Rule.RuleTextEdit} RuleTextEdit
|
|
36
42
|
* @typedef {import('eslint').Rule.RuleTextEditor} RuleTextEditor
|
|
37
43
|
* @typedef {import('eslint').Rule.RuleContext} RuleContext
|
|
44
|
+
* @typedef {import('estree').Comment} Comment
|
|
45
|
+
* @typedef {import('estree').Program} Program
|
|
46
|
+
* @typedef {import("estree").SourceLocation} SourceLocation
|
|
38
47
|
*/
|
|
39
48
|
|
|
40
49
|
/**
|
|
@@ -52,13 +61,6 @@
|
|
|
52
61
|
* } HeaderOptions
|
|
53
62
|
*/
|
|
54
63
|
|
|
55
|
-
const assert = require("assert");
|
|
56
|
-
const fs = require("fs");
|
|
57
|
-
const os = require("os");
|
|
58
|
-
const commentParser = require("../comment-parser");
|
|
59
|
-
const { description, recommended, url } = require("./header.docs");
|
|
60
|
-
const { commentTypeOptions, lineEndingOptions, schema } = require("./header.schema");
|
|
61
|
-
|
|
62
64
|
/**
|
|
63
65
|
* Tests if the passed line configuration string or object is a pattern
|
|
64
66
|
* definition.
|
|
@@ -90,8 +92,8 @@ function match(actual, expected) {
|
|
|
90
92
|
* Remove Unix she-bangs from the list of comments.
|
|
91
93
|
* @param {Comment[]} comments the list of comment lines.
|
|
92
94
|
* @returns {Comment[]} the list of comments with containing all incomming
|
|
93
|
-
* comments from `comments` with the shebang
|
|
94
|
-
*
|
|
95
|
+
* comments from `comments` with the shebang comments
|
|
96
|
+
* omitted.
|
|
95
97
|
*/
|
|
96
98
|
function excludeShebangs(comments) {
|
|
97
99
|
return comments.filter(function(comment) {
|
|
@@ -109,13 +111,14 @@ function excludeShebangs(comments) {
|
|
|
109
111
|
* @returns {Comment[]} lines that constitute the leading comment.
|
|
110
112
|
*/
|
|
111
113
|
function getLeadingComments(context, node) {
|
|
112
|
-
const
|
|
114
|
+
const sourceCode = contextSourceCode(context);
|
|
115
|
+
const all = excludeShebangs(sourceCode.getAllComments(node.body.length ? node.body[0] : node));
|
|
113
116
|
if (all[0].type.toLowerCase() === commentTypeOptions.block) {
|
|
114
117
|
return [all[0]];
|
|
115
118
|
}
|
|
116
119
|
let i = 1;
|
|
117
120
|
for (; i < all.length; ++i) {
|
|
118
|
-
const txt =
|
|
121
|
+
const txt = sourceCode.text.slice(all[i - 1].range[1], all[i].range[0]);
|
|
119
122
|
if (!txt.match(/^(\r\n|\r|\n)$/)) {
|
|
120
123
|
break;
|
|
121
124
|
}
|
|
@@ -186,14 +189,15 @@ function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
|
|
|
186
189
|
return function(fixer) {
|
|
187
190
|
let insertPos = 0;
|
|
188
191
|
let newHeader = genCommentBody(commentType, headerLines, eol, numNewlines);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
const sourceCode = contextSourceCode(context);
|
|
193
|
+
if (sourceCode.text.startsWith("#!")) {
|
|
194
|
+
const firstNewLinePos = sourceCode.text.indexOf("\n");
|
|
195
|
+
insertPos = firstNewLinePos === -1 ? sourceCode.text.length : firstNewLinePos + 1;
|
|
192
196
|
if (firstNewLinePos === -1) {
|
|
193
197
|
newHeader = eol + newHeader;
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
|
-
const numEmptyLines = leadingEmptyLines(
|
|
200
|
+
const numEmptyLines = leadingEmptyLines(sourceCode.text.substring(insertPos));
|
|
197
201
|
const additionalEmptyLines = Math.max(0, numNewlines - numEmptyLines);
|
|
198
202
|
newHeader += eol.repeat(additionalEmptyLines);
|
|
199
203
|
return fixer.insertTextBeforeRange(
|
|
@@ -218,7 +222,7 @@ function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
|
|
|
218
222
|
function genReplaceFixer(commentType, context, leadingComments, headerLines, eol, numNewlines) {
|
|
219
223
|
return function(fixer) {
|
|
220
224
|
const commentRange = genCommentsRange(leadingComments);
|
|
221
|
-
const emptyLines = leadingEmptyLines(context.
|
|
225
|
+
const emptyLines = leadingEmptyLines(contextSourceCode(context).text.substring(commentRange[1]));
|
|
222
226
|
const missingNewlines = Math.max(0, numNewlines - emptyLines);
|
|
223
227
|
const eols = eol.repeat(missingNewlines);
|
|
224
228
|
return fixer.replaceTextRange(
|
|
@@ -288,16 +292,33 @@ function getEOL(options) {
|
|
|
288
292
|
* @param {string} src source code to test.
|
|
289
293
|
* @returns {boolean} `true` if there is a comment or `false` otherwise.
|
|
290
294
|
*/
|
|
291
|
-
// TODO: check if it is valid to have the copyright notice separated by an empty
|
|
292
|
-
// line from the shebang.
|
|
293
295
|
function hasHeader(src) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
const srcWithoutShebang = src.replace(/^#![^\n]*\r?\n/, "");
|
|
297
|
+
return srcWithoutShebang.startsWith("/*") || srcWithoutShebang.startsWith("//");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Calculates the source location of the violation that not enough empty lines
|
|
302
|
+
* follow the header.
|
|
303
|
+
* The behavior chosen is that the violation is shown over the empty (but
|
|
304
|
+
* insufficient) lines that trail the comment. A special case is when there are
|
|
305
|
+
* no empty lines after the header in which case we highlight the next character
|
|
306
|
+
* in the source regardless of which one it is).
|
|
307
|
+
* @param {Comment[]} leadingComments the comment lines that constitute the
|
|
308
|
+
* header.
|
|
309
|
+
* @param {number} actualEmptyLines the number of empty lines that follow the
|
|
310
|
+
* header.
|
|
311
|
+
* @returns {SourceLocation} the location (line and column) of the violation.
|
|
312
|
+
*/
|
|
313
|
+
function missingEmptyLinesViolationLoc(leadingComments, actualEmptyLines) {
|
|
314
|
+
const lastCommentLineLocEnd = leadingComments[leadingComments.length - 1].loc.end;
|
|
315
|
+
return {
|
|
316
|
+
start: lastCommentLineLocEnd,
|
|
317
|
+
end: {
|
|
318
|
+
column: actualEmptyLines === 0 ? lastCommentLineLocEnd.column + 1 : 0,
|
|
319
|
+
line: lastCommentLineLocEnd.line + actualEmptyLines
|
|
298
320
|
}
|
|
299
|
-
}
|
|
300
|
-
return src.startsWith("/*") || src.startsWith("//");
|
|
321
|
+
};
|
|
301
322
|
}
|
|
302
323
|
|
|
303
324
|
module.exports = {
|
|
@@ -312,10 +333,16 @@ module.exports = {
|
|
|
312
333
|
schema,
|
|
313
334
|
defaultOptions: [{}],
|
|
314
335
|
messages: {
|
|
336
|
+
headerLineMismatchAtPos: "header line does not match expected after this position; expected: {{expected}}",
|
|
337
|
+
headerLineTooLong: "header line longer than expected",
|
|
338
|
+
headerLineTooShort: "header line shorter than expected; missing: {{remainder}}",
|
|
339
|
+
headerTooShort: "header too short: missing lines: {{remainder}}",
|
|
340
|
+
headerTooLong: "header too long",
|
|
315
341
|
incorrectCommentType: "header should be a {{commentType}} comment",
|
|
316
342
|
incorrectHeader: "incorrect header",
|
|
343
|
+
incorrectHeaderLine: "header line does not match pattern: {{pattern}}",
|
|
317
344
|
missingHeader: "missing header",
|
|
318
|
-
noNewlineAfterHeader: "
|
|
345
|
+
noNewlineAfterHeader: "not enough newlines after header: expected: {{expected}}, actual: {{actual}}"
|
|
319
346
|
}
|
|
320
347
|
},
|
|
321
348
|
/**
|
|
@@ -335,6 +362,7 @@ module.exports = {
|
|
|
335
362
|
}
|
|
336
363
|
|
|
337
364
|
const commentType = options[0].toLowerCase();
|
|
365
|
+
/** @type {(string | RegExp)[]} */
|
|
338
366
|
let headerLines;
|
|
339
367
|
let fixLines = [];
|
|
340
368
|
// If any of the lines are regular expressions, then we can't
|
|
@@ -372,9 +400,21 @@ module.exports = {
|
|
|
372
400
|
* @returns {void}
|
|
373
401
|
*/
|
|
374
402
|
Program: function(node) {
|
|
375
|
-
|
|
403
|
+
const sourceCode = contextSourceCode(context);
|
|
404
|
+
if (!hasHeader(sourceCode.text)) {
|
|
405
|
+
const hasShebang = sourceCode.text.startsWith("#!");
|
|
406
|
+
const line = hasShebang ? 2 : 1;
|
|
376
407
|
context.report({
|
|
377
|
-
loc:
|
|
408
|
+
loc: {
|
|
409
|
+
start: {
|
|
410
|
+
column: 1,
|
|
411
|
+
line
|
|
412
|
+
},
|
|
413
|
+
end: {
|
|
414
|
+
column: 1,
|
|
415
|
+
line
|
|
416
|
+
}
|
|
417
|
+
},
|
|
378
418
|
messageId: "missingHeader",
|
|
379
419
|
fix: genPrependFixer(commentType, context, fixLines, eol, numNewlines)
|
|
380
420
|
});
|
|
@@ -384,7 +424,10 @@ module.exports = {
|
|
|
384
424
|
|
|
385
425
|
if (leadingComments[0].type.toLowerCase() !== commentType) {
|
|
386
426
|
context.report({
|
|
387
|
-
loc:
|
|
427
|
+
loc: {
|
|
428
|
+
start: leadingComments[0].loc.start,
|
|
429
|
+
end: leadingComments[leadingComments.length - 1].loc.end
|
|
430
|
+
},
|
|
388
431
|
messageId: "incorrectCommentType",
|
|
389
432
|
data: {
|
|
390
433
|
commentType: commentType
|
|
@@ -396,16 +439,6 @@ module.exports = {
|
|
|
396
439
|
return;
|
|
397
440
|
}
|
|
398
441
|
if (commentType === commentTypeOptions.line) {
|
|
399
|
-
if (leadingComments.length < headerLines.length) {
|
|
400
|
-
context.report({
|
|
401
|
-
loc: node.loc,
|
|
402
|
-
messageId: "incorrectHeader",
|
|
403
|
-
fix: canFix
|
|
404
|
-
? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines)
|
|
405
|
-
: null
|
|
406
|
-
});
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
442
|
if (headerLines.length === 1) {
|
|
410
443
|
const leadingCommentValues = leadingComments.map((c) => c.value);
|
|
411
444
|
if (
|
|
@@ -413,7 +446,10 @@ module.exports = {
|
|
|
413
446
|
&& !match(leadingCommentValues.join("\r\n"), headerLines[0])
|
|
414
447
|
) {
|
|
415
448
|
context.report({
|
|
416
|
-
loc:
|
|
449
|
+
loc: {
|
|
450
|
+
start: leadingComments[0].loc.start,
|
|
451
|
+
end: leadingComments[leadingComments.length - 1].loc.end
|
|
452
|
+
},
|
|
417
453
|
messageId: "incorrectHeader",
|
|
418
454
|
fix: canFix
|
|
419
455
|
? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines)
|
|
@@ -423,10 +459,15 @@ module.exports = {
|
|
|
423
459
|
}
|
|
424
460
|
} else {
|
|
425
461
|
for (let i = 0; i < headerLines.length; i++) {
|
|
426
|
-
if (
|
|
462
|
+
if (leadingComments.length - 1 < i) {
|
|
427
463
|
context.report({
|
|
428
|
-
loc:
|
|
429
|
-
|
|
464
|
+
loc: {
|
|
465
|
+
start: leadingComments[leadingComments.length - 1].loc.end
|
|
466
|
+
},
|
|
467
|
+
messageId: "headerTooShort",
|
|
468
|
+
data: {
|
|
469
|
+
remainder: headerLines.slice(i).join(eol)
|
|
470
|
+
},
|
|
430
471
|
fix: canFix
|
|
431
472
|
? genReplaceFixer(
|
|
432
473
|
commentType,
|
|
@@ -439,17 +480,119 @@ module.exports = {
|
|
|
439
480
|
});
|
|
440
481
|
return;
|
|
441
482
|
}
|
|
483
|
+
if (typeof headerLines[i] === "string") {
|
|
484
|
+
const leadingCommentLength = leadingComments[i].value.length;
|
|
485
|
+
const headerLineLength = headerLines[i].length;
|
|
486
|
+
for (let j = 0; j < Math.min(leadingCommentLength, headerLineLength); j++) {
|
|
487
|
+
if (leadingComments[i].value[j] !== headerLines[i][j]) {
|
|
488
|
+
context.report({
|
|
489
|
+
loc: {
|
|
490
|
+
start: {
|
|
491
|
+
column: "//".length + j,
|
|
492
|
+
line: leadingComments[i].loc.start.line
|
|
493
|
+
},
|
|
494
|
+
end: leadingComments[i].loc.end
|
|
495
|
+
},
|
|
496
|
+
messageId: "headerLineMismatchAtPos",
|
|
497
|
+
data: {
|
|
498
|
+
expected: headerLines[i].substring(j)
|
|
499
|
+
},
|
|
500
|
+
fix: genReplaceFixer(
|
|
501
|
+
commentType,
|
|
502
|
+
context,
|
|
503
|
+
leadingComments,
|
|
504
|
+
fixLines,
|
|
505
|
+
eol,
|
|
506
|
+
numNewlines)
|
|
507
|
+
});
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (leadingCommentLength < headerLineLength) {
|
|
512
|
+
context.report({
|
|
513
|
+
loc: {
|
|
514
|
+
start: leadingComments[i].loc.end,
|
|
515
|
+
},
|
|
516
|
+
messageId: "headerLineTooShort",
|
|
517
|
+
data: {
|
|
518
|
+
remainder: headerLines[i].substring(leadingCommentLength)
|
|
519
|
+
},
|
|
520
|
+
fix: canFix
|
|
521
|
+
? genReplaceFixer(
|
|
522
|
+
commentType,
|
|
523
|
+
context,
|
|
524
|
+
leadingComments,
|
|
525
|
+
fixLines,
|
|
526
|
+
eol,
|
|
527
|
+
numNewlines)
|
|
528
|
+
: null
|
|
529
|
+
});
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (leadingCommentLength > headerLineLength) {
|
|
533
|
+
context.report({
|
|
534
|
+
loc: {
|
|
535
|
+
start: {
|
|
536
|
+
column: "//".length + headerLineLength,
|
|
537
|
+
line: leadingComments[i].loc.start.line
|
|
538
|
+
},
|
|
539
|
+
end: leadingComments[i].loc.end,
|
|
540
|
+
},
|
|
541
|
+
messageId: "headerLineTooLong",
|
|
542
|
+
fix: canFix
|
|
543
|
+
? genReplaceFixer(
|
|
544
|
+
commentType,
|
|
545
|
+
context,
|
|
546
|
+
leadingComments,
|
|
547
|
+
fixLines,
|
|
548
|
+
eol,
|
|
549
|
+
numNewlines)
|
|
550
|
+
: null
|
|
551
|
+
});
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
if (!match(leadingComments[i].value, headerLines[i])) {
|
|
556
|
+
context.report({
|
|
557
|
+
loc: {
|
|
558
|
+
start: {
|
|
559
|
+
column: "//".length,
|
|
560
|
+
line: leadingComments[i].loc.start.line,
|
|
561
|
+
},
|
|
562
|
+
end: leadingComments[i].loc.end,
|
|
563
|
+
},
|
|
564
|
+
messageId: "incorrectHeaderLine",
|
|
565
|
+
data: {
|
|
566
|
+
pattern: headerLines[i]
|
|
567
|
+
},
|
|
568
|
+
fix: canFix
|
|
569
|
+
? genReplaceFixer(
|
|
570
|
+
commentType,
|
|
571
|
+
context,
|
|
572
|
+
leadingComments,
|
|
573
|
+
fixLines,
|
|
574
|
+
eol,
|
|
575
|
+
numNewlines)
|
|
576
|
+
: null
|
|
577
|
+
});
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
442
581
|
}
|
|
443
582
|
}
|
|
444
583
|
|
|
445
|
-
const actualLeadingEmptyLines =
|
|
446
|
-
|
|
584
|
+
const actualLeadingEmptyLines =
|
|
585
|
+
leadingEmptyLines(sourceCode.text.substring(leadingComments[headerLines.length - 1].range[1]));
|
|
447
586
|
const missingEmptyLines = numNewlines - actualLeadingEmptyLines;
|
|
448
587
|
if (missingEmptyLines > 0) {
|
|
449
588
|
context.report({
|
|
450
|
-
loc:
|
|
589
|
+
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
451
590
|
messageId: "noNewlineAfterHeader",
|
|
452
|
-
|
|
591
|
+
data: {
|
|
592
|
+
expected: numNewlines,
|
|
593
|
+
actual: actualLeadingEmptyLines
|
|
594
|
+
},
|
|
595
|
+
fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
|
|
453
596
|
});
|
|
454
597
|
}
|
|
455
598
|
return;
|
|
@@ -461,26 +604,116 @@ module.exports = {
|
|
|
461
604
|
leadingLines = leadingComments[0].value.split(/\r?\n/);
|
|
462
605
|
}
|
|
463
606
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
607
|
+
/** @type {null | string} */
|
|
608
|
+
let errorMessageId = null;
|
|
609
|
+
/** @type {null | Record<string, string | RegExp>} */
|
|
610
|
+
let errorMessageData = null;
|
|
611
|
+
/** @type {null | SourceLocation} */
|
|
612
|
+
let errorMessageLoc = null;
|
|
613
|
+
for (let i = 0; i < headerLines.length; i++) {
|
|
469
614
|
const leadingLine = leadingLines[i];
|
|
470
615
|
const headerLine = headerLines[i];
|
|
471
|
-
if (
|
|
472
|
-
|
|
473
|
-
|
|
616
|
+
if (typeof headerLine === "string") {
|
|
617
|
+
for (let j = 0; j < Math.min(leadingLine.length, headerLine.length); j++) {
|
|
618
|
+
if (leadingLine[j] !== headerLine[j]) {
|
|
619
|
+
errorMessageId = "headerLineMismatchAtPos";
|
|
620
|
+
const columnOffset = i === 0 ? "/*".length : 0;
|
|
621
|
+
const line = leadingComments[0].loc.start.line + i;
|
|
622
|
+
errorMessageLoc = {
|
|
623
|
+
start: {
|
|
624
|
+
column: columnOffset + j,
|
|
625
|
+
line
|
|
626
|
+
},
|
|
627
|
+
end: {
|
|
628
|
+
column: columnOffset + leadingLine.length,
|
|
629
|
+
line
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
errorMessageData = {
|
|
633
|
+
expected: headerLine.substring(j)
|
|
634
|
+
};
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (errorMessageId) {
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
if (leadingLine.length < headerLine.length) {
|
|
642
|
+
errorMessageId = "headerLineTooShort";
|
|
643
|
+
const startColumn = (i === 0 ? "/*".length : 0) + leadingLine.length;
|
|
644
|
+
errorMessageLoc = {
|
|
645
|
+
start: {
|
|
646
|
+
column: startColumn,
|
|
647
|
+
line: leadingComments[0].loc.start.line + i
|
|
648
|
+
},
|
|
649
|
+
end: {
|
|
650
|
+
column: startColumn + 1,
|
|
651
|
+
line: leadingComments[0].loc.start.line + i
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
errorMessageData = {
|
|
655
|
+
remainder: headerLines[i].substring(leadingLine.length)
|
|
656
|
+
};
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
if (leadingLine.length > headerLine.length) {
|
|
660
|
+
errorMessageId = "headerLineTooLong";
|
|
661
|
+
errorMessageLoc = {
|
|
662
|
+
start: {
|
|
663
|
+
column: (i === 0 ? "/*".length : 0) + headerLine.length,
|
|
664
|
+
line: leadingComments[0].loc.start.line + i
|
|
665
|
+
},
|
|
666
|
+
end: {
|
|
667
|
+
column: (i === 0 ? "/*".length : 0) + leadingLine.length,
|
|
668
|
+
line: leadingComments[0].loc.start.line + i
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
if (!match(leadingLine, headerLine)) {
|
|
675
|
+
errorMessageId = "incorrectHeaderLine";
|
|
676
|
+
errorMessageData = {
|
|
677
|
+
pattern: headerLine
|
|
678
|
+
};
|
|
679
|
+
const columnOffset = i === 0 ? "/*".length : 0;
|
|
680
|
+
errorMessageLoc = {
|
|
681
|
+
start: {
|
|
682
|
+
column: columnOffset + 0,
|
|
683
|
+
line: leadingComments[0].loc.start.line + i
|
|
684
|
+
},
|
|
685
|
+
end: {
|
|
686
|
+
column: columnOffset + leadingLine.length,
|
|
687
|
+
line: leadingComments[0].loc.start.line + i
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
474
692
|
}
|
|
475
693
|
}
|
|
476
694
|
|
|
477
|
-
if (
|
|
695
|
+
if (!errorMessageId && leadingLines.length > headerLines.length) {
|
|
696
|
+
errorMessageId = "headerTooLong";
|
|
697
|
+
errorMessageLoc = {
|
|
698
|
+
start: {
|
|
699
|
+
column: (headerLines.length === 0 ? "/*".length : 0) + 0,
|
|
700
|
+
line: leadingComments[0].loc.start.line + headerLines.length
|
|
701
|
+
},
|
|
702
|
+
end: {
|
|
703
|
+
column: leadingComments[leadingComments.length - 1].loc.end.column - "*/".length,
|
|
704
|
+
line: leadingComments[leadingComments.length - 1].loc.end.line
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (errorMessageId) {
|
|
478
710
|
if (canFix && headerLines.length > 1) {
|
|
479
711
|
fixLines = [fixLines.join(eol)];
|
|
480
712
|
}
|
|
481
713
|
context.report({
|
|
482
|
-
loc:
|
|
483
|
-
messageId:
|
|
714
|
+
loc: errorMessageLoc,
|
|
715
|
+
messageId: errorMessageId,
|
|
716
|
+
data: errorMessageData,
|
|
484
717
|
fix: canFix
|
|
485
718
|
? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines)
|
|
486
719
|
: null
|
|
@@ -488,14 +721,18 @@ module.exports = {
|
|
|
488
721
|
return;
|
|
489
722
|
}
|
|
490
723
|
|
|
491
|
-
const actualLeadingEmptyLines =
|
|
492
|
-
|
|
724
|
+
const actualLeadingEmptyLines =
|
|
725
|
+
leadingEmptyLines(sourceCode.text.substring(leadingComments[0].range[1]));
|
|
493
726
|
const missingEmptyLines = numNewlines - actualLeadingEmptyLines;
|
|
494
727
|
if (missingEmptyLines > 0) {
|
|
495
728
|
context.report({
|
|
496
|
-
loc:
|
|
729
|
+
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
497
730
|
messageId: "noNewlineAfterHeader",
|
|
498
|
-
|
|
731
|
+
data: {
|
|
732
|
+
expected: numNewlines,
|
|
733
|
+
actual: actualLeadingEmptyLines
|
|
734
|
+
},
|
|
735
|
+
fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
|
|
499
736
|
});
|
|
500
737
|
}
|
|
501
738
|
}
|
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.1.12",
|
|
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
|
"files": [
|
|
@@ -11,19 +11,21 @@
|
|
|
11
11
|
"eslint": "npx eslint .",
|
|
12
12
|
"lint": "npm run eslint && npm run markdownlint",
|
|
13
13
|
"markdownlint": "npx markdownlint-cli *.md",
|
|
14
|
-
"test": "npm run lint && npm run unit",
|
|
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"
|
|
14
|
+
"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"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
|
-
"@eslint/eslintrc": "^3.3.
|
|
19
|
-
"@eslint/js": "^9.
|
|
19
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
20
|
+
"@eslint/js": "^9.39.2",
|
|
20
21
|
"@eslint/markdown": "^7.5.1",
|
|
21
|
-
"@stylistic/eslint-plugin": "^5.
|
|
22
|
-
"eslint": "^9.
|
|
23
|
-
"eslint-plugin-eslint-plugin": "^7.
|
|
24
|
-
"eslint-plugin-jsdoc": "^
|
|
25
|
-
"eslint-plugin-n": "^17.23.
|
|
26
|
-
"
|
|
22
|
+
"@stylistic/eslint-plugin": "^5.7.0",
|
|
23
|
+
"eslint": "^9.39.2",
|
|
24
|
+
"eslint-plugin-eslint-plugin": "^7.3.0",
|
|
25
|
+
"eslint-plugin-jsdoc": "^62.1.0",
|
|
26
|
+
"eslint-plugin-n": "^17.23.2",
|
|
27
|
+
"markdownlint-cli": "^0.47.0",
|
|
28
|
+
"mocha": "^12.0.0-beta-5",
|
|
27
29
|
"nyc": "^17.1.0",
|
|
28
30
|
"testdouble": "^3.20.2"
|
|
29
31
|
},
|
|
@@ -40,7 +42,7 @@
|
|
|
40
42
|
},
|
|
41
43
|
"author": "Stuart Knightley",
|
|
42
44
|
"license": "MIT",
|
|
43
|
-
"
|
|
45
|
+
"maintainers": [
|
|
44
46
|
"Tony Ganchev"
|
|
45
47
|
]
|
|
46
48
|
}
|