Skip to content

Commit

Permalink
Add a third option to specify number of linebreaks after the header.
Browse files Browse the repository at this point in the history
  • Loading branch information
emoller committed Aug 24, 2020
1 parent 3fc1807 commit 1fb4ad0
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 65 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
language: node_js
node_js:
- "8"
- "10"
- "14"
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Often you will want to have a copyright notice at the top of every file. This ES

## Usage

This rule takes 1 or 2 arguments with an optional settings object.
This rule takes 1, 2 or 3 arguments with an optional settings object.

### 1 argument

Expand Down Expand Up @@ -48,6 +48,61 @@ In the 2 argument form the first must be either `"block"` or `"line"` to indicat
}
```

### 3 arguments

The optional third argument which defaults to 1 specifies the number of newlines that are enforced after the header.

Zero newlines:
```json
{
"plugins": [
"header"
],
"rules": {
"header/header": [2, "block", [" Copyright now","My Company "], 0]
}
}
```
```js
/* Copyright now
My Company */ console.log(1)
```

One newline (default)
```json
{
"plugins": [
"header"
],
"rules": {
"header/header": [2, "block", [" Copyright now","My Company "], 1]
}
}
```
```js
/* Copyright now
My Company */
console.log(1)
```

two newlines
```json
{
"plugins": [
"header"
],
"rules": {
"header/header": [2, "block", [" Copyright now","My Company "], 2]
}
}
```
```js
/* Copyright now
My Company */

console.log(1)
```

#### Regular expressions

Instead of a string to be checked for exact matching you can also supply a regular expression. Be aware that you have to escape backslashes:
Expand Down
178 changes: 121 additions & 57 deletions lib/rules/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,30 @@ function excludeShebangs(comments) {
});
}

// Returns either the first block comment or the first set of line comments that
// are ONLY separated by a single newline. Note that this does not actually
// check if they are at the start of the file since that is already checked by
// hasHeader().
function getLeadingComments(context, node) {
return context.getSourceCode().getAllComments(node.body.length ? node.body[0] : node);
var all = excludeShebangs(context.getSourceCode().getAllComments(node.body.length ? node.body[0] : node));
if (all[0].type.toLowerCase() === "block") {
return [all[0]];
}
for (var i = 1; i < all.length; ++i) {
var txt = context.getSourceCode().getText().slice(all[i - 1].range[1], all[i].range[0]);
if (!txt.match(/^(\r\n|\r|\n)$/)) {
break;
}
}
return all.slice(0, i);
}

function genCommentBody(commentType, textArray, eol) {
function genCommentBody(commentType, textArray, eol, numNewlines) {
var eols = eol.repeat(numNewlines);
if (commentType === "block") {
return "/*" + textArray.join(eol) + "*/" + eol;
return "/*" + textArray.join(eol) + "*/" + eols;
} else {
return "//" + textArray.join(eol + "//") + eol;
return "//" + textArray.join(eol + "//") + eols;
}
}

Expand All @@ -43,20 +58,20 @@ function genCommentsRange(context, comments, eol) {
return [start, end];
}

function genPrependFixer(commentType, node, headerLines, eol) {
function genPrependFixer(commentType, node, headerLines, eol, numNewlines) {
return function(fixer) {
return fixer.insertTextBefore(
node,
genCommentBody(commentType, headerLines, eol)
genCommentBody(commentType, headerLines, eol, numNewlines)
);
};
}

function genReplaceFixer(commentType, context, leadingComments, headerLines, eol) {
function genReplaceFixer(commentType, context, leadingComments, headerLines, eol, numNewlines) {
return function(fixer) {
return fixer.replaceTextRange(
genCommentsRange(context, leadingComments, eol),
genCommentBody(commentType, headerLines, eol)
genCommentBody(commentType, headerLines, eol, numNewlines)
);
};
}
Expand All @@ -81,14 +96,36 @@ function getEOL(options) {
return os.EOL;
}

function hasHeader(src) {
if (src.substr(0, 2) === "#!") {
var m = src.match(/(\r\n|\r|\n)/);
if (m) {
src = src.slice(m.index + 1);
}
}
return src.substr(0, 2) === "/*" || src.substr(0, 2) === "//";
}

function matchesLineEndings(src, num) {
for (var j = 0; j < num; ++j) {
var m = src.match(/^(\r\n|\r|\n)/);
if (m) {
src = src.slice(m.index + m[0].length);
} else {
return false;
}
}
return true;
}

module.exports = {
meta: {
type: "layout",
fixable: "whitespace"
},
create: function(context) {
var options = context.options;

var numNewlines = options.length > 2 ? options[2] : 1;
var eol = getEOL(options);

// If just one option then read comment from file
Expand All @@ -97,7 +134,7 @@ module.exports = {
options = commentParser(text);
}

var commentType = options[0];
var commentType = options[0].toLowerCase();
var headerLines, fixLines = [];
// If any of the lines are regular expressions, then we can't
// automatically fix them. We set this to true below once we
Expand Down Expand Up @@ -128,69 +165,96 @@ module.exports = {

return {
Program: function(node) {
var leadingComments = excludeShebangs(getLeadingComments(context, node));

if (!leadingComments.length) {
if (!hasHeader(context.getSourceCode().getText())) {
context.report({
loc: node.loc,
message: "missing header",
fix: canFix ? genPrependFixer(commentType, node, fixLines, eol) : null
});
} else if (leadingComments[0].type.toLowerCase() !== commentType) {
context.report({
loc: node.loc,
message: "header should be a {{commentType}} comment",
data: {
commentType: commentType
},
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol) : null
fix: genPrependFixer(commentType, node, fixLines, eol, numNewlines)
});
} else {
if (commentType === "line") {
if (leadingComments.length < headerLines.length) {
context.report({
loc: node.loc,
message: "incorrect header",
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol) : null
});
return;
}
for (var i = 0; i < headerLines.length; i++) {
if (!match(leadingComments[i].value, headerLines[i])) {
var leadingComments = getLeadingComments(context, node);

if (!leadingComments.length) {
context.report({
loc: node.loc,
message: "missing header",
fix: canFix ? genPrependFixer(commentType, node, fixLines, eol, numNewlines) : null
});
} else if (leadingComments[0].type.toLowerCase() !== commentType) {
context.report({
loc: node.loc,
message: "header should be a {{commentType}} comment",
data: {
commentType: commentType
},
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
});
} else {
if (commentType === "line") {
if (leadingComments.length < headerLines.length) {
context.report({
loc: node.loc,
message: "incorrect header",
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol) : null
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
});
return;
}
}
} else {
// if block comment pattern has more than 1 line, we also split the comment
var leadingLines = [leadingComments[0].value];
if (headerLines.length > 1) {
leadingLines = leadingComments[0].value.split(/\r?\n/);
}
for (var i = 0; i < headerLines.length; i++) {
if (!match(leadingComments[i].value, headerLines[i])) {
context.report({
loc: node.loc,
message: "incorrect header",
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
});
return;
}
}

var hasError = false;
if (leadingLines.length > headerLines.length) {
hasError = true;
}
for (i = 0; !hasError && i < headerLines.length; i++) {
if (!match(leadingLines[i], headerLines[i])) {
var postLineHeader = context.getSourceCode().text.substr(leadingComments[headerLines.length - 1].range[1], numNewlines * 2);
if (!matchesLineEndings(postLineHeader, numNewlines)) {
context.report({
loc: node.loc,
message: "no newline after header",
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
});
}

} else {
// if block comment pattern has more than 1 line, we also split the comment
var leadingLines = [leadingComments[0].value];
if (headerLines.length > 1) {
leadingLines = leadingComments[0].value.split(/\r?\n/);
}

var hasError = false;
if (leadingLines.length > headerLines.length) {
hasError = true;
}
}
for (i = 0; !hasError && i < headerLines.length; i++) {
if (!match(leadingLines[i], headerLines[i])) {
hasError = true;
}
}

if (hasError) {
if (canFix && headerLines.length > 1) {
fixLines = [fixLines.join(eol)];
if (hasError) {
if (canFix && headerLines.length > 1) {
fixLines = [fixLines.join(eol)];
}
context.report({
loc: node.loc,
message: "incorrect header",
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
});
} else {
var postBlockHeader = context.getSourceCode().text.substr(leadingComments[0].range[1], numNewlines * 2);
if (!matchesLineEndings(postBlockHeader, numNewlines)) {
context.report({
loc: node.loc,
message: "no newline after header",
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol, numNewlines) : null
});
}
}
context.report({
loc: node.loc,
message: "incorrect header",
fix: canFix ? genReplaceFixer(commentType, context, leadingComments, fixLines, eol) : null
});
}
}
}
Expand Down

0 comments on commit 1fb4ad0

Please sign in to comment.