@tony.ganchev/eslint-plugin-header 3.1.11 → 3.2.0
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 +488 -186
- package/lib/comment-parser.js +3 -3
- package/lib/rules/eslint-utils.js +42 -0
- package/lib/rules/header.js +261 -107
- package/lib/rules/header.schema.js +91 -13
- package/package.json +14 -12
package/lib/comment-parser.js
CHANGED
|
@@ -33,8 +33,8 @@ const assert = require("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 " +
|
|
@@ -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
|
@@ -28,8 +28,9 @@ const assert = require("assert");
|
|
|
28
28
|
const fs = require("fs");
|
|
29
29
|
const os = require("os");
|
|
30
30
|
const commentParser = require("../comment-parser");
|
|
31
|
+
const { contextSourceCode } = require("./eslint-utils");
|
|
31
32
|
const { description, recommended, url } = require("./header.docs");
|
|
32
|
-
const {
|
|
33
|
+
const { lineEndingOptions, commentTypeOptions, schema } = require("./header.schema");
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Import type definitions.
|
|
@@ -46,18 +47,40 @@ const { commentTypeOptions, lineEndingOptions, schema } = require("./header.sche
|
|
|
46
47
|
*/
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
|
-
* Local type
|
|
50
|
-
* @typedef {
|
|
50
|
+
* Local type definitions.
|
|
51
|
+
* @typedef {{ pattern: string | RegExp, template?: string }} HeaderLinePattern
|
|
52
|
+
* @typedef {string | RegExp | HeaderLinePattern} HeaderLine
|
|
51
53
|
* @typedef {(HeaderLine | HeaderLine[])} HeaderLines
|
|
52
|
-
* @typedef {{ lineEndings
|
|
54
|
+
* @typedef {{ lineEndings?: ('unix' | 'windows' | 'os') }} HeaderSettings
|
|
53
55
|
* @typedef {
|
|
54
56
|
* [string]
|
|
55
57
|
* | [string, HeaderSettings]
|
|
56
|
-
* | [('block' | 'line')
|
|
57
|
-
* | [('block' | 'line')
|
|
58
|
-
* | [('block' | 'line')
|
|
59
|
-
* | [('block' | 'line')
|
|
60
|
-
* }
|
|
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
|
+
* {
|
|
65
|
+
* file: string,
|
|
66
|
+
* encoding?: string
|
|
67
|
+
* }
|
|
68
|
+
* } FileBasedConfig
|
|
69
|
+
* @typedef {
|
|
70
|
+
* {
|
|
71
|
+
* commentType: 'line' | 'block',
|
|
72
|
+
* lines: HeaderLine[]
|
|
73
|
+
* }
|
|
74
|
+
* } LinesBasedConfig
|
|
75
|
+
* @typedef {{ minimum?: number }} TrailingEmptyLines
|
|
76
|
+
* @typedef {
|
|
77
|
+
* {
|
|
78
|
+
* header: FileBasedConfig | LinesBasedConfig,
|
|
79
|
+
* trailingEmptyLines?: TrailingEmptyLines
|
|
80
|
+
* }
|
|
81
|
+
* & HeaderSettings
|
|
82
|
+
* } NewHeaderOptions
|
|
83
|
+
* @typedef {LegacyHeaderOptions | [NewHeaderOptions]} HeaderOptions
|
|
61
84
|
*/
|
|
62
85
|
|
|
63
86
|
/**
|
|
@@ -68,7 +91,8 @@ const { commentTypeOptions, lineEndingOptions, schema } = require("./header.sche
|
|
|
68
91
|
* object or `false` otherwise.
|
|
69
92
|
*/
|
|
70
93
|
function isPattern(object) {
|
|
71
|
-
return typeof object === "object"
|
|
94
|
+
return typeof object === "object"
|
|
95
|
+
&& (object instanceof RegExp || Object.prototype.hasOwnProperty.call(object, "pattern"));
|
|
72
96
|
}
|
|
73
97
|
|
|
74
98
|
/**
|
|
@@ -90,7 +114,7 @@ function match(actual, expected) {
|
|
|
90
114
|
/**
|
|
91
115
|
* Remove Unix she-bangs from the list of comments.
|
|
92
116
|
* @param {Comment[]} comments the list of comment lines.
|
|
93
|
-
* @returns {Comment[]} the list of comments with containing all
|
|
117
|
+
* @returns {Comment[]} the list of comments with containing all incoming
|
|
94
118
|
* comments from `comments` with the shebang comments
|
|
95
119
|
* omitted.
|
|
96
120
|
*/
|
|
@@ -106,17 +130,18 @@ function excludeShebangs(comments) {
|
|
|
106
130
|
* check if they are at the start of the file since that is already checked by
|
|
107
131
|
* `hasHeader()`.
|
|
108
132
|
* @param {RuleContext} context ESLint execution environment.
|
|
109
|
-
* @param {Program} node ESLint AST
|
|
133
|
+
* @param {Program} node ESLint AST tree node being processed.
|
|
110
134
|
* @returns {Comment[]} lines that constitute the leading comment.
|
|
111
135
|
*/
|
|
112
136
|
function getLeadingComments(context, node) {
|
|
113
|
-
const
|
|
137
|
+
const sourceCode = contextSourceCode(context);
|
|
138
|
+
const all = excludeShebangs(sourceCode.getAllComments(node.body.length ? node.body[0] : node));
|
|
114
139
|
if (all[0].type.toLowerCase() === commentTypeOptions.block) {
|
|
115
140
|
return [all[0]];
|
|
116
141
|
}
|
|
117
142
|
let i = 1;
|
|
118
143
|
for (; i < all.length; ++i) {
|
|
119
|
-
const txt =
|
|
144
|
+
const txt = sourceCode.text.slice(all[i - 1].range[1], all[i].range[0]);
|
|
120
145
|
if (!txt.match(/^(\r\n|\r|\n)$/)) {
|
|
121
146
|
break;
|
|
122
147
|
}
|
|
@@ -186,15 +211,16 @@ function leadingEmptyLines(src) {
|
|
|
186
211
|
function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
|
|
187
212
|
return function(fixer) {
|
|
188
213
|
let insertPos = 0;
|
|
189
|
-
let newHeader = genCommentBody(commentType, headerLines, eol
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
214
|
+
let newHeader = genCommentBody(commentType, headerLines, eol);
|
|
215
|
+
const sourceCode = contextSourceCode(context);
|
|
216
|
+
if (sourceCode.text.startsWith("#!")) {
|
|
217
|
+
const firstNewLinePos = sourceCode.text.indexOf("\n");
|
|
218
|
+
insertPos = firstNewLinePos === -1 ? sourceCode.text.length : firstNewLinePos + 1;
|
|
193
219
|
if (firstNewLinePos === -1) {
|
|
194
220
|
newHeader = eol + newHeader;
|
|
195
221
|
}
|
|
196
222
|
}
|
|
197
|
-
const numEmptyLines = leadingEmptyLines(
|
|
223
|
+
const numEmptyLines = leadingEmptyLines(sourceCode.text.substring(insertPos));
|
|
198
224
|
const additionalEmptyLines = Math.max(0, numNewlines - numEmptyLines);
|
|
199
225
|
newHeader += eol.repeat(additionalEmptyLines);
|
|
200
226
|
return fixer.insertTextBeforeRange(
|
|
@@ -219,7 +245,7 @@ function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
|
|
|
219
245
|
function genReplaceFixer(commentType, context, leadingComments, headerLines, eol, numNewlines) {
|
|
220
246
|
return function(fixer) {
|
|
221
247
|
const commentRange = genCommentsRange(leadingComments);
|
|
222
|
-
const emptyLines = leadingEmptyLines(context.
|
|
248
|
+
const emptyLines = leadingEmptyLines(contextSourceCode(context).text.substring(commentRange[1]));
|
|
223
249
|
const missingNewlines = Math.max(0, numNewlines - emptyLines);
|
|
224
250
|
const eols = eol.repeat(missingNewlines);
|
|
225
251
|
return fixer.replaceTextRange(
|
|
@@ -248,39 +274,25 @@ function genEmptyLinesFixer(leadingComments, eol, missingEmptyLinesCount) {
|
|
|
248
274
|
};
|
|
249
275
|
}
|
|
250
276
|
|
|
251
|
-
/**
|
|
252
|
-
* Finds the option parameter within the list of rule config options.
|
|
253
|
-
* @param {HeaderOptions} options the config options passed to the rule.
|
|
254
|
-
* @returns {HeaderSettings | null} the settings parameter or `null` if no such
|
|
255
|
-
* is defined.
|
|
256
|
-
*/
|
|
257
|
-
function findSettings(options) {
|
|
258
|
-
const lastOption = options[options.length - 1];
|
|
259
|
-
if (typeof lastOption === "object" && !Array.isArray(lastOption) && lastOption !== null
|
|
260
|
-
&& !Object.prototype.hasOwnProperty.call(lastOption, "pattern")) {
|
|
261
|
-
return lastOption;
|
|
262
|
-
}
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
277
|
/**
|
|
267
278
|
* Returns the used line-termination characters per the rule's config if any or
|
|
268
279
|
* else based on the runtime environments.
|
|
269
|
-
* @param {
|
|
280
|
+
* @param {'os' | 'unix' | 'windows'} style line-ending styles.
|
|
270
281
|
* @returns {'\n' | '\r\n'} the correct line ending characters for the
|
|
271
|
-
*
|
|
282
|
+
* environment.
|
|
272
283
|
*/
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
284
|
+
function getEol(style) {
|
|
285
|
+
assert.strictEqual(Object.prototype.hasOwnProperty.call(lineEndingOptions, style), true,
|
|
286
|
+
"lineEnding style should have been populated in normalizeOptions().");
|
|
287
|
+
switch (style) {
|
|
288
|
+
case lineEndingOptions.unix:
|
|
277
289
|
return "\n";
|
|
278
|
-
|
|
279
|
-
if (settings.lineEndings === lineEndingOptions.windows) {
|
|
290
|
+
case lineEndingOptions.windows:
|
|
280
291
|
return "\r\n";
|
|
281
|
-
|
|
292
|
+
case lineEndingOptions.os:
|
|
293
|
+
default:
|
|
294
|
+
return os.EOL;
|
|
282
295
|
}
|
|
283
|
-
return os.EOL;
|
|
284
296
|
}
|
|
285
297
|
|
|
286
298
|
/**
|
|
@@ -294,6 +306,134 @@ function hasHeader(src) {
|
|
|
294
306
|
return srcWithoutShebang.startsWith("/*") || srcWithoutShebang.startsWith("//");
|
|
295
307
|
}
|
|
296
308
|
|
|
309
|
+
/**
|
|
310
|
+
* asserts on an expression and adds template texts to the failure message.
|
|
311
|
+
* Helper to write cleaner code.
|
|
312
|
+
* @param {boolean} condition assert condition.
|
|
313
|
+
* @param {string} message assert message on violation.
|
|
314
|
+
*/
|
|
315
|
+
function schemaAssert(condition, message) {
|
|
316
|
+
assert.strictEqual(condition, true, message + " - should have been handled by eslint schema validation.");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Ensures that if legacy options as defined in the original
|
|
321
|
+
* `eslint-plugin-header` are used, they'd be converted to the new object-based
|
|
322
|
+
* configuration. This is not a normalized internal representation of the
|
|
323
|
+
* options in that some settings are still union types and unspecified
|
|
324
|
+
* properties are not replaced by defaults. If the options follow the new
|
|
325
|
+
* format, a simple seep copy would be returned.
|
|
326
|
+
* @param {HeaderOptions} originalOptions the options as configured by the user.
|
|
327
|
+
* @returns {NewHeaderOptions} the transformed new-style options with no
|
|
328
|
+
* normalization.
|
|
329
|
+
*/
|
|
330
|
+
function transformLegacyOptions(originalOptions) {
|
|
331
|
+
schemaAssert(originalOptions?.length > 0,
|
|
332
|
+
"header options are required (at least one in addition to the severity)");
|
|
333
|
+
schemaAssert(originalOptions.length <= 4,
|
|
334
|
+
"header options should not be more than four (five including issue severity)");
|
|
335
|
+
if (originalOptions.length === 1 && typeof originalOptions[0] === "object") {
|
|
336
|
+
// The user chose to use the new-style config. We pass a copy of the
|
|
337
|
+
// incoming sole option to not mess with ESLint not relying on the old
|
|
338
|
+
// values of the option.
|
|
339
|
+
return structuredClone(originalOptions[0]);
|
|
340
|
+
}
|
|
341
|
+
// The user chose the legacy-style config. Transform them to new-style
|
|
342
|
+
// config.
|
|
343
|
+
schemaAssert(typeof originalOptions[0] === "string",
|
|
344
|
+
"first header option after severity should be either a filename or 'block' | 'line'");
|
|
345
|
+
/** @type {NewHeaderOptions} */
|
|
346
|
+
const transformedOptions = {};
|
|
347
|
+
// populate header
|
|
348
|
+
if (
|
|
349
|
+
originalOptions.length === 1
|
|
350
|
+
|| (
|
|
351
|
+
originalOptions.length === 2
|
|
352
|
+
&& typeof originalOptions[1] === "object"
|
|
353
|
+
&& !Array.isArray(originalOptions[1])
|
|
354
|
+
&& !isPattern(originalOptions[1])
|
|
355
|
+
)) {
|
|
356
|
+
transformedOptions.header = { file: originalOptions[0], encoding: "utf8" };
|
|
357
|
+
} else {
|
|
358
|
+
schemaAssert(Object.prototype.hasOwnProperty.call(commentTypeOptions, originalOptions[0]),
|
|
359
|
+
"Only 'block' or 'line' is accepted as comment type");
|
|
360
|
+
schemaAssert(
|
|
361
|
+
typeof originalOptions[1] === "string"
|
|
362
|
+
|| Array.isArray(originalOptions[1])
|
|
363
|
+
|| isPattern(originalOptions[1]),
|
|
364
|
+
"second header option after severity should be a string, a pattern, or an array of the previous two");
|
|
365
|
+
transformedOptions.header = {
|
|
366
|
+
commentType: originalOptions[0],
|
|
367
|
+
lines: Array.isArray(originalOptions[1]) ? originalOptions[1] : [originalOptions[1]]
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// configure required line settings
|
|
371
|
+
if (originalOptions.length >= 3) {
|
|
372
|
+
if (typeof originalOptions[2] === "number") {
|
|
373
|
+
transformedOptions.trailingEmptyLines = { minimum: originalOptions[2] };
|
|
374
|
+
if (originalOptions.length === 4) {
|
|
375
|
+
schemaAssert(typeof originalOptions[3] === "object",
|
|
376
|
+
"Fourth header option after severity should be either number of required trailing empty lines or " +
|
|
377
|
+
"a settings object");
|
|
378
|
+
Object.assign(transformedOptions, originalOptions[3]);
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
schemaAssert(typeof originalOptions[2] === "object",
|
|
382
|
+
"Third header option after severity should be either number of required trailing empty lines or a " +
|
|
383
|
+
"settings object");
|
|
384
|
+
Object.assign(transformedOptions, originalOptions[2]);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return transformedOptions;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Transforms a set of new-style options adding defaults and standardizing on
|
|
392
|
+
* one of multiple config styles.
|
|
393
|
+
* @param {NewHeaderOptions} originalOptions new-style options to normalize.
|
|
394
|
+
* @returns {NewHeaderOptions} normalized options.
|
|
395
|
+
*/
|
|
396
|
+
function normalizeOptions(originalOptions) {
|
|
397
|
+
const options = structuredClone(originalOptions);
|
|
398
|
+
|
|
399
|
+
if (options.header.file) {
|
|
400
|
+
const text = fs.readFileSync(originalOptions.header.file, originalOptions.header.encoding || "utf8");
|
|
401
|
+
const [commentType, lines] = commentParser(text);
|
|
402
|
+
options.header = { commentType, lines };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
options.header.lines = options.header.lines.flatMap(
|
|
406
|
+
(line) => {
|
|
407
|
+
if (typeof line === "string") {
|
|
408
|
+
return line.split(/\r?\n/);
|
|
409
|
+
}
|
|
410
|
+
if (line instanceof RegExp) {
|
|
411
|
+
return [{ pattern: line }];
|
|
412
|
+
}
|
|
413
|
+
assert.ok(Object.prototype.hasOwnProperty.call(line, "pattern"));
|
|
414
|
+
const pattern = line.pattern instanceof RegExp ? line.pattern : new RegExp(line.pattern);
|
|
415
|
+
if (Object.prototype.hasOwnProperty.call(line, "template")) {
|
|
416
|
+
return [{
|
|
417
|
+
pattern,
|
|
418
|
+
template: line.template
|
|
419
|
+
}];
|
|
420
|
+
}
|
|
421
|
+
return [{ pattern }];
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
if (!options.lineEndings) {
|
|
425
|
+
options.lineEndings = "os";
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (!options.trailingEmptyLines) {
|
|
429
|
+
options.trailingEmptyLines = {};
|
|
430
|
+
}
|
|
431
|
+
if (typeof options.trailingEmptyLines.minimum !== "number") {
|
|
432
|
+
options.trailingEmptyLines.minimum = 1;
|
|
433
|
+
}
|
|
434
|
+
return options;
|
|
435
|
+
}
|
|
436
|
+
|
|
297
437
|
/**
|
|
298
438
|
* Calculates the source location of the violation that not enough empty lines
|
|
299
439
|
* follow the header.
|
|
@@ -328,7 +468,15 @@ module.exports = {
|
|
|
328
468
|
},
|
|
329
469
|
fixable: "whitespace",
|
|
330
470
|
schema,
|
|
331
|
-
defaultOptions: [
|
|
471
|
+
defaultOptions: [
|
|
472
|
+
/** @type {HeaderOptions} */
|
|
473
|
+
{
|
|
474
|
+
lineEndings: lineEndingOptions.os,
|
|
475
|
+
trailingEmptyLines: {
|
|
476
|
+
minimum: 1
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
],
|
|
332
480
|
messages: {
|
|
333
481
|
headerLineMismatchAtPos: "header line does not match expected after this position; expected: {{expected}}",
|
|
334
482
|
headerLineTooLong: "header line longer than expected",
|
|
@@ -348,46 +496,26 @@ module.exports = {
|
|
|
348
496
|
* @returns {NodeListener} the rule definition.
|
|
349
497
|
*/
|
|
350
498
|
create: function(context) {
|
|
351
|
-
let options = context.options;
|
|
352
|
-
const numNewlines = options.length > 2 && typeof options[2] === "number" ? options[2] : 1;
|
|
353
|
-
const eol = getEOL(options);
|
|
354
|
-
|
|
355
|
-
// If just one option then read comment from file
|
|
356
|
-
if (options.length === 1 || (options.length === 2 && findSettings(options))) {
|
|
357
|
-
const text = fs.readFileSync(context.options[0], "utf8");
|
|
358
|
-
options = commentParser(text);
|
|
359
|
-
}
|
|
360
499
|
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
500
|
+
const newStyleOptions = transformLegacyOptions(context.options);
|
|
501
|
+
const options = normalizeOptions(newStyleOptions);
|
|
502
|
+
|
|
503
|
+
const eol = getEol(options.lineEndings);
|
|
504
|
+
|
|
364
505
|
let fixLines = [];
|
|
365
506
|
// If any of the lines are regular expressions, then we can't
|
|
366
507
|
// automatically fix them. We set this to true below once we
|
|
367
508
|
// ensure none of the lines are of type RegExp
|
|
368
|
-
let canFix =
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
canFix = false;
|
|
376
|
-
}
|
|
377
|
-
fixLines.push(line.template || line);
|
|
378
|
-
return isRegex ? new RegExp(line.pattern) : line;
|
|
379
|
-
});
|
|
380
|
-
} else if (isPattern(options[1])) {
|
|
381
|
-
const line = options[1];
|
|
382
|
-
headerLines = [new RegExp(line.pattern)];
|
|
509
|
+
let canFix = true;
|
|
510
|
+
const headerLines = options.header.lines.map(function(line) {
|
|
511
|
+
const isRegex = isPattern(line);
|
|
512
|
+
// Can only fix regex option if a template is also provided
|
|
513
|
+
if (isRegex && !line.template) {
|
|
514
|
+
canFix = false;
|
|
515
|
+
}
|
|
383
516
|
fixLines.push(line.template || line);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
} else {
|
|
387
|
-
canFix = true;
|
|
388
|
-
headerLines = options[1].split(/\r?\n/);
|
|
389
|
-
fixLines = headerLines;
|
|
390
|
-
}
|
|
517
|
+
return isRegex ? line.pattern : line;
|
|
518
|
+
});
|
|
391
519
|
|
|
392
520
|
return {
|
|
393
521
|
/**
|
|
@@ -397,8 +525,9 @@ module.exports = {
|
|
|
397
525
|
* @returns {void}
|
|
398
526
|
*/
|
|
399
527
|
Program: function(node) {
|
|
400
|
-
|
|
401
|
-
|
|
528
|
+
const sourceCode = contextSourceCode(context);
|
|
529
|
+
if (!hasHeader(sourceCode.text)) {
|
|
530
|
+
const hasShebang = sourceCode.text.startsWith("#!");
|
|
402
531
|
const line = hasShebang ? 2 : 1;
|
|
403
532
|
context.report({
|
|
404
533
|
loc: {
|
|
@@ -412,13 +541,19 @@ module.exports = {
|
|
|
412
541
|
}
|
|
413
542
|
},
|
|
414
543
|
messageId: "missingHeader",
|
|
415
|
-
fix: genPrependFixer(
|
|
544
|
+
fix: genPrependFixer(
|
|
545
|
+
options.header.commentType,
|
|
546
|
+
context,
|
|
547
|
+
fixLines,
|
|
548
|
+
eol,
|
|
549
|
+
options.trailingEmptyLines.minimum)
|
|
416
550
|
});
|
|
417
551
|
return;
|
|
418
552
|
}
|
|
553
|
+
|
|
419
554
|
const leadingComments = getLeadingComments(context, node);
|
|
420
555
|
|
|
421
|
-
if (leadingComments[0].type.toLowerCase() !== commentType) {
|
|
556
|
+
if (leadingComments[0].type.toLowerCase() !== options.header.commentType) {
|
|
422
557
|
context.report({
|
|
423
558
|
loc: {
|
|
424
559
|
start: leadingComments[0].loc.start,
|
|
@@ -426,15 +561,21 @@ module.exports = {
|
|
|
426
561
|
},
|
|
427
562
|
messageId: "incorrectCommentType",
|
|
428
563
|
data: {
|
|
429
|
-
commentType: commentType
|
|
564
|
+
commentType: options.header.commentType
|
|
430
565
|
},
|
|
431
566
|
fix: canFix
|
|
432
|
-
? genReplaceFixer(
|
|
567
|
+
? genReplaceFixer(
|
|
568
|
+
options.header.commentType,
|
|
569
|
+
context,
|
|
570
|
+
leadingComments,
|
|
571
|
+
fixLines,
|
|
572
|
+
eol,
|
|
573
|
+
options.trailingEmptyLines.minimum)
|
|
433
574
|
: null
|
|
434
575
|
});
|
|
435
576
|
return;
|
|
436
577
|
}
|
|
437
|
-
if (commentType === commentTypeOptions.line) {
|
|
578
|
+
if (options.header.commentType === commentTypeOptions.line) {
|
|
438
579
|
if (headerLines.length === 1) {
|
|
439
580
|
const leadingCommentValues = leadingComments.map((c) => c.value);
|
|
440
581
|
if (
|
|
@@ -448,7 +589,13 @@ module.exports = {
|
|
|
448
589
|
},
|
|
449
590
|
messageId: "incorrectHeader",
|
|
450
591
|
fix: canFix
|
|
451
|
-
? genReplaceFixer(
|
|
592
|
+
? genReplaceFixer(
|
|
593
|
+
options.header.commentType,
|
|
594
|
+
context,
|
|
595
|
+
leadingComments,
|
|
596
|
+
fixLines,
|
|
597
|
+
eol,
|
|
598
|
+
options.trailingEmptyLines.minimum)
|
|
452
599
|
: null
|
|
453
600
|
});
|
|
454
601
|
return;
|
|
@@ -466,12 +613,12 @@ module.exports = {
|
|
|
466
613
|
},
|
|
467
614
|
fix: canFix
|
|
468
615
|
? genReplaceFixer(
|
|
469
|
-
commentType,
|
|
616
|
+
options.header.commentType,
|
|
470
617
|
context,
|
|
471
618
|
leadingComments,
|
|
472
619
|
fixLines,
|
|
473
620
|
eol,
|
|
474
|
-
|
|
621
|
+
options.trailingEmptyLines.minimum)
|
|
475
622
|
: null
|
|
476
623
|
});
|
|
477
624
|
return;
|
|
@@ -494,12 +641,12 @@ module.exports = {
|
|
|
494
641
|
expected: headerLines[i].substring(j)
|
|
495
642
|
},
|
|
496
643
|
fix: genReplaceFixer(
|
|
497
|
-
commentType,
|
|
644
|
+
options.header.commentType,
|
|
498
645
|
context,
|
|
499
646
|
leadingComments,
|
|
500
647
|
fixLines,
|
|
501
648
|
eol,
|
|
502
|
-
|
|
649
|
+
options.trailingEmptyLines.minimum)
|
|
503
650
|
});
|
|
504
651
|
return;
|
|
505
652
|
}
|
|
@@ -515,12 +662,12 @@ module.exports = {
|
|
|
515
662
|
},
|
|
516
663
|
fix: canFix
|
|
517
664
|
? genReplaceFixer(
|
|
518
|
-
commentType,
|
|
665
|
+
options.header.commentType,
|
|
519
666
|
context,
|
|
520
667
|
leadingComments,
|
|
521
668
|
fixLines,
|
|
522
669
|
eol,
|
|
523
|
-
|
|
670
|
+
options.trailingEmptyLines.minimum)
|
|
524
671
|
: null
|
|
525
672
|
});
|
|
526
673
|
return;
|
|
@@ -537,12 +684,12 @@ module.exports = {
|
|
|
537
684
|
messageId: "headerLineTooLong",
|
|
538
685
|
fix: canFix
|
|
539
686
|
? genReplaceFixer(
|
|
540
|
-
commentType,
|
|
687
|
+
options.header.commentType,
|
|
541
688
|
context,
|
|
542
689
|
leadingComments,
|
|
543
690
|
fixLines,
|
|
544
691
|
eol,
|
|
545
|
-
|
|
692
|
+
options.trailingEmptyLines.minimum)
|
|
546
693
|
: null
|
|
547
694
|
});
|
|
548
695
|
return;
|
|
@@ -563,12 +710,12 @@ module.exports = {
|
|
|
563
710
|
},
|
|
564
711
|
fix: canFix
|
|
565
712
|
? genReplaceFixer(
|
|
566
|
-
commentType,
|
|
713
|
+
options.header.commentType,
|
|
567
714
|
context,
|
|
568
715
|
leadingComments,
|
|
569
716
|
fixLines,
|
|
570
717
|
eol,
|
|
571
|
-
|
|
718
|
+
options.trailingEmptyLines.minimum)
|
|
572
719
|
: null
|
|
573
720
|
});
|
|
574
721
|
return;
|
|
@@ -577,15 +724,15 @@ module.exports = {
|
|
|
577
724
|
}
|
|
578
725
|
}
|
|
579
726
|
|
|
580
|
-
const actualLeadingEmptyLines =
|
|
581
|
-
|
|
582
|
-
const missingEmptyLines =
|
|
727
|
+
const actualLeadingEmptyLines =
|
|
728
|
+
leadingEmptyLines(sourceCode.text.substring(leadingComments[headerLines.length - 1].range[1]));
|
|
729
|
+
const missingEmptyLines = options.trailingEmptyLines.minimum - actualLeadingEmptyLines;
|
|
583
730
|
if (missingEmptyLines > 0) {
|
|
584
731
|
context.report({
|
|
585
732
|
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
586
733
|
messageId: "noNewlineAfterHeader",
|
|
587
734
|
data: {
|
|
588
|
-
expected:
|
|
735
|
+
expected: options.trailingEmptyLines.minimum,
|
|
589
736
|
actual: actualLeadingEmptyLines
|
|
590
737
|
},
|
|
591
738
|
fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
|
|
@@ -711,21 +858,27 @@ module.exports = {
|
|
|
711
858
|
messageId: errorMessageId,
|
|
712
859
|
data: errorMessageData,
|
|
713
860
|
fix: canFix
|
|
714
|
-
? genReplaceFixer(
|
|
861
|
+
? genReplaceFixer(
|
|
862
|
+
options.header.commentType,
|
|
863
|
+
context,
|
|
864
|
+
leadingComments,
|
|
865
|
+
fixLines,
|
|
866
|
+
eol,
|
|
867
|
+
options.trailingEmptyLines.minimum)
|
|
715
868
|
: null
|
|
716
869
|
});
|
|
717
870
|
return;
|
|
718
871
|
}
|
|
719
872
|
|
|
720
|
-
const actualLeadingEmptyLines =
|
|
721
|
-
|
|
722
|
-
const missingEmptyLines =
|
|
873
|
+
const actualLeadingEmptyLines =
|
|
874
|
+
leadingEmptyLines(sourceCode.text.substring(leadingComments[0].range[1]));
|
|
875
|
+
const missingEmptyLines = options.trailingEmptyLines.minimum - actualLeadingEmptyLines;
|
|
723
876
|
if (missingEmptyLines > 0) {
|
|
724
877
|
context.report({
|
|
725
878
|
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
726
879
|
messageId: "noNewlineAfterHeader",
|
|
727
880
|
data: {
|
|
728
|
-
expected:
|
|
881
|
+
expected: options.trailingEmptyLines.minimum,
|
|
729
882
|
actual: actualLeadingEmptyLines
|
|
730
883
|
},
|
|
731
884
|
fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
|
|
@@ -735,3 +888,4 @@ module.exports = {
|
|
|
735
888
|
};
|
|
736
889
|
}
|
|
737
890
|
};
|
|
891
|
+
|