|
|
|
|
|
|
|
|
|
"use strict"; |
|
|
|
|
|
|
|
|
|
|
|
const LETTER_PATTERN = require("./utils/patterns/letters"); |
|
const astUtils = require("./utils/ast-utils"); |
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN, |
|
WHITESPACE = /\s/gu, |
|
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u; |
|
|
|
|
|
|
|
|
|
|
|
|
|
const SCHEMA_BODY = { |
|
type: "object", |
|
properties: { |
|
ignorePattern: { |
|
type: "string" |
|
}, |
|
ignoreInlineComments: { |
|
type: "boolean" |
|
}, |
|
ignoreConsecutiveComments: { |
|
type: "boolean" |
|
} |
|
}, |
|
additionalProperties: false |
|
}; |
|
const DEFAULTS = { |
|
ignorePattern: "", |
|
ignoreInlineComments: false, |
|
ignoreConsecutiveComments: false |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getNormalizedOptions(rawOptions, which) { |
|
return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getAllNormalizedOptions(rawOptions = {}) { |
|
return { |
|
Line: getNormalizedOptions(rawOptions, "line"), |
|
Block: getNormalizedOptions(rawOptions, "block") |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createRegExpForIgnorePatterns(normalizedOptions) { |
|
Object.keys(normalizedOptions).forEach(key => { |
|
const ignorePatternStr = normalizedOptions[key].ignorePattern; |
|
|
|
if (ignorePatternStr) { |
|
const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u"); |
|
|
|
normalizedOptions[key].ignorePatternRegExp = regExp; |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { |
|
meta: { |
|
type: "suggestion", |
|
|
|
docs: { |
|
description: "Enforce or disallow capitalization of the first letter of a comment", |
|
recommended: false, |
|
url: "https://eslint.org/docs/latest/rules/capitalized-comments" |
|
}, |
|
|
|
fixable: "code", |
|
|
|
schema: [ |
|
{ enum: ["always", "never"] }, |
|
{ |
|
oneOf: [ |
|
SCHEMA_BODY, |
|
{ |
|
type: "object", |
|
properties: { |
|
line: SCHEMA_BODY, |
|
block: SCHEMA_BODY |
|
}, |
|
additionalProperties: false |
|
} |
|
] |
|
} |
|
], |
|
|
|
messages: { |
|
unexpectedLowercaseComment: "Comments should not begin with a lowercase character.", |
|
unexpectedUppercaseComment: "Comments should not begin with an uppercase character." |
|
} |
|
}, |
|
|
|
create(context) { |
|
|
|
const capitalize = context.options[0] || "always", |
|
normalizedOptions = getAllNormalizedOptions(context.options[1]), |
|
sourceCode = context.sourceCode; |
|
|
|
createRegExpForIgnorePatterns(normalizedOptions); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isInlineComment(comment) { |
|
const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }), |
|
nextToken = sourceCode.getTokenAfter(comment, { includeComments: true }); |
|
|
|
return Boolean( |
|
previousToken && |
|
nextToken && |
|
comment.loc.start.line === previousToken.loc.end.line && |
|
comment.loc.end.line === nextToken.loc.start.line |
|
); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function isConsecutiveComment(comment) { |
|
const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true }); |
|
|
|
return Boolean( |
|
previousTokenOrComment && |
|
["Block", "Line"].includes(previousTokenOrComment.type) |
|
); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isCommentValid(comment, options) { |
|
|
|
|
|
if (DEFAULT_IGNORE_PATTERN.test(comment.value)) { |
|
return true; |
|
} |
|
|
|
|
|
const commentWithoutAsterisks = comment.value |
|
.replace(/\*/gu, ""); |
|
|
|
if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) { |
|
return true; |
|
} |
|
|
|
|
|
if (options.ignoreInlineComments && isInlineComment(comment)) { |
|
return true; |
|
} |
|
|
|
|
|
if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) { |
|
return true; |
|
} |
|
|
|
|
|
if (MAYBE_URL.test(commentWithoutAsterisks)) { |
|
return true; |
|
} |
|
|
|
|
|
const commentWordCharsOnly = commentWithoutAsterisks |
|
.replace(WHITESPACE, ""); |
|
|
|
if (commentWordCharsOnly.length === 0) { |
|
return true; |
|
} |
|
|
|
const firstWordChar = commentWordCharsOnly[0]; |
|
|
|
if (!LETTER_PATTERN.test(firstWordChar)) { |
|
return true; |
|
} |
|
|
|
|
|
const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(), |
|
isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase(); |
|
|
|
if (capitalize === "always" && isLowercase) { |
|
return false; |
|
} |
|
if (capitalize === "never" && isUppercase) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function processComment(comment) { |
|
const options = normalizedOptions[comment.type], |
|
commentValid = isCommentValid(comment, options); |
|
|
|
if (!commentValid) { |
|
const messageId = capitalize === "always" |
|
? "unexpectedLowercaseComment" |
|
: "unexpectedUppercaseComment"; |
|
|
|
context.report({ |
|
node: null, |
|
loc: comment.loc, |
|
messageId, |
|
fix(fixer) { |
|
const match = comment.value.match(LETTER_PATTERN); |
|
|
|
return fixer.replaceTextRange( |
|
|
|
|
|
[comment.range[0] + match.index + 2, comment.range[0] + match.index + 3], |
|
capitalize === "always" ? match[0].toLocaleUpperCase() : match[0].toLocaleLowerCase() |
|
); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
Program() { |
|
const comments = sourceCode.getAllComments(); |
|
|
|
comments.filter(token => token.type !== "Shebang").forEach(processComment); |
|
} |
|
}; |
|
} |
|
}; |
|
|