"use strict" const execall = require("execall") const optionsMatches = require("../../utils/optionsMatches") const report = require("../../utils/report") const ruleMessages = require("../../utils/ruleMessages") const validateOptions = require("../../utils/validateOptions") const _ = require("lodash") const styleSearch = require("style-search") const ruleName = "max-line-length" const messages = ruleMessages(ruleName, { expected: l => `Expected line length to be no more than ${l} characters`, }) const rule = function (maxLength, options) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: maxLength, possible: _.isNumber, }, { actual: options, possible: { ignore: [ "non-comments", "comments", ], ignorePattern: [_.isString], }, optional: true, }) if (!validOptions) { return } const rootString = root.source.input.css const ignoreNonComments = optionsMatches(options, "ignore", "non-comments") const ignoreComments = optionsMatches(options, "ignore", "comments") // Check first line checkNewline({ endIndex: 0 }) // Check subsequent lines styleSearch({ source: rootString, target: ["\n"], comments: "check" }, checkNewline) function complain(index) { report({ index, result, ruleName, message: messages.expected(maxLength), node: root, }) } function checkNewline(match) { let nextNewlineIndex = rootString.indexOf("\n", match.endIndex) if (rootString[nextNewlineIndex - 1] === "\r") { nextNewlineIndex -= 1 } // Accommodate last line if (nextNewlineIndex === -1) { nextNewlineIndex = rootString.length } const rawLineLength = nextNewlineIndex - match.endIndex const lineText = rootString.slice(match.endIndex, nextNewlineIndex) if (optionsMatches(options, "ignorePattern", lineText)) { return } const urlArgumentsLength = execall(/url\((.*)\)/ig, lineText).reduce((result, match) => { return result + _.get(match, "sub[0].length", 0) }, 0) const importUrlsLength = execall(/\@import\s+(['"].*['"])/ig, lineText).reduce((result, match) => { return result + _.get(match, "sub[0].length", 0) }, 0) // If the line's length is less than or equal to the specified // max, ignore it ... So anything below is liable to be complained about. // **Note that the length of any url arguments or import urls // are excluded from the calculation.** if (rawLineLength - urlArgumentsLength - importUrlsLength <= maxLength) { return } const complaintIndex = nextNewlineIndex - 1 if (ignoreComments) { if (match.insideComment) { return } // This trimming business is to notice when the line starts a // comment but that comment is indented, e.g. // /* something here */ const nextTwoChars = rootString.slice(match.endIndex).trim().slice(0, 2) if (nextTwoChars === "/*" || nextTwoChars === "//") { return } } if (ignoreNonComments) { if (match.insideComment) { return complain(complaintIndex) } // This trimming business is to notice when the line starts a // comment but that comment is indented, e.g. // /* something here */ const nextTwoChars = rootString.slice(match.endIndex).trim().slice(0, 2) if (nextTwoChars !== "/*" && nextTwoChars !== "//") { return } return complain(complaintIndex) } // If there are no spaces besides initial (indent) spaces, ignore it const lineString = rootString.slice(match.endIndex, nextNewlineIndex) if (lineString.replace(/^\s+/, "").indexOf(" ") === -1) { return } return complain(complaintIndex) } } } rule.ruleName = ruleName rule.messages = messages module.exports = rule