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.
183 lines
4.9 KiB
183 lines
4.9 KiB
// @ts-nocheck |
|
|
|
'use strict'; |
|
|
|
const _ = require('lodash'); |
|
const execall = require('execall'); |
|
const optionsMatches = require('../../utils/optionsMatches'); |
|
const report = require('../../utils/report'); |
|
const ruleMessages = require('../../utils/ruleMessages'); |
|
const styleSearch = require('style-search'); |
|
const validateOptions = require('../../utils/validateOptions'); |
|
|
|
const ruleName = 'max-line-length'; |
|
const EXCLUDED_PATTERNS = [ |
|
/url\(\s*(\S.*\S)\s*\)/gi, // allow tab, whitespace in url content |
|
/@import\s+(['"].*['"])/gi, |
|
]; |
|
|
|
const messages = ruleMessages(ruleName, { |
|
expected: (max) => |
|
`Expected line length to be no more than ${max} ${max === 1 ? 'character' : 'characters'}`, |
|
}); |
|
|
|
function rule(maxLength, options, context) { |
|
return (root, result) => { |
|
const validOptions = validateOptions( |
|
result, |
|
ruleName, |
|
{ |
|
actual: maxLength, |
|
possible: _.isNumber, |
|
}, |
|
{ |
|
actual: options, |
|
possible: { |
|
ignore: ['non-comments', 'comments'], |
|
ignorePattern: [_.isString, _.isRegExp], |
|
}, |
|
optional: true, |
|
}, |
|
); |
|
|
|
if (!validOptions) { |
|
return; |
|
} |
|
|
|
const ignoreNonComments = optionsMatches(options, 'ignore', 'non-comments'); |
|
const ignoreComments = optionsMatches(options, 'ignore', 'comments'); |
|
const rootString = context.fix ? root.toString() : root.source.input.css; |
|
// Array of skipped sub strings, i.e `url(...)`, `@import "..."` |
|
let skippedSubStrings = []; |
|
let skippedSubStringsIndex = 0; |
|
|
|
EXCLUDED_PATTERNS.forEach((pattern) => |
|
execall(pattern, rootString).forEach((match) => { |
|
const startOfSubString = |
|
match.index + match.match.indexOf(_.get(match, 'subMatches[0]', '')); |
|
|
|
return skippedSubStrings.push([ |
|
startOfSubString, |
|
startOfSubString + _.get(match, 'subMatches[0].length', 0), |
|
]); |
|
}), |
|
); |
|
|
|
skippedSubStrings = skippedSubStrings.sort((a, b) => a[0] - b[0]); |
|
|
|
// Check first line |
|
checkNewline({ endIndex: 0 }); |
|
// Check subsequent lines |
|
styleSearch({ source: rootString, target: ['\n'], comments: 'check' }, (match) => |
|
checkNewline(match), |
|
); |
|
|
|
function complain(index) { |
|
report({ |
|
index, |
|
result, |
|
ruleName, |
|
message: messages.expected(maxLength), |
|
node: root, |
|
}); |
|
} |
|
|
|
function tryToPopSubString(start, end) { |
|
const [startSubString, endSubString] = skippedSubStrings[skippedSubStringsIndex]; |
|
|
|
// Excluded substring does not presented in current line |
|
if (end < startSubString) { |
|
return 0; |
|
} |
|
|
|
// Compute excluded substring size regarding to current line indexes |
|
const excluded = Math.min(end, endSubString) - Math.max(start, startSubString); |
|
|
|
// Current substring is out of range for next lines |
|
if (endSubString <= end) { |
|
skippedSubStringsIndex++; |
|
} |
|
|
|
return excluded; |
|
} |
|
|
|
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 excludedLength = skippedSubStrings[skippedSubStringsIndex] |
|
? tryToPopSubString(match.endIndex, nextNewlineIndex) |
|
: 0; |
|
const lineText = rootString.slice(match.endIndex, nextNewlineIndex); |
|
|
|
// Case sensitive ignorePattern match |
|
if (optionsMatches(options, 'ignorePattern', lineText)) { |
|
return; |
|
} |
|
|
|
// 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 - excludedLength <= 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+/, '').includes(' ')) { |
|
return; |
|
} |
|
|
|
return complain(complaintIndex); |
|
} |
|
}; |
|
} |
|
|
|
rule.ruleName = ruleName; |
|
rule.messages = messages; |
|
module.exports = rule;
|
|
|