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 22, 2020
1 parent 3fc1807 commit 88c195d
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 63 deletions.
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
164 changes: 107 additions & 57 deletions lib/rules/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ function getLeadingComments(context, node) {
return context.getSourceCode().getAllComments(node.body.length ? node.body[0] : node);
}

function genCommentBody(commentType, textArray, eol) {
function genCommentBody(commentType, textArray, eol, newlines) {
var eols = "";
for (var j = 0; j < newlines; ++j) {
eols += eol;
}
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,28 +47,27 @@ function genCommentsRange(context, comments, eol) {
return [start, end];
}

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

function genReplaceFixer(commentType, context, leadingComments, headerLines, eol) {
function genReplaceFixer(commentType, context, leadingComments, headerLines, eol, newlines) {
return function(fixer) {
return fixer.replaceTextRange(
genCommentsRange(context, leadingComments, eol),
genCommentBody(commentType, headerLines, eol)
genCommentBody(commentType, headerLines, eol, newlines)
);
};
}

function findSettings(options) {
var lastOption = options.length > 0 ? options[options.length - 1] : null;
if (typeof lastOption === "object" && !Array.isArray(lastOption) && lastOption !== null
&& !Object.prototype.hasOwnProperty.call(lastOption, "pattern")) {
if (typeof lastOption === "object" && !Array.isArray(lastOption) && lastOption !== null && !Object.prototype.hasOwnProperty.call(lastOption, "pattern")) {
return lastOption;
}
return null;
Expand All @@ -81,14 +84,34 @@ function getEOL(options) {
return os.EOL;
}

function hasHeader(src) {
if (src.substr(0, 2) === "#!") {
src = src.split(os.EOL)[1];
}
return src.substr(0, 2) === "/*" || src.substr(0, 2) === "//";
}

function matchesLineEndings(src, num) {
for (var j = 0; j < num; ++j) {
if (src.substr(0, 2) === "\r\n") {
src = src.substr(2);
} else if (src[0] === "\n") {
src = src.substr(1);
} 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 Down Expand Up @@ -128,69 +151,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 = excludeShebangs(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
46 changes: 41 additions & 5 deletions tests/lib/rules/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ruleTester.run("header", rule, {
},
{
code: "/*Copyright 2015, My Company*/",
options: ["block", "Copyright 2015, My Company"]
options: ["block", "Copyright 2015, My Company", 0]
},
{
code: "//Copyright 2015\n//My Company\nconsole.log(1)",
Expand Down Expand Up @@ -53,23 +53,23 @@ ruleTester.run("header", rule, {
},
{
code: "// Copyright 2017",
options: ["line", {pattern: "^ Copyright \\d+$"}]
options: ["line", {pattern: "^ Copyright \\d+$"}, 0]
},
{
code: "// Copyright 2017\n// Author: abc@example.com",
options: ["line", [{pattern: "^ Copyright \\d+$"}, {pattern: "^ Author: \\w+@\\w+\\.\\w+$"}]]
options: ["line", [{pattern: "^ Copyright \\d+$"}, {pattern: "^ Author: \\w+@\\w+\\.\\w+$"}], 0]
},
{
code: "/* Copyright 2017\n Author: abc@example.com */",
options: ["block", {pattern: "^ Copyright \\d{4}\\n Author: \\w+@\\w+\\.\\w+ $"}]
options: ["block", {pattern: "^ Copyright \\d{4}\\n Author: \\w+@\\w+\\.\\w+ $"}, 0]
},
{
code: "#!/usr/bin/env node\n/**\n * Copyright\n */",
options: ["block", [
"*",
" * Copyright",
" "
]]
], 0]
},
{
code: "// Copyright 2015\r\n// My Company\r\nconsole.log(1)",
Expand All @@ -95,6 +95,26 @@ ruleTester.run("header", rule, {
" * My Company",
" ************************"
]]
},
{
code: "/*Copyright 2020, My Company*/\nconsole.log(1);",
options: ["block", "Copyright 2020, My Company", 1],
},
{
code: "/*Copyright 2020, My Company*/\n\nconsole.log(1);",
options: ["block", "Copyright 2020, My Company", 2],
},
{
code: "/*Copyright 2020, My Company*/\n\n// Log number one\nconsole.log(1);",
options: ["block", "Copyright 2020, My Company", 2],
},
{
code: "/*Copyright 2020, My Company*/\n\n/*Log number one*/\nconsole.log(1);",
options: ["block", "Copyright 2020, My Company", 2],
},
{
code: "/**\n * Copyright 2020\n * My Company\n **/\n\n/*Log number one*/\nconsole.log(1);",
options: ["block", "*\n * Copyright 2020\n * My Company\n *", 2],
}
],
invalid: [
Expand Down Expand Up @@ -212,6 +232,22 @@ ruleTester.run("header", rule, {
{message: "incorrect header"}
],
output: "/*************************\n * Copyright 2019\n * My Company\n *************************/\nconsole.log(1)"
},
{
code: "/*Copyright 2020, My Company*/\nconsole.log(1);",
options: ["block", "Copyright 2020, My Company", 2],
errors: [
{message: "no newline after header"}
],
output: "/*Copyright 2020, My Company*/\n\nconsole.log(1);"
},
{
code: "/*Copyright 2020, My Company*/console.log(1);",
options: ["block", "Copyright 2020, My Company", 1],
errors: [
{message: "no newline after header"}
],
output: "/*Copyright 2020, My Company*/\nconsole.log(1);"
}
]
});

0 comments on commit 88c195d

Please sign in to comment.