@tony.ganchev/eslint-plugin-header 3.2.5 → 3.3.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/README.md +291 -2
- package/lib/rules/eslint-utils.js +1 -1
- package/lib/rules/header.js +536 -537
- package/lib/rules/header.schema.js +24 -6
- package/package.json +9 -8
- package/types/lib/rules/header.d.ts +18 -0
- package/types/lib/rules/header.d.ts.map +1 -1
- package/types/lib/rules/header.schema.d.ts.map +1 -1
package/lib/rules/header.js
CHANGED
|
@@ -33,8 +33,9 @@ const { description, recommended } = require("./header.docs");
|
|
|
33
33
|
const { lineEndingOptions, commentTypeOptions, schema } = require("./header.schema");
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* @import { Linter, Rule } from "eslint"
|
|
37
|
-
* @import { Comment, SourceLocation
|
|
36
|
+
* @import { JSSyntaxElement, Linter, Rule, SourceCode } from "eslint"
|
|
37
|
+
* @import { Comment, SourceLocation } from "estree"
|
|
38
|
+
* @import { ViolationReport } from "@eslint/core";
|
|
38
39
|
* @typedef {Rule.NodeListener} NodeListener
|
|
39
40
|
* @typedef {Rule.ReportFixer} ReportFixer
|
|
40
41
|
* @typedef {Rule.RuleFixer} RuleFixer
|
|
@@ -88,6 +89,14 @@ const { lineEndingOptions, commentTypeOptions, schema } = require("./header.sche
|
|
|
88
89
|
* lines together.
|
|
89
90
|
*/
|
|
90
91
|
|
|
92
|
+
/**
|
|
93
|
+
* @typedef {object} LeadingComments A set of comments that can appear before
|
|
94
|
+
* the header.
|
|
95
|
+
* @property {(FileBasedConfig | InlineConfig)[]} comments The set of comments
|
|
96
|
+
* that are allowed. If none of the matching rules matches the first comment the
|
|
97
|
+
* rule assumes the first comment *is* the header.
|
|
98
|
+
*/
|
|
99
|
+
|
|
91
100
|
/**
|
|
92
101
|
* @typedef {object} TrailingEmptyLines Rule configuration on the handling of
|
|
93
102
|
* empty lines after the header comment.
|
|
@@ -99,6 +108,9 @@ const { lineEndingOptions, commentTypeOptions, schema } = require("./header.sche
|
|
|
99
108
|
* @typedef {object} HeaderOptionsWithoutSettings
|
|
100
109
|
* @property {FileBasedConfig | InlineConfig} header The text matching rules
|
|
101
110
|
* for the header.
|
|
111
|
+
* @property {LeadingComments} [leadingComments] The set of allowed comments to
|
|
112
|
+
* precede the header. Useful to allow position-sensitive pragma comments for
|
|
113
|
+
* certain tools.
|
|
102
114
|
* @property {TrailingEmptyLines} [trailingEmptyLines] Rules about empty lines
|
|
103
115
|
* after the header comment.
|
|
104
116
|
*/
|
|
@@ -167,67 +179,66 @@ function match(actual, expected) {
|
|
|
167
179
|
}
|
|
168
180
|
|
|
169
181
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* lines.
|
|
173
|
-
* @
|
|
174
|
-
*
|
|
182
|
+
* Returns an array of comment groups before the actual code.
|
|
183
|
+
* Block comments form single-element groups.
|
|
184
|
+
* Line comments without empty lines between them form grouped elements.
|
|
185
|
+
* @param {SourceCode} sourceCode AST.
|
|
186
|
+
* @returns {Comment[][]} Array of groups of leading comments.
|
|
175
187
|
*/
|
|
176
|
-
function
|
|
188
|
+
function getLeadingComments(sourceCode) {
|
|
189
|
+
const all = sourceCode.getAllComments();
|
|
190
|
+
if (all.length === 0) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
// Determine where the actual code starts. If no code, use end of file.
|
|
194
|
+
const firstToken = sourceCode.getFirstToken(sourceCode.ast);
|
|
195
|
+
const codeStart = firstToken ? firstToken.range[0] : sourceCode.text.length;
|
|
196
|
+
// Filter comments that appear before the actual code starts.
|
|
197
|
+
const commentsBeforeCode = all.filter((c) => /** @type {[number, number]} */(c.range)[1] <= codeStart);
|
|
198
|
+
if (commentsBeforeCode.length === 0) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
/** @type {Comment[][]} */
|
|
202
|
+
const groups = [];
|
|
177
203
|
/** @type {Comment[]} */
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* TypeScript helper to confirm defined type.
|
|
185
|
-
* @template T Target type to validate for definiteness.
|
|
186
|
-
* @param {T | undefined} val The value to validate.
|
|
187
|
-
* @returns {asserts val is T} Validates defined type.
|
|
188
|
-
*/
|
|
189
|
-
function assertDefined(val) {
|
|
190
|
-
assert.strict.notEqual(typeof val, "undefined");
|
|
191
|
-
}
|
|
204
|
+
let currentGroup = [];
|
|
205
|
+
for (let i = 0; i < commentsBeforeCode.length; ++i) {
|
|
206
|
+
const comment = commentsBeforeCode[i];
|
|
192
207
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
208
|
+
if (comment.type === "Block") {
|
|
209
|
+
// Push any existing current group first
|
|
210
|
+
if (currentGroup.length > 0) {
|
|
211
|
+
groups.push(currentGroup);
|
|
212
|
+
currentGroup = [];
|
|
213
|
+
}
|
|
214
|
+
groups.push([comment]);
|
|
215
|
+
} else {
|
|
216
|
+
if (currentGroup.length === 0) {
|
|
217
|
+
currentGroup.push(comment);
|
|
218
|
+
} else {
|
|
219
|
+
const previous = currentGroup[currentGroup.length - 1];
|
|
220
|
+
const previousRange = /** @type {[number, number]} */ (previous.range);
|
|
221
|
+
const currentRange = /** @type {[number, number]} */ (comment.range);
|
|
222
|
+
const txt = sourceCode.text.slice(previousRange[1], currentRange[0]);
|
|
202
223
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const all = excludeShebangs(sourceCode.getAllComments());
|
|
214
|
-
assert.ok(all);
|
|
215
|
-
assert.ok(all.length);
|
|
216
|
-
if (all[0].type.toLowerCase() === commentTypeOptions.block) {
|
|
217
|
-
return [all[0]];
|
|
218
|
-
}
|
|
219
|
-
let i = 1;
|
|
220
|
-
for (; i < all.length; ++i) {
|
|
221
|
-
const previousRange = all[i - 1].range;
|
|
222
|
-
assertDefined(previousRange);
|
|
223
|
-
const currentRange = all[i].range;
|
|
224
|
-
assertDefined(currentRange);
|
|
225
|
-
const txt = sourceCode.text.slice(previousRange[1], currentRange[0]);
|
|
226
|
-
if (!txt.match(/^(\r\n|\r|\n)$/)) {
|
|
227
|
-
break;
|
|
224
|
+
// If there is more than 1 newline, there is an empty line
|
|
225
|
+
// between comments.
|
|
226
|
+
const newlineCount = /** @type {RegExpMatchArray} */ (txt.match(/\r?\n/g)).length;
|
|
227
|
+
if (newlineCount <= 1) {
|
|
228
|
+
currentGroup.push(comment);
|
|
229
|
+
} else {
|
|
230
|
+
groups.push(currentGroup);
|
|
231
|
+
currentGroup = [comment];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
228
234
|
}
|
|
229
235
|
}
|
|
230
|
-
|
|
236
|
+
|
|
237
|
+
if (currentGroup.length > 0) {
|
|
238
|
+
groups.push(currentGroup);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return groups;
|
|
231
242
|
}
|
|
232
243
|
|
|
233
244
|
/**
|
|
@@ -257,14 +268,10 @@ function genCommentBody(commentType, textArray, eol) {
|
|
|
257
268
|
function genCommentsRange(comments) {
|
|
258
269
|
assert.ok(comments.length);
|
|
259
270
|
const firstComment = comments[0];
|
|
260
|
-
|
|
261
|
-
const firstCommentRange = firstComment.range;
|
|
262
|
-
assertDefined(firstCommentRange);
|
|
271
|
+
const firstCommentRange = /** @type {[number, number]} */ (firstComment.range);
|
|
263
272
|
const start = firstCommentRange[0];
|
|
264
273
|
const lastComment = comments.slice(-1)[0];
|
|
265
|
-
|
|
266
|
-
const lastCommentRange = lastComment.range;
|
|
267
|
-
assertDefined(lastCommentRange);
|
|
274
|
+
const lastCommentRange = /** @type {[number, number]} */ (lastComment.range);
|
|
268
275
|
const end = lastCommentRange[1];
|
|
269
276
|
return [start, end];
|
|
270
277
|
}
|
|
@@ -292,17 +299,16 @@ function leadingEmptyLines(src) {
|
|
|
292
299
|
/**
|
|
293
300
|
* Factory for fixer that adds a missing header.
|
|
294
301
|
* @param {CommentType} commentType Type of comment to use.
|
|
295
|
-
* @param {
|
|
302
|
+
* @param {SourceCode} sourceCode AST.
|
|
296
303
|
* @param {string[]} headerLines Lines of the header comment.
|
|
297
304
|
* @param {LineEnding} eol End-of-line characters.
|
|
298
305
|
* @param {number} numNewlines Number of trailing lines after the comment.
|
|
299
306
|
* @returns {ReportFixer} The fix to apply.
|
|
300
307
|
*/
|
|
301
|
-
function genPrependFixer(commentType,
|
|
302
|
-
return function(fixer) {
|
|
308
|
+
function genPrependFixer(commentType, sourceCode, headerLines, eol, numNewlines) {
|
|
309
|
+
return function (fixer) {
|
|
303
310
|
let insertPos = 0;
|
|
304
311
|
let newHeader = genCommentBody(commentType, headerLines, eol);
|
|
305
|
-
const sourceCode = contextSourceCode(context);
|
|
306
312
|
if (sourceCode.text.startsWith("#!")) {
|
|
307
313
|
const firstNewLinePos = sourceCode.text.indexOf("\n");
|
|
308
314
|
insertPos = firstNewLinePos === -1 ? sourceCode.text.length : firstNewLinePos + 1;
|
|
@@ -323,17 +329,17 @@ function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
|
|
|
323
329
|
/**
|
|
324
330
|
* Factory for fixer that replaces an incorrect header.
|
|
325
331
|
* @param {CommentType} commentType Type of comment to use.
|
|
326
|
-
* @param {
|
|
332
|
+
* @param {SourceCode} sourceCode AST.
|
|
327
333
|
* @param {Comment[]} leadingComments Comment elements to replace.
|
|
328
334
|
* @param {string[]} headerLines Lines of the header comment.
|
|
329
335
|
* @param {LineEnding} eol End-of-line characters.
|
|
330
336
|
* @param {number} numNewlines Number of trailing lines after the comment.
|
|
331
337
|
* @returns {ReportFixer} The fix to apply.
|
|
332
338
|
*/
|
|
333
|
-
function genReplaceFixer(commentType,
|
|
334
|
-
return function(fixer) {
|
|
339
|
+
function genReplaceFixer(commentType, sourceCode, leadingComments, headerLines, eol, numNewlines) {
|
|
340
|
+
return function (fixer) {
|
|
335
341
|
const commentRange = genCommentsRange(leadingComments);
|
|
336
|
-
const emptyLines = leadingEmptyLines(
|
|
342
|
+
const emptyLines = leadingEmptyLines(sourceCode.text.substring(commentRange[1]));
|
|
337
343
|
const missingNewlines = Math.max(0, numNewlines - emptyLines);
|
|
338
344
|
const eols = eol.repeat(missingNewlines);
|
|
339
345
|
return fixer.replaceTextRange(
|
|
@@ -352,7 +358,7 @@ function genReplaceFixer(commentType, context, leadingComments, headerLines, eol
|
|
|
352
358
|
* @returns {ReportFixer} The fix to apply.
|
|
353
359
|
*/
|
|
354
360
|
function genEmptyLinesFixer(leadingComments, eol, missingEmptyLinesCount) {
|
|
355
|
-
return function(fixer) {
|
|
361
|
+
return function (fixer) {
|
|
356
362
|
return fixer.insertTextAfterRange(
|
|
357
363
|
genCommentsRange(leadingComments),
|
|
358
364
|
eol.repeat(missingEmptyLinesCount)
|
|
@@ -380,17 +386,6 @@ function getEol(style) {
|
|
|
380
386
|
}
|
|
381
387
|
}
|
|
382
388
|
|
|
383
|
-
/**
|
|
384
|
-
* Tests if the first line in the source code (after a Unix she-bang) is a
|
|
385
|
-
* comment. Does not tolerate empty lines before the first match.
|
|
386
|
-
* @param {string} src Source code to test.
|
|
387
|
-
* @returns {boolean} `true` if there is a comment or `false` otherwise.
|
|
388
|
-
*/
|
|
389
|
-
function hasHeader(src) {
|
|
390
|
-
const srcWithoutShebang = src.replace(/^#![^\n]*\r?\n/, "");
|
|
391
|
-
return srcWithoutShebang.startsWith("/*") || srcWithoutShebang.startsWith("//");
|
|
392
|
-
}
|
|
393
|
-
|
|
394
389
|
/**
|
|
395
390
|
* Asserts on an expression and adds template texts to the failure message.
|
|
396
391
|
* Helper to write cleaner code.
|
|
@@ -462,14 +457,14 @@ function transformLegacyOptions(originalOptions) {
|
|
|
462
457
|
transformedOptions.trailingEmptyLines = { minimum: originalOptions[2] };
|
|
463
458
|
if (originalOptions.length === 4) {
|
|
464
459
|
schemaAssert(typeof originalOptions[3] === "object",
|
|
465
|
-
"Fourth header option after severity should be either number of required trailing empty lines or "
|
|
466
|
-
"a settings object");
|
|
460
|
+
"Fourth header option after severity should be either number of required trailing empty lines or "
|
|
461
|
+
+ "a settings object");
|
|
467
462
|
Object.assign(transformedOptions, originalOptions[3]);
|
|
468
463
|
}
|
|
469
464
|
} else {
|
|
470
465
|
schemaAssert(typeof originalOptions[2] === "object",
|
|
471
|
-
"Third header option after severity should be either number of required trailing empty lines or a "
|
|
472
|
-
"settings object");
|
|
466
|
+
"Third header option after severity should be either number of required trailing empty lines or a "
|
|
467
|
+
+ "settings object");
|
|
473
468
|
Object.assign(transformedOptions, originalOptions[2]);
|
|
474
469
|
}
|
|
475
470
|
}
|
|
@@ -483,35 +478,23 @@ function transformLegacyOptions(originalOptions) {
|
|
|
483
478
|
* else `false`.
|
|
484
479
|
*/
|
|
485
480
|
function isFileBasedHeaderConfig(config) {
|
|
486
|
-
return
|
|
481
|
+
return "file" in config;
|
|
487
482
|
}
|
|
488
483
|
|
|
489
484
|
/**
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
* @
|
|
493
|
-
*
|
|
485
|
+
* Transforms file template-based matching rules to inline rules for further
|
|
486
|
+
* use.
|
|
487
|
+
* @param {FileBasedConfig | InlineConfig} matcher The matching rule.
|
|
488
|
+
* @returns {InlineConfig} The resulting normalized configuration.
|
|
494
489
|
*/
|
|
495
|
-
function
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Transforms a set of new-style options adding defaults and standardizing on
|
|
501
|
-
* one of multiple config styles.
|
|
502
|
-
* @param {HeaderOptions} originalOptions New-style options to normalize.
|
|
503
|
-
* @returns {HeaderOptions} Normalized options.
|
|
504
|
-
*/
|
|
505
|
-
function normalizeOptions(originalOptions) {
|
|
506
|
-
const options = structuredClone(originalOptions);
|
|
507
|
-
|
|
508
|
-
if (isFileBasedHeaderConfig(originalOptions.header)) {
|
|
509
|
-
const text = fs.readFileSync(originalOptions.header.file, originalOptions.header.encoding || "utf8");
|
|
490
|
+
function normalizeMatchingRules(matcher) {
|
|
491
|
+
if (isFileBasedHeaderConfig(matcher)) {
|
|
492
|
+
const text = fs.readFileSync(matcher.file, matcher.encoding || "utf8");
|
|
510
493
|
const [commentType, lines] = commentParser(text);
|
|
511
|
-
|
|
494
|
+
return { commentType, lines };
|
|
512
495
|
}
|
|
513
|
-
|
|
514
|
-
|
|
496
|
+
const commentType = matcher.commentType;
|
|
497
|
+
const lines = matcher.lines.flatMap(
|
|
515
498
|
(line) => {
|
|
516
499
|
if (typeof line === "string") {
|
|
517
500
|
return /** @type {HeaderLine[]} */(line.split(/\r?\n/));
|
|
@@ -529,11 +512,31 @@ function normalizeOptions(originalOptions) {
|
|
|
529
512
|
}
|
|
530
513
|
return [{ pattern }];
|
|
531
514
|
});
|
|
515
|
+
return { commentType, lines };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Transforms a set of new-style options adding defaults and standardizing on
|
|
520
|
+
* one of multiple config styles.
|
|
521
|
+
* @param {HeaderOptions} originalOptions New-style options to normalize.
|
|
522
|
+
* @returns {HeaderOptions} Normalized options.
|
|
523
|
+
*/
|
|
524
|
+
function normalizeOptions(originalOptions) {
|
|
525
|
+
const options = structuredClone(originalOptions);
|
|
526
|
+
|
|
527
|
+
options.header = normalizeMatchingRules(originalOptions.header);
|
|
532
528
|
|
|
533
529
|
if (!options.lineEndings) {
|
|
534
530
|
options.lineEndings = "os";
|
|
535
531
|
}
|
|
536
532
|
|
|
533
|
+
if (originalOptions.leadingComments) {
|
|
534
|
+
options.leadingComments = {
|
|
535
|
+
comments: originalOptions.leadingComments.comments.map((c) => normalizeMatchingRules(c))
|
|
536
|
+
};
|
|
537
|
+
} else {
|
|
538
|
+
options.leadingComments = { comments: [] };
|
|
539
|
+
}
|
|
537
540
|
if (!options.trailingEmptyLines) {
|
|
538
541
|
options.trailingEmptyLines = {};
|
|
539
542
|
}
|
|
@@ -558,9 +561,7 @@ function normalizeOptions(originalOptions) {
|
|
|
558
561
|
*/
|
|
559
562
|
function missingEmptyLinesViolationLoc(leadingComments, actualEmptyLines) {
|
|
560
563
|
assert.ok(leadingComments);
|
|
561
|
-
const loc = leadingComments[leadingComments.length - 1].loc;
|
|
562
|
-
assertDefined(loc);
|
|
563
|
-
assertNotNull(loc);
|
|
564
|
+
const loc = /** @type {SourceLocation} */ (leadingComments[leadingComments.length - 1].loc);
|
|
564
565
|
const lastCommentLineLocEnd = loc.end;
|
|
565
566
|
return {
|
|
566
567
|
start: lastCommentLineLocEnd,
|
|
@@ -571,6 +572,322 @@ function missingEmptyLinesViolationLoc(leadingComments, actualEmptyLines) {
|
|
|
571
572
|
};
|
|
572
573
|
}
|
|
573
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Matches comments against of header content-matching rules. An object performs
|
|
577
|
+
* a number of expensive operations only once and thus can be used multiple
|
|
578
|
+
* times to test different comments.
|
|
579
|
+
*/
|
|
580
|
+
class CommentMatcher {
|
|
581
|
+
/**
|
|
582
|
+
* Initializes the matcher for a specific comment-matching rules.
|
|
583
|
+
* @param {InlineConfig} headerConfig Content-matching rules.
|
|
584
|
+
* @param {string} eol The EOL characters used.
|
|
585
|
+
* @param {number} numLines The requirred minimum number of trailing empty
|
|
586
|
+
* lines.
|
|
587
|
+
*/
|
|
588
|
+
constructor(headerConfig, eol, numLines) {
|
|
589
|
+
this.commentType = headerConfig.commentType;
|
|
590
|
+
this.headerLines = headerConfig.lines.map((line) => isPattern(line) ? line.pattern : line);
|
|
591
|
+
this.eol = eol;
|
|
592
|
+
this.numLines = numLines;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* @typedef {ViolationReport<JSSyntaxElement, string>} ViolationReportBad
|
|
597
|
+
* @typedef {ViolationReportBad & { messageId: string }} ViolationReportEx
|
|
598
|
+
*/
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Performs a validation of a comment against a header matching
|
|
602
|
+
* configuration.
|
|
603
|
+
* @param {Comment[]} leadingComments The block comment or sequence of line
|
|
604
|
+
* comments to test.
|
|
605
|
+
* @param {SourceCode} sourceCode The source code AST.
|
|
606
|
+
* @returns {ViolationReportEx | null} If set a
|
|
607
|
+
* violation report to pass back to ESLint or interpret as necessary.
|
|
608
|
+
*/
|
|
609
|
+
validate(leadingComments, sourceCode) {
|
|
610
|
+
|
|
611
|
+
const firstLeadingCommentLoc = /** @type {SourceLocation} */ (leadingComments[0].loc);
|
|
612
|
+
const firstLeadingCommentRange = /** @type {[number, number]} */ (leadingComments[0].range);
|
|
613
|
+
|
|
614
|
+
const lastLeadingCommentLoc = /** @type {SourceLocation} */ (leadingComments[leadingComments.length - 1].loc);
|
|
615
|
+
|
|
616
|
+
if (leadingComments[0].type.toLowerCase() !== this.commentType) {
|
|
617
|
+
return {
|
|
618
|
+
loc: {
|
|
619
|
+
start: firstLeadingCommentLoc.start,
|
|
620
|
+
end: lastLeadingCommentLoc.end
|
|
621
|
+
},
|
|
622
|
+
messageId: "incorrectCommentType",
|
|
623
|
+
data: {
|
|
624
|
+
commentType: this.commentType
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
if (this.commentType === commentTypeOptions.line) {
|
|
629
|
+
if (this.headerLines.length === 1) {
|
|
630
|
+
const leadingCommentValues = leadingComments.map((c) => c.value);
|
|
631
|
+
if (
|
|
632
|
+
!match(leadingCommentValues.join("\n"), this.headerLines[0])
|
|
633
|
+
&& !match(leadingCommentValues.join("\r\n"), this.headerLines[0])
|
|
634
|
+
) {
|
|
635
|
+
return {
|
|
636
|
+
loc: {
|
|
637
|
+
start: firstLeadingCommentLoc.start,
|
|
638
|
+
end: lastLeadingCommentLoc.end
|
|
639
|
+
},
|
|
640
|
+
messageId: "incorrectHeader",
|
|
641
|
+
data: {
|
|
642
|
+
pattern: this.headerLines[0].toString()
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
} else {
|
|
647
|
+
for (let i = 0; i < this.headerLines.length; i++) {
|
|
648
|
+
if (leadingComments.length - 1 < i) {
|
|
649
|
+
return {
|
|
650
|
+
loc: {
|
|
651
|
+
start: lastLeadingCommentLoc.end,
|
|
652
|
+
end: lastLeadingCommentLoc.end
|
|
653
|
+
},
|
|
654
|
+
messageId: "headerTooShort",
|
|
655
|
+
data: {
|
|
656
|
+
remainder: this.headerLines.slice(i).join(this.eol)
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
const headerLine = this.headerLines[i];
|
|
661
|
+
const comment = leadingComments[i];
|
|
662
|
+
const commentLoc = /** @type {SourceLocation} */ (comment.loc);
|
|
663
|
+
if (typeof headerLine === "string") {
|
|
664
|
+
const leadingCommentLength = comment.value.length;
|
|
665
|
+
const headerLineLength = headerLine.length;
|
|
666
|
+
for (let j = 0; j < Math.min(leadingCommentLength, headerLineLength); j++) {
|
|
667
|
+
if (comment.value[j] !== headerLine[j]) {
|
|
668
|
+
return {
|
|
669
|
+
loc: {
|
|
670
|
+
start: {
|
|
671
|
+
column: "//".length + j,
|
|
672
|
+
line: commentLoc.start.line
|
|
673
|
+
},
|
|
674
|
+
end: commentLoc.end
|
|
675
|
+
},
|
|
676
|
+
messageId: "headerLineMismatchAtPos",
|
|
677
|
+
data: {
|
|
678
|
+
expected: headerLine.substring(j)
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (leadingCommentLength < headerLineLength) {
|
|
684
|
+
return {
|
|
685
|
+
loc: {
|
|
686
|
+
start: commentLoc.end,
|
|
687
|
+
end: commentLoc.end,
|
|
688
|
+
},
|
|
689
|
+
messageId: "headerLineTooShort",
|
|
690
|
+
data: {
|
|
691
|
+
remainder: headerLine.substring(leadingCommentLength)
|
|
692
|
+
},
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
if (leadingCommentLength > headerLineLength) {
|
|
696
|
+
return {
|
|
697
|
+
loc: {
|
|
698
|
+
start: {
|
|
699
|
+
column: "//".length + headerLineLength,
|
|
700
|
+
line: commentLoc.start.line
|
|
701
|
+
},
|
|
702
|
+
end: commentLoc.end,
|
|
703
|
+
},
|
|
704
|
+
messageId: "headerLineTooLong",
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
if (!match(comment.value, headerLine)) {
|
|
709
|
+
return {
|
|
710
|
+
loc: {
|
|
711
|
+
start: {
|
|
712
|
+
column: "//".length,
|
|
713
|
+
line: commentLoc.start.line,
|
|
714
|
+
},
|
|
715
|
+
end: commentLoc.end,
|
|
716
|
+
},
|
|
717
|
+
messageId: "incorrectHeaderLine",
|
|
718
|
+
data: {
|
|
719
|
+
pattern: headerLine.toString()
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const commentRange = /** @type {[number, number]} */ (leadingComments[this.headerLines.length - 1].range);
|
|
728
|
+
const actualLeadingEmptyLines = leadingEmptyLines(sourceCode.text.substring(commentRange[1]));
|
|
729
|
+
const missingEmptyLines = this.numLines - actualLeadingEmptyLines;
|
|
730
|
+
if (missingEmptyLines > 0) {
|
|
731
|
+
return {
|
|
732
|
+
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
733
|
+
messageId: "noNewlineAfterHeader",
|
|
734
|
+
data: {
|
|
735
|
+
expected: this.numLines,
|
|
736
|
+
actual: actualLeadingEmptyLines
|
|
737
|
+
},
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
// if block comment pattern has more than 1 line, we also split the
|
|
744
|
+
// comment
|
|
745
|
+
let leadingLines = [leadingComments[0].value];
|
|
746
|
+
if (this.headerLines.length > 1) {
|
|
747
|
+
leadingLines = leadingComments[0].value.split(/\r?\n/);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** @type {null | string} */
|
|
751
|
+
let errorMessageId = null;
|
|
752
|
+
/** @type {undefined | Record<string, string>} */
|
|
753
|
+
let errorMessageData;
|
|
754
|
+
/** @type {null | SourceLocation} */
|
|
755
|
+
let errorMessageLoc = null;
|
|
756
|
+
for (let i = 0; i < this.headerLines.length; i++) {
|
|
757
|
+
if (leadingLines.length - 1 < i) {
|
|
758
|
+
return {
|
|
759
|
+
loc: {
|
|
760
|
+
start: lastLeadingCommentLoc.end,
|
|
761
|
+
end: lastLeadingCommentLoc.end
|
|
762
|
+
},
|
|
763
|
+
messageId: "headerTooShort",
|
|
764
|
+
data: {
|
|
765
|
+
remainder: this.headerLines.slice(i).join(this.eol)
|
|
766
|
+
},
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const leadingLine = leadingLines[i];
|
|
770
|
+
const headerLine = this.headerLines[i];
|
|
771
|
+
if (typeof headerLine === "string") {
|
|
772
|
+
for (let j = 0; j < Math.min(leadingLine.length, headerLine.length); j++) {
|
|
773
|
+
if (leadingLine[j] !== headerLine[j]) {
|
|
774
|
+
errorMessageId = "headerLineMismatchAtPos";
|
|
775
|
+
const columnOffset = i === 0 ? "/*".length : 0;
|
|
776
|
+
const line = firstLeadingCommentLoc.start.line + i;
|
|
777
|
+
errorMessageLoc = {
|
|
778
|
+
start: {
|
|
779
|
+
column: columnOffset + j,
|
|
780
|
+
line
|
|
781
|
+
},
|
|
782
|
+
end: {
|
|
783
|
+
column: columnOffset + leadingLine.length,
|
|
784
|
+
line
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
errorMessageData = {
|
|
788
|
+
expected: headerLine.substring(j)
|
|
789
|
+
};
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (errorMessageId) {
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
if (leadingLine.length < headerLine.length) {
|
|
797
|
+
errorMessageId = "headerLineTooShort";
|
|
798
|
+
const startColumn = (i === 0 ? "/*".length : 0) + leadingLine.length;
|
|
799
|
+
errorMessageLoc = {
|
|
800
|
+
start: {
|
|
801
|
+
column: startColumn,
|
|
802
|
+
line: firstLeadingCommentLoc.start.line + i
|
|
803
|
+
},
|
|
804
|
+
end: {
|
|
805
|
+
column: startColumn + 1,
|
|
806
|
+
line: firstLeadingCommentLoc.start.line + i
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
errorMessageData = {
|
|
810
|
+
remainder: headerLine.substring(leadingLine.length)
|
|
811
|
+
};
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
if (leadingLine.length > headerLine.length) {
|
|
815
|
+
errorMessageId = "headerLineTooLong";
|
|
816
|
+
errorMessageLoc = {
|
|
817
|
+
start: {
|
|
818
|
+
column: (i === 0 ? "/*".length : 0) + headerLine.length,
|
|
819
|
+
line: firstLeadingCommentLoc.start.line + i
|
|
820
|
+
},
|
|
821
|
+
end: {
|
|
822
|
+
column: (i === 0 ? "/*".length : 0) + leadingLine.length,
|
|
823
|
+
line: firstLeadingCommentLoc.start.line + i
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
if (!match(leadingLine, headerLine)) {
|
|
830
|
+
errorMessageId = "incorrectHeaderLine";
|
|
831
|
+
errorMessageData = {
|
|
832
|
+
pattern: headerLine.toString()
|
|
833
|
+
};
|
|
834
|
+
const columnOffset = i === 0 ? "/*".length : 0;
|
|
835
|
+
errorMessageLoc = {
|
|
836
|
+
start: {
|
|
837
|
+
column: columnOffset + 0,
|
|
838
|
+
line: firstLeadingCommentLoc.start.line + i
|
|
839
|
+
},
|
|
840
|
+
end: {
|
|
841
|
+
column: columnOffset + leadingLine.length,
|
|
842
|
+
line: firstLeadingCommentLoc.start.line + i
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (!errorMessageId && leadingLines.length > this.headerLines.length) {
|
|
851
|
+
errorMessageId = "headerTooLong";
|
|
852
|
+
errorMessageLoc = {
|
|
853
|
+
start: {
|
|
854
|
+
column: (this.headerLines.length === 0 ? "/*".length : 0) + 0,
|
|
855
|
+
line: firstLeadingCommentLoc.start.line + this.headerLines.length
|
|
856
|
+
},
|
|
857
|
+
end: {
|
|
858
|
+
column: lastLeadingCommentLoc.end.column - "*/".length,
|
|
859
|
+
line: lastLeadingCommentLoc.end.line
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (errorMessageId) {
|
|
865
|
+
return {
|
|
866
|
+
loc: /** @type {SourceLocation} */ (errorMessageLoc),
|
|
867
|
+
messageId: errorMessageId,
|
|
868
|
+
data: errorMessageData,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const actualLeadingEmptyLines =
|
|
873
|
+
leadingEmptyLines(sourceCode.text.substring(firstLeadingCommentRange[1]));
|
|
874
|
+
const missingEmptyLines = this.numLines - actualLeadingEmptyLines;
|
|
875
|
+
if (missingEmptyLines > 0) {
|
|
876
|
+
return {
|
|
877
|
+
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
878
|
+
messageId: "noNewlineAfterHeader",
|
|
879
|
+
data: {
|
|
880
|
+
expected: this.numLines,
|
|
881
|
+
actual: actualLeadingEmptyLines
|
|
882
|
+
},
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
|
|
574
891
|
/** @type {Rule.RuleModule} */
|
|
575
892
|
const headerRule = {
|
|
576
893
|
meta: {
|
|
@@ -591,14 +908,33 @@ const headerRule = {
|
|
|
591
908
|
}
|
|
592
909
|
],
|
|
593
910
|
messages: {
|
|
594
|
-
|
|
911
|
+
// messages customized for header validation.
|
|
912
|
+
headerLineMismatchAtPos:
|
|
913
|
+
"header line does not match expected after this position; expected: '{{expected}}'",
|
|
595
914
|
headerLineTooLong: "header line longer than expected",
|
|
596
|
-
headerLineTooShort: "header line shorter than expected; missing: {{remainder}}",
|
|
597
|
-
headerTooShort: "header too short
|
|
915
|
+
headerLineTooShort: "header line shorter than expected; missing: '{{remainder}}'",
|
|
916
|
+
headerTooShort: "header too short; missing lines: '{{remainder}}'",
|
|
598
917
|
headerTooLong: "header too long",
|
|
599
918
|
incorrectCommentType: "header should be a {{commentType}} comment",
|
|
600
|
-
incorrectHeader: "
|
|
601
|
-
incorrectHeaderLine: "header line does not match pattern: {{pattern}}",
|
|
919
|
+
incorrectHeader: "header does not match pattern: '{{pattern}}'",
|
|
920
|
+
incorrectHeaderLine: "header line does not match pattern: '{{pattern}}'",
|
|
921
|
+
// messages customized for leading comments validation.
|
|
922
|
+
"leadingComment-headerLineMismatchAtPos":
|
|
923
|
+
"leading comment validation failed: line does not match expected after this position; "
|
|
924
|
+
+ "expected: '{{expected}}'",
|
|
925
|
+
"leadingComment-headerLineTooLong": "leading comment validation failed: line longer than expected",
|
|
926
|
+
"leadingComment-headerLineTooShort":
|
|
927
|
+
"leading comment validation failed: line shorter than expected; missing: '{{remainder}}'",
|
|
928
|
+
"leadingComment-headerTooShort":
|
|
929
|
+
"leading comment validation failed: comment too short; missing lines: '{{remainder}}'",
|
|
930
|
+
"leadingComment-headerTooLong": "leading comment validation failed: comment too long",
|
|
931
|
+
"leadingComment-incorrectCommentType":
|
|
932
|
+
"leading comment validation failed: should be a {{commentType}} comment",
|
|
933
|
+
"leadingComment-incorrectHeader":
|
|
934
|
+
"leading comment validation failed: comment does not match pattern: '{{pattern}}'",
|
|
935
|
+
"leadingComment-incorrectHeaderLine":
|
|
936
|
+
"leading comment validation failed: comment line does not match pattern: '{{pattern}}'",
|
|
937
|
+
// messages only applicable to header validation.
|
|
602
938
|
missingHeader: "missing header",
|
|
603
939
|
noNewlineAfterHeader: "not enough newlines after header: expected: {{expected}}, actual: {{actual}}"
|
|
604
940
|
}
|
|
@@ -609,42 +945,24 @@ const headerRule = {
|
|
|
609
945
|
* @param {RuleContext} context ESLint rule execution context.
|
|
610
946
|
* @returns {NodeListener} The rule definition.
|
|
611
947
|
*/
|
|
612
|
-
create: function(context) {
|
|
948
|
+
create: function (context) {
|
|
613
949
|
|
|
614
|
-
const newStyleOptions = transformLegacyOptions(/** @type {AllHeaderOptions} */
|
|
950
|
+
const newStyleOptions = transformLegacyOptions(/** @type {AllHeaderOptions} */(context.options));
|
|
615
951
|
const options = normalizeOptions(newStyleOptions);
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
const eol = getEol(
|
|
621
|
-
/** @type {LineEndingOption} */ (options.lineEndings)
|
|
622
|
-
);
|
|
623
|
-
|
|
624
|
-
/** @type {string[]} */
|
|
625
|
-
let fixLines = [];
|
|
626
|
-
// If any of the lines are regular expressions, then we can't
|
|
627
|
-
// automatically fix them. We set this to true below once we
|
|
628
|
-
// ensure none of the lines are of type RegExp
|
|
629
|
-
let canFix = true;
|
|
630
|
-
const headerLines = options.header.lines.map(function(line) {
|
|
631
|
-
// Can only fix regex option if a template is also provided
|
|
952
|
+
const eol = getEol(/** @type {LineEndingOption} */(options.lineEndings));
|
|
953
|
+
const header = /** @type {InlineConfig} */ (options.header);
|
|
954
|
+
const canFix = !header.lines.some((line) => isPattern(line) && !("template" in line));
|
|
955
|
+
const fixLines = header.lines.map((line) => {
|
|
632
956
|
if (isPattern(line)) {
|
|
633
|
-
|
|
634
|
-
fixLines.push(/** @type {string} */ (line.template));
|
|
635
|
-
} else {
|
|
636
|
-
canFix = false;
|
|
637
|
-
fixLines.push("");
|
|
638
|
-
}
|
|
639
|
-
return line.pattern;
|
|
640
|
-
} else {
|
|
641
|
-
fixLines.push(/** @type {string} */ (line));
|
|
642
|
-
return line;
|
|
957
|
+
return ("template" in line) ? /** @type {string} */(line.template) : "";
|
|
643
958
|
}
|
|
959
|
+
return /** @type {string} */(line);
|
|
644
960
|
});
|
|
645
|
-
|
|
646
|
-
|
|
647
961
|
const numLines = /** @type {number} */ (options.trailingEmptyLines?.minimum);
|
|
962
|
+
const headerMatcher = new CommentMatcher(header, eol, numLines);
|
|
963
|
+
const allowedLeadingComments = /** @type {LeadingComments} */ (options.leadingComments).comments;
|
|
964
|
+
const allowedCommentsMatchers =
|
|
965
|
+
allowedLeadingComments.map((c) => new CommentMatcher(/** @type {InlineConfig} */(c), eol, 0));
|
|
648
966
|
|
|
649
967
|
return {
|
|
650
968
|
/**
|
|
@@ -652,58 +970,36 @@ const headerRule = {
|
|
|
652
970
|
* header validation.
|
|
653
971
|
* @returns {void}
|
|
654
972
|
*/
|
|
655
|
-
Program: function() {
|
|
973
|
+
Program: function () {
|
|
656
974
|
const sourceCode = contextSourceCode(context);
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
975
|
+
const leadingComments = getLeadingComments(sourceCode);
|
|
976
|
+
const hasShebang = leadingComments.length > 0
|
|
977
|
+
&& /** @type {string} */ (leadingComments[0][0].type) === "Shebang";
|
|
978
|
+
let startingHeaderLine = 1;
|
|
979
|
+
if (hasShebang) {
|
|
980
|
+
leadingComments.splice(0, 1);
|
|
981
|
+
startingHeaderLine = 2;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (leadingComments.length === 0
|
|
985
|
+
|| /** @type {SourceLocation} */ (leadingComments[0][0].loc).start.line > startingHeaderLine) {
|
|
986
|
+
|
|
660
987
|
context.report({
|
|
661
988
|
loc: {
|
|
662
989
|
start: {
|
|
663
|
-
column:
|
|
664
|
-
line
|
|
990
|
+
column: 0,
|
|
991
|
+
line: startingHeaderLine
|
|
665
992
|
},
|
|
666
993
|
end: {
|
|
667
|
-
column:
|
|
668
|
-
line
|
|
994
|
+
column: 0,
|
|
995
|
+
line: startingHeaderLine
|
|
669
996
|
}
|
|
670
997
|
},
|
|
671
998
|
messageId: "missingHeader",
|
|
672
|
-
fix: genPrependFixer(
|
|
673
|
-
commentType,
|
|
674
|
-
context,
|
|
675
|
-
fixLines,
|
|
676
|
-
eol,
|
|
677
|
-
numLines)
|
|
678
|
-
});
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
const leadingComments = getLeadingComments(context);
|
|
682
|
-
const firstLeadingCommentLoc = leadingComments[0].loc;
|
|
683
|
-
const firstLeadingCommentRange = leadingComments[0].range;
|
|
684
|
-
assertDefined(firstLeadingCommentRange);
|
|
685
|
-
|
|
686
|
-
const lastLeadingCommentLoc = leadingComments[leadingComments.length - 1].loc;
|
|
687
|
-
|
|
688
|
-
if (leadingComments[0].type.toLowerCase() !== commentType) {
|
|
689
|
-
assertDefined(firstLeadingCommentLoc);
|
|
690
|
-
assertNotNull(firstLeadingCommentLoc);
|
|
691
|
-
assertDefined(lastLeadingCommentLoc);
|
|
692
|
-
assertNotNull(lastLeadingCommentLoc);
|
|
693
|
-
context.report({
|
|
694
|
-
loc: {
|
|
695
|
-
start: firstLeadingCommentLoc.start,
|
|
696
|
-
end: lastLeadingCommentLoc.end
|
|
697
|
-
},
|
|
698
|
-
messageId: "incorrectCommentType",
|
|
699
|
-
data: {
|
|
700
|
-
commentType: commentType
|
|
701
|
-
},
|
|
702
999
|
fix: canFix
|
|
703
|
-
?
|
|
704
|
-
commentType,
|
|
705
|
-
|
|
706
|
-
leadingComments,
|
|
1000
|
+
? genPrependFixer(
|
|
1001
|
+
headerMatcher.commentType,
|
|
1002
|
+
sourceCode,
|
|
707
1003
|
fixLines,
|
|
708
1004
|
eol,
|
|
709
1005
|
numLines)
|
|
@@ -711,366 +1007,69 @@ const headerRule = {
|
|
|
711
1007
|
});
|
|
712
1008
|
return;
|
|
713
1009
|
}
|
|
714
|
-
if (commentType === commentTypeOptions.line) {
|
|
715
|
-
if (headerLines.length === 1) {
|
|
716
|
-
const leadingCommentValues = leadingComments.map((c) => c.value);
|
|
717
|
-
if (
|
|
718
|
-
!match(leadingCommentValues.join("\n"), headerLines[0])
|
|
719
|
-
&& !match(leadingCommentValues.join("\r\n"), headerLines[0])
|
|
720
|
-
) {
|
|
721
|
-
assertDefined(firstLeadingCommentLoc);
|
|
722
|
-
assertNotNull(firstLeadingCommentLoc);
|
|
723
|
-
assertDefined(lastLeadingCommentLoc);
|
|
724
|
-
assertNotNull(lastLeadingCommentLoc);
|
|
725
|
-
context.report({
|
|
726
|
-
loc: {
|
|
727
|
-
start: firstLeadingCommentLoc.start,
|
|
728
|
-
end: lastLeadingCommentLoc.end
|
|
729
|
-
},
|
|
730
|
-
messageId: "incorrectHeader",
|
|
731
|
-
fix: canFix
|
|
732
|
-
? genReplaceFixer(
|
|
733
|
-
commentType,
|
|
734
|
-
context,
|
|
735
|
-
leadingComments,
|
|
736
|
-
fixLines,
|
|
737
|
-
eol,
|
|
738
|
-
numLines)
|
|
739
|
-
: null
|
|
740
|
-
});
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
} else {
|
|
744
|
-
for (let i = 0; i < headerLines.length; i++) {
|
|
745
|
-
if (leadingComments.length - 1 < i) {
|
|
746
|
-
assertDefined(lastLeadingCommentLoc);
|
|
747
|
-
assertNotNull(lastLeadingCommentLoc);
|
|
748
|
-
context.report({
|
|
749
|
-
loc: {
|
|
750
|
-
start: lastLeadingCommentLoc.end,
|
|
751
|
-
end: lastLeadingCommentLoc.end
|
|
752
|
-
},
|
|
753
|
-
messageId: "headerTooShort",
|
|
754
|
-
data: {
|
|
755
|
-
remainder: headerLines.slice(i).join(eol)
|
|
756
|
-
},
|
|
757
|
-
fix: canFix
|
|
758
|
-
? genReplaceFixer(
|
|
759
|
-
commentType,
|
|
760
|
-
context,
|
|
761
|
-
leadingComments,
|
|
762
|
-
fixLines,
|
|
763
|
-
eol,
|
|
764
|
-
numLines)
|
|
765
|
-
: null
|
|
766
|
-
});
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
const headerLine = headerLines[i];
|
|
770
|
-
const comment = leadingComments[i];
|
|
771
|
-
const commentLoc = comment.loc;
|
|
772
|
-
assertDefined(commentLoc);
|
|
773
|
-
assertNotNull(commentLoc);
|
|
774
|
-
if (typeof headerLine === "string") {
|
|
775
|
-
const leadingCommentLength = comment.value.length;
|
|
776
|
-
const headerLineLength = headerLine.length;
|
|
777
|
-
for (let j = 0; j < Math.min(leadingCommentLength, headerLineLength); j++) {
|
|
778
|
-
if (comment.value[j] !== headerLine[j]) {
|
|
779
|
-
context.report({
|
|
780
|
-
loc: {
|
|
781
|
-
start: {
|
|
782
|
-
column: "//".length + j,
|
|
783
|
-
line: commentLoc.start.line
|
|
784
|
-
},
|
|
785
|
-
end: commentLoc.end
|
|
786
|
-
},
|
|
787
|
-
messageId: "headerLineMismatchAtPos",
|
|
788
|
-
data: {
|
|
789
|
-
expected: headerLine.substring(j)
|
|
790
|
-
},
|
|
791
|
-
fix: genReplaceFixer(
|
|
792
|
-
commentType,
|
|
793
|
-
context,
|
|
794
|
-
leadingComments,
|
|
795
|
-
fixLines,
|
|
796
|
-
eol,
|
|
797
|
-
numLines)
|
|
798
|
-
});
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
if (leadingCommentLength < headerLineLength) {
|
|
803
|
-
context.report({
|
|
804
|
-
loc: {
|
|
805
|
-
start: commentLoc.end,
|
|
806
|
-
end: commentLoc.end,
|
|
807
|
-
},
|
|
808
|
-
messageId: "headerLineTooShort",
|
|
809
|
-
data: {
|
|
810
|
-
remainder: headerLine.substring(leadingCommentLength)
|
|
811
|
-
},
|
|
812
|
-
fix: canFix
|
|
813
|
-
? genReplaceFixer(
|
|
814
|
-
commentType,
|
|
815
|
-
context,
|
|
816
|
-
leadingComments,
|
|
817
|
-
fixLines,
|
|
818
|
-
eol,
|
|
819
|
-
numLines)
|
|
820
|
-
: null
|
|
821
|
-
});
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
if (leadingCommentLength > headerLineLength) {
|
|
825
|
-
context.report({
|
|
826
|
-
loc: {
|
|
827
|
-
start: {
|
|
828
|
-
column: "//".length + headerLineLength,
|
|
829
|
-
line: commentLoc.start.line
|
|
830
|
-
},
|
|
831
|
-
end: commentLoc.end,
|
|
832
|
-
},
|
|
833
|
-
messageId: "headerLineTooLong",
|
|
834
|
-
fix: canFix
|
|
835
|
-
? genReplaceFixer(
|
|
836
|
-
commentType,
|
|
837
|
-
context,
|
|
838
|
-
leadingComments,
|
|
839
|
-
fixLines,
|
|
840
|
-
eol,
|
|
841
|
-
numLines)
|
|
842
|
-
: null
|
|
843
|
-
});
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
} else {
|
|
847
|
-
if (!match(comment.value, headerLine)) {
|
|
848
|
-
context.report({
|
|
849
|
-
loc: {
|
|
850
|
-
start: {
|
|
851
|
-
column: "//".length,
|
|
852
|
-
line: commentLoc.start.line,
|
|
853
|
-
},
|
|
854
|
-
end: commentLoc.end,
|
|
855
|
-
},
|
|
856
|
-
messageId: "incorrectHeaderLine",
|
|
857
|
-
data: {
|
|
858
|
-
pattern: headerLine.toString()
|
|
859
|
-
},
|
|
860
|
-
fix: canFix
|
|
861
|
-
? genReplaceFixer(
|
|
862
|
-
commentType,
|
|
863
|
-
context,
|
|
864
|
-
leadingComments,
|
|
865
|
-
fixLines,
|
|
866
|
-
eol,
|
|
867
|
-
numLines)
|
|
868
|
-
: null
|
|
869
|
-
});
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
1010
|
|
|
876
|
-
|
|
877
|
-
assertDefined(commentRange);
|
|
878
|
-
const actualLeadingEmptyLines = leadingEmptyLines(sourceCode.text.substring(commentRange[1]));
|
|
879
|
-
const missingEmptyLines = numLines - actualLeadingEmptyLines;
|
|
880
|
-
if (missingEmptyLines > 0) {
|
|
881
|
-
context.report({
|
|
882
|
-
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
883
|
-
messageId: "noNewlineAfterHeader",
|
|
884
|
-
data: {
|
|
885
|
-
expected: numLines,
|
|
886
|
-
actual: actualLeadingEmptyLines
|
|
887
|
-
},
|
|
888
|
-
fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
// if block comment pattern has more than 1 line, we also split
|
|
894
|
-
// the comment
|
|
895
|
-
let leadingLines = [leadingComments[0].value];
|
|
896
|
-
if (headerLines.length > 1) {
|
|
897
|
-
leadingLines = leadingComments[0].value.split(/\r?\n/);
|
|
898
|
-
}
|
|
1011
|
+
for (const leadingComment of leadingComments) {
|
|
899
1012
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
/** @type {undefined | Record<string, string>} */
|
|
903
|
-
let errorMessageData;
|
|
904
|
-
/** @type {null | SourceLocation} */
|
|
905
|
-
let errorMessageLoc = null;
|
|
906
|
-
for (let i = 0; i < headerLines.length; i++) {
|
|
907
|
-
if (leadingLines.length - 1 < i) {
|
|
908
|
-
assertDefined(lastLeadingCommentLoc);
|
|
909
|
-
assertNotNull(lastLeadingCommentLoc);
|
|
910
|
-
context.report({
|
|
911
|
-
loc: {
|
|
912
|
-
start: lastLeadingCommentLoc.end,
|
|
913
|
-
end: lastLeadingCommentLoc.end
|
|
914
|
-
},
|
|
915
|
-
messageId: "headerTooShort",
|
|
916
|
-
data: {
|
|
917
|
-
remainder: headerLines.slice(i).join(eol)
|
|
918
|
-
},
|
|
919
|
-
fix: canFix
|
|
920
|
-
? genReplaceFixer(
|
|
921
|
-
commentType,
|
|
922
|
-
context,
|
|
923
|
-
leadingComments,
|
|
924
|
-
fixLines,
|
|
925
|
-
eol,
|
|
926
|
-
numLines)
|
|
927
|
-
: null
|
|
928
|
-
});
|
|
1013
|
+
const headerReport = headerMatcher.validate(leadingComment, sourceCode);
|
|
1014
|
+
if (headerReport === null) {
|
|
929
1015
|
return;
|
|
930
1016
|
}
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
line
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
errorMessageData = {
|
|
952
|
-
expected: headerLine.substring(j)
|
|
953
|
-
};
|
|
954
|
-
break;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
if (errorMessageId) {
|
|
958
|
-
break;
|
|
959
|
-
}
|
|
960
|
-
if (leadingLine.length < headerLine.length) {
|
|
961
|
-
errorMessageId = "headerLineTooShort";
|
|
962
|
-
const startColumn = (i === 0 ? "/*".length : 0) + leadingLine.length;
|
|
963
|
-
assertDefined(firstLeadingCommentLoc);
|
|
964
|
-
assertNotNull(firstLeadingCommentLoc);
|
|
965
|
-
errorMessageLoc = {
|
|
966
|
-
start: {
|
|
967
|
-
column: startColumn,
|
|
968
|
-
line: firstLeadingCommentLoc.start.line + i
|
|
969
|
-
},
|
|
970
|
-
end: {
|
|
971
|
-
column: startColumn + 1,
|
|
972
|
-
line: firstLeadingCommentLoc.start.line + i
|
|
973
|
-
}
|
|
974
|
-
};
|
|
975
|
-
errorMessageData = {
|
|
976
|
-
remainder: headerLine.substring(leadingLine.length)
|
|
977
|
-
};
|
|
978
|
-
break;
|
|
979
|
-
}
|
|
980
|
-
if (leadingLine.length > headerLine.length) {
|
|
981
|
-
assertDefined(firstLeadingCommentLoc);
|
|
982
|
-
assertNotNull(firstLeadingCommentLoc);
|
|
983
|
-
errorMessageId = "headerLineTooLong";
|
|
984
|
-
errorMessageLoc = {
|
|
985
|
-
start: {
|
|
986
|
-
column: (i === 0 ? "/*".length : 0) + headerLine.length,
|
|
987
|
-
line: firstLeadingCommentLoc.start.line + i
|
|
988
|
-
},
|
|
989
|
-
end: {
|
|
990
|
-
column: (i === 0 ? "/*".length : 0) + leadingLine.length,
|
|
991
|
-
line: firstLeadingCommentLoc.start.line + i
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
break;
|
|
1017
|
+
const leadingCommentReports =
|
|
1018
|
+
allowedCommentsMatchers.map((m) => m.validate(leadingComment, sourceCode));
|
|
1019
|
+
const commentMatched = leadingCommentReports.some((report) => report === null);
|
|
1020
|
+
|
|
1021
|
+
if (!commentMatched) {
|
|
1022
|
+
if ("messageId" in headerReport && headerReport.messageId === "noNewlineAfterHeader") {
|
|
1023
|
+
const { expected, actual } =
|
|
1024
|
+
/** @type {{ expected: number, actual: number }} */ (headerReport.data);
|
|
1025
|
+
headerReport.fix = genEmptyLinesFixer(leadingComment, eol, expected - actual);
|
|
1026
|
+
} else if (canFix) {
|
|
1027
|
+
headerReport.fix = genReplaceFixer(
|
|
1028
|
+
headerMatcher.commentType,
|
|
1029
|
+
sourceCode,
|
|
1030
|
+
leadingComment,
|
|
1031
|
+
fixLines,
|
|
1032
|
+
eol,
|
|
1033
|
+
numLines);
|
|
995
1034
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
assertNotNull(firstLeadingCommentLoc);
|
|
1005
|
-
errorMessageLoc = {
|
|
1006
|
-
start: {
|
|
1007
|
-
column: columnOffset + 0,
|
|
1008
|
-
line: firstLeadingCommentLoc.start.line + i
|
|
1009
|
-
},
|
|
1010
|
-
end: {
|
|
1011
|
-
column: columnOffset + leadingLine.length,
|
|
1012
|
-
line: firstLeadingCommentLoc.start.line + i
|
|
1013
|
-
}
|
|
1014
|
-
};
|
|
1015
|
-
break;
|
|
1035
|
+
|
|
1036
|
+
context.report(headerReport);
|
|
1037
|
+
for (const commentReport of leadingCommentReports) {
|
|
1038
|
+
if (commentReport !== null) {
|
|
1039
|
+
/** @type {{ messageId: string }} */ (commentReport).messageId =
|
|
1040
|
+
"leadingComment-" + /** @type {{ messageId: string }} */ (commentReport).messageId;
|
|
1041
|
+
context.report(commentReport);
|
|
1042
|
+
}
|
|
1016
1043
|
}
|
|
1044
|
+
return;
|
|
1017
1045
|
}
|
|
1018
1046
|
}
|
|
1019
1047
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
errorMessageLoc = {
|
|
1048
|
+
const lastComment = leadingComments[leadingComments.length - 1];
|
|
1049
|
+
const lastCommentLine = lastComment[lastComment.length - 1];
|
|
1050
|
+
const lineIndex = /** @type {number} */ (lastCommentLine.loc?.end.line) + 1;
|
|
1051
|
+
|
|
1052
|
+
context.report({
|
|
1053
|
+
loc: {
|
|
1027
1054
|
start: {
|
|
1028
|
-
column:
|
|
1029
|
-
line:
|
|
1055
|
+
column: 0,
|
|
1056
|
+
line: lineIndex
|
|
1030
1057
|
},
|
|
1031
1058
|
end: {
|
|
1032
|
-
column:
|
|
1033
|
-
line:
|
|
1059
|
+
column: 0,
|
|
1060
|
+
line: lineIndex
|
|
1034
1061
|
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
data: errorMessageData,
|
|
1047
|
-
fix: canFix
|
|
1048
|
-
? genReplaceFixer(
|
|
1049
|
-
commentType,
|
|
1050
|
-
context,
|
|
1051
|
-
leadingComments,
|
|
1052
|
-
fixLines,
|
|
1053
|
-
eol,
|
|
1054
|
-
numLines)
|
|
1055
|
-
: null
|
|
1056
|
-
});
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
const actualLeadingEmptyLines =
|
|
1061
|
-
leadingEmptyLines(sourceCode.text.substring(firstLeadingCommentRange[1]));
|
|
1062
|
-
const missingEmptyLines = numLines - actualLeadingEmptyLines;
|
|
1063
|
-
if (missingEmptyLines > 0) {
|
|
1064
|
-
context.report({
|
|
1065
|
-
loc: missingEmptyLinesViolationLoc(leadingComments, actualLeadingEmptyLines),
|
|
1066
|
-
messageId: "noNewlineAfterHeader",
|
|
1067
|
-
data: {
|
|
1068
|
-
expected: numLines,
|
|
1069
|
-
actual: actualLeadingEmptyLines
|
|
1070
|
-
},
|
|
1071
|
-
fix: genEmptyLinesFixer(leadingComments, eol, missingEmptyLines)
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1062
|
+
},
|
|
1063
|
+
messageId: "missingHeader",
|
|
1064
|
+
fix: canFix
|
|
1065
|
+
? genPrependFixer(
|
|
1066
|
+
headerMatcher.commentType,
|
|
1067
|
+
sourceCode,
|
|
1068
|
+
fixLines,
|
|
1069
|
+
eol,
|
|
1070
|
+
numLines)
|
|
1071
|
+
: null
|
|
1072
|
+
});
|
|
1074
1073
|
}
|
|
1075
1074
|
};
|
|
1076
1075
|
}
|