Permalink
Cannot retrieve contributors at this time
468 lines (409 sloc)
17.8 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/eslint/lib/rules/lines-around-comment.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @fileoverview Enforces empty lines around comments. | |
* @author Jamund Ferguson | |
*/ | |
"use strict"; | |
//------------------------------------------------------------------------------ | |
// Requirements | |
//------------------------------------------------------------------------------ | |
const astUtils = require("./utils/ast-utils"); | |
//------------------------------------------------------------------------------ | |
// Helpers | |
//------------------------------------------------------------------------------ | |
/** | |
* Return an array with any line numbers that are empty. | |
* @param {Array} lines An array of each line of the file. | |
* @returns {Array} An array of line numbers. | |
*/ | |
function getEmptyLineNums(lines) { | |
const emptyLines = lines.map((line, i) => ({ | |
code: line.trim(), | |
num: i + 1 | |
})).filter(line => !line.code).map(line => line.num); | |
return emptyLines; | |
} | |
/** | |
* Return an array with any line numbers that contain comments. | |
* @param {Array} comments An array of comment tokens. | |
* @returns {Array} An array of line numbers. | |
*/ | |
function getCommentLineNums(comments) { | |
const lines = []; | |
comments.forEach(token => { | |
const start = token.loc.start.line; | |
const end = token.loc.end.line; | |
lines.push(start, end); | |
}); | |
return lines; | |
} | |
//------------------------------------------------------------------------------ | |
// Rule Definition | |
//------------------------------------------------------------------------------ | |
/** @type {import('../shared/types').Rule} */ | |
module.exports = { | |
meta: { | |
type: "layout", | |
docs: { | |
description: "Require empty lines around comments", | |
recommended: false, | |
url: "https://eslint.org/docs/latest/rules/lines-around-comment" | |
}, | |
fixable: "whitespace", | |
schema: [ | |
{ | |
type: "object", | |
properties: { | |
beforeBlockComment: { | |
type: "boolean", | |
default: true | |
}, | |
afterBlockComment: { | |
type: "boolean", | |
default: false | |
}, | |
beforeLineComment: { | |
type: "boolean", | |
default: false | |
}, | |
afterLineComment: { | |
type: "boolean", | |
default: false | |
}, | |
allowBlockStart: { | |
type: "boolean", | |
default: false | |
}, | |
allowBlockEnd: { | |
type: "boolean", | |
default: false | |
}, | |
allowClassStart: { | |
type: "boolean" | |
}, | |
allowClassEnd: { | |
type: "boolean" | |
}, | |
allowObjectStart: { | |
type: "boolean" | |
}, | |
allowObjectEnd: { | |
type: "boolean" | |
}, | |
allowArrayStart: { | |
type: "boolean" | |
}, | |
allowArrayEnd: { | |
type: "boolean" | |
}, | |
ignorePattern: { | |
type: "string" | |
}, | |
applyDefaultIgnorePatterns: { | |
type: "boolean" | |
}, | |
afterHashbangComment: { | |
type: "boolean", | |
default: false | |
} | |
}, | |
additionalProperties: false | |
} | |
], | |
messages: { | |
after: "Expected line after comment.", | |
before: "Expected line before comment." | |
} | |
}, | |
create(context) { | |
const options = Object.assign({}, context.options[0]); | |
const ignorePattern = options.ignorePattern; | |
const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; | |
const customIgnoreRegExp = new RegExp(ignorePattern, "u"); | |
const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false; | |
options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; | |
const sourceCode = context.sourceCode; | |
const lines = sourceCode.lines, | |
numLines = lines.length + 1, | |
comments = sourceCode.getAllComments(), | |
commentLines = getCommentLineNums(comments), | |
emptyLines = getEmptyLineNums(lines), | |
commentAndEmptyLines = new Set(commentLines.concat(emptyLines)); | |
/** | |
* Returns whether or not comments are on lines starting with or ending with code | |
* @param {token} token The comment token to check. | |
* @returns {boolean} True if the comment is not alone. | |
*/ | |
function codeAroundComment(token) { | |
let currentToken = token; | |
do { | |
currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true }); | |
} while (currentToken && astUtils.isCommentToken(currentToken)); | |
if (currentToken && astUtils.isTokenOnSameLine(currentToken, token)) { | |
return true; | |
} | |
currentToken = token; | |
do { | |
currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true }); | |
} while (currentToken && astUtils.isCommentToken(currentToken)); | |
if (currentToken && astUtils.isTokenOnSameLine(token, currentToken)) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Returns whether or not comments are inside a node type or not. | |
* @param {ASTNode} parent The Comment parent node. | |
* @param {string} nodeType The parent type to check against. | |
* @returns {boolean} True if the comment is inside nodeType. | |
*/ | |
function isParentNodeType(parent, nodeType) { | |
return parent.type === nodeType || | |
(parent.body && parent.body.type === nodeType) || | |
(parent.consequent && parent.consequent.type === nodeType); | |
} | |
/** | |
* Returns the parent node that contains the given token. | |
* @param {token} token The token to check. | |
* @returns {ASTNode|null} The parent node that contains the given token. | |
*/ | |
function getParentNodeOfToken(token) { | |
const node = sourceCode.getNodeByRangeIndex(token.range[0]); | |
/* | |
* For the purpose of this rule, the comment token is in a `StaticBlock` node only | |
* if it's inside the braces of that `StaticBlock` node. | |
* | |
* Example where this function returns `null`: | |
* | |
* static | |
* // comment | |
* { | |
* } | |
* | |
* Example where this function returns `StaticBlock` node: | |
* | |
* static | |
* { | |
* // comment | |
* } | |
* | |
*/ | |
if (node && node.type === "StaticBlock") { | |
const openingBrace = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token | |
return token.range[0] >= openingBrace.range[0] | |
? node | |
: null; | |
} | |
return node; | |
} | |
/** | |
* Returns whether or not comments are at the parent start or not. | |
* @param {token} token The Comment token. | |
* @param {string} nodeType The parent type to check against. | |
* @returns {boolean} True if the comment is at parent start. | |
*/ | |
function isCommentAtParentStart(token, nodeType) { | |
const parent = getParentNodeOfToken(token); | |
if (parent && isParentNodeType(parent, nodeType)) { | |
let parentStartNodeOrToken = parent; | |
if (parent.type === "StaticBlock") { | |
parentStartNodeOrToken = sourceCode.getFirstToken(parent, { skip: 1 }); // opening brace of the static block | |
} else if (parent.type === "SwitchStatement") { | |
parentStartNodeOrToken = sourceCode.getTokenAfter(parent.discriminant, { | |
filter: astUtils.isOpeningBraceToken | |
}); // opening brace of the switch statement | |
} | |
return token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1; | |
} | |
return false; | |
} | |
/** | |
* Returns whether or not comments are at the parent end or not. | |
* @param {token} token The Comment token. | |
* @param {string} nodeType The parent type to check against. | |
* @returns {boolean} True if the comment is at parent end. | |
*/ | |
function isCommentAtParentEnd(token, nodeType) { | |
const parent = getParentNodeOfToken(token); | |
return !!parent && isParentNodeType(parent, nodeType) && | |
parent.loc.end.line - token.loc.end.line === 1; | |
} | |
/** | |
* Returns whether or not comments are at the block start or not. | |
* @param {token} token The Comment token. | |
* @returns {boolean} True if the comment is at block start. | |
*/ | |
function isCommentAtBlockStart(token) { | |
return ( | |
isCommentAtParentStart(token, "ClassBody") || | |
isCommentAtParentStart(token, "BlockStatement") || | |
isCommentAtParentStart(token, "StaticBlock") || | |
isCommentAtParentStart(token, "SwitchCase") || | |
isCommentAtParentStart(token, "SwitchStatement") | |
); | |
} | |
/** | |
* Returns whether or not comments are at the block end or not. | |
* @param {token} token The Comment token. | |
* @returns {boolean} True if the comment is at block end. | |
*/ | |
function isCommentAtBlockEnd(token) { | |
return ( | |
isCommentAtParentEnd(token, "ClassBody") || | |
isCommentAtParentEnd(token, "BlockStatement") || | |
isCommentAtParentEnd(token, "StaticBlock") || | |
isCommentAtParentEnd(token, "SwitchCase") || | |
isCommentAtParentEnd(token, "SwitchStatement") | |
); | |
} | |
/** | |
* Returns whether or not comments are at the class start or not. | |
* @param {token} token The Comment token. | |
* @returns {boolean} True if the comment is at class start. | |
*/ | |
function isCommentAtClassStart(token) { | |
return isCommentAtParentStart(token, "ClassBody"); | |
} | |
/** | |
* Returns whether or not comments are at the class end or not. | |
* @param {token} token The Comment token. | |
* @returns {boolean} True if the comment is at class end. | |
*/ | |
function isCommentAtClassEnd(token) { | |
return isCommentAtParentEnd(token, "ClassBody"); | |
} | |
/** | |
* Returns whether or not comments are at the object start or not. | |
* @param {token} token The Comment token. | |
* @returns {boolean} True if the comment is at object start. | |
*/ | |
function isCommentAtObjectStart(token) { | |
return isCommentAtParentStart(token, "ObjectExpression") || isCommentAtParentStart(token, "ObjectPattern"); | |
} | |
/** | |
* Returns whether or not comments are at the object end or not. | |
* @param {token} token The Comment token. | |
* @returns {boolean} True if the comment is at object end. | |
*/ | |
function isCommentAtObjectEnd(token) { | |
return isCommentAtParentEnd(token, "ObjectExpression") || isCommentAtParentEnd(token, "ObjectPattern"); | |
} | |
/** | |
* Returns whether or not comments are at the array start or not. | |
* @param {token} token The Comment token. | |
* @returns {boolean} True if the comment is at array start. | |
*/ | |
function isCommentAtArrayStart(token) { | |
return isCommentAtParentStart(token, "ArrayExpression") || isCommentAtParentStart(token, "ArrayPattern"); | |
} | |
/** | |
* Returns whether or not comments are at the array end or not. | |
* @param {token} token The Comment token. | |
* @returns {boolean} True if the comment is at array end. | |
*/ | |
function isCommentAtArrayEnd(token) { | |
return isCommentAtParentEnd(token, "ArrayExpression") || isCommentAtParentEnd(token, "ArrayPattern"); | |
} | |
/** | |
* Checks if a comment token has lines around it (ignores inline comments) | |
* @param {token} token The Comment token. | |
* @param {Object} opts Options to determine the newline. | |
* @param {boolean} opts.after Should have a newline after this line. | |
* @param {boolean} opts.before Should have a newline before this line. | |
* @returns {void} | |
*/ | |
function checkForEmptyLine(token, opts) { | |
if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value)) { | |
return; | |
} | |
if (ignorePattern && customIgnoreRegExp.test(token.value)) { | |
return; | |
} | |
let after = opts.after, | |
before = opts.before; | |
const prevLineNum = token.loc.start.line - 1, | |
nextLineNum = token.loc.end.line + 1, | |
commentIsNotAlone = codeAroundComment(token); | |
const blockStartAllowed = options.allowBlockStart && | |
isCommentAtBlockStart(token) && | |
!(options.allowClassStart === false && | |
isCommentAtClassStart(token)), | |
blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)), | |
classStartAllowed = options.allowClassStart && isCommentAtClassStart(token), | |
classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token), | |
objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token), | |
objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token), | |
arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token), | |
arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token); | |
const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed; | |
const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed; | |
// ignore top of the file and bottom of the file | |
if (prevLineNum < 1) { | |
before = false; | |
} | |
if (nextLineNum >= numLines) { | |
after = false; | |
} | |
// we ignore all inline comments | |
if (commentIsNotAlone) { | |
return; | |
} | |
const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true }); | |
const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true }); | |
// check for newline before | |
if (!exceptionStartAllowed && before && !commentAndEmptyLines.has(prevLineNum) && | |
!(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) { | |
const lineStart = token.range[0] - token.loc.start.column; | |
const range = [lineStart, lineStart]; | |
context.report({ | |
node: token, | |
messageId: "before", | |
fix(fixer) { | |
return fixer.insertTextBeforeRange(range, "\n"); | |
} | |
}); | |
} | |
// check for newline after | |
if (!exceptionEndAllowed && after && !commentAndEmptyLines.has(nextLineNum) && | |
!(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) { | |
context.report({ | |
node: token, | |
messageId: "after", | |
fix(fixer) { | |
return fixer.insertTextAfter(token, "\n"); | |
} | |
}); | |
} | |
} | |
//-------------------------------------------------------------------------- | |
// Public | |
//-------------------------------------------------------------------------- | |
return { | |
Program() { | |
comments.forEach(token => { | |
if (token.type === "Line") { | |
if (options.beforeLineComment || options.afterLineComment) { | |
checkForEmptyLine(token, { | |
after: options.afterLineComment, | |
before: options.beforeLineComment | |
}); | |
} | |
} else if (token.type === "Block") { | |
if (options.beforeBlockComment || options.afterBlockComment) { | |
checkForEmptyLine(token, { | |
after: options.afterBlockComment, | |
before: options.beforeBlockComment | |
}); | |
} | |
} else if (token.type === "Shebang") { | |
if (options.afterHashbangComment) { | |
checkForEmptyLine(token, { | |
after: options.afterHashbangComment, | |
before: false | |
}); | |
} | |
} | |
}); | |
} | |
}; | |
} | |
}; |