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.
220 lines
6.0 KiB
220 lines
6.0 KiB
/** |
|
* @fileoverview Require or disallow padding lines between blocks |
|
* @author Yosuke Ota |
|
*/ |
|
'use strict' |
|
const utils = require('../utils') |
|
|
|
/** |
|
* Split the source code into multiple lines based on the line delimiters. |
|
* @param {string} text Source code as a string. |
|
* @returns {string[]} Array of source code lines. |
|
*/ |
|
function splitLines(text) { |
|
return text.split(/\r\n|[\r\n\u2028\u2029]/gu) |
|
} |
|
|
|
/** |
|
* Check and report blocks for `never` configuration. |
|
* This autofix removes blank lines between the given 2 blocks. |
|
* @param {RuleContext} context The rule context to report. |
|
* @param {VElement} prevBlock The previous block to check. |
|
* @param {VElement} nextBlock The next block to check. |
|
* @param {Token[]} betweenTokens The array of tokens between blocks. |
|
* @returns {void} |
|
* @private |
|
*/ |
|
function verifyForNever(context, prevBlock, nextBlock, betweenTokens) { |
|
if (prevBlock.loc.end.line === nextBlock.loc.start.line) { |
|
// same line |
|
return |
|
} |
|
const tokenOrNodes = [...betweenTokens, nextBlock] |
|
/** @type {ASTNode | Token} */ |
|
let prev = prevBlock |
|
/** @type {[ASTNode | Token, ASTNode | Token][]} */ |
|
const paddingLines = [] |
|
for (const tokenOrNode of tokenOrNodes) { |
|
const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line |
|
if (numOfLineBreaks > 1) { |
|
paddingLines.push([prev, tokenOrNode]) |
|
} |
|
prev = tokenOrNode |
|
} |
|
if (!paddingLines.length) { |
|
return |
|
} |
|
|
|
context.report({ |
|
node: nextBlock, |
|
messageId: 'never', |
|
*fix(fixer) { |
|
for (const [prevToken, nextToken] of paddingLines) { |
|
const start = prevToken.range[1] |
|
const end = nextToken.range[0] |
|
const paddingText = context.getSourceCode().text.slice(start, end) |
|
const lastSpaces = splitLines(paddingText).pop() |
|
yield fixer.replaceTextRange([start, end], `\n${lastSpaces}`) |
|
} |
|
} |
|
}) |
|
} |
|
|
|
/** |
|
* Check and report blocks for `always` configuration. |
|
* This autofix inserts a blank line between the given 2 blocks. |
|
* @param {RuleContext} context The rule context to report. |
|
* @param {VElement} prevBlock The previous block to check. |
|
* @param {VElement} nextBlock The next block to check. |
|
* @param {Token[]} betweenTokens The array of tokens between blocks. |
|
* @returns {void} |
|
* @private |
|
*/ |
|
function verifyForAlways(context, prevBlock, nextBlock, betweenTokens) { |
|
const tokenOrNodes = [...betweenTokens, nextBlock] |
|
/** @type {ASTNode | Token} */ |
|
let prev = prevBlock |
|
/** @type {ASTNode | Token | undefined} */ |
|
let linebreak |
|
for (const tokenOrNode of tokenOrNodes) { |
|
const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line |
|
if (numOfLineBreaks > 1) { |
|
// Already padded. |
|
return |
|
} |
|
if (!linebreak && numOfLineBreaks > 0) { |
|
linebreak = prev |
|
} |
|
prev = tokenOrNode |
|
} |
|
|
|
context.report({ |
|
node: nextBlock, |
|
messageId: 'always', |
|
fix(fixer) { |
|
if (linebreak) { |
|
return fixer.insertTextAfter(linebreak, '\n') |
|
} |
|
return fixer.insertTextAfter(prevBlock, '\n\n') |
|
} |
|
}) |
|
} |
|
|
|
/** |
|
* Types of blank lines. |
|
* `never` and `always` are defined. |
|
* Those have `verify` method to check and report statements. |
|
* @private |
|
*/ |
|
const PaddingTypes = { |
|
never: { verify: verifyForNever }, |
|
always: { verify: verifyForAlways } |
|
} |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Rule Definition |
|
// ------------------------------------------------------------------------------ |
|
|
|
module.exports = { |
|
meta: { |
|
type: 'layout', |
|
docs: { |
|
description: 'require or disallow padding lines between blocks', |
|
categories: undefined, |
|
url: 'https://eslint.vuejs.org/rules/padding-line-between-blocks.html' |
|
}, |
|
fixable: 'whitespace', |
|
schema: [ |
|
{ |
|
enum: Object.keys(PaddingTypes) |
|
} |
|
], |
|
messages: { |
|
never: 'Unexpected blank line before this block.', |
|
always: 'Expected blank line before this block.' |
|
} |
|
}, |
|
/** @param {RuleContext} context */ |
|
create(context) { |
|
if (!context.parserServices.getDocumentFragment) { |
|
return {} |
|
} |
|
const df = context.parserServices.getDocumentFragment() |
|
if (!df) { |
|
return {} |
|
} |
|
const documentFragment = df |
|
|
|
/** @type {'always' | 'never'} */ |
|
const option = context.options[0] || 'always' |
|
const paddingType = PaddingTypes[option] |
|
|
|
/** @type {Token[]} */ |
|
let tokens |
|
/** |
|
* @returns {VElement[]} |
|
*/ |
|
function getTopLevelHTMLElements() { |
|
return documentFragment.children.filter(utils.isVElement) |
|
} |
|
|
|
/** |
|
* @param {VElement} prev |
|
* @param {VElement} next |
|
*/ |
|
function getTokenAndCommentsBetween(prev, next) { |
|
// When there is no <template>, tokenStore.getTokensBetween cannot be used. |
|
if (!tokens) { |
|
tokens = [ |
|
...documentFragment.tokens.filter( |
|
(token) => token.type !== 'HTMLWhitespace' |
|
), |
|
...documentFragment.comments |
|
].sort((a, b) => |
|
a.range[0] > b.range[0] ? 1 : a.range[0] < b.range[0] ? -1 : 0 |
|
) |
|
} |
|
|
|
let token = tokens.shift() |
|
|
|
const results = [] |
|
while (token) { |
|
if (prev.range[1] <= token.range[0]) { |
|
if (next.range[0] <= token.range[0]) { |
|
tokens.unshift(token) |
|
break |
|
} else { |
|
results.push(token) |
|
} |
|
} |
|
token = tokens.shift() |
|
} |
|
|
|
return results |
|
} |
|
|
|
return utils.defineTemplateBodyVisitor( |
|
context, |
|
{}, |
|
{ |
|
/** @param {Program} node */ |
|
Program(node) { |
|
if (utils.hasInvalidEOF(node)) { |
|
return |
|
} |
|
const elements = [...getTopLevelHTMLElements()] |
|
|
|
let prev = elements.shift() |
|
for (const element of elements) { |
|
if (!prev) { |
|
return |
|
} |
|
const betweenTokens = getTokenAndCommentsBetween(prev, element) |
|
paddingType.verify(context, prev, element, betweenTokens) |
|
prev = element |
|
} |
|
} |
|
} |
|
) |
|
} |
|
}
|
|
|