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