@tony.ganchev/eslint-plugin-header 3.1.9 → 3.1.10
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 +3 -1
- package/README.md +0 -3
- package/index.js +3 -3
- package/lib/comment-parser.js +16 -14
- package/lib/rules/header.docs.js +40 -0
- package/lib/rules/header.js +256 -278
- package/lib/rules/header.schema.js +135 -0
- package/package.json +9 -3
package/LICENSE.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
1
3
|
Copyright (c) 2015-present Stuart Knightley, Tony Ganchev and contributors
|
|
2
4
|
|
|
3
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
@@ -15,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
|
15
17
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
16
18
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
19
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
18
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -112,7 +112,6 @@ Suppose we want our header to look like this:
|
|
|
112
112
|
* Copyright (c) 2015
|
|
113
113
|
* My Company
|
|
114
114
|
*/
|
|
115
|
-
...
|
|
116
115
|
```
|
|
117
116
|
|
|
118
117
|
All of the following configurations will match the header:
|
|
@@ -211,7 +210,6 @@ perfectly valid, such as:
|
|
|
211
210
|
* Copyright 2020
|
|
212
211
|
* My company
|
|
213
212
|
*/
|
|
214
|
-
...
|
|
215
213
|
```
|
|
216
214
|
|
|
217
215
|
Moreover, suppose your legal department expects that the year of first and last
|
|
@@ -223,7 +221,6 @@ support:
|
|
|
223
221
|
* Copyright 2017-2022
|
|
224
222
|
* My company
|
|
225
223
|
*/
|
|
226
|
-
...
|
|
227
224
|
```
|
|
228
225
|
|
|
229
226
|
We can use a regular expression to support all of these cases for your header:
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* MIT License
|
|
3
3
|
*
|
|
4
|
-
* Copyright (c) 2015-present Stuart Knightley and contributors
|
|
4
|
+
* Copyright (c) 2015-present Stuart Knightley, Tony Ganchev and contributors
|
|
5
5
|
*
|
|
6
6
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
* of this software and associated documentation files (the “Software”), to deal
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* copies of the Software, and to permit persons to whom the Software is
|
|
11
11
|
* furnished to do so, subject to the following conditions:
|
|
12
12
|
*
|
|
13
|
-
* The above copyright notice and this permission notice shall be included in
|
|
14
|
-
* copies or substantial portions of the Software.
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in
|
|
14
|
+
* all copies or substantial portions of the Software.
|
|
15
15
|
*
|
|
16
16
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
17
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
package/lib/comment-parser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* MIT License
|
|
3
3
|
*
|
|
4
|
-
* Copyright (c) 2015-present Stuart Knightley and contributors
|
|
4
|
+
* Copyright (c) 2015-present Stuart Knightley, Tony Ganchev and contributors
|
|
5
5
|
*
|
|
6
6
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
* of this software and associated documentation files (the “Software”), to deal
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* copies of the Software, and to permit persons to whom the Software is
|
|
11
11
|
* furnished to do so, subject to the following conditions:
|
|
12
12
|
*
|
|
13
|
-
* The above copyright notice and this permission notice shall be included in
|
|
14
|
-
* copies or substantial portions of the Software.
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in
|
|
14
|
+
* all copies or substantial portions of the Software.
|
|
15
15
|
*
|
|
16
16
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
17
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
@@ -24,30 +24,32 @@
|
|
|
24
24
|
|
|
25
25
|
"use strict";
|
|
26
26
|
|
|
27
|
+
const assert = require("assert");
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
|
-
* Parses a line or block comment and returns the type of comment and an array
|
|
30
|
+
* Parses a line or block comment and returns the type of comment and an array
|
|
31
|
+
* of content lines.
|
|
29
32
|
*
|
|
30
33
|
* This is a really simple and dumb parser, that looks just for a
|
|
31
34
|
* single kind of comment. It won't detect multiple block comments.
|
|
32
35
|
* @param {string} commentText comment text.
|
|
33
|
-
* @returns {['block' | 'line', string[]]} comment type and comment
|
|
36
|
+
* @returns {['block' | 'line', string | string[]]} comment type and comment
|
|
37
|
+
* content broken into lines.
|
|
34
38
|
*/
|
|
35
39
|
module.exports = function commentParser(commentText) {
|
|
40
|
+
assert.strictEqual(typeof commentText, "string");
|
|
36
41
|
const text = commentText.trim();
|
|
37
42
|
|
|
38
|
-
if (text.
|
|
43
|
+
if (text.startsWith("//")) {
|
|
39
44
|
return [
|
|
40
45
|
"line",
|
|
41
|
-
text.split(/\r?\n/).map(
|
|
42
|
-
return line.substr(2);
|
|
43
|
-
})
|
|
46
|
+
text.split(/\r?\n/).map((line) => line.substring(2))
|
|
44
47
|
];
|
|
45
|
-
} else if (
|
|
46
|
-
text.substr(0, 2) === "/*" &&
|
|
47
|
-
text.substr(-2) === "*/"
|
|
48
|
-
) {
|
|
48
|
+
} else if (text.startsWith("/*") && text.endsWith("*/")) {
|
|
49
49
|
return ["block", text.substring(2, text.length - 2)];
|
|
50
50
|
} else {
|
|
51
|
-
throw new Error(
|
|
51
|
+
throw new Error(
|
|
52
|
+
"Could not parse comment file: the file must contain either just line comments (//) or a single block " +
|
|
53
|
+
"comment (/* ... */)");
|
|
52
54
|
}
|
|
53
55
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
|
|
26
|
+
"use strict";
|
|
27
|
+
|
|
28
|
+
const assert = require("node:assert");
|
|
29
|
+
const fs = require("node:fs");
|
|
30
|
+
const path = require("node:path");
|
|
31
|
+
|
|
32
|
+
const packageJsonContent = fs.readFileSync(path.resolve(__dirname, "../../package.json"));
|
|
33
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
34
|
+
assert.equal(Object.prototype.hasOwnProperty.call(packageJson, "version"), true,
|
|
35
|
+
"The plugin's package.json should be available two directories above the rule.");
|
|
36
|
+
const pluginVersion = packageJsonContent.version;
|
|
37
|
+
|
|
38
|
+
exports.description = "";
|
|
39
|
+
exports.recommended = true;
|
|
40
|
+
exports.url = "https://www.npmjs.com/package/@tony.ganchev/eslint-plugin-header/v/" + pluginVersion;
|
package/lib/rules/header.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* MIT License
|
|
3
3
|
*
|
|
4
|
-
* Copyright (c) 2015-present Stuart Knightley and contributors
|
|
4
|
+
* Copyright (c) 2015-present Stuart Knightley, Tony Ganchev, and contributors
|
|
5
5
|
*
|
|
6
6
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
* of this software and associated documentation files (the “Software”), to deal
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* copies of the Software, and to permit persons to whom the Software is
|
|
11
11
|
* furnished to do so, subject to the following conditions:
|
|
12
12
|
*
|
|
13
|
-
* The above copyright notice and this permission notice shall be included in
|
|
14
|
-
* copies or substantial portions of the Software.
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in
|
|
14
|
+
* all copies or substantial portions of the Software.
|
|
15
15
|
*
|
|
16
16
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
17
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
@@ -37,48 +37,46 @@
|
|
|
37
37
|
* @typedef {import('eslint').Rule.RuleContext} RuleContext
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
-
/**
|
|
41
|
-
* @enum {string}
|
|
42
|
-
*/
|
|
43
|
-
const lineEndingOptions = Object.freeze({
|
|
44
|
-
unix: "unix", // \n
|
|
45
|
-
windows: "windows", // \n
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @enum {string}
|
|
50
|
-
*/
|
|
51
|
-
const commentTypeOptions = Object.freeze({
|
|
52
|
-
block: "block",
|
|
53
|
-
line: "line"
|
|
54
|
-
});
|
|
55
|
-
|
|
56
40
|
/**
|
|
57
41
|
* Local type defintions.
|
|
58
42
|
* @typedef {string | { pattern: string, template?: string }} HeaderLine
|
|
59
43
|
* @typedef {(HeaderLine | HeaderLine[])} HeaderLines
|
|
60
44
|
* @typedef {{ lineEndings: ('unix' | 'windows') }} HeaderSettings
|
|
61
|
-
* @typedef {
|
|
45
|
+
* @typedef {
|
|
46
|
+
* [string]
|
|
47
|
+
* | [string, HeaderSettings]
|
|
48
|
+
* | [('block' | 'line') | HeaderLines ]
|
|
49
|
+
* | [('block' | 'line') | HeaderLines | HeaderSettings]
|
|
50
|
+
* | [('block' | 'line') | HeaderLines | number ]
|
|
51
|
+
* | [('block' | 'line') | HeaderLines | number | HeaderSettings]
|
|
52
|
+
* } HeaderOptions
|
|
62
53
|
*/
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
const assert = require("assert");
|
|
56
|
+
const fs = require("fs");
|
|
57
|
+
const os = require("os");
|
|
58
|
+
const commentParser = require("../comment-parser");
|
|
59
|
+
const { description, recommended, url } = require("./header.docs");
|
|
60
|
+
const { commentTypeOptions, lineEndingOptions, schema } = require("./header.schema");
|
|
67
61
|
|
|
68
62
|
/**
|
|
69
|
-
* Tests if the passed line configuration string or object is a pattern
|
|
63
|
+
* Tests if the passed line configuration string or object is a pattern
|
|
64
|
+
* definition.
|
|
70
65
|
* @param {HeaderLine} object line configuration object or string
|
|
71
|
-
* @returns {boolean} `true` if the line configuration is a pattern-
|
|
66
|
+
* @returns {boolean} `true` if the line configuration is a pattern-defining
|
|
67
|
+
* object or `false` otherwise.
|
|
72
68
|
*/
|
|
73
69
|
function isPattern(object) {
|
|
74
70
|
return typeof object === "object" && Object.prototype.hasOwnProperty.call(object, "pattern");
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
/**
|
|
78
|
-
* Utility over a line config argument to match an expected string either
|
|
74
|
+
* Utility over a line config argument to match an expected string either
|
|
75
|
+
* against a regex or for full match against a string.
|
|
79
76
|
* @param {HeaderLine} actual the string to test.
|
|
80
77
|
* @param {string} expected The string or regex to test again.
|
|
81
|
-
* @returns {boolean} `true` if the passed string matches the expected line
|
|
78
|
+
* @returns {boolean} `true` if the passed string matches the expected line
|
|
79
|
+
* config or `false` otherwise.
|
|
82
80
|
*/
|
|
83
81
|
function match(actual, expected) {
|
|
84
82
|
if (expected.test) {
|
|
@@ -91,7 +89,9 @@ function match(actual, expected) {
|
|
|
91
89
|
/**
|
|
92
90
|
* Remove Unix she-bangs from the list of comments.
|
|
93
91
|
* @param {Comment[]} comments the list of comment lines.
|
|
94
|
-
* @returns {Comment[]} the list of comments with containing all incomming
|
|
92
|
+
* @returns {Comment[]} the list of comments with containing all incomming
|
|
93
|
+
* comments from `comments` with the shebang
|
|
94
|
+
* comments omitted.
|
|
95
95
|
*/
|
|
96
96
|
function excludeShebangs(comments) {
|
|
97
97
|
return comments.filter(function(comment) {
|
|
@@ -109,12 +109,13 @@ function excludeShebangs(comments) {
|
|
|
109
109
|
* @returns {Comment[]} lines that constitute the leading comment.
|
|
110
110
|
*/
|
|
111
111
|
function getLeadingComments(context, node) {
|
|
112
|
-
|
|
112
|
+
const all = excludeShebangs(context.sourceCode.getAllComments(node.body.length ? node.body[0] : node));
|
|
113
113
|
if (all[0].type.toLowerCase() === commentTypeOptions.block) {
|
|
114
114
|
return [all[0]];
|
|
115
115
|
}
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
let i = 1;
|
|
117
|
+
for (; i < all.length; ++i) {
|
|
118
|
+
const txt = context.sourceCode.text.slice(all[i - 1].range[1], all[i].range[0]);
|
|
118
119
|
if (!txt.match(/^(\r\n|\r|\n)$/)) {
|
|
119
120
|
break;
|
|
120
121
|
}
|
|
@@ -123,40 +124,55 @@ function getLeadingComments(context, node) {
|
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
/**
|
|
126
|
-
* Generate a comment including trailing spaces out of a number of comment body
|
|
127
|
+
* Generate a comment including trailing spaces out of a number of comment body
|
|
128
|
+
* lines.
|
|
127
129
|
* @param {'block' | 'line'} commentType the type of comment to generate.
|
|
128
130
|
* @param {string[]} textArray list of lines of the comment content.
|
|
129
131
|
* @param {'\n' | '\r\n'} eol end-of-line characters.
|
|
130
|
-
* @param {number} numNewlines number of trailing lines after the comment.
|
|
131
132
|
* @returns {string} resulting comment.
|
|
132
133
|
*/
|
|
133
|
-
function genCommentBody(commentType, textArray, eol
|
|
134
|
-
var eols = eol.repeat(numNewlines);
|
|
134
|
+
function genCommentBody(commentType, textArray, eol) {
|
|
135
135
|
if (commentType === commentTypeOptions.block) {
|
|
136
|
-
return "/*" + textArray.join(eol) + "*/"
|
|
136
|
+
return "/*" + textArray.join(eol) + "*/";
|
|
137
137
|
} else {
|
|
138
|
-
|
|
138
|
+
// We need one trailing EOL on line comments to ensure the fixed source
|
|
139
|
+
// is parsable.
|
|
140
|
+
return "//" + textArray.join(eol + "//");
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
+
* Determines the start and end position in the source code of the leading
|
|
146
|
+
* comment.
|
|
145
147
|
* @param {string[]} comments list of comments.
|
|
146
|
-
* @param {'\n' | '\r\n'} eol end-of-line characters
|
|
147
148
|
* @returns {[number, number]} resulting range.
|
|
148
149
|
*/
|
|
149
|
-
function genCommentsRange(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const sourceCode = context.getSourceCode().text;
|
|
153
|
-
const headerTrailingChars = sourceCode.substring(end, end + eol.length);
|
|
154
|
-
if (headerTrailingChars === eol) {
|
|
155
|
-
end += eol.length;
|
|
156
|
-
}
|
|
150
|
+
function genCommentsRange(comments) {
|
|
151
|
+
const start = comments[0].range[0];
|
|
152
|
+
const end = comments.slice(-1)[0].range[1];
|
|
157
153
|
return [start, end];
|
|
158
154
|
}
|
|
159
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Calculates the number of leading empty lines in the source code. The function
|
|
158
|
+
* counts both Windows and POSIX line endings.
|
|
159
|
+
* @param {string} src the source code to traverse.
|
|
160
|
+
* @returns {number} the number of leading empty lines.
|
|
161
|
+
*/
|
|
162
|
+
function leadingEmptyLines(src) {
|
|
163
|
+
let numLines = 0;
|
|
164
|
+
while (true) {
|
|
165
|
+
const m = src.match(/^(\r\n|\n)/);
|
|
166
|
+
if (!m) {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
assert.strictEqual(m.index, 0);
|
|
170
|
+
numLines++;
|
|
171
|
+
src = src.slice(m.index + m[0].length);
|
|
172
|
+
}
|
|
173
|
+
return numLines;
|
|
174
|
+
}
|
|
175
|
+
|
|
160
176
|
/**
|
|
161
177
|
* Factory for fixer that adds a missing header.
|
|
162
178
|
* @param {'block' | 'line'} commentType type of comment to use.
|
|
@@ -168,38 +184,65 @@ function genCommentsRange(context, comments, eol) {
|
|
|
168
184
|
*/
|
|
169
185
|
function genPrependFixer(commentType, context, headerLines, eol, numNewlines) {
|
|
170
186
|
return function(fixer) {
|
|
171
|
-
|
|
187
|
+
let insertPos = 0;
|
|
188
|
+
let newHeader = genCommentBody(commentType, headerLines, eol, numNewlines);
|
|
172
189
|
if (context.sourceCode.text.substring(0, 2) === "#!") {
|
|
173
190
|
const firstNewLinePos = context.sourceCode.text.indexOf("\n");
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
179
|
-
} else {
|
|
180
|
-
return fixer.insertTextBeforeRange(
|
|
181
|
-
[0, 0 /* don't care */],
|
|
182
|
-
newHeader
|
|
183
|
-
);
|
|
191
|
+
insertPos = firstNewLinePos === -1 ? context.sourceCode.text.length : firstNewLinePos + 1;
|
|
192
|
+
if (firstNewLinePos === -1) {
|
|
193
|
+
newHeader = eol + newHeader;
|
|
194
|
+
}
|
|
184
195
|
}
|
|
196
|
+
const numEmptyLines = leadingEmptyLines(context.sourceCode.text.substring(insertPos));
|
|
197
|
+
const additionalEmptyLines = Math.max(0, numNewlines - numEmptyLines);
|
|
198
|
+
newHeader += eol.repeat(additionalEmptyLines);
|
|
199
|
+
return fixer.insertTextBeforeRange(
|
|
200
|
+
[insertPos, insertPos /* don't care */],
|
|
201
|
+
newHeader
|
|
202
|
+
);
|
|
185
203
|
};
|
|
186
204
|
}
|
|
187
205
|
|
|
188
206
|
/**
|
|
189
207
|
* Factory for fixer that replaces an incorrect header.
|
|
190
208
|
* @param {'block' | 'line'} commentType type of comment to use.
|
|
191
|
-
* @param {RuleContext} context ESLint
|
|
209
|
+
* @param {RuleContext} context ESLint execution context.
|
|
192
210
|
* @param {Comment[]} leadingComments comment elements to replace.
|
|
193
211
|
* @param {string[]} headerLines lines of the header comment.
|
|
194
212
|
* @param {'\n' | '\r\n'} eol end-of-line characters
|
|
195
213
|
* @param {number} numNewlines number of trailing lines after the comment.
|
|
196
|
-
* @returns {
|
|
214
|
+
* @returns {
|
|
215
|
+
* (fixer: RuleTextEditor) => RuleTextEdit | RuleTextEdit[] | null
|
|
216
|
+
* } the fixer.
|
|
197
217
|
*/
|
|
198
218
|
function genReplaceFixer(commentType, context, leadingComments, headerLines, eol, numNewlines) {
|
|
199
219
|
return function(fixer) {
|
|
220
|
+
const commentRange = genCommentsRange(leadingComments);
|
|
221
|
+
const emptyLines = leadingEmptyLines(context.sourceCode.text.substring(commentRange[1]));
|
|
222
|
+
const missingNewlines = Math.max(0, numNewlines - emptyLines);
|
|
223
|
+
const eols = eol.repeat(missingNewlines);
|
|
200
224
|
return fixer.replaceTextRange(
|
|
201
|
-
|
|
202
|
-
genCommentBody(commentType, headerLines, eol, numNewlines)
|
|
225
|
+
commentRange,
|
|
226
|
+
genCommentBody(commentType, headerLines, eol, numNewlines) + eols
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Factory for fixer that replaces an incorrect header.
|
|
233
|
+
* @param {Comment[]} leadingComments comment elements to replace.
|
|
234
|
+
* @param {'\n' | '\r\n'} eol end-of-line characters
|
|
235
|
+
* @param {number} missingEmptyLinesCount number of trailing lines after the
|
|
236
|
+
* comment.
|
|
237
|
+
* @returns {
|
|
238
|
+
* (fixer: RuleTextEditor) => RuleTextEdit | RuleTextEdit[] | null
|
|
239
|
+
* } the fixer.
|
|
240
|
+
*/
|
|
241
|
+
function genEmptyLinesFixer(leadingComments, eol, missingEmptyLinesCount) {
|
|
242
|
+
return function(fixer) {
|
|
243
|
+
return fixer.insertTextAfterRange(
|
|
244
|
+
genCommentsRange(leadingComments),
|
|
245
|
+
eol.repeat(missingEmptyLinesCount)
|
|
203
246
|
);
|
|
204
247
|
};
|
|
205
248
|
}
|
|
@@ -207,10 +250,11 @@ function genReplaceFixer(commentType, context, leadingComments, headerLines, eol
|
|
|
207
250
|
/**
|
|
208
251
|
* Finds the option parameter within the list of rule config options.
|
|
209
252
|
* @param {HeaderOptions} options the config options passed to the rule.
|
|
210
|
-
* @returns {HeaderSettings | null} the settings parameter or `null` if no such
|
|
253
|
+
* @returns {HeaderSettings | null} the settings parameter or `null` if no such
|
|
254
|
+
* is defined.
|
|
211
255
|
*/
|
|
212
256
|
function findSettings(options) {
|
|
213
|
-
|
|
257
|
+
const lastOption = options[options.length - 1];
|
|
214
258
|
if (typeof lastOption === "object" && !Array.isArray(lastOption) && lastOption !== null
|
|
215
259
|
&& !Object.prototype.hasOwnProperty.call(lastOption, "pattern")) {
|
|
216
260
|
return lastOption;
|
|
@@ -219,12 +263,14 @@ function findSettings(options) {
|
|
|
219
263
|
}
|
|
220
264
|
|
|
221
265
|
/**
|
|
222
|
-
* Returns the used line-termination characters per the rule's config if any or
|
|
266
|
+
* Returns the used line-termination characters per the rule's config if any or
|
|
267
|
+
* else based on the runtime environments.
|
|
223
268
|
* @param {HeaderOptions} options rule configuration.
|
|
224
|
-
* @returns {'\n' | '\r\n'} the correct line ending characters for the
|
|
269
|
+
* @returns {'\n' | '\r\n'} the correct line ending characters for the
|
|
270
|
+
* environment.
|
|
225
271
|
*/
|
|
226
272
|
function getEOL(options) {
|
|
227
|
-
|
|
273
|
+
const settings = findSettings(options);
|
|
228
274
|
if (settings) {
|
|
229
275
|
if (settings.lineEndings === lineEndingOptions.unix) {
|
|
230
276
|
return "\n";
|
|
@@ -237,132 +283,39 @@ function getEOL(options) {
|
|
|
237
283
|
}
|
|
238
284
|
|
|
239
285
|
/**
|
|
240
|
-
* Tests if the first line in the source code (after a Unix she-bang) is a
|
|
286
|
+
* Tests if the first line in the source code (after a Unix she-bang) is a
|
|
287
|
+
* comment. Does not tolerate empty lines before the first match.
|
|
241
288
|
* @param {string} src source code to test.
|
|
242
289
|
* @returns {boolean} `true` if there is a comment or `false` otherwise.
|
|
243
290
|
*/
|
|
244
|
-
// TODO: check if it is valid to have the copyright notice separated by an empty
|
|
291
|
+
// TODO: check if it is valid to have the copyright notice separated by an empty
|
|
292
|
+
// line from the shebang.
|
|
245
293
|
function hasHeader(src) {
|
|
246
|
-
if (src.
|
|
247
|
-
|
|
294
|
+
if (src.startsWith("#!")) {
|
|
295
|
+
const m = src.match(/(\r\n|\r|\n)/);
|
|
248
296
|
if (m) {
|
|
249
297
|
src = src.slice(m.index + m[0].length);
|
|
250
298
|
}
|
|
251
299
|
}
|
|
252
|
-
return src.
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Ensures that the right amount of empty lines trail the header.
|
|
257
|
-
* @param {string} src source to validate.
|
|
258
|
-
* @param {number} num expected number of trailing empty lines.
|
|
259
|
-
* @returns {boolean} `true` if the `num` number of empty lines are appended at the end or `false` otherwise.
|
|
260
|
-
*/
|
|
261
|
-
function matchesLineEndings(src, num) {
|
|
262
|
-
for (var j = 0; j < num; ++j) {
|
|
263
|
-
var m = src.match(/^(\r\n|\r|\n)/);
|
|
264
|
-
if (m) {
|
|
265
|
-
src = src.slice(m.index + m[0].length);
|
|
266
|
-
} else {
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return true;
|
|
300
|
+
return src.startsWith("/*") || src.startsWith("//");
|
|
271
301
|
}
|
|
272
302
|
|
|
273
303
|
module.exports = {
|
|
274
304
|
meta: {
|
|
275
305
|
type: "layout",
|
|
306
|
+
docs: {
|
|
307
|
+
description,
|
|
308
|
+
recommended,
|
|
309
|
+
url
|
|
310
|
+
},
|
|
276
311
|
fixable: "whitespace",
|
|
277
|
-
schema
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
line: {
|
|
285
|
-
anyOf: [
|
|
286
|
-
{
|
|
287
|
-
type: "string"
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
type: "object",
|
|
291
|
-
properties: {
|
|
292
|
-
pattern: {
|
|
293
|
-
type: "string"
|
|
294
|
-
},
|
|
295
|
-
template: {
|
|
296
|
-
type: "string"
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
required: ["pattern"],
|
|
300
|
-
additionalProperties: false
|
|
301
|
-
}
|
|
302
|
-
]
|
|
303
|
-
},
|
|
304
|
-
headerLines: {
|
|
305
|
-
anyOf: [
|
|
306
|
-
{
|
|
307
|
-
$ref: "#/definitions/line"
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
type: "array",
|
|
311
|
-
items: {
|
|
312
|
-
$ref: "#/definitions/line"
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
]
|
|
316
|
-
},
|
|
317
|
-
numNewlines: {
|
|
318
|
-
type: "integer",
|
|
319
|
-
minimum: 0
|
|
320
|
-
},
|
|
321
|
-
settings: {
|
|
322
|
-
type: "object",
|
|
323
|
-
properties: {
|
|
324
|
-
lineEndings: {
|
|
325
|
-
type: "string",
|
|
326
|
-
enum: [lineEndingOptions.unix, lineEndingOptions.windows]
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
additionalProperties: false
|
|
330
|
-
},
|
|
331
|
-
options: {
|
|
332
|
-
anyOf: [
|
|
333
|
-
{
|
|
334
|
-
type: "array",
|
|
335
|
-
minItems: 1,
|
|
336
|
-
maxItems: 2,
|
|
337
|
-
items: [
|
|
338
|
-
{ type: "string" },
|
|
339
|
-
{ $ref: "#/definitions/settings" }
|
|
340
|
-
]
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
type: "array",
|
|
344
|
-
minItems: 2,
|
|
345
|
-
maxItems: 3,
|
|
346
|
-
items: [
|
|
347
|
-
{ $ref: "#/definitions/commentType" },
|
|
348
|
-
{ $ref: "#/definitions/headerLines" },
|
|
349
|
-
{ $ref: "#/definitions/settings" }
|
|
350
|
-
]
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
type: "array",
|
|
354
|
-
minItems: 3,
|
|
355
|
-
maxItems: 4,
|
|
356
|
-
items: [
|
|
357
|
-
{ $ref: "#/definitions/commentType" },
|
|
358
|
-
{ $ref: "#/definitions/headerLines" },
|
|
359
|
-
{ $ref: "#/definitions/numNewlines" },
|
|
360
|
-
{ $ref: "#/definitions/settings" }
|
|
361
|
-
]
|
|
362
|
-
}
|
|
363
|
-
]
|
|
364
|
-
}
|
|
365
|
-
}
|
|
312
|
+
schema,
|
|
313
|
+
defaultOptions: [{}],
|
|
314
|
+
messages: {
|
|
315
|
+
incorrectCommentType: "header should be a {{commentType}} comment",
|
|
316
|
+
incorrectHeader: "incorrect header",
|
|
317
|
+
missingHeader: "missing header",
|
|
318
|
+
noNewlineAfterHeader: "no newline after header"
|
|
366
319
|
}
|
|
367
320
|
},
|
|
368
321
|
/**
|
|
@@ -371,26 +324,27 @@ module.exports = {
|
|
|
371
324
|
* @returns {NodeListener} the rule definition.
|
|
372
325
|
*/
|
|
373
326
|
create: function(context) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
327
|
+
let options = context.options;
|
|
328
|
+
const numNewlines = options.length > 2 && typeof options[2] === "number" ? options[2] : 1;
|
|
329
|
+
const eol = getEOL(options);
|
|
377
330
|
|
|
378
331
|
// If just one option then read comment from file
|
|
379
332
|
if (options.length === 1 || (options.length === 2 && findSettings(options))) {
|
|
380
|
-
|
|
333
|
+
const text = fs.readFileSync(context.options[0], "utf8");
|
|
381
334
|
options = commentParser(text);
|
|
382
335
|
}
|
|
383
336
|
|
|
384
|
-
|
|
385
|
-
|
|
337
|
+
const commentType = options[0].toLowerCase();
|
|
338
|
+
let headerLines;
|
|
339
|
+
let fixLines = [];
|
|
386
340
|
// If any of the lines are regular expressions, then we can't
|
|
387
341
|
// automatically fix them. We set this to true below once we
|
|
388
342
|
// ensure none of the lines are of type RegExp
|
|
389
|
-
|
|
343
|
+
let canFix = false;
|
|
390
344
|
if (Array.isArray(options[1])) {
|
|
391
345
|
canFix = true;
|
|
392
346
|
headerLines = options[1].map(function(line) {
|
|
393
|
-
|
|
347
|
+
const isRegex = isPattern(line);
|
|
394
348
|
// Can only fix regex option if a template is also provided
|
|
395
349
|
if (isRegex && !line.template) {
|
|
396
350
|
canFix = false;
|
|
@@ -399,7 +353,7 @@ module.exports = {
|
|
|
399
353
|
return isRegex ? new RegExp(line.pattern) : line;
|
|
400
354
|
});
|
|
401
355
|
} else if (isPattern(options[1])) {
|
|
402
|
-
|
|
356
|
+
const line = options[1];
|
|
403
357
|
headerLines = [new RegExp(line.pattern)];
|
|
404
358
|
fixLines.push(line.template || line);
|
|
405
359
|
// Same as above for regex and template
|
|
@@ -411,114 +365,138 @@ module.exports = {
|
|
|
411
365
|
}
|
|
412
366
|
|
|
413
367
|
return {
|
|
368
|
+
/**
|
|
369
|
+
* Hooks into the processing of the overall script node to do the
|
|
370
|
+
* header validation.
|
|
371
|
+
* @param {Program} node the whole script node
|
|
372
|
+
* @returns {void}
|
|
373
|
+
*/
|
|
414
374
|
Program: function(node) {
|
|
415
|
-
if (!hasHeader(context.sourceCode.
|
|
375
|
+
if (!hasHeader(context.sourceCode.text)) {
|
|
416
376
|
context.report({
|
|
417
377
|
loc: node.loc,
|
|
418
|
-
|
|
378
|
+
messageId: "missingHeader",
|
|
419
379
|
fix: genPrependFixer(commentType, context, fixLines, eol, numNewlines)
|
|
420
380
|
});
|
|
421
|
-
|
|
422
|
-
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const leadingComments = getLeadingComments(context, node);
|
|
423
384
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
385
|
+
if (leadingComments[0].type.toLowerCase() !== commentType) {
|
|
386
|
+
context.report({
|
|
387
|
+
loc: node.loc,
|
|
388
|
+
messageId: "incorrectCommentType",
|
|
389
|
+
data: {
|
|
390
|
+
commentType: commentType
|
|
391
|
+
},
|
|
392
|
+
fix: canFix
|
|
393
|
+
? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines)
|
|
394
|
+
: null
|
|
395
|
+
});
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (commentType === commentTypeOptions.line) {
|
|
399
|
+
if (leadingComments.length < headerLines.length) {
|
|
431
400
|
context.report({
|
|
432
401
|
loc: node.loc,
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
commentType
|
|
436
|
-
|
|
437
|
-
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
|
|
402
|
+
messageId: "incorrectHeader",
|
|
403
|
+
fix: canFix
|
|
404
|
+
? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines)
|
|
405
|
+
: null
|
|
438
406
|
});
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (headerLines.length === 1) {
|
|
410
|
+
const leadingCommentValues = leadingComments.map((c) => c.value);
|
|
411
|
+
if (
|
|
412
|
+
!match(leadingCommentValues.join("\n"), headerLines[0])
|
|
413
|
+
&& !match(leadingCommentValues.join("\r\n"), headerLines[0])
|
|
414
|
+
) {
|
|
415
|
+
context.report({
|
|
416
|
+
loc: node.loc,
|
|
417
|
+
messageId: "incorrectHeader",
|
|
418
|
+
fix: canFix
|
|
419
|
+
? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines)
|
|
420
|
+
: null
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
439
424
|
} else {
|
|
440
|
-
|
|
441
|
-
if (leadingComments.
|
|
425
|
+
for (let i = 0; i < headerLines.length; i++) {
|
|
426
|
+
if (!match(leadingComments[i].value, headerLines[i])) {
|
|
442
427
|
context.report({
|
|
443
428
|
loc: node.loc,
|
|
444
|
-
|
|
445
|
-
fix: canFix
|
|
429
|
+
messageId: "incorrectHeader",
|
|
430
|
+
fix: canFix
|
|
431
|
+
? genReplaceFixer(
|
|
432
|
+
commentType,
|
|
433
|
+
context,
|
|
434
|
+
leadingComments,
|
|
435
|
+
fixLines,
|
|
436
|
+
eol,
|
|
437
|
+
numNewlines)
|
|
438
|
+
: null
|
|
446
439
|
});
|
|
447
440
|
return;
|
|
448
441
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (!match(leadingCommentValues.join("\n"), headerLines[0]) && !match(leadingCommentValues.join("\r\n"), headerLines[0])) {
|
|
452
|
-
context.report({
|
|
453
|
-
loc: node.loc,
|
|
454
|
-
message: "incorrect header",
|
|
455
|
-
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
|
|
456
|
-
});
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
} else {
|
|
460
|
-
for (var i = 0; i < headerLines.length; i++) {
|
|
461
|
-
if (!match(leadingComments[i].value, headerLines[i])) {
|
|
462
|
-
context.report({
|
|
463
|
-
loc: node.loc,
|
|
464
|
-
message: "incorrect header",
|
|
465
|
-
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
|
|
466
|
-
});
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
var postLineHeader = context.getSourceCode().text.substr(leadingComments[headerLines.length - 1].range[1], numNewlines * 2);
|
|
473
|
-
if (!matchesLineEndings(postLineHeader, numNewlines)) {
|
|
474
|
-
context.report({
|
|
475
|
-
loc: node.loc,
|
|
476
|
-
message: "no newline after header",
|
|
477
|
-
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
|
|
478
|
-
});
|
|
479
|
-
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
480
444
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
445
|
+
const actualLeadingEmptyLines = leadingEmptyLines(
|
|
446
|
+
context.sourceCode.text.substring(leadingComments[headerLines.length - 1].range[1]));
|
|
447
|
+
const missingEmptyLines = numNewlines - actualLeadingEmptyLines;
|
|
448
|
+
if (missingEmptyLines > 0) {
|
|
449
|
+
context.report({
|
|
450
|
+
loc: node.loc,
|
|
451
|
+
messageId: "noNewlineAfterHeader",
|
|
452
|
+
fix: canFix ? genEmptyLinesFixer(leadingComments, eol, missingEmptyLines) : null
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
// if block comment pattern has more than 1 line, we also split
|
|
458
|
+
// the comment
|
|
459
|
+
let leadingLines = [leadingComments[0].value];
|
|
460
|
+
if (headerLines.length > 1) {
|
|
461
|
+
leadingLines = leadingComments[0].value.split(/\r?\n/);
|
|
462
|
+
}
|
|
487
463
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
464
|
+
let hasError = false;
|
|
465
|
+
if (leadingLines.length > headerLines.length) {
|
|
466
|
+
hasError = true;
|
|
467
|
+
}
|
|
468
|
+
for (let i = 0; !hasError && i < headerLines.length; i++) {
|
|
469
|
+
const leadingLine = leadingLines[i];
|
|
470
|
+
const headerLine = headerLines[i];
|
|
471
|
+
if (!match(leadingLine, headerLine)) {
|
|
472
|
+
hasError = true;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
500
476
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
context.report({
|
|
506
|
-
loc: node.loc,
|
|
507
|
-
message: "incorrect header",
|
|
508
|
-
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
|
|
509
|
-
});
|
|
510
|
-
} else {
|
|
511
|
-
var postBlockHeader = context.getSourceCode().text.substr(leadingComments[0].range[1], numNewlines * 2);
|
|
512
|
-
if (!matchesLineEndings(postBlockHeader, numNewlines)) {
|
|
513
|
-
context.report({
|
|
514
|
-
loc: node.loc,
|
|
515
|
-
message: "no newline after header",
|
|
516
|
-
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
477
|
+
if (hasError) {
|
|
478
|
+
if (canFix && headerLines.length > 1) {
|
|
479
|
+
fixLines = [fixLines.join(eol)];
|
|
521
480
|
}
|
|
481
|
+
context.report({
|
|
482
|
+
loc: node.loc,
|
|
483
|
+
messageId: "incorrectHeader",
|
|
484
|
+
fix: canFix
|
|
485
|
+
? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines)
|
|
486
|
+
: null
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const actualLeadingEmptyLines = leadingEmptyLines(
|
|
492
|
+
context.sourceCode.text.substring(leadingComments[0].range[1]));
|
|
493
|
+
const missingEmptyLines = numNewlines - actualLeadingEmptyLines;
|
|
494
|
+
if (missingEmptyLines > 0) {
|
|
495
|
+
context.report({
|
|
496
|
+
loc: node.loc,
|
|
497
|
+
messageId: "noNewlineAfterHeader",
|
|
498
|
+
fix: canFix ? genEmptyLinesFixer(leadingComments, eol, missingEmptyLines) : null
|
|
499
|
+
});
|
|
522
500
|
}
|
|
523
501
|
}
|
|
524
502
|
};
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
* @enum {string}
|
|
29
|
+
*/
|
|
30
|
+
const lineEndingOptions = Object.freeze({
|
|
31
|
+
os: "os",
|
|
32
|
+
unix: "unix",
|
|
33
|
+
windows: "windows",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @enum {string}
|
|
38
|
+
*/
|
|
39
|
+
const commentTypeOptions = Object.freeze({
|
|
40
|
+
block: "block",
|
|
41
|
+
line: "line"
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const schema = Object.freeze({
|
|
45
|
+
$ref: "#/definitions/options",
|
|
46
|
+
definitions: {
|
|
47
|
+
commentType: {
|
|
48
|
+
type: "string",
|
|
49
|
+
enum: [commentTypeOptions.block, commentTypeOptions.line]
|
|
50
|
+
},
|
|
51
|
+
line: {
|
|
52
|
+
anyOf: [
|
|
53
|
+
{
|
|
54
|
+
type: "string"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
pattern: {
|
|
60
|
+
type: "string"
|
|
61
|
+
},
|
|
62
|
+
template: {
|
|
63
|
+
type: "string"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
required: ["pattern"],
|
|
67
|
+
additionalProperties: false
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
headerLines: {
|
|
72
|
+
anyOf: [
|
|
73
|
+
{
|
|
74
|
+
$ref: "#/definitions/line"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: "array",
|
|
78
|
+
items: {
|
|
79
|
+
$ref: "#/definitions/line"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
numNewlines: {
|
|
85
|
+
type: "integer",
|
|
86
|
+
minimum: 0
|
|
87
|
+
},
|
|
88
|
+
settings: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
lineEndings: {
|
|
92
|
+
type: "string",
|
|
93
|
+
enum: [lineEndingOptions.unix, lineEndingOptions.windows]
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
additionalProperties: false
|
|
97
|
+
},
|
|
98
|
+
options: {
|
|
99
|
+
anyOf: [
|
|
100
|
+
{
|
|
101
|
+
type: "array",
|
|
102
|
+
minItems: 1,
|
|
103
|
+
maxItems: 2,
|
|
104
|
+
items: [
|
|
105
|
+
{ type: "string" },
|
|
106
|
+
{ $ref: "#/definitions/settings" }
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: "array",
|
|
111
|
+
minItems: 2,
|
|
112
|
+
maxItems: 3,
|
|
113
|
+
items: [
|
|
114
|
+
{ $ref: "#/definitions/commentType" },
|
|
115
|
+
{ $ref: "#/definitions/headerLines" },
|
|
116
|
+
{ $ref: "#/definitions/settings" }
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: "array",
|
|
121
|
+
minItems: 3,
|
|
122
|
+
maxItems: 4,
|
|
123
|
+
items: [
|
|
124
|
+
{ $ref: "#/definitions/commentType" },
|
|
125
|
+
{ $ref: "#/definitions/headerLines" },
|
|
126
|
+
{ $ref: "#/definitions/numNewlines" },
|
|
127
|
+
{ $ref: "#/definitions/settings" }
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
module.exports = { lineEndingOptions, commentTypeOptions, schema };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tony.ganchev/eslint-plugin-header",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.10",
|
|
4
4
|
"description": "ESLint plugin to ensure files begin with a given comment, usually a copyright or license notice.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -8,15 +8,21 @@
|
|
|
8
8
|
"!/lib/rules/test-utils.js"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
+
"eslint": "npx eslint .",
|
|
12
|
+
"lint": "npm run eslint && npm run markdownlint",
|
|
13
|
+
"markdownlint": "npx markdownlint-cli *.md",
|
|
11
14
|
"test": "npm run lint && npm run unit",
|
|
12
|
-
"unit": "nyc --reporter=html --reporter=text --reporter=text-summary --check-coverage=true --statements=
|
|
13
|
-
"lint": "eslint ."
|
|
15
|
+
"unit": "npx nyc --reporter=html --reporter=text --reporter=text-summary --reporter=lcov --check-coverage=true --statements=100 --branches=100 --lines=100 --functions=100 mocha tests/lib/*.js tests/lib/**/*.js"
|
|
14
16
|
},
|
|
15
17
|
"devDependencies": {
|
|
16
18
|
"@eslint/eslintrc": "^3.3.1",
|
|
17
19
|
"@eslint/js": "^9.32.0",
|
|
20
|
+
"@eslint/markdown": "^7.5.1",
|
|
21
|
+
"@stylistic/eslint-plugin": "^5.5.0",
|
|
18
22
|
"eslint": "^9.32.0",
|
|
23
|
+
"eslint-plugin-eslint-plugin": "^7.2.0",
|
|
19
24
|
"eslint-plugin-jsdoc": "^52.0.4",
|
|
25
|
+
"eslint-plugin-n": "^17.23.1",
|
|
20
26
|
"mocha": "^11.7.1",
|
|
21
27
|
"nyc": "^17.1.0",
|
|
22
28
|
"testdouble": "^3.20.2"
|