You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
8.0 KiB
259 lines
8.0 KiB
/** |
|
* @typedef { { exceptions?: string[] } } CommentParserConfig |
|
* @typedef { (comment: ParsedHTMLComment) => void } HTMLCommentVisitor |
|
* @typedef { { includeDirectives?: boolean } } CommentVisitorOption |
|
* |
|
* @typedef { Token & { type: 'HTMLCommentOpen' } } HTMLCommentOpen |
|
* @typedef { Token & { type: 'HTMLCommentOpenDecoration' } } HTMLCommentOpenDecoration |
|
* @typedef { Token & { type: 'HTMLCommentValue' } } HTMLCommentValue |
|
* @typedef { Token & { type: 'HTMLCommentClose' } } HTMLCommentClose |
|
* @typedef { Token & { type: 'HTMLCommentCloseDecoration' } } HTMLCommentCloseDecoration |
|
* @typedef { { open: HTMLCommentOpen, openDecoration: HTMLCommentOpenDecoration | null, value: HTMLCommentValue | null, closeDecoration: HTMLCommentCloseDecoration | null, close: HTMLCommentClose } } ParsedHTMLComment |
|
*/ |
|
// ----------------------------------------------------------------------------- |
|
// Requirements |
|
// ----------------------------------------------------------------------------- |
|
|
|
const utils = require('./') |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Helpers |
|
// ------------------------------------------------------------------------------ |
|
|
|
const COMMENT_DIRECTIVE = /^\s*eslint-(?:en|dis)able/ |
|
const IE_CONDITIONAL_IF = /^\[if\s+/ |
|
const IE_CONDITIONAL_ENDIF = /\[endif\]$/ |
|
|
|
/** @type { 'HTMLCommentOpen' } */ |
|
const TYPE_HTML_COMMENT_OPEN = 'HTMLCommentOpen' |
|
/** @type { 'HTMLCommentOpenDecoration' } */ |
|
const TYPE_HTML_COMMENT_OPEN_DECORATION = 'HTMLCommentOpenDecoration' |
|
/** @type { 'HTMLCommentValue' } */ |
|
const TYPE_HTML_COMMENT_VALUE = 'HTMLCommentValue' |
|
/** @type { 'HTMLCommentClose' } */ |
|
const TYPE_HTML_COMMENT_CLOSE = 'HTMLCommentClose' |
|
/** @type { 'HTMLCommentCloseDecoration' } */ |
|
const TYPE_HTML_COMMENT_CLOSE_DECORATION = 'HTMLCommentCloseDecoration' |
|
|
|
/** |
|
* @param {HTMLComment} comment |
|
* @returns {boolean} |
|
*/ |
|
function isCommentDirective(comment) { |
|
return COMMENT_DIRECTIVE.test(comment.value) |
|
} |
|
|
|
/** |
|
* @param {HTMLComment} comment |
|
* @returns {boolean} |
|
*/ |
|
function isIEConditionalComment(comment) { |
|
return ( |
|
IE_CONDITIONAL_IF.test(comment.value) || |
|
IE_CONDITIONAL_ENDIF.test(comment.value) |
|
) |
|
} |
|
|
|
/** |
|
* Define HTML comment parser |
|
* |
|
* @param {SourceCode} sourceCode The source code instance. |
|
* @param {CommentParserConfig | null} config The config. |
|
* @returns { (node: Token) => (ParsedHTMLComment | null) } HTML comment parser. |
|
*/ |
|
function defineParser(sourceCode, config) { |
|
config = config || {} |
|
|
|
const exceptions = config.exceptions || [] |
|
|
|
/** |
|
* Get a open decoration string from comment contents. |
|
* @param {string} contents comment contents |
|
* @returns {string} decoration string |
|
*/ |
|
function getOpenDecoration(contents) { |
|
let decoration = '' |
|
for (const exception of exceptions) { |
|
const length = exception.length |
|
let index = 0 |
|
while (contents.startsWith(exception, index)) { |
|
index += length |
|
} |
|
const exceptionLength = index |
|
if (decoration.length < exceptionLength) { |
|
decoration = contents.slice(0, exceptionLength) |
|
} |
|
} |
|
return decoration |
|
} |
|
|
|
/** |
|
* Get a close decoration string from comment contents. |
|
* @param {string} contents comment contents |
|
* @returns {string} decoration string |
|
*/ |
|
function getCloseDecoration(contents) { |
|
let decoration = '' |
|
for (const exception of exceptions) { |
|
const length = exception.length |
|
let index = contents.length |
|
while (contents.endsWith(exception, index)) { |
|
index -= length |
|
} |
|
const exceptionLength = contents.length - index |
|
if (decoration.length < exceptionLength) { |
|
decoration = contents.slice(index) |
|
} |
|
} |
|
return decoration |
|
} |
|
|
|
/** |
|
* Parse HTMLComment. |
|
* @param {Token} node a comment token |
|
* @returns {ParsedHTMLComment | null} the result of HTMLComment tokens. |
|
*/ |
|
return function parseHTMLComment(node) { |
|
if (node.type !== 'HTMLComment') { |
|
// Is not HTMLComment |
|
return null |
|
} |
|
|
|
const htmlCommentText = sourceCode.getText(node) |
|
|
|
if ( |
|
!htmlCommentText.startsWith('<!--') || |
|
!htmlCommentText.endsWith('-->') |
|
) { |
|
// Is not normal HTML Comment |
|
// e.g. Error Code: "abrupt-closing-of-empty-comment", "incorrectly-closed-comment" |
|
return null |
|
} |
|
|
|
let valueText = htmlCommentText.slice(4, -3) |
|
const openDecorationText = getOpenDecoration(valueText) |
|
valueText = valueText.slice(openDecorationText.length) |
|
const firstCharIndex = valueText.search(/\S/) |
|
const beforeSpace = |
|
firstCharIndex >= 0 ? valueText.slice(0, firstCharIndex) : valueText |
|
valueText = valueText.slice(beforeSpace.length) |
|
|
|
const closeDecorationText = getCloseDecoration(valueText) |
|
if (closeDecorationText) { |
|
valueText = valueText.slice(0, -closeDecorationText.length) |
|
} |
|
const lastCharIndex = valueText.search(/\S\s*$/) |
|
const afterSpace = |
|
lastCharIndex >= 0 ? valueText.slice(lastCharIndex + 1) : valueText |
|
if (afterSpace) { |
|
valueText = valueText.slice(0, -afterSpace.length) |
|
} |
|
|
|
let tokenIndex = node.range[0] |
|
/** |
|
* @param {string} type |
|
* @param {string} value |
|
* @returns {any} |
|
*/ |
|
const createToken = (type, value) => { |
|
/** @type {Range} */ |
|
const range = [tokenIndex, tokenIndex + value.length] |
|
tokenIndex = range[1] |
|
/** @type {SourceLocation} */ |
|
let loc |
|
return { |
|
type, |
|
value, |
|
range, |
|
get loc() { |
|
if (loc) { |
|
return loc |
|
} |
|
return (loc = { |
|
start: sourceCode.getLocFromIndex(range[0]), |
|
end: sourceCode.getLocFromIndex(range[1]) |
|
}) |
|
} |
|
} |
|
} |
|
|
|
/** @type {HTMLCommentOpen} */ |
|
const open = createToken(TYPE_HTML_COMMENT_OPEN, '<!--') |
|
/** @type {HTMLCommentOpenDecoration | null} */ |
|
const openDecoration = openDecorationText |
|
? createToken(TYPE_HTML_COMMENT_OPEN_DECORATION, openDecorationText) |
|
: null |
|
tokenIndex += beforeSpace.length |
|
/** @type {HTMLCommentValue | null} */ |
|
const value = valueText |
|
? createToken(TYPE_HTML_COMMENT_VALUE, valueText) |
|
: null |
|
tokenIndex += afterSpace.length |
|
/** @type {HTMLCommentCloseDecoration | null} */ |
|
const closeDecoration = closeDecorationText |
|
? createToken(TYPE_HTML_COMMENT_CLOSE_DECORATION, closeDecorationText) |
|
: null |
|
/** @type {HTMLCommentClose} */ |
|
const close = createToken(TYPE_HTML_COMMENT_CLOSE, '-->') |
|
|
|
return { |
|
/** HTML comment open (`<!--`) */ |
|
open, |
|
/** decoration of the start of HTML comments. (`*****` when `<!--*****`) */ |
|
openDecoration, |
|
/** value of HTML comment. whitespaces and other tokens are not included. */ |
|
value, |
|
/** decoration of the end of HTML comments. (`*****` when `*****-->`) */ |
|
closeDecoration, |
|
/** HTML comment close (`-->`) */ |
|
close |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Define HTML comment visitor |
|
* |
|
* @param {RuleContext} context The rule context. |
|
* @param {CommentParserConfig | null} config The config. |
|
* @param {HTMLCommentVisitor} visitHTMLComment The HTML comment visitor. |
|
* @param {CommentVisitorOption} [visitorOption] The option for visitor. |
|
* @returns {RuleListener} HTML comment visitor. |
|
*/ |
|
function defineVisitor(context, config, visitHTMLComment, visitorOption) { |
|
return { |
|
Program(node) { |
|
visitorOption = visitorOption || {} |
|
if (utils.hasInvalidEOF(node)) { |
|
return |
|
} |
|
if (!node.templateBody) { |
|
return |
|
} |
|
const parse = defineParser(context.getSourceCode(), config) |
|
|
|
for (const comment of node.templateBody.comments) { |
|
if (comment.type !== 'HTMLComment') { |
|
continue |
|
} |
|
if (!visitorOption.includeDirectives && isCommentDirective(comment)) { |
|
// ignore directives |
|
continue |
|
} |
|
if (isIEConditionalComment(comment)) { |
|
// ignore IE conditional |
|
continue |
|
} |
|
|
|
const tokens = parse(comment) |
|
if (tokens) { |
|
visitHTMLComment(tokens) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
module.exports = { |
|
defineVisitor |
|
}
|
|
|