UNPKG

eslint-plugin-san

Version:

Official ESLint plugin for San

196 lines (175 loc) 7.31 kB
/** * @fileoverview Define the number of attributes allows per line * @author Filipa Lacerda */ 'use strict'; /* eslint-disable */ // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ const utils = require('../utils'); module.exports = { meta: { type: 'layout', docs: { description: 'enforce the maximum number of attributes per line', categories: ['strongly-recommended'], url: 'https://ecomfe.github.io/eslint-plugin-san/rules/max-attributes-per-line.html' }, fixable: 'whitespace', // or "code" or "whitespace" schema: [ { type: 'object', properties: { singleline: { anyOf: [ { type: 'number', minimum: 1 }, { type: 'object', properties: { max: { type: 'number', minimum: 1 } }, additionalProperties: false } ] }, multiline: { anyOf: [ { type: 'number', minimum: 1 }, { type: 'object', properties: { max: { type: 'number', minimum: 1 }, allowFirstLine: { type: 'boolean' } }, additionalProperties: false } ] } } } ] }, /** @param {RuleContext} context */ create(context) { const sourceCode = context.getSourceCode(); const configuration = parseOptions(context.options[0]); const multilineMaximum = configuration.multiline; const singlelinemMaximum = configuration.singleline; const canHaveFirstLine = configuration.allowFirstLine; const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore(); return utils.defineTemplateBodyVisitor(context, { VStartTag(node) { const numberOfAttributes = node.attributes.length; if (!numberOfAttributes) return; if (utils.isSingleLine(node) && numberOfAttributes > singlelinemMaximum) { showErrors(node.attributes.slice(singlelinemMaximum)); } if (!utils.isSingleLine(node)) { if (!canHaveFirstLine && node.attributes[0].loc.start.line === node.loc.start.line) { showErrors([node.attributes[0]]); } groupAttrsByLine(node.attributes) .filter(attrs => attrs.length > multilineMaximum) .forEach(attrs => showErrors(attrs.splice(multilineMaximum))); } } }); // ---------------------------------------------------------------------- // Helpers // ---------------------------------------------------------------------- /** * @param {any} options */ function parseOptions(options) { const defaults = { singleline: 1, multiline: 1, allowFirstLine: false }; if (options) { if (typeof options.singleline === 'number') { defaults.singleline = options.singleline; } else if (options.singleline && options.singleline.max) { defaults.singleline = options.singleline.max; } if (options.multiline) { if (typeof options.multiline === 'number') { defaults.multiline = options.multiline; } else if (typeof options.multiline === 'object') { if (options.multiline.max) { defaults.multiline = options.multiline.max; } if (options.multiline.allowFirstLine) { defaults.allowFirstLine = options.multiline.allowFirstLine; } } } } return defaults; } /** * @param {(VDirective | VAttribute)[]} attributes */ function showErrors(attributes) { attributes.forEach((prop, i) => { context.report({ node: prop, loc: prop.loc, message: "'{{name}}' should be on a new line.", data: {name: sourceCode.getText(prop.key)}, fix(fixer) { if (i !== 0) return null; // Find the closest token before the current prop // that is not a white space const prevToken = /** @type {Token} */ (template.getTokenBefore(prop, { filter: token => token.type !== 'HTMLWhitespace' })); /** @type {Range} */ const range = [prevToken.range[1], prop.range[0]]; let prevAttr = prop.parent; if (prevAttr.attributes) { prevAttr = prevAttr.attributes[0]; } let indent = 0; if (prevAttr) { indent = prevAttr.loc.start.column; indent < 0 && (indent = 0); } return fixer.replaceTextRange(range, `\n${' '.repeat(indent)}`); } }); }); } /** * @param {(VDirective | VAttribute)[]} attributes */ function groupAttrsByLine(attributes) { const propsPerLine = [[attributes[0]]]; attributes.reduce((previous, current) => { if (previous.loc.end.line === current.loc.start.line) { propsPerLine[propsPerLine.length - 1].push(current); } else { propsPerLine.push([current]); } return current; }); return propsPerLine; } } };