123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- // @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;
|