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.
		
		
		
		
		
			
		
			
				
					
					
						
							221 lines
						
					
					
						
							6.0 KiB
						
					
					
				
			
		
		
	
	
							221 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
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    )
 | 
						|
  }
 | 
						|
}
 |