eslint-plugin-complete
Version:
An ESLint plugin that contains useful rules.
119 lines (118 loc) • 5.47 kB
JavaScript
import { assertDefined } from "../completeCommon.js";
import { formatText } from "../format.js";
import { allCommentsInBlockAreCommentedOutArrayElements, getCommentBlocks, getLeadingLineComments, } from "../leadingLineComments.js";
import { areStringsEqualExcludingTrailingSpaces, createRule, } from "../utils.js";
const SLASH_SLASH = "//";
const DEBUG = false;
export const formatLineComments = createRule({
name: "format-line-comments",
meta: {
type: "layout",
docs: {
description: "Disallows `//` comments longer than N characters and multi-line comments that can be merged together",
recommended: true,
requiresTypeChecking: false,
},
schema: [
{
type: "object",
properties: {
maxLength: { type: "number" },
},
additionalProperties: false,
},
],
messages: {
incorrectlyFormatted: "Comment is not formatted correctly.",
},
fixable: "whitespace",
},
defaultOptions: [
{
/**
* Matches the Airbnb style guide, which is the most popular JavaScript style guide in the
* world.
*/
maxLength: 100,
},
],
/**
* We need to write the rule in such a way that it operates on the entire source code instead of
* individual AST nodes:
* https://stackoverflow.com/questions/47429792/is-it-possible-to-get-comments-as-nodes-in-the-ast-using-the-typescript-compiler
*/
create(context, [options]) {
const { maxLength } = options;
const { sourceCode } = context;
const comments = sourceCode.getAllComments();
// We only look at `//` style comments on their own line.
const leadingLineComments = getLeadingLineComments(sourceCode, comments);
// Sort the comments by blocks.
const commentBlocks = getCommentBlocks(leadingLineComments);
for (const commentBlock of commentBlocks) {
const firstComment = commentBlock.originalComments.at(0);
assertDefined(firstComment, "Failed to get the first comment.");
const lastComment = commentBlock.originalComments.at(-1);
assertDefined(lastComment, "Failed to get the last comment.");
// Commented out array elements are whitelisted.
if (allCommentsInBlockAreCommentedOutArrayElements(commentBlock)) {
continue;
}
const leftWhitespaceLength = firstComment.loc.start.column;
const leftWhitespace = " ".repeat(leftWhitespaceLength);
const originalText = getTextFromComments(commentBlock.originalComments, leftWhitespace);
const effectiveMaxLength = maxLength - leftWhitespaceLength - "// ".length;
const formattedTextRaw = formatText(commentBlock.mergedText, effectiveMaxLength, false);
const formattedText = convertTextToLeadingLineComments(formattedTextRaw, leftWhitespace);
if (DEBUG && originalText !== formattedText) {
console.log("originalText:");
console.log(originalText);
console.log("formattedText:");
console.log(formattedText);
}
if (!areStringsEqualExcludingTrailingSpaces(originalText, formattedText)) {
context.report({
loc: {
start: firstComment.loc.start,
end: lastComment.loc.end,
},
messageId: "incorrectlyFormatted",
fix: (fixer) => {
const [firstCommentStart, _firstCommentEnd] = firstComment.range;
const [_lastCommentStart, lastCommentEnd] = lastComment.range;
const firstCommentBeginningOfLine = firstCommentStart - firstComment.loc.start.column;
const range = [
firstCommentBeginningOfLine,
lastCommentEnd,
];
return fixer.replaceTextRange(range, formattedText);
},
});
}
}
return {};
},
});
/**
* Given an array of comments, transform the text back into how it would look in the real source
* code.
*
* Note that this should not include the left whitespace before the comment actually begins, because
* we need to compare the vanilla source code to the formatted source code without worrying about
* any leading whitespace.
*/
function getTextFromComments(comments, leftWhitespace) {
const lines = comments.map(
// `comment.value` will almost always have a leading leading space, due to Prettier changing
// `//Comment` to `// Comment`. But it is also possible that the rule is running before Prettier
// has had a chance to format the code. Either way, we want this function to represent the text
// as it really is in the source code.
(comment) => `${leftWhitespace}${SLASH_SLASH}${comment.value}`);
return lines.join("\n");
}
/** Converts "Foo" to "// Foo". */
function convertTextToLeadingLineComments(text, leftWhitespace) {
const lines = text.split("\n");
const linesWithPrefix = lines.map((line) => `${leftWhitespace}${SLASH_SLASH} ${line}`);
return linesWithPrefix.join("\n");
}